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.io.ByteStreams.*
8 import com.google.common.util.concurrent.MoreExecutors.*
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.*
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.*
31 import kotlin.test.Test
34 * Unit test for [SoneInserter] and its subclasses.
36 class SoneInserterTest {
38 private val metricRegistry = MetricRegistry()
39 private val core = mock<Core>()
40 private val eventBus = mock<EventBus>()
41 private val freenetInterface = mock<FreenetInterface>()
45 val updateChecker = mock<UpdateChecker>()
46 whenever(core.updateChecker).thenReturn(updateChecker)
47 whenever(core.getSone(anyString())).thenReturn(null)
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))
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)
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))
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))
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"))
91 fun `sone inserter stops when it should`() {
92 val soneInserter = SoneInserter(core, eventBus, freenetInterface, metricRegistry, "SoneId")
94 soneInserter.serviceRun()
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)
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))
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 {
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()
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)
149 } catch (ie1: InterruptedException) {
150 throw RuntimeException(ie1)
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)))
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 {
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()
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()
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>> {
197 throw NullPointerException()
199 whenever(soneModificationDetector.isEligibleForInsert).thenAnswer(stopInserterAndThrowException)
200 soneInserter.serviceRun()
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"))
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"),
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"),
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)
251 }.whenever(core).touchConfiguration()
252 soneInserter.serviceRun()
253 val histogram = metricRegistry.histogram("sone.insert.duration")
254 assertThat(histogram.count, equalTo(1L))
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 {
265 throw SoneException(Exception())
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))
276 val insertUri = createInsertUri
277 val expectedInsertUri: FreenetURI = FreenetURI(insertUri.toString())
280 .setMetaString(kotlin.emptyArray())
281 .setSuggestedEdition(0)