🔀 Merge branch 'release/v82'
[Sone.git] / src / test / kotlin / net / pterodactylus / sone / core / UpdateCheckerTest.kt
1 package net.pterodactylus.sone.core
2
3 import com.google.common.eventbus.*
4 import freenet.client.*
5 import freenet.keys.*
6 import freenet.support.io.*
7 import net.pterodactylus.sone.core.FreenetInterface.*
8 import net.pterodactylus.sone.core.event.*
9 import net.pterodactylus.sone.main.*
10 import net.pterodactylus.sone.test.*
11 import net.pterodactylus.util.version.Version
12 import org.hamcrest.MatcherAssert.*
13 import org.hamcrest.Matchers.equalTo
14 import org.hamcrest.Matchers.instanceOf
15 import org.junit.*
16 import org.mockito.ArgumentCaptor.*
17 import org.mockito.ArgumentMatchers.any
18 import org.mockito.Mockito.never
19 import org.mockito.Mockito.times
20 import org.mockito.Mockito.verify
21 import org.mockito.hamcrest.MockitoHamcrest.*
22 import java.io.*
23 import kotlin.Long.Companion.MAX_VALUE
24
25 /**
26  * Unit test for [UpdateChecker].
27  */
28 class UpdateCheckerTest {
29
30         private val eventBus = mock<EventBus>()
31         private val freenetInterface = mock<FreenetInterface>()
32         private val currentVersion = Version(1, 0, 0)
33         private val pluginHomepage = PluginHomepage("KSK@homepage")
34         private val updateChecker = UpdateChecker(eventBus, freenetInterface, currentVersion, pluginHomepage)
35
36         @Before
37         fun startUpdateChecker() {
38                 updateChecker.start()
39         }
40
41         @Test
42         fun `new update checker does not have a latest version`() {
43                 assertThat(updateChecker.hasLatestVersion(), equalTo(false))
44                 assertThat(updateChecker.latestVersion, equalTo(currentVersion))
45         }
46
47         @Test
48         fun `starting an update checker register a usk`() {
49                 verify(freenetInterface).registerUsk(any(FreenetURI::class.java), any(Callback::class.java))
50         }
51
52         @Test
53         fun `stopping an update checker unregisters a usk`() {
54                 updateChecker.stop()
55                 verify(freenetInterface).unregisterUsk(any(FreenetURI::class.java))
56         }
57
58         @Test
59         fun `callback does not download if new edition is not found`() {
60                 setupCallbackWithEdition(MAX_VALUE, false)
61                 verify(freenetInterface, never()).fetchUri(any(FreenetURI::class.java))
62                 verify(eventBus, never()).post(argThat(instanceOf(UpdateFoundEvent::class.java)))
63         }
64
65         private fun setupCallbackWithEdition(edition: Long, newKnownGood: Boolean, newSlot: Boolean = false) {
66                 val uri = forClass(FreenetURI::class.java)
67                 val callback = forClass(Callback::class.java)
68                 verify(freenetInterface).registerUsk(uri.capture(), callback.capture())
69                 callback.value.editionFound(uri.value, edition, newKnownGood, newSlot)
70         }
71
72         @Test
73         fun `callback starts if new edition is found`() {
74                 setupFetchResult(createFutureFetchResult())
75                 setupCallbackWithEdition(MAX_VALUE, true)
76                 verifyAFreenetUriIsFetched()
77                 verifyEventIsFired(Version(99, 0, 0), 11865368297000L, false)
78                 verifyThatUpdateCheckerKnowsLatestVersion(Version(99, 0, 0), 11865368297000L)
79         }
80
81         private fun createFutureFetchResult(): FetchResult {
82                 val clientMetadata = ClientMetadata("application/xml")
83                 val fetched = ArrayBucket(("# MapConfigurationBackendVersion=1\n" +
84                                 "CurrentVersion/Version: 99.0.0\n" +
85                                 "CurrentVersion/ReleaseTime: 11865368297000\n" +
86                                 "DisruptiveVersion/0.1.2: true").toByteArray())
87                 return FetchResult(clientMetadata, fetched)
88         }
89
90         private fun verifyEventIsFired(version: Version, releaseTime: Long, disruptive: Boolean) {
91                 val updateFoundEvent = forClass(UpdateFoundEvent::class.java)
92                 verify(eventBus, times(1)).post(updateFoundEvent.capture())
93                 assertThat(updateFoundEvent.value.version, equalTo(version))
94                 assertThat(updateFoundEvent.value.releaseTime, equalTo(releaseTime))
95                 assertThat(updateFoundEvent.value.isDisruptive, equalTo(disruptive))
96         }
97
98         private fun verifyThatUpdateCheckerKnowsLatestVersion(version: Version, releaseTime: Long) {
99                 assertThat(updateChecker.latestVersion, equalTo(version))
100                 assertThat(updateChecker.latestVersionDate, equalTo(releaseTime))
101                 assertThat(updateChecker.hasLatestVersion(), equalTo(true))
102         }
103
104         @Test
105         fun `callback does not start if no new edition is found`() {
106                 setupFetchResult(createPastFetchResult())
107                 setupCallbackWithEdition(updateChecker.latestEdition, true)
108                 verifyAFreenetUriIsFetched()
109                 verifyNoUpdateFoundEventIsFired()
110         }
111
112         private fun setupFetchResult(pastFetchResult: FetchResult) {
113                 whenever(freenetInterface.fetchUri(any(FreenetURI::class.java))).thenAnswer { invocation ->
114                         val freenetUri = invocation.arguments[0] as FreenetURI
115                         Fetched(freenetUri, pastFetchResult)
116                 }
117         }
118
119         private fun createPastFetchResult(): FetchResult {
120                 val clientMetadata = ClientMetadata("application/xml")
121                 val fetched = ArrayBucket(("# MapConfigurationBackendVersion=1\n" +
122                                 "CurrentVersion/Version: 0.2\n" +
123                                 "CurrentVersion/ReleaseTime: 1289417883000").toByteArray())
124                 return FetchResult(clientMetadata, fetched)
125         }
126
127         @Test
128         fun `invalid update file does not start callback`() {
129                 setupFetchResult(createInvalidFetchResult())
130                 setupCallbackWithEdition(MAX_VALUE, true)
131                 verifyAFreenetUriIsFetched()
132                 verifyNoUpdateFoundEventIsFired()
133         }
134
135         private fun createInvalidFetchResult(): FetchResult {
136                 val clientMetadata = ClientMetadata("text/plain")
137                 val fetched = ArrayBucket("Some other data.".toByteArray())
138                 return FetchResult(clientMetadata, fetched)
139         }
140
141         @Test
142         fun `non existing properties will not cause update to be found`() {
143                 setupCallbackWithEdition(MAX_VALUE, true)
144                 verifyAFreenetUriIsFetched()
145                 verifyNoUpdateFoundEventIsFired()
146         }
147
148         private fun verifyNoUpdateFoundEventIsFired() {
149                 verify(eventBus, never()).post(any(UpdateFoundEvent::class.java))
150         }
151
152         private fun verifyAFreenetUriIsFetched() {
153                 verify(freenetInterface).fetchUri(any(FreenetURI::class.java))
154         }
155
156         @Test
157         fun `broken bucket does not cause update to be found`() {
158                 setupFetchResult(createBrokenBucketFetchResult())
159                 setupCallbackWithEdition(MAX_VALUE, true)
160                 verifyAFreenetUriIsFetched()
161                 verifyNoUpdateFoundEventIsFired()
162         }
163
164         private fun createBrokenBucketFetchResult(): FetchResult {
165                 val clientMetadata = ClientMetadata("text/plain")
166                 val fetched = object : ArrayBucket("Some other data.".toByteArray()) {
167                         override fun getInputStream() =
168                                         whenever(mock<InputStream>().read()).thenThrow(IOException()).getMock<InputStream>()
169                 }
170                 return FetchResult(clientMetadata, fetched)
171         }
172
173         @Test
174         fun `invalid time does not cause an update to be found`() {
175                 setupFetchResult(createInvalidTimeFetchResult())
176                 setupCallbackWithEdition(MAX_VALUE, true)
177                 verifyAFreenetUriIsFetched()
178                 verifyNoUpdateFoundEventIsFired()
179         }
180
181         private fun createInvalidTimeFetchResult(): FetchResult {
182                 val clientMetadata = ClientMetadata("application/xml")
183                 val fetched = ArrayBucket(("# MapConfigurationBackendVersion=1\n" +
184                                 "CurrentVersion/Version: 0.2\n" +
185                                 "CurrentVersion/ReleaseTime: invalid").toByteArray())
186                 return FetchResult(clientMetadata, fetched)
187         }
188
189         @Test
190         fun `invalid properties does not cause an update to be found`() {
191                 setupFetchResult(createMissingTimeFetchResult())
192                 setupCallbackWithEdition(MAX_VALUE, true)
193                 verifyAFreenetUriIsFetched()
194                 verifyNoUpdateFoundEventIsFired()
195         }
196
197         private fun createMissingTimeFetchResult(): FetchResult {
198                 val clientMetadata = ClientMetadata("application/xml")
199                 val fetched = ArrayBucket(("# MapConfigurationBackendVersion=1\nCurrentVersion/Version: 0.2\n").toByteArray())
200                 return FetchResult(clientMetadata, fetched)
201         }
202
203         @Test
204         fun `invalid version does not cause an update to be found`() {
205                 setupFetchResult(createInvalidVersionFetchResult())
206                 setupCallbackWithEdition(MAX_VALUE, true)
207                 verifyAFreenetUriIsFetched()
208                 verifyNoUpdateFoundEventIsFired()
209         }
210
211         private fun createInvalidVersionFetchResult(): FetchResult {
212                 val clientMetadata = ClientMetadata("application/xml")
213                 val fetched = ArrayBucket(("# MapConfigurationBackendVersion=1\n" +
214                                 "CurrentVersion/Version: foo\n" +
215                                 "CurrentVersion/ReleaseTime: 1289417883000").toByteArray())
216                 return FetchResult(clientMetadata, fetched)
217         }
218
219         @Test
220         fun `disruptive version gets notification`() {
221                 setupFetchResult(createDisruptiveVersionFetchResult())
222                 setupCallbackWithEdition(MAX_VALUE, true)
223                 verifyAFreenetUriIsFetched()
224                 verifyEventIsFired(Version(1, 2, 3), 1289417883000L, true)
225                 verifyThatUpdateCheckerKnowsLatestVersion(Version(1, 2, 3), 1289417883000L)
226         }
227
228         private fun createDisruptiveVersionFetchResult(): FetchResult {
229                 val clientMetadata = ClientMetadata("application/xml")
230                 val fetched = ArrayBucket(("# MapConfigurationBackendVersion=1\n" +
231                                 "CurrentVersion/Version: 1.2.3\n" +
232                                 "CurrentVersion/ReleaseTime: 1289417883000\n" +
233                                 "DisruptiveVersion/1.2.3: true").toByteArray())
234                 return FetchResult(clientMetadata, fetched)
235         }
236
237 }