1 package net.pterodactylus.sone.core
3 import com.codahale.metrics.*
4 import com.google.common.base.*
5 import com.google.common.base.Optional
6 import com.google.common.eventbus.*
7 import com.google.common.util.concurrent.MoreExecutors.*
9 import net.pterodactylus.sone.core.SoneInserter.*
10 import net.pterodactylus.sone.core.event.*
11 import net.pterodactylus.sone.data.*
12 import net.pterodactylus.sone.freenet.wot.*
13 import net.pterodactylus.sone.main.*
14 import net.pterodactylus.sone.test.*
15 import org.hamcrest.MatcherAssert.*
16 import org.hamcrest.Matchers.*
19 import org.mockito.ArgumentMatchers.any
20 import org.mockito.ArgumentMatchers.anyString
21 import org.mockito.ArgumentMatchers.eq
22 import org.mockito.Mockito.doAnswer
23 import org.mockito.Mockito.never
24 import org.mockito.Mockito.times
25 import org.mockito.Mockito.verify
26 import org.mockito.hamcrest.MockitoHamcrest.*
27 import org.mockito.stubbing.*
28 import java.lang.System.*
30 import kotlin.test.Test
33 * Unit test for [SoneInserter] and its subclasses.
35 class SoneInserterTest {
37 private val metricRegistry = MetricRegistry()
38 private val core = mock<Core>()
39 private val eventBus = mock<EventBus>()
40 private val freenetInterface = mock<FreenetInterface>()
44 val updateChecker = mock<UpdateChecker>()
45 whenever(core.updateChecker).thenReturn(updateChecker)
46 whenever(core.getSone(anyString())).thenReturn(null)
50 fun `insertion delay is forwarded to sone inserter`() {
51 val eventBus = AsyncEventBus(directExecutor())
52 eventBus.register(SoneInserter(core, eventBus, freenetInterface, metricRegistry, "SoneId"))
53 eventBus.post(InsertionDelayChangedEvent(15))
54 assertThat(SoneInserter.getInsertionDelay().get(), equalTo(15))
57 private fun createSone(insertUri: FreenetURI, fingerprint: String = "fingerprint"): Sone {
58 val ownIdentity = DefaultOwnIdentity("", "", "", insertUri.toString())
59 val sone = mock<Sone>()
60 whenever(sone.identity).thenReturn(ownIdentity)
61 whenever(sone.fingerprint).thenReturn(fingerprint)
62 whenever(sone.rootAlbum).thenReturn(mock())
63 whenever(core.getSone(anyString())).thenReturn(sone)
68 fun `isModified is true if modification detector says so`() {
69 val soneModificationDetector = mock<SoneModificationDetector>()
70 whenever(soneModificationDetector.isModified).thenReturn(true)
71 val soneInserter = SoneInserter(core, eventBus, freenetInterface, metricRegistry, "SoneId", soneModificationDetector, 1)
72 assertThat(soneInserter.isModified, equalTo(true))
76 fun `isModified is false if modification detector says so`() {
77 val soneModificationDetector = mock<SoneModificationDetector>()
78 val soneInserter = SoneInserter(core, eventBus, freenetInterface, metricRegistry, "SoneId", soneModificationDetector, 1)
79 assertThat(soneInserter.isModified, equalTo(false))
83 fun `last fingerprint is stored correctly`() {
84 val soneInserter = SoneInserter(core, eventBus, freenetInterface, metricRegistry, "SoneId")
85 soneInserter.lastInsertFingerprint = "last-fingerprint"
86 assertThat(soneInserter.lastInsertFingerprint, equalTo("last-fingerprint"))
90 fun `sone inserter stops when it should`() {
91 val soneInserter = SoneInserter(core, eventBus, freenetInterface, metricRegistry, "SoneId")
93 soneInserter.serviceRun()
97 fun `sone inserter inserts a sone if it is eligible`() {
98 val finalUri = mock<FreenetURI>()
99 val sone = createSone(insertUri)
100 val soneModificationDetector = mock<SoneModificationDetector>()
101 whenever(soneModificationDetector.isEligibleForInsert).thenReturn(true)
102 whenever(freenetInterface.insertDirectory(eq(expectedInsertUri), any<HashMap<String, Any>>(), eq("index.html"))).thenReturn(finalUri)
103 val soneInserter = SoneInserter(core, eventBus, freenetInterface, metricRegistry, "SoneId", soneModificationDetector, 1)
107 }.whenever(core).touchConfiguration()
108 soneInserter.serviceRun()
109 val soneEvents = ArgumentCaptor.forClass(SoneEvent::class.java)
110 verify(freenetInterface).insertDirectory(eq(expectedInsertUri), any<HashMap<String, Any>>(), eq("index.html"))
111 verify(eventBus, times(2)).post(soneEvents.capture())
112 assertThat(soneEvents.allValues[0], instanceOf(SoneInsertingEvent::class.java))
113 assertThat(soneEvents.allValues[0].sone, equalTo(sone))
114 assertThat(soneEvents.allValues[1], instanceOf(SoneInsertedEvent::class.java))
115 assertThat(soneEvents.allValues[1].sone, equalTo(sone))
119 fun `sone inserter bails out if it is stopped while inserting`() {
120 val finalUri = mock<FreenetURI>()
121 val sone = createSone(insertUri)
122 val soneModificationDetector = mock<SoneModificationDetector>()
123 whenever(soneModificationDetector.isEligibleForInsert).thenReturn(true)
124 val soneInserter = SoneInserter(core, eventBus, freenetInterface, metricRegistry, "SoneId", soneModificationDetector, 1)
125 whenever(freenetInterface.insertDirectory(eq(expectedInsertUri), any<HashMap<String, Any>>(), eq("index.html"))).thenAnswer {
129 soneInserter.serviceRun()
130 val soneEvents = ArgumentCaptor.forClass(SoneEvent::class.java)
131 verify(freenetInterface).insertDirectory(eq(expectedInsertUri), any<HashMap<String, Any>>(), eq("index.html"))
132 verify(eventBus, times(2)).post(soneEvents.capture())
133 assertThat(soneEvents.allValues[0], instanceOf(SoneInsertingEvent::class.java))
134 assertThat(soneEvents.allValues[0].sone, equalTo(sone))
135 assertThat(soneEvents.allValues[1], instanceOf(SoneInsertedEvent::class.java))
136 assertThat(soneEvents.allValues[1].sone, equalTo(sone))
137 verify(core, never()).touchConfiguration()
141 fun `sone inserter does not insert sone if it is not eligible`() {
142 createSone(insertUri)
143 val soneModificationDetector = mock<SoneModificationDetector>()
144 val soneInserter = SoneInserter(core, eventBus, freenetInterface, metricRegistry, "SoneId", soneModificationDetector, 1)
148 } catch (ie1: InterruptedException) {
149 throw RuntimeException(ie1)
154 soneInserter.serviceRun()
155 verify(freenetInterface, never()).insertDirectory(eq(expectedInsertUri), any<HashMap<String, Any>>(), eq("index.html"))
156 verify(eventBus, never()).post(argThat(org.hamcrest.Matchers.any(SoneEvent::class.java)))
160 fun `sone inserter posts aborted event if an exception occurs`() {
161 val sone = createSone(insertUri)
162 val soneModificationDetector = mock<SoneModificationDetector>()
163 whenever(soneModificationDetector.isEligibleForInsert).thenReturn(true)
164 val soneInserter = SoneInserter(core, eventBus, freenetInterface, metricRegistry, "SoneId", soneModificationDetector, 1)
165 val soneException = SoneException(Exception())
166 whenever(freenetInterface.insertDirectory(eq(expectedInsertUri), any<HashMap<String, Any>>(), eq("index.html"))).thenAnswer {
170 soneInserter.serviceRun()
171 val soneEvents = ArgumentCaptor.forClass(SoneEvent::class.java)
172 verify(freenetInterface).insertDirectory(eq(expectedInsertUri), any<HashMap<String, Any>>(), eq("index.html"))
173 verify(eventBus, times(2)).post(soneEvents.capture())
174 assertThat(soneEvents.allValues[0], instanceOf(SoneInsertingEvent::class.java))
175 assertThat(soneEvents.allValues[0].sone, equalTo(sone))
176 assertThat(soneEvents.allValues[1], instanceOf(SoneInsertAbortedEvent::class.java))
177 assertThat(soneEvents.allValues[1].sone, equalTo(sone))
178 verify(core, never()).touchConfiguration()
182 fun `sone inserter exits if sone is unknown`() {
183 val soneModificationDetector = mock<SoneModificationDetector>()
184 val soneInserter = SoneInserter(core, eventBus, freenetInterface, metricRegistry, "SoneId", soneModificationDetector, 1)
185 whenever(soneModificationDetector.isEligibleForInsert).thenReturn(true)
186 whenever(core.getSone("SoneId")).thenReturn(null)
187 soneInserter.serviceRun()
191 fun `sone inserter catches exception and continues`() {
192 val soneModificationDetector = mock<SoneModificationDetector>()
193 val soneInserter = SoneInserter(core, eventBus, freenetInterface, metricRegistry, "SoneId", soneModificationDetector, 1)
194 val stopInserterAndThrowException = Answer<Optional<Sone>> {
196 throw NullPointerException()
198 whenever(soneModificationDetector.isEligibleForInsert).thenAnswer(stopInserterAndThrowException)
199 soneInserter.serviceRun()
203 fun `template is rendered correctly for manifest element`() {
204 val soneProperties = HashMap<String, Any>()
205 soneProperties["id"] = "SoneId"
206 val manifestCreator = ManifestCreator(core, soneProperties)
207 val now = currentTimeMillis()
208 whenever(core.startupTime).thenReturn(now)
209 val manifestElement = manifestCreator.createManifestElement("test.txt", "plain/text; charset=utf-8", "sone-inserter-manifest.txt")
210 assertThat(manifestElement!!.name, equalTo("test.txt"))
211 assertThat(manifestElement.mimeTypeOverride, equalTo("plain/text; charset=utf-8"))
212 val templateContent = String(manifestElement.data.inputStream.readBytes(), Charsets.UTF_8)
213 assertThat(templateContent, containsString("Sone Version: ${SonePlugin.getPluginVersion()}\n"))
214 assertThat(templateContent, containsString("Core Startup: $now\n"))
215 assertThat(templateContent, containsString("Sone ID: SoneId\n"))
219 fun `invalid template returns anull manifest element`() {
220 val soneProperties = HashMap<String, Any>()
221 val manifestCreator = ManifestCreator(core, soneProperties)
222 assertThat(manifestCreator.createManifestElement("test.txt",
223 "plain/text; charset=utf-8",
224 "sone-inserter-invalid-manifest.txt"),
229 fun `error while rendering template returns a null manifest element`() {
230 val soneProperties = HashMap<String, Any>()
231 val manifestCreator = ManifestCreator(core, soneProperties)
232 whenever(core.toString()).thenThrow(NullPointerException::class.java)
233 assertThat(manifestCreator.createManifestElement("test.txt",
234 "plain/text; charset=utf-8",
235 "sone-inserter-faulty-manifest.txt"),
240 fun `successful insert updates metrics`() {
241 val finalUri = mock<FreenetURI>()
242 createSone(insertUri)
243 val soneModificationDetector = mock<SoneModificationDetector>()
244 whenever(soneModificationDetector.isEligibleForInsert).thenReturn(true)
245 whenever(freenetInterface.insertDirectory(eq(expectedInsertUri), any<HashMap<String, Any>>(), eq("index.html"))).thenReturn(finalUri)
246 val soneInserter = SoneInserter(core, eventBus, freenetInterface, metricRegistry,"SoneId", soneModificationDetector, 1)
250 }.whenever(core).touchConfiguration()
251 soneInserter.serviceRun()
252 val histogram = metricRegistry.histogram("sone.insert.duration")
253 assertThat(histogram.count, equalTo(1L))
257 fun `unsuccessful insert does not update histogram but records error`() {
258 createSone(insertUri)
259 val soneModificationDetector = mock<SoneModificationDetector>()
260 whenever(soneModificationDetector.isEligibleForInsert).thenReturn(true)
261 val soneInserter = SoneInserter(core, eventBus, freenetInterface, metricRegistry, "SoneId", soneModificationDetector, 1)
262 whenever(freenetInterface.insertDirectory(eq(expectedInsertUri), any<HashMap<String, Any>>(), eq("index.html"))).thenAnswer {
264 throw SoneException(Exception())
266 soneInserter.serviceRun()
267 val histogram = metricRegistry.histogram("sone.insert.duration")
268 assertThat(histogram.count, equalTo(0L))
269 val meter = metricRegistry.meter("sone.insert.errors")
270 assertThat(meter.count, equalTo(1L))
275 val insertUri = createInsertUri
276 val expectedInsertUri: FreenetURI = FreenetURI(insertUri.toString())
279 .setMetaString(kotlin.emptyArray())
280 .setSuggestedEdition(0)