7ac9e0643354a96c03a4be07eb0f4ac5bf2de4ce
[Sone.git] / src / test / kotlin / net / pterodactylus / sone / core / SoneInserterTest.kt
1 package net.pterodactylus.sone.core
2
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.io.ByteStreams.*
8 import com.google.common.util.concurrent.MoreExecutors.*
9 import freenet.keys.*
10 import net.pterodactylus.sone.core.SoneInserter.*
11 import net.pterodactylus.sone.core.event.*
12 import net.pterodactylus.sone.data.*
13 import net.pterodactylus.sone.freenet.wot.*
14 import net.pterodactylus.sone.main.*
15 import net.pterodactylus.sone.test.*
16 import org.hamcrest.MatcherAssert.*
17 import org.hamcrest.Matchers.*
18 import org.junit.*
19 import org.mockito.*
20 import org.mockito.ArgumentMatchers.any
21 import org.mockito.ArgumentMatchers.anyString
22 import org.mockito.ArgumentMatchers.eq
23 import org.mockito.Mockito.doAnswer
24 import org.mockito.Mockito.never
25 import org.mockito.Mockito.times
26 import org.mockito.Mockito.verify
27 import org.mockito.hamcrest.MockitoHamcrest.*
28 import org.mockito.stubbing.*
29 import java.lang.System.*
30 import java.util.*
31 import kotlin.test.Test
32
33 /**
34  * Unit test for [SoneInserter] and its subclasses.
35  */
36 class SoneInserterTest {
37
38         private val metricRegistry = MetricRegistry()
39         private val core = mock<Core>()
40         private val eventBus = mock<EventBus>()
41         private val freenetInterface = mock<FreenetInterface>()
42
43         @Before
44         fun setupCore() {
45                 val updateChecker = mock<UpdateChecker>()
46                 whenever(core.updateChecker).thenReturn(updateChecker)
47                 whenever(core.getSone(anyString())).thenReturn(null)
48         }
49
50         @Test
51         fun `insertion delay is forwarded to sone inserter`() {
52                 val eventBus = AsyncEventBus(directExecutor())
53                 eventBus.register(SoneInserter(core, eventBus, freenetInterface, metricRegistry, "SoneId"))
54                 eventBus.post(InsertionDelayChangedEvent(15))
55                 assertThat(SoneInserter.getInsertionDelay().get(), equalTo(15))
56         }
57
58         private fun createSone(insertUri: FreenetURI, fingerprint: String = "fingerprint"): Sone {
59                 val ownIdentity = DefaultOwnIdentity("", "", "", insertUri.toString())
60                 val sone = mock<Sone>()
61                 whenever(sone.identity).thenReturn(ownIdentity)
62                 whenever(sone.fingerprint).thenReturn(fingerprint)
63                 whenever(sone.rootAlbum).thenReturn(mock())
64                 whenever(core.getSone(anyString())).thenReturn(sone)
65                 return sone
66         }
67
68         @Test
69         fun `isModified is true if modification detector says so`() {
70                 val soneModificationDetector = mock<SoneModificationDetector>()
71                 whenever(soneModificationDetector.isModified).thenReturn(true)
72                 val soneInserter = SoneInserter(core, eventBus, freenetInterface, metricRegistry, "SoneId", soneModificationDetector, 1)
73                 assertThat(soneInserter.isModified, equalTo(true))
74         }
75
76         @Test
77         fun `isModified is false if modification detector says so`() {
78                 val soneModificationDetector = mock<SoneModificationDetector>()
79                 val soneInserter = SoneInserter(core, eventBus, freenetInterface, metricRegistry, "SoneId", soneModificationDetector, 1)
80                 assertThat(soneInserter.isModified, equalTo(false))
81         }
82
83         @Test
84         fun `last fingerprint is stored correctly`() {
85                 val soneInserter = SoneInserter(core, eventBus, freenetInterface, metricRegistry, "SoneId")
86                 soneInserter.lastInsertFingerprint = "last-fingerprint"
87                 assertThat(soneInserter.lastInsertFingerprint, equalTo("last-fingerprint"))
88         }
89
90         @Test
91         fun `sone inserter stops when it should`() {
92                 val soneInserter = SoneInserter(core, eventBus, freenetInterface, metricRegistry, "SoneId")
93                 soneInserter.stop()
94                 soneInserter.serviceRun()
95         }
96
97         @Test
98         fun `sone inserter inserts a sone if it is eligible`() {
99                 val finalUri = mock<FreenetURI>()
100                 val sone = createSone(insertUri)
101                 val soneModificationDetector = mock<SoneModificationDetector>()
102                 whenever(soneModificationDetector.isEligibleForInsert).thenReturn(true)
103                 whenever(freenetInterface.insertDirectory(eq(expectedInsertUri), any<HashMap<String, Any>>(), eq("index.html"))).thenReturn(finalUri)
104                 val soneInserter = SoneInserter(core, eventBus, freenetInterface, metricRegistry, "SoneId", soneModificationDetector, 1)
105                 doAnswer {
106                         soneInserter.stop()
107                         null
108                 }.whenever(core).touchConfiguration()
109                 soneInserter.serviceRun()
110                 val soneEvents = ArgumentCaptor.forClass(SoneEvent::class.java)
111                 verify(freenetInterface).insertDirectory(eq(expectedInsertUri), any<HashMap<String, Any>>(), eq("index.html"))
112                 verify(eventBus, times(2)).post(soneEvents.capture())
113                 assertThat(soneEvents.allValues[0], instanceOf(SoneInsertingEvent::class.java))
114                 assertThat(soneEvents.allValues[0].sone, equalTo(sone))
115                 assertThat(soneEvents.allValues[1], instanceOf(SoneInsertedEvent::class.java))
116                 assertThat(soneEvents.allValues[1].sone, equalTo(sone))
117         }
118
119         @Test
120         fun `sone inserter bails out if it is stopped while inserting`() {
121                 val finalUri = mock<FreenetURI>()
122                 val sone = createSone(insertUri)
123                 val soneModificationDetector = mock<SoneModificationDetector>()
124                 whenever(soneModificationDetector.isEligibleForInsert).thenReturn(true)
125                 val soneInserter = SoneInserter(core, eventBus, freenetInterface, metricRegistry, "SoneId", soneModificationDetector, 1)
126                 whenever(freenetInterface.insertDirectory(eq(expectedInsertUri), any<HashMap<String, Any>>(), eq("index.html"))).thenAnswer {
127                         soneInserter.stop()
128                         finalUri
129                 }
130                 soneInserter.serviceRun()
131                 val soneEvents = ArgumentCaptor.forClass(SoneEvent::class.java)
132                 verify(freenetInterface).insertDirectory(eq(expectedInsertUri), any<HashMap<String, Any>>(), eq("index.html"))
133                 verify(eventBus, times(2)).post(soneEvents.capture())
134                 assertThat(soneEvents.allValues[0], instanceOf(SoneInsertingEvent::class.java))
135                 assertThat(soneEvents.allValues[0].sone, equalTo(sone))
136                 assertThat(soneEvents.allValues[1], instanceOf(SoneInsertedEvent::class.java))
137                 assertThat(soneEvents.allValues[1].sone, equalTo(sone))
138                 verify(core, never()).touchConfiguration()
139         }
140
141         @Test
142         fun `sone inserter does not insert sone if it is not eligible`() {
143                 createSone(insertUri)
144                 val soneModificationDetector = mock<SoneModificationDetector>()
145                 val soneInserter = SoneInserter(core, eventBus, freenetInterface, metricRegistry, "SoneId", soneModificationDetector, 1)
146                 Thread(Runnable {
147                         try {
148                                 Thread.sleep(500)
149                         } catch (ie1: InterruptedException) {
150                                 throw RuntimeException(ie1)
151                         }
152
153                         soneInserter.stop()
154                 }).start()
155                 soneInserter.serviceRun()
156                 verify(freenetInterface, never()).insertDirectory(eq(expectedInsertUri), any<HashMap<String, Any>>(), eq("index.html"))
157                 verify(eventBus, never()).post(argThat(org.hamcrest.Matchers.any(SoneEvent::class.java)))
158         }
159
160         @Test
161         fun `sone inserter posts aborted event if an exception occurs`() {
162                 val sone = createSone(insertUri)
163                 val soneModificationDetector = mock<SoneModificationDetector>()
164                 whenever(soneModificationDetector.isEligibleForInsert).thenReturn(true)
165                 val soneInserter = SoneInserter(core, eventBus, freenetInterface, metricRegistry, "SoneId", soneModificationDetector, 1)
166                 val soneException = SoneException(Exception())
167                 whenever(freenetInterface.insertDirectory(eq(expectedInsertUri), any<HashMap<String, Any>>(), eq("index.html"))).thenAnswer {
168                         soneInserter.stop()
169                         throw soneException
170                 }
171                 soneInserter.serviceRun()
172                 val soneEvents = ArgumentCaptor.forClass(SoneEvent::class.java)
173                 verify(freenetInterface).insertDirectory(eq(expectedInsertUri), any<HashMap<String, Any>>(), eq("index.html"))
174                 verify(eventBus, times(2)).post(soneEvents.capture())
175                 assertThat(soneEvents.allValues[0], instanceOf(SoneInsertingEvent::class.java))
176                 assertThat(soneEvents.allValues[0].sone, equalTo(sone))
177                 assertThat(soneEvents.allValues[1], instanceOf(SoneInsertAbortedEvent::class.java))
178                 assertThat(soneEvents.allValues[1].sone, equalTo(sone))
179                 verify(core, never()).touchConfiguration()
180         }
181
182         @Test
183         fun `sone inserter exits if sone is unknown`() {
184                 val soneModificationDetector = mock<SoneModificationDetector>()
185                 val soneInserter = SoneInserter(core, eventBus, freenetInterface, metricRegistry, "SoneId", soneModificationDetector, 1)
186                 whenever(soneModificationDetector.isEligibleForInsert).thenReturn(true)
187                 whenever(core.getSone("SoneId")).thenReturn(null)
188                 soneInserter.serviceRun()
189         }
190
191         @Test
192         fun `sone inserter catches exception and continues`() {
193                 val soneModificationDetector = mock<SoneModificationDetector>()
194                 val soneInserter = SoneInserter(core, eventBus, freenetInterface, metricRegistry, "SoneId", soneModificationDetector, 1)
195                 val stopInserterAndThrowException = Answer<Optional<Sone>> {
196                         soneInserter.stop()
197                         throw NullPointerException()
198                 }
199                 whenever(soneModificationDetector.isEligibleForInsert).thenAnswer(stopInserterAndThrowException)
200                 soneInserter.serviceRun()
201         }
202
203         @Test
204         fun `template is rendered correctly for manifest element`() {
205                 val soneProperties = HashMap<String, Any>()
206                 soneProperties["id"] = "SoneId"
207                 val manifestCreator = ManifestCreator(core, soneProperties)
208                 val now = currentTimeMillis()
209                 whenever(core.startupTime).thenReturn(now)
210                 val manifestElement = manifestCreator.createManifestElement("test.txt", "plain/text; charset=utf-8", "sone-inserter-manifest.txt")
211                 assertThat(manifestElement!!.name, equalTo("test.txt"))
212                 assertThat(manifestElement.mimeTypeOverride, equalTo("plain/text; charset=utf-8"))
213                 val templateContent = String(toByteArray(manifestElement.data.inputStream), Charsets.UTF_8)
214                 assertThat(templateContent, containsString("Sone Version: ${SonePlugin.getPluginVersion()}\n"))
215                 assertThat(templateContent, containsString("Core Startup: $now\n"))
216                 assertThat(templateContent, containsString("Sone ID: SoneId\n"))
217         }
218
219         @Test
220         fun `invalid template returns anull manifest element`() {
221                 val soneProperties = HashMap<String, Any>()
222                 val manifestCreator = ManifestCreator(core, soneProperties)
223                 assertThat(manifestCreator.createManifestElement("test.txt",
224                                 "plain/text; charset=utf-8",
225                                 "sone-inserter-invalid-manifest.txt"),
226                                 nullValue())
227         }
228
229         @Test
230         fun `error while rendering template returns a null manifest element`() {
231                 val soneProperties = HashMap<String, Any>()
232                 val manifestCreator = ManifestCreator(core, soneProperties)
233                 whenever(core.toString()).thenThrow(NullPointerException::class.java)
234                 assertThat(manifestCreator.createManifestElement("test.txt",
235                                 "plain/text; charset=utf-8",
236                                 "sone-inserter-faulty-manifest.txt"),
237                                 nullValue())
238         }
239
240         @Test
241         fun `successful insert updates metrics`() {
242                 val finalUri = mock<FreenetURI>()
243                 createSone(insertUri)
244                 val soneModificationDetector = mock<SoneModificationDetector>()
245                 whenever(soneModificationDetector.isEligibleForInsert).thenReturn(true)
246                 whenever(freenetInterface.insertDirectory(eq(expectedInsertUri), any<HashMap<String, Any>>(), eq("index.html"))).thenReturn(finalUri)
247                 val soneInserter = SoneInserter(core, eventBus, freenetInterface, metricRegistry,"SoneId", soneModificationDetector, 1)
248                 doAnswer {
249                         soneInserter.stop()
250                         null
251                 }.whenever(core).touchConfiguration()
252                 soneInserter.serviceRun()
253                 val histogram = metricRegistry.histogram("sone.insert.duration")
254                 assertThat(histogram.count, equalTo(1L))
255         }
256
257         @Test
258         fun `unsuccessful insert does not update histogram but records error`() {
259                 createSone(insertUri)
260                 val soneModificationDetector = mock<SoneModificationDetector>()
261                 whenever(soneModificationDetector.isEligibleForInsert).thenReturn(true)
262                 val soneInserter = SoneInserter(core, eventBus, freenetInterface, metricRegistry, "SoneId", soneModificationDetector, 1)
263                 whenever(freenetInterface.insertDirectory(eq(expectedInsertUri), any<HashMap<String, Any>>(), eq("index.html"))).thenAnswer {
264                         soneInserter.stop()
265                         throw SoneException(Exception())
266                 }
267                 soneInserter.serviceRun()
268                 val histogram = metricRegistry.histogram("sone.insert.duration")
269                 assertThat(histogram.count, equalTo(0L))
270                 val meter = metricRegistry.meter("sone.insert.errors")
271                 assertThat(meter.count, equalTo(1L))
272         }
273
274 }
275
276 val insertUri = createInsertUri
277 val expectedInsertUri: FreenetURI = FreenetURI(insertUri.toString())
278                 .setKeyType("USK")
279                 .setDocName("Sone")
280                 .setMetaString(kotlin.emptyArray())
281                 .setSuggestedEdition(0)