✏️ Fix test name
[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.util.concurrent.MoreExecutors.*
8 import freenet.keys.*
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.*
17 import org.junit.*
18 import org.mockito.*
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.*
29 import java.util.*
30 import kotlin.test.Test
31
32 /**
33  * Unit test for [SoneInserter] and its subclasses.
34  */
35 class SoneInserterTest {
36
37         private val metricRegistry = MetricRegistry()
38         private val core = mock<Core>()
39         private val eventBus = mock<EventBus>()
40         private val freenetInterface = mock<FreenetInterface>()
41
42         @Before
43         fun setupCore() {
44                 val updateChecker = mock<UpdateChecker>()
45                 whenever(core.updateChecker).thenReturn(updateChecker)
46                 whenever(core.getSone(anyString())).thenReturn(null)
47         }
48
49         @Test
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))
55         }
56
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)
64                 return sone
65         }
66
67         @Test
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))
73         }
74
75         @Test
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))
80         }
81
82         @Test
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"))
87         }
88
89         @Test
90         fun `sone inserter stops when it should`() {
91                 val soneInserter = SoneInserter(core, eventBus, freenetInterface, metricRegistry, "SoneId")
92                 soneInserter.stop()
93                 soneInserter.serviceRun()
94         }
95
96         @Test
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)
104                 doAnswer {
105                         soneInserter.stop()
106                         null
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))
116         }
117
118         @Test
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 {
126                         soneInserter.stop()
127                         finalUri
128                 }
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()
138         }
139
140         @Test
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)
145                 Thread(Runnable {
146                         try {
147                                 Thread.sleep(500)
148                         } catch (ie1: InterruptedException) {
149                                 throw RuntimeException(ie1)
150                         }
151
152                         soneInserter.stop()
153                 }).start()
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)))
157         }
158
159         @Test
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 {
167                         soneInserter.stop()
168                         throw soneException
169                 }
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()
179         }
180
181         @Test
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()
188         }
189
190         @Test
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>> {
195                         soneInserter.stop()
196                         throw NullPointerException()
197                 }
198                 whenever(soneModificationDetector.isEligibleForInsert).thenAnswer(stopInserterAndThrowException)
199                 soneInserter.serviceRun()
200         }
201
202         @Test
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"))
216         }
217
218         @Test
219         fun `invalid template returns a null 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"),
225                                 nullValue())
226         }
227
228         @Test
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"),
236                                 nullValue())
237         }
238
239         @Test
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)
247                 doAnswer {
248                         soneInserter.stop()
249                         null
250                 }.whenever(core).touchConfiguration()
251                 soneInserter.serviceRun()
252                 val histogram = metricRegistry.histogram("sone.insert.duration")
253                 assertThat(histogram.count, equalTo(1L))
254         }
255
256         @Test
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 {
263                         soneInserter.stop()
264                         throw SoneException(Exception())
265                 }
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))
271         }
272
273 }
274
275 val insertUri = createInsertUri
276 val expectedInsertUri: FreenetURI = FreenetURI(insertUri.toString())
277                 .setKeyType("USK")
278                 .setDocName("Sone")
279                 .setMetaString(kotlin.emptyArray())
280                 .setSuggestedEdition(0)