package net.pterodactylus.sone.core;
import static java.lang.Long.MAX_VALUE;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.instanceOf;
import static org.hamcrest.Matchers.is;
import static org.mockito.ArgumentCaptor.forClass;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import static org.mockito.hamcrest.MockitoHamcrest.argThat;
import java.io.IOException;
import java.io.InputStream;
import net.pterodactylus.sone.core.FreenetInterface.Callback;
import net.pterodactylus.sone.core.FreenetInterface.Fetched;
import net.pterodactylus.sone.core.event.UpdateFoundEvent;
import net.pterodactylus.util.version.Version;
import freenet.client.ClientMetadata;
import freenet.client.FetchResult;
import freenet.keys.FreenetURI;
import freenet.support.api.Bucket;
import freenet.support.io.ArrayBucket;
import com.google.common.eventbus.EventBus;
import org.junit.Before;
import org.junit.Test;
import org.mockito.ArgumentCaptor;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
/**
* Unit test for {@link UpdateChecker}.
*
* @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
*/
public class UpdateCheckerTest {
private final EventBus eventBus = mock(EventBus.class);
private final FreenetInterface freenetInterface = mock(FreenetInterface.class);
private final Version currentVersion = new Version(1, 0, 0);
private final UpdateChecker updateChecker = new UpdateChecker(eventBus, freenetInterface, currentVersion);
@Before
public void startUpdateChecker() {
updateChecker.start();
}
@Test
public void newUpdateCheckerDoesNotHaveALatestVersion() {
assertThat(updateChecker.hasLatestVersion(), is(false));
assertThat(updateChecker.getLatestVersion(), is(currentVersion));
}
@Test
public void startingAnUpdateCheckerRegisterAUsk() {
verify(freenetInterface).registerUsk(any(FreenetURI.class), any(Callback.class));
}
@Test
public void stoppingAnUpdateCheckerUnregistersAUsk() {
updateChecker.stop();
verify(freenetInterface).unregisterUsk(any(FreenetURI.class));
}
@Test
public void callbackDoesNotDownloadIfNewEditionIsNotFound() {
setupCallbackWithEdition(MAX_VALUE, false, false);
verify(freenetInterface, never()).fetchUri(any(FreenetURI.class));
verify(eventBus, never()).post(argThat(instanceOf(UpdateFoundEvent.class)));
}
private void setupCallbackWithEdition(long edition, boolean newKnownGood, boolean newSlot) {
ArgumentCaptor<FreenetURI> uri = forClass(FreenetURI.class);
ArgumentCaptor<Callback> callback = forClass(Callback.class);
verify(freenetInterface).registerUsk(uri.capture(), callback.capture());
callback.getValue().editionFound(uri.getValue(), edition, newKnownGood, newSlot);
}
@Test
public void callbackStartsIfNewEditionIsFound() {
setupFetchResult(createFutureFetchResult());
setupCallbackWithEdition(MAX_VALUE, true, false);
verifyAFreenetUriIsFetched();
verifyEventIsFired(new Version(99, 0, 0), 11865368297000L, false);
verifyThatUpdateCheckerKnowsLatestVersion(new Version(99, 0, 0), 11865368297000L);
}
private FetchResult createFutureFetchResult() {
ClientMetadata clientMetadata = new ClientMetadata("application/xml");
Bucket fetched = new ArrayBucket(("# MapConfigurationBackendVersion=1\n" +
"CurrentVersion/Version: 99.0.0\n" +
"CurrentVersion/ReleaseTime: 11865368297000\n" +
"DisruptiveVersion/0.1.2: true").getBytes());
return new FetchResult(clientMetadata, fetched);
}
private void verifyEventIsFired(Version version, long releaseTime, boolean disruptive) {
ArgumentCaptor<UpdateFoundEvent> updateFoundEvent = forClass(UpdateFoundEvent.class);
verify(eventBus, times(1)).post(updateFoundEvent.capture());
assertThat(updateFoundEvent.getValue().version(), is(version));
assertThat(updateFoundEvent.getValue().releaseTime(), is(releaseTime));
assertThat(updateFoundEvent.getValue().disruptive(), is(disruptive));
}
private void verifyThatUpdateCheckerKnowsLatestVersion(Version version, long releaseTime) {
assertThat(updateChecker.getLatestVersion(), is(version));
assertThat(updateChecker.getLatestVersionDate(), is(releaseTime));
assertThat(updateChecker.hasLatestVersion(), is(true));
}
@Test
public void callbackDoesNotStartIfNoNewEditionIsFound() {
setupFetchResult(createPastFetchResult());
setupCallbackWithEdition(updateChecker.getLatestEdition(), true, false);
verifyAFreenetUriIsFetched();
verifyNoUpdateFoundEventIsFired();
}
private void setupFetchResult(final FetchResult pastFetchResult) {
when(freenetInterface.fetchUri(any(FreenetURI.class))).thenAnswer(new Answer<Fetched>() {
@Override
public Fetched answer(InvocationOnMock invocation) throws Throwable {
FreenetURI freenetUri = (FreenetURI) invocation.getArguments()[0];
return new Fetched(freenetUri, pastFetchResult);
}
});
}
private FetchResult createPastFetchResult() {
ClientMetadata clientMetadata = new ClientMetadata("application/xml");
Bucket fetched = new ArrayBucket(("# MapConfigurationBackendVersion=1\n" +
"CurrentVersion/Version: 0.2\n" +
"CurrentVersion/ReleaseTime: 1289417883000").getBytes());
return new FetchResult(clientMetadata, fetched);
}
@Test
public void invalidUpdateFileDoesNotStartCallback() {
setupFetchResult(createInvalidFetchResult());
setupCallbackWithEdition(MAX_VALUE, true, false);
verifyAFreenetUriIsFetched();
verifyNoUpdateFoundEventIsFired();
}
private FetchResult createInvalidFetchResult() {
ClientMetadata clientMetadata = new ClientMetadata("text/plain");
Bucket fetched = new ArrayBucket("Some other data.".getBytes());
return new FetchResult(clientMetadata, fetched);
}
@Test
public void nonExistingPropertiesWillNotCauseUpdateToBeFound() {
setupCallbackWithEdition(MAX_VALUE, true, false);
verifyAFreenetUriIsFetched();
verifyNoUpdateFoundEventIsFired();
}
private void verifyNoUpdateFoundEventIsFired() {
verify(eventBus, never()).post(any(UpdateFoundEvent.class));
}
private void verifyAFreenetUriIsFetched() {
verify(freenetInterface).fetchUri(any(FreenetURI.class));
}
@Test
public void brokenBucketDoesNotCauseUpdateToBeFound() {
setupFetchResult(createBrokenBucketFetchResult());
setupCallbackWithEdition(MAX_VALUE, true, false);
verifyAFreenetUriIsFetched();
verifyNoUpdateFoundEventIsFired();
}
private FetchResult createBrokenBucketFetchResult() {
ClientMetadata clientMetadata = new ClientMetadata("text/plain");
Bucket fetched = new ArrayBucket("Some other data.".getBytes()) {
@Override
public InputStream getInputStream() {
try {
return when(mock(InputStream.class).read()).thenThrow(IOException.class).getMock();
} catch (IOException ioe1) {
/* won’t throw here. */
return null;
}
}
};
return new FetchResult(clientMetadata, fetched);
}
@Test
public void invalidTimeDoesNotCauseAnUpdateToBeFound() {
setupFetchResult(createInvalidTimeFetchResult());
setupCallbackWithEdition(MAX_VALUE, true, false);
verifyAFreenetUriIsFetched();
verifyNoUpdateFoundEventIsFired();
}
private FetchResult createInvalidTimeFetchResult() {
ClientMetadata clientMetadata = new ClientMetadata("application/xml");
Bucket fetched = new ArrayBucket(("# MapConfigurationBackendVersion=1\n" +
"CurrentVersion/Version: 0.2\n" +
"CurrentVersion/ReleaseTime: invalid").getBytes());
return new FetchResult(clientMetadata, fetched);
}
@Test
public void invalidPropertiesDoesNotCauseAnUpdateToBeFound() {
setupFetchResult(createMissingTimeFetchResult());
setupCallbackWithEdition(MAX_VALUE, true, false);
verifyAFreenetUriIsFetched();
verifyNoUpdateFoundEventIsFired();
}
private FetchResult createMissingTimeFetchResult() {
ClientMetadata clientMetadata = new ClientMetadata("application/xml");
Bucket fetched = new ArrayBucket(("# MapConfigurationBackendVersion=1\n" +
"CurrentVersion/Version: 0.2\n").getBytes());
return new FetchResult(clientMetadata, fetched);
}
@Test
public void invalidVersionDoesNotCauseAnUpdateToBeFound() {
setupFetchResult(createInvalidVersionFetchResult());
setupCallbackWithEdition(MAX_VALUE, true, false);
verifyAFreenetUriIsFetched();
verifyNoUpdateFoundEventIsFired();
}
private FetchResult createInvalidVersionFetchResult() {
ClientMetadata clientMetadata = new ClientMetadata("application/xml");
Bucket fetched = new ArrayBucket(("# MapConfigurationBackendVersion=1\n" +
"CurrentVersion/Version: foo\n" +
"CurrentVersion/ReleaseTime: 1289417883000").getBytes());
return new FetchResult(clientMetadata, fetched);
}
@Test
public void disruptiveVersionGetsNotification() {
setupFetchResult(createDisruptiveVersionFetchResult());
setupCallbackWithEdition(MAX_VALUE, true, false);
verifyAFreenetUriIsFetched();
verifyEventIsFired(new Version(1, 2, 3), 1289417883000L, true);
verifyThatUpdateCheckerKnowsLatestVersion(new Version(1, 2, 3), 1289417883000L);
}
private FetchResult createDisruptiveVersionFetchResult() {
ClientMetadata clientMetadata = new ClientMetadata("application/xml");
Bucket fetched = new ArrayBucket(("# MapConfigurationBackendVersion=1\n" +
"CurrentVersion/Version: 1.2.3\n" +
"CurrentVersion/ReleaseTime: 1289417883000\n" +
"DisruptiveVersion/1.2.3: true").getBytes());
return new FetchResult(clientMetadata, fetched);
}
}