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.main.*
14 import net.pterodactylus.sone.test.*
15 import org.hamcrest.MatcherAssert.*
16 import org.hamcrest.Matchers.*
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.*
30 import kotlin.test.Test
33 * Unit test for [SoneInserter] and its subclasses.
35 class SoneInserterTest {
37 private val metricRegistry = MetricRegistry()
38 private val core = mock<Core>()
39 private val eventBus = mock<EventBus>()
40 private val freenetInterface = mock<FreenetInterface>()
44 val updateChecker = mock<UpdateChecker>()
45 whenever(core.updateChecker).thenReturn(updateChecker)
46 whenever(core.getSone(anyString())).thenReturn(null)
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))
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)
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))
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))
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"))
89 fun `sone inserter stops when it should`() {
90 val soneInserter = SoneInserter(core, eventBus, freenetInterface, metricRegistry, "SoneId")
92 soneInserter.serviceRun()
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)
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))
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 {
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()
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)
150 } catch (ie1: InterruptedException) {
151 throw RuntimeException(ie1)
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)))
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 {
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()
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()
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>> {
199 throw NullPointerException()
201 whenever(soneModificationDetector.isEligibleForInsert).thenAnswer(stopInserterAndThrowException)
202 soneInserter.serviceRun()
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"))
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"),
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"),
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)
254 }.whenever(core).touchConfiguration()
255 soneInserter.serviceRun()
256 val histogram = metricRegistry.histogram("sone.insert.duration")
257 assertThat(histogram.count, equalTo(1L))
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 {
269 throw SoneException(Exception())
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))