package net.pterodactylus.sone.core import com.google.common.eventbus.* import freenet.client.* import freenet.keys.* import freenet.support.io.* import net.pterodactylus.sone.core.FreenetInterface.* import net.pterodactylus.sone.core.event.* import net.pterodactylus.sone.main.* import net.pterodactylus.sone.test.* import net.pterodactylus.util.version.Version import org.hamcrest.MatcherAssert.* import org.hamcrest.Matchers.equalTo import org.hamcrest.Matchers.instanceOf import org.junit.* import org.mockito.ArgumentCaptor.* import org.mockito.ArgumentMatchers.any import org.mockito.Mockito.never import org.mockito.Mockito.times import org.mockito.Mockito.verify import org.mockito.hamcrest.MockitoHamcrest.* import java.io.* import kotlin.Long.Companion.MAX_VALUE /** * Unit test for [UpdateChecker]. */ class UpdateCheckerTest { private val eventBus = mock() private val freenetInterface = mock() private val currentVersion = Version(1, 0, 0) private val pluginHomepage = PluginHomepage("KSK@homepage") private val updateChecker = UpdateChecker(eventBus, freenetInterface, currentVersion, pluginHomepage) @Before fun startUpdateChecker() { updateChecker.start() } @Test fun `new update checker does not have a latest version`() { assertThat(updateChecker.hasLatestVersion(), equalTo(false)) assertThat(updateChecker.latestVersion, equalTo(currentVersion)) } @Test fun `starting an update checker register a usk`() { verify(freenetInterface).registerUsk(any(FreenetURI::class.java), any(Callback::class.java)) } @Test fun `stopping an update checker unregisters a usk`() { updateChecker.stop() verify(freenetInterface).unregisterUsk(any(FreenetURI::class.java)) } @Test fun `callback does not download if new edition is not found`() { setupCallbackWithEdition(MAX_VALUE, false) verify(freenetInterface, never()).fetchUri(any(FreenetURI::class.java)) verify(eventBus, never()).post(argThat(instanceOf(UpdateFoundEvent::class.java))) } private fun setupCallbackWithEdition(edition: Long, newKnownGood: Boolean, newSlot: Boolean = false) { val uri = forClass(FreenetURI::class.java) val callback = forClass(Callback::class.java) verify(freenetInterface).registerUsk(uri.capture(), callback.capture()) callback.value.editionFound(uri.value, edition, newKnownGood, newSlot) } @Test fun `callback starts if new edition is found`() { setupFetchResult(createFutureFetchResult()) setupCallbackWithEdition(MAX_VALUE, true) verifyAFreenetUriIsFetched() verifyEventIsFired(Version(99, 0, 0), 11865368297000L, false) verifyThatUpdateCheckerKnowsLatestVersion(Version(99, 0, 0), 11865368297000L) } private fun createFutureFetchResult(): FetchResult { val clientMetadata = ClientMetadata("application/xml") val fetched = ArrayBucket(("# MapConfigurationBackendVersion=1\n" + "CurrentVersion/Version: 99.0.0\n" + "CurrentVersion/ReleaseTime: 11865368297000\n" + "DisruptiveVersion/0.1.2: true").toByteArray()) return FetchResult(clientMetadata, fetched) } private fun verifyEventIsFired(version: Version, releaseTime: Long, disruptive: Boolean) { val updateFoundEvent = forClass(UpdateFoundEvent::class.java) verify(eventBus, times(1)).post(updateFoundEvent.capture()) assertThat(updateFoundEvent.value.version, equalTo(version)) assertThat(updateFoundEvent.value.releaseTime, equalTo(releaseTime)) assertThat(updateFoundEvent.value.isDisruptive, equalTo(disruptive)) } private fun verifyThatUpdateCheckerKnowsLatestVersion(version: Version, releaseTime: Long) { assertThat(updateChecker.latestVersion, equalTo(version)) assertThat(updateChecker.latestVersionDate, equalTo(releaseTime)) assertThat(updateChecker.hasLatestVersion(), equalTo(true)) } @Test fun `callback does not start if no new edition is found`() { setupFetchResult(createPastFetchResult()) setupCallbackWithEdition(updateChecker.latestEdition, true) verifyAFreenetUriIsFetched() verifyNoUpdateFoundEventIsFired() } private fun setupFetchResult(pastFetchResult: FetchResult) { whenever(freenetInterface.fetchUri(any(FreenetURI::class.java))).thenAnswer { invocation -> val freenetUri = invocation.arguments[0] as FreenetURI Fetched(freenetUri, pastFetchResult) } } private fun createPastFetchResult(): FetchResult { val clientMetadata = ClientMetadata("application/xml") val fetched = ArrayBucket(("# MapConfigurationBackendVersion=1\n" + "CurrentVersion/Version: 0.2\n" + "CurrentVersion/ReleaseTime: 1289417883000").toByteArray()) return FetchResult(clientMetadata, fetched) } @Test fun `invalid update file does not start callback`() { setupFetchResult(createInvalidFetchResult()) setupCallbackWithEdition(MAX_VALUE, true) verifyAFreenetUriIsFetched() verifyNoUpdateFoundEventIsFired() } private fun createInvalidFetchResult(): FetchResult { val clientMetadata = ClientMetadata("text/plain") val fetched = ArrayBucket("Some other data.".toByteArray()) return FetchResult(clientMetadata, fetched) } @Test fun `non existing properties will not cause update to be found`() { setupCallbackWithEdition(MAX_VALUE, true) verifyAFreenetUriIsFetched() verifyNoUpdateFoundEventIsFired() } private fun verifyNoUpdateFoundEventIsFired() { verify(eventBus, never()).post(any(UpdateFoundEvent::class.java)) } private fun verifyAFreenetUriIsFetched() { verify(freenetInterface).fetchUri(any(FreenetURI::class.java)) } @Test fun `broken bucket does not cause update to be found`() { setupFetchResult(createBrokenBucketFetchResult()) setupCallbackWithEdition(MAX_VALUE, true) verifyAFreenetUriIsFetched() verifyNoUpdateFoundEventIsFired() } private fun createBrokenBucketFetchResult(): FetchResult { val clientMetadata = ClientMetadata("text/plain") val fetched = object : ArrayBucket("Some other data.".toByteArray()) { override fun getInputStream() = whenever(mock().read()).thenThrow(IOException()).getMock() } return FetchResult(clientMetadata, fetched) } @Test fun `invalid time does not cause an update to be found`() { setupFetchResult(createInvalidTimeFetchResult()) setupCallbackWithEdition(MAX_VALUE, true) verifyAFreenetUriIsFetched() verifyNoUpdateFoundEventIsFired() } private fun createInvalidTimeFetchResult(): FetchResult { val clientMetadata = ClientMetadata("application/xml") val fetched = ArrayBucket(("# MapConfigurationBackendVersion=1\n" + "CurrentVersion/Version: 0.2\n" + "CurrentVersion/ReleaseTime: invalid").toByteArray()) return FetchResult(clientMetadata, fetched) } @Test fun `invalid properties does not cause an update to be found`() { setupFetchResult(createMissingTimeFetchResult()) setupCallbackWithEdition(MAX_VALUE, true) verifyAFreenetUriIsFetched() verifyNoUpdateFoundEventIsFired() } private fun createMissingTimeFetchResult(): FetchResult { val clientMetadata = ClientMetadata("application/xml") val fetched = ArrayBucket(("# MapConfigurationBackendVersion=1\nCurrentVersion/Version: 0.2\n").toByteArray()) return FetchResult(clientMetadata, fetched) } @Test fun `invalid version does not cause an update to be found`() { setupFetchResult(createInvalidVersionFetchResult()) setupCallbackWithEdition(MAX_VALUE, true) verifyAFreenetUriIsFetched() verifyNoUpdateFoundEventIsFired() } private fun createInvalidVersionFetchResult(): FetchResult { val clientMetadata = ClientMetadata("application/xml") val fetched = ArrayBucket(("# MapConfigurationBackendVersion=1\n" + "CurrentVersion/Version: foo\n" + "CurrentVersion/ReleaseTime: 1289417883000").toByteArray()) return FetchResult(clientMetadata, fetched) } @Test fun `disruptive version gets notification`() { setupFetchResult(createDisruptiveVersionFetchResult()) setupCallbackWithEdition(MAX_VALUE, true) verifyAFreenetUriIsFetched() verifyEventIsFired(Version(1, 2, 3), 1289417883000L, true) verifyThatUpdateCheckerKnowsLatestVersion(Version(1, 2, 3), 1289417883000L) } private fun createDisruptiveVersionFetchResult(): FetchResult { val clientMetadata = ClientMetadata("application/xml") val fetched = ArrayBucket(("# MapConfigurationBackendVersion=1\n" + "CurrentVersion/Version: 1.2.3\n" + "CurrentVersion/ReleaseTime: 1289417883000\n" + "DisruptiveVersion/1.2.3: true").toByteArray()) return FetchResult(clientMetadata, fetched) } }