🔀 Merge branch 'release/v82'
[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         private val soneUriCreator = object : SoneUriCreator() {
42                 override fun getInsertUri(sone: Sone): FreenetURI = expectedInsertUri
43         }
44
45         @Before
46         fun setupCore() {
47                 val updateChecker = mock<UpdateChecker>()
48                 whenever(core.updateChecker).thenReturn(updateChecker)
49                 whenever(core.getSone(anyString())).thenReturn(null)
50         }
51
52         @Test
53         fun `insertion delay is forwarded to sone inserter`() {
54                 val eventBus = AsyncEventBus(directExecutor())
55                 eventBus.register(SoneInserter(core, eventBus, freenetInterface, metricRegistry, soneUriCreator, "SoneId"))
56                 eventBus.post(InsertionDelayChangedEvent(15))
57                 assertThat(SoneInserter.getInsertionDelay().get(), equalTo(15))
58         }
59
60         private fun createSone(insertUri: FreenetURI, fingerprint: String = "fingerprint"): Sone {
61                 val ownIdentity = DefaultOwnIdentity("", "", "", insertUri.toString())
62                 val sone = mock<Sone>()
63                 whenever(sone.identity).thenReturn(ownIdentity)
64                 whenever(sone.fingerprint).thenReturn(fingerprint)
65                 whenever(sone.rootAlbum).thenReturn(mock())
66                 whenever(core.getSone(anyString())).thenReturn(sone)
67                 return sone
68         }
69
70         @Test
71         fun `isModified is true if modification detector says so`() {
72                 val soneModificationDetector = mock<SoneModificationDetector>()
73                 whenever(soneModificationDetector.isModified).thenReturn(true)
74                 val soneInserter = SoneInserter(core, eventBus, freenetInterface, metricRegistry, soneUriCreator, "SoneId", soneModificationDetector, 1)
75                 assertThat(soneInserter.isModified, equalTo(true))
76         }
77
78         @Test
79         fun `isModified is false if modification detector says so`() {
80                 val soneModificationDetector = mock<SoneModificationDetector>()
81                 val soneInserter = SoneInserter(core, eventBus, freenetInterface, metricRegistry, soneUriCreator, "SoneId", soneModificationDetector, 1)
82                 assertThat(soneInserter.isModified, equalTo(false))
83         }
84
85         @Test
86         fun `last fingerprint is stored correctly`() {
87                 val soneInserter = SoneInserter(core, eventBus, freenetInterface, metricRegistry, soneUriCreator, "SoneId")
88                 soneInserter.lastInsertFingerprint = "last-fingerprint"
89                 assertThat(soneInserter.lastInsertFingerprint, equalTo("last-fingerprint"))
90         }
91
92         @Test
93         fun `sone inserter stops when it should`() {
94                 val soneInserter = SoneInserter(core, eventBus, freenetInterface, metricRegistry, soneUriCreator, "SoneId")
95                 soneInserter.stop()
96                 soneInserter.serviceRun()
97         }
98
99         @Test
100         fun `sone inserter inserts a sone if it is eligible`() {
101                 val finalUri = mock<FreenetURI>()
102                 val sone = createSone(insertUri)
103                 val soneModificationDetector = mock<SoneModificationDetector>()
104                 whenever(soneModificationDetector.isEligibleForInsert).thenReturn(true)
105                 whenever(freenetInterface.insertDirectory(eq(expectedInsertUri), any<HashMap<String, Any>>(), eq("index.html"))).thenReturn(finalUri)
106                 val soneInserter = SoneInserter(core, eventBus, freenetInterface, metricRegistry, soneUriCreator, "SoneId", soneModificationDetector, 1)
107                 doAnswer {
108                         soneInserter.stop()
109                         null
110                 }.whenever(core).touchConfiguration()
111                 soneInserter.serviceRun()
112                 val soneEvents = ArgumentCaptor.forClass(SoneEvent::class.java)
113                 verify(freenetInterface).insertDirectory(eq(expectedInsertUri), any<HashMap<String, Any>>(), eq("index.html"))
114                 verify(eventBus, times(2)).post(soneEvents.capture())
115                 assertThat(soneEvents.allValues[0], instanceOf(SoneInsertingEvent::class.java))
116                 assertThat(soneEvents.allValues[0].sone, equalTo(sone))
117                 assertThat(soneEvents.allValues[1], instanceOf(SoneInsertedEvent::class.java))
118                 assertThat(soneEvents.allValues[1].sone, equalTo(sone))
119         }
120
121         @Test
122         fun `sone inserter bails out if it is stopped while inserting`() {
123                 val finalUri = mock<FreenetURI>()
124                 val sone = createSone(insertUri)
125                 val soneModificationDetector = mock<SoneModificationDetector>()
126                 whenever(soneModificationDetector.isEligibleForInsert).thenReturn(true)
127                 val soneInserter = SoneInserter(core, eventBus, freenetInterface, metricRegistry, soneUriCreator, "SoneId", soneModificationDetector, 1)
128                 whenever(freenetInterface.insertDirectory(eq(expectedInsertUri), any<HashMap<String, Any>>(), eq("index.html"))).thenAnswer {
129                         soneInserter.stop()
130                         finalUri
131                 }
132                 soneInserter.serviceRun()
133                 val soneEvents = ArgumentCaptor.forClass(SoneEvent::class.java)
134                 verify(freenetInterface).insertDirectory(eq(expectedInsertUri), any<HashMap<String, Any>>(), eq("index.html"))
135                 verify(eventBus, times(2)).post(soneEvents.capture())
136                 assertThat(soneEvents.allValues[0], instanceOf(SoneInsertingEvent::class.java))
137                 assertThat(soneEvents.allValues[0].sone, equalTo(sone))
138                 assertThat(soneEvents.allValues[1], instanceOf(SoneInsertedEvent::class.java))
139                 assertThat(soneEvents.allValues[1].sone, equalTo(sone))
140                 verify(core, never()).touchConfiguration()
141         }
142
143         @Test
144         fun `sone inserter does not insert sone if it is not eligible`() {
145                 createSone(insertUri)
146                 val soneModificationDetector = mock<SoneModificationDetector>()
147                 val soneInserter = SoneInserter(core, eventBus, freenetInterface, metricRegistry, soneUriCreator, "SoneId", soneModificationDetector, 1)
148                 Thread(Runnable {
149                         try {
150                                 Thread.sleep(500)
151                         } catch (ie1: InterruptedException) {
152                                 throw RuntimeException(ie1)
153                         }
154
155                         soneInserter.stop()
156                 }).start()
157                 soneInserter.serviceRun()
158                 verify(freenetInterface, never()).insertDirectory(eq(expectedInsertUri), any<HashMap<String, Any>>(), eq("index.html"))
159                 verify(eventBus, never()).post(argThat(org.hamcrest.Matchers.any(SoneEvent::class.java)))
160         }
161
162         @Test
163         fun `sone inserter posts aborted event if an exception occurs`() {
164                 val sone = createSone(insertUri)
165                 val soneModificationDetector = mock<SoneModificationDetector>()
166                 whenever(soneModificationDetector.isEligibleForInsert).thenReturn(true)
167                 val soneInserter = SoneInserter(core, eventBus, freenetInterface, metricRegistry, soneUriCreator, "SoneId", soneModificationDetector, 1)
168                 val soneException = SoneException(Exception())
169                 whenever(freenetInterface.insertDirectory(eq(expectedInsertUri), 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(expectedInsertUri), 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, soneUriCreator, "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, soneUriCreator, "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(manifestElement.data.inputStream.readBytes(), 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 a null 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 finalUri = mock<FreenetURI>()
245                 createSone(insertUri)
246                 val soneModificationDetector = mock<SoneModificationDetector>()
247                 whenever(soneModificationDetector.isEligibleForInsert).thenReturn(true)
248                 whenever(freenetInterface.insertDirectory(eq(expectedInsertUri), any<HashMap<String, Any>>(), eq("index.html"))).thenReturn(finalUri)
249                 val soneInserter = SoneInserter(core, eventBus, freenetInterface, metricRegistry, soneUriCreator, "SoneId", soneModificationDetector, 1)
250                 doAnswer {
251                         soneInserter.stop()
252                         null
253                 }.whenever(core).touchConfiguration()
254                 soneInserter.serviceRun()
255                 val histogram = metricRegistry.histogram("sone.insert.duration")
256                 assertThat(histogram.count, equalTo(1L))
257         }
258
259         @Test
260         fun `unsuccessful insert does not update histogram but records error`() {
261                 createSone(insertUri)
262                 val soneModificationDetector = mock<SoneModificationDetector>()
263                 whenever(soneModificationDetector.isEligibleForInsert).thenReturn(true)
264                 val soneInserter = SoneInserter(core, eventBus, freenetInterface, metricRegistry, soneUriCreator, "SoneId", soneModificationDetector, 1)
265                 whenever(freenetInterface.insertDirectory(eq(expectedInsertUri), any<HashMap<String, Any>>(), eq("index.html"))).thenAnswer {
266                         soneInserter.stop()
267                         throw SoneException(Exception())
268                 }
269                 soneInserter.serviceRun()
270                 val histogram = metricRegistry.histogram("sone.insert.duration")
271                 assertThat(histogram.count, equalTo(0L))
272                 val meter = metricRegistry.meter("sone.insert.errors")
273                 assertThat(meter.count, equalTo(1L))
274         }
275
276 }
277
278 val insertUri = createInsertUri
279 val expectedInsertUri = createInsertUri