🔀 Merge “release/v81” into “master”
[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.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 sone = mock<Sone>()
59                 whenever(sone.insertUri).thenReturn(insertUri)
60                 whenever(sone.fingerprint).thenReturn(fingerprint)
61                 whenever(sone.rootAlbum).thenReturn(mock())
62                 whenever(core.getSone(anyString())).thenReturn(sone)
63                 return sone
64         }
65
66         @Test
67         fun `isModified is true if modification detector says so`() {
68                 val soneModificationDetector = mock<SoneModificationDetector>()
69                 whenever(soneModificationDetector.isModified).thenReturn(true)
70                 val soneInserter = SoneInserter(core, eventBus, freenetInterface, metricRegistry, "SoneId", soneModificationDetector, 1)
71                 assertThat(soneInserter.isModified, equalTo(true))
72         }
73
74         @Test
75         fun `isModified is false if modification detector says so`() {
76                 val soneModificationDetector = mock<SoneModificationDetector>()
77                 val soneInserter = SoneInserter(core, eventBus, freenetInterface, metricRegistry, "SoneId", soneModificationDetector, 1)
78                 assertThat(soneInserter.isModified, equalTo(false))
79         }
80
81         @Test
82         fun `last fingerprint is stored correctly`() {
83                 val soneInserter = SoneInserter(core, eventBus, freenetInterface, metricRegistry, "SoneId")
84                 soneInserter.lastInsertFingerprint = "last-fingerprint"
85                 assertThat(soneInserter.lastInsertFingerprint, equalTo("last-fingerprint"))
86         }
87
88         @Test
89         fun `sone inserter stops when it should`() {
90                 val soneInserter = SoneInserter(core, eventBus, freenetInterface, metricRegistry, "SoneId")
91                 soneInserter.stop()
92                 soneInserter.serviceRun()
93         }
94
95         @Test
96         fun `sone inserter inserts a sone if it is eligible`() {
97                 val insertUri = mock<FreenetURI>()
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(insertUri), 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(insertUri), 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 insertUri = mock<FreenetURI>()
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(insertUri), 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(insertUri), 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                 val insertUri = mock<FreenetURI>()
144                 createSone(insertUri)
145                 val soneModificationDetector = mock<SoneModificationDetector>()
146                 val soneInserter = SoneInserter(core, eventBus, freenetInterface, metricRegistry, "SoneId", soneModificationDetector, 1)
147                 Thread(Runnable {
148                         try {
149                                 Thread.sleep(500)
150                         } catch (ie1: InterruptedException) {
151                                 throw RuntimeException(ie1)
152                         }
153
154                         soneInserter.stop()
155                 }).start()
156                 soneInserter.serviceRun()
157                 verify(freenetInterface, never()).insertDirectory(eq(insertUri), any<HashMap<String, Any>>(), eq("index.html"))
158                 verify(eventBus, never()).post(argThat(org.hamcrest.Matchers.any(SoneEvent::class.java)))
159         }
160
161         @Test
162         fun `sone inserter posts aborted event if an exception occurs`() {
163                 val insertUri = mock<FreenetURI>()
164                 val sone = createSone(insertUri)
165                 val soneModificationDetector = mock<SoneModificationDetector>()
166                 whenever(soneModificationDetector.isEligibleForInsert).thenReturn(true)
167                 val soneInserter = SoneInserter(core, eventBus, freenetInterface, metricRegistry, "SoneId", soneModificationDetector, 1)
168                 val soneException = SoneException(Exception())
169                 whenever(freenetInterface.insertDirectory(eq(insertUri), any<HashMap<String, Any>>(), eq("index.html"))).thenAnswer {
170                         soneInserter.stop()
171                         throw soneException
172                 }
173                 soneInserter.serviceRun()
174                 val soneEvents = ArgumentCaptor.forClass(SoneEvent::class.java)
175                 verify(freenetInterface).insertDirectory(eq(insertUri), any<HashMap<String, Any>>(), eq("index.html"))
176                 verify(eventBus, times(2)).post(soneEvents.capture())
177                 assertThat(soneEvents.allValues[0], instanceOf(SoneInsertingEvent::class.java))
178                 assertThat(soneEvents.allValues[0].sone, equalTo(sone))
179                 assertThat(soneEvents.allValues[1], instanceOf(SoneInsertAbortedEvent::class.java))
180                 assertThat(soneEvents.allValues[1].sone, equalTo(sone))
181                 verify(core, never()).touchConfiguration()
182         }
183
184         @Test
185         fun `sone inserter exits if sone is unknown`() {
186                 val soneModificationDetector = mock<SoneModificationDetector>()
187                 val soneInserter = SoneInserter(core, eventBus, freenetInterface, metricRegistry, "SoneId", soneModificationDetector, 1)
188                 whenever(soneModificationDetector.isEligibleForInsert).thenReturn(true)
189                 whenever(core.getSone("SoneId")).thenReturn(null)
190                 soneInserter.serviceRun()
191         }
192
193         @Test
194         fun `sone inserter catches exception and continues`() {
195                 val soneModificationDetector = mock<SoneModificationDetector>()
196                 val soneInserter = SoneInserter(core, eventBus, freenetInterface, metricRegistry, "SoneId", soneModificationDetector, 1)
197                 val stopInserterAndThrowException = Answer<Optional<Sone>> {
198                         soneInserter.stop()
199                         throw NullPointerException()
200                 }
201                 whenever(soneModificationDetector.isEligibleForInsert).thenAnswer(stopInserterAndThrowException)
202                 soneInserter.serviceRun()
203         }
204
205         @Test
206         fun `template is rendered correctly for manifest element`() {
207                 val soneProperties = HashMap<String, Any>()
208                 soneProperties["id"] = "SoneId"
209                 val manifestCreator = ManifestCreator(core, soneProperties)
210                 val now = currentTimeMillis()
211                 whenever(core.startupTime).thenReturn(now)
212                 val manifestElement = manifestCreator.createManifestElement("test.txt", "plain/text; charset=utf-8", "sone-inserter-manifest.txt")
213                 assertThat(manifestElement!!.name, equalTo("test.txt"))
214                 assertThat(manifestElement.mimeTypeOverride, equalTo("plain/text; charset=utf-8"))
215                 val templateContent = String(toByteArray(manifestElement.data.inputStream), Charsets.UTF_8)
216                 assertThat(templateContent, containsString("Sone Version: ${SonePlugin.getPluginVersion()}\n"))
217                 assertThat(templateContent, containsString("Core Startup: $now\n"))
218                 assertThat(templateContent, containsString("Sone ID: SoneId\n"))
219         }
220
221         @Test
222         fun `invalid template returns anull manifest element`() {
223                 val soneProperties = HashMap<String, Any>()
224                 val manifestCreator = ManifestCreator(core, soneProperties)
225                 assertThat(manifestCreator.createManifestElement("test.txt",
226                                 "plain/text; charset=utf-8",
227                                 "sone-inserter-invalid-manifest.txt"),
228                                 nullValue())
229         }
230
231         @Test
232         fun `error while rendering template returns a null manifest element`() {
233                 val soneProperties = HashMap<String, Any>()
234                 val manifestCreator = ManifestCreator(core, soneProperties)
235                 whenever(core.toString()).thenThrow(NullPointerException::class.java)
236                 assertThat(manifestCreator.createManifestElement("test.txt",
237                                 "plain/text; charset=utf-8",
238                                 "sone-inserter-faulty-manifest.txt"),
239                                 nullValue())
240         }
241
242         @Test
243         fun `successful insert updates metrics`() {
244                 val insertUri = mock<FreenetURI>()
245                 val finalUri = mock<FreenetURI>()
246                 createSone(insertUri)
247                 val soneModificationDetector = mock<SoneModificationDetector>()
248                 whenever(soneModificationDetector.isEligibleForInsert).thenReturn(true)
249                 whenever(freenetInterface.insertDirectory(eq(insertUri), any<HashMap<String, Any>>(), eq("index.html"))).thenReturn(finalUri)
250                 val soneInserter = SoneInserter(core, eventBus, freenetInterface, metricRegistry,"SoneId", soneModificationDetector, 1)
251                 doAnswer {
252                         soneInserter.stop()
253                         null
254                 }.whenever(core).touchConfiguration()
255                 soneInserter.serviceRun()
256                 val histogram = metricRegistry.histogram("sone.insert.duration")
257                 assertThat(histogram.count, equalTo(1L))
258         }
259
260         @Test
261         fun `unsuccessful insert does not update histogram but records error`() {
262                 val insertUri = mock<FreenetURI>()
263                 createSone(insertUri)
264                 val soneModificationDetector = mock<SoneModificationDetector>()
265                 whenever(soneModificationDetector.isEligibleForInsert).thenReturn(true)
266                 val soneInserter = SoneInserter(core, eventBus, freenetInterface, metricRegistry, "SoneId", soneModificationDetector, 1)
267                 whenever(freenetInterface.insertDirectory(eq(insertUri), any<HashMap<String, Any>>(), eq("index.html"))).thenAnswer {
268                         soneInserter.stop()
269                         throw SoneException(Exception())
270                 }
271                 soneInserter.serviceRun()
272                 val histogram = metricRegistry.histogram("sone.insert.duration")
273                 assertThat(histogram.count, equalTo(0L))
274                 val meter = metricRegistry.meter("sone.insert.errors")
275                 assertThat(meter.count, equalTo(1L))
276         }
277
278 }