package net.pterodactylus.sone.core;
import static freenet.keys.InsertableClientSSK.createRandom;
import static java.lang.System.currentTimeMillis;
import static java.util.concurrent.TimeUnit.DAYS;
import static net.pterodactylus.sone.data.Sone.SoneStatus.downloading;
import static net.pterodactylus.sone.data.Sone.SoneStatus.idle;
import static net.pterodactylus.sone.data.Sone.SoneStatus.unknown;
import static net.pterodactylus.sone.web.AllPagesTestKt.getBaseInjector;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.notNullValue;
import static org.mockito.ArgumentCaptor.forClass;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
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 java.io.IOException;
import java.io.InputStream;
import net.pterodactylus.sone.core.FreenetInterface.Fetched;
import net.pterodactylus.sone.data.Sone;
import net.pterodactylus.sone.data.Sone.SoneStatus;
import net.pterodactylus.sone.freenet.wot.Identity;
import freenet.client.ClientMetadata;
import freenet.client.FetchResult;
import freenet.client.async.USKCallback;
import freenet.crypt.DummyRandomSource;
import freenet.keys.FreenetURI;
import freenet.keys.InsertableClientSSK;
import freenet.support.api.Bucket;
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 SoneDownloaderImpl} and its subclasses.
*
* @author David ‘Bombe’ Roden
*/
public class SoneDownloaderTest {
private final Core core = mock(Core.class);
private final FreenetInterface freenetInterface = mock(FreenetInterface.class);
private final SoneParser soneParser = mock(SoneParser.class);
private final SoneDownloaderImpl soneDownloader = new SoneDownloaderImpl(core, freenetInterface, soneParser);
private FreenetURI requestUri = mock(FreenetURI.class);
private Sone sone = mock(Sone.class);
@Before
public void setupSone() {
Sone sone = SoneDownloaderTest.this.sone;
Identity identity = mock(Identity.class);
InsertableClientSSK clientSSK = createRandom(new DummyRandomSource(), "WoT");
when(identity.getRequestUri()).thenReturn(clientSSK.getURI().toString());
when(identity.getId()).thenReturn("identity");
when(sone.getId()).thenReturn("identity");
when(sone.getIdentity()).thenReturn(identity);
requestUri = clientSSK.getURI().setKeyType("USK").setDocName("Sone");
when(sone.getRequestUri()).thenAnswer(new Answer() {
@Override
public FreenetURI answer(InvocationOnMock invocation)
throws Throwable {
return requestUri;
}
});
when(sone.getTime()).thenReturn(currentTimeMillis() - DAYS.toMillis(1));
}
private void setupSoneAsUnknown() {
when(sone.getTime()).thenReturn(0L);
}
@Test
public void addingASoneWillRegisterItsKey() {
soneDownloader.addSone(sone);
verify(freenetInterface).registerActiveUsk(eq(sone.getRequestUri()), any(
USKCallback.class));
verify(freenetInterface, never()).unregisterUsk(sone);
}
@Test
public void addingASoneTwiceWillAlsoDeregisterItsKey() {
soneDownloader.addSone(sone);
soneDownloader.addSone(sone);
verify(freenetInterface, times(2)).registerActiveUsk(eq(
sone.getRequestUri()), any(USKCallback.class));
verify(freenetInterface).unregisterUsk(sone);
}
@Test
public void stoppingTheSoneDownloaderUnregistersTheSone() {
soneDownloader.addSone(sone);
soneDownloader.stop();
verify(freenetInterface).unregisterUsk(sone);
}
@Test
public void notBeingAbleToFetchAnUnknownSoneDoesNotUpdateCore() {
FreenetURI finalRequestUri = requestUri.sskForUSK()
.setMetaString(new String[] { "sone.xml" });
setupSoneAsUnknown();
soneDownloader.fetchSoneAction(sone).run();
verify(freenetInterface).fetchUri(finalRequestUri);
verifyThatSoneStatusWasChangedToDownloadingAndBackTo(unknown);
verify(core, never()).updateSone(any(Sone.class));
}
private void verifyThatSoneStatusWasChangedToDownloadingAndBackTo(SoneStatus soneStatus) {
ArgumentCaptor soneStatuses = forClass(SoneStatus.class);
verify(sone, times(2)).setStatus(soneStatuses.capture());
assertThat(soneStatuses.getAllValues().get(0), is(downloading));
assertThat(soneStatuses.getAllValues().get(1), is(soneStatus));
}
@Test
public void notBeingAbleToFetchAKnownSoneDoesNotUpdateCore() {
FreenetURI finalRequestUri = requestUri.sskForUSK()
.setMetaString(new String[] { "sone.xml" });
soneDownloader.fetchSoneAction(sone).run();
verify(freenetInterface).fetchUri(finalRequestUri);
verifyThatSoneStatusWasChangedToDownloadingAndBackTo(idle);
verify(core, never()).updateSone(any(Sone.class));
}
@Test(expected = NullPointerException.class)
public void exceptionWhileFetchingAnUnknownSoneDoesNotUpdateCore() {
FreenetURI finalRequestUri = requestUri.sskForUSK()
.setMetaString(new String[] { "sone.xml" });
setupSoneAsUnknown();
when(freenetInterface.fetchUri(finalRequestUri)).thenThrow(NullPointerException.class);
try {
soneDownloader.fetchSoneAction(sone).run();
} finally {
verify(freenetInterface).fetchUri(finalRequestUri);
verifyThatSoneStatusWasChangedToDownloadingAndBackTo(unknown);
verify(core, never()).updateSone(any(Sone.class));
}
}
@Test(expected = NullPointerException.class)
public void exceptionWhileFetchingAKnownSoneDoesNotUpdateCore() {
FreenetURI finalRequestUri = requestUri.sskForUSK()
.setMetaString(new String[] { "sone.xml" });
when(freenetInterface.fetchUri(finalRequestUri)).thenThrow( NullPointerException.class);
try {
soneDownloader.fetchSoneAction(sone).run();
} finally {
verify(freenetInterface).fetchUri(finalRequestUri);
verifyThatSoneStatusWasChangedToDownloadingAndBackTo(idle);
verify(core, never()).updateSone(any(Sone.class));
}
}
@Test
public void fetchingSoneWithInvalidXmlWillNotUpdateTheCore() throws IOException {
final Fetched fetchResult = createFetchResult(requestUri, getClass().getResourceAsStream("sone-parser-not-xml.xml"));
when(freenetInterface.fetchUri(requestUri)).thenReturn(fetchResult);
soneDownloader.fetchSoneAction(sone).run();
verify(core, never()).updateSone(any(Sone.class));
}
@Test
public void exceptionWhileFetchingSoneWillNotUpdateTheCore() throws IOException {
final Fetched fetchResult = createFetchResult(requestUri, getClass().getResourceAsStream("sone-parser-no-payload.xml"));
when(core.soneBuilder()).thenReturn(null);
when(freenetInterface.fetchUri(requestUri)).thenReturn(fetchResult);
soneDownloader.fetchSoneAction(sone).run();
verify(core, never()).updateSone(any(Sone.class));
}
@Test
public void onlyFetchingASoneWillNotUpdateTheCore() throws IOException {
final Fetched fetchResult = createFetchResult(requestUri, getClass().getResourceAsStream("sone-parser-no-payload.xml"));
when(freenetInterface.fetchUri(requestUri)).thenReturn(fetchResult);
soneDownloader.fetchSone(sone, sone.getRequestUri(), true);
verify(core, never()).updateSone(any(Sone.class));
verifyThatSoneStatusWasChangedToDownloadingAndBackTo(idle);
}
private Fetched createFetchResult(FreenetURI uri, InputStream inputStream) throws IOException {
ClientMetadata clientMetadata = new ClientMetadata("application/xml");
Bucket bucket = mock(Bucket.class);
when(bucket.getInputStream()).thenReturn(inputStream);
FetchResult fetchResult = new FetchResult(clientMetadata, bucket);
return new Fetched(uri, fetchResult);
}
@Test
public void soneDownloaderCanBeCreatedByDependencyInjection() {
assertThat(getBaseInjector().getInstance(SoneDownloader.class), notNullValue());
}
}