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.util.concurrent.MoreExecutors.*
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.*
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>()
41 private val soneUriCreator = object : SoneUriCreator() {
42 override fun getInsertUri(sone: Sone): FreenetURI = expectedInsertUri
47 val updateChecker = mock<UpdateChecker>()
48 whenever(core.updateChecker).thenReturn(updateChecker)
49 whenever(core.getSone(anyString())).thenReturn(null)
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))
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)
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))
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))
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"))
93 fun `sone inserter stops when it should`() {
94 val soneInserter = SoneInserter(core, eventBus, freenetInterface, metricRegistry, soneUriCreator, "SoneId")
96 soneInserter.serviceRun()
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)
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))
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 {
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()
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)
151 } catch (ie1: InterruptedException) {
152 throw RuntimeException(ie1)
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)))
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 {
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()
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()
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>> {
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(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"))
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"),
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 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)
253 }.whenever(core).touchConfiguration()
254 soneInserter.serviceRun()
255 val histogram = metricRegistry.histogram("sone.insert.duration")
256 assertThat(histogram.count, equalTo(1L))
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 {
267 throw SoneException(Exception())
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))
278 val insertUri = createInsertUri
279 val expectedInsertUri = createInsertUri