package net.pterodactylus.sone.core;
import static java.lang.Thread.sleep;
import static java.util.concurrent.TimeUnit.SECONDS;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.not;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import java.util.concurrent.CountDownLatch;
import net.pterodactylus.sone.core.WebOfTrustUpdaterImpl.AddContextJob;
import net.pterodactylus.sone.core.WebOfTrustUpdaterImpl.RemoveContextJob;
import net.pterodactylus.sone.core.WebOfTrustUpdaterImpl.SetPropertyJob;
import net.pterodactylus.sone.core.WebOfTrustUpdaterImpl.SetTrustJob;
import net.pterodactylus.sone.core.WebOfTrustUpdaterImpl.WebOfTrustContextUpdateJob;
import net.pterodactylus.sone.core.WebOfTrustUpdaterImpl.WebOfTrustUpdateJob;
import net.pterodactylus.sone.freenet.plugin.PluginException;
import net.pterodactylus.sone.freenet.wot.Identity;
import net.pterodactylus.sone.freenet.wot.OwnIdentity;
import net.pterodactylus.sone.freenet.wot.Trust;
import net.pterodactylus.sone.freenet.wot.WebOfTrustConnector;
import net.pterodactylus.sone.freenet.wot.WebOfTrustException;
import org.junit.Test;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
/**
* Unit test for {@link WebOfTrustUpdaterImpl} and its subclasses.
*
* @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
*/
public class WebOfTrustUpdaterTest {
private static final String CONTEXT = "test-context";
private static final Integer SCORE = 50;
private static final Integer OTHER_SCORE = 25;
private static final String TRUST_COMMENT = "set in a test";
private static final String PROPERTY_NAME = "test-property";
private final WebOfTrustConnector webOfTrustConnector = mock(WebOfTrustConnector.class);
private final WebOfTrustUpdaterImpl webOfTrustUpdater = new WebOfTrustUpdaterImpl(webOfTrustConnector);
private final OwnIdentity ownIdentity = when(mock(OwnIdentity.class).getId()).thenReturn("own-identity-id").getMock();
private final WebOfTrustUpdateJob successfulWebOfTrustUpdateJob = createWebOfTrustUpdateJob(true);
private final WebOfTrustUpdateJob failingWebOfTrustUpdateJob = createWebOfTrustUpdateJob(false);
private final WebOfTrustContextUpdateJob contextUpdateJob = webOfTrustUpdater.new WebOfTrustContextUpdateJob(ownIdentity, CONTEXT);
private final AddContextJob addContextJob = webOfTrustUpdater.new AddContextJob(ownIdentity, CONTEXT);
private final RemoveContextJob removeContextJob = webOfTrustUpdater.new RemoveContextJob(ownIdentity, CONTEXT);
private final Identity trustee = when(mock(Identity.class).getId()).thenReturn("trustee-id").getMock();
private WebOfTrustUpdateJob createWebOfTrustUpdateJob(final boolean success) {
return webOfTrustUpdater.new WebOfTrustUpdateJob() {
@Override
public void run() {
super.run();
try {
sleep(100);
} catch (InterruptedException ie1) {
throw new RuntimeException(ie1);
}
finish(success);
}
};
}
@Test
public void webOfTrustUpdateJobWaitsUntilFinishedHasBeenCalledAndReturnsSuccess() throws InterruptedException {
new Thread(successfulWebOfTrustUpdateJob).start();
assertThat(successfulWebOfTrustUpdateJob.waitForCompletion(), is(true));
}
@Test
public void webOfTrustUpdateJobWaitsUntilFinishedHasBeenCalledAndReturnsFailure() throws InterruptedException {
new Thread(failingWebOfTrustUpdateJob).start();
assertThat(failingWebOfTrustUpdateJob.waitForCompletion(), is(false));
}
@Test
public void webOfTrustContextUpdateJobsAreEqualIfTheirClassOwnIdentityAndContextAreEqual() {
WebOfTrustContextUpdateJob secondContextUpdateJob = webOfTrustUpdater.new WebOfTrustContextUpdateJob(ownIdentity, CONTEXT);
assertThat(contextUpdateJob.equals(secondContextUpdateJob), is(true));
assertThat(secondContextUpdateJob.equals(contextUpdateJob), is(true));
assertThat(contextUpdateJob.hashCode(), is(secondContextUpdateJob.hashCode()));
}
@Test
public void webOfTrustContextUpdatesJobsAreNotEqualIfTheirClassDiffers() {
assertThat(contextUpdateJob.equals(addContextJob), is(false));
}
@Test
public void webOfTrustContextUpdateJobToStringContainsIdentityAndContext() {
assertThat(contextUpdateJob.toString(), containsString(ownIdentity.toString()));
assertThat(contextUpdateJob.toString(), containsString(CONTEXT));
}
@Test
public void webOfTrustContextUpdateJobsAreNotEqualIfTheIdentitiesDiffer() {
OwnIdentity ownIdentity = mock(OwnIdentity.class);
WebOfTrustContextUpdateJob secondContextUpdateJob = webOfTrustUpdater.new WebOfTrustContextUpdateJob(ownIdentity, CONTEXT);
assertThat(contextUpdateJob.equals(secondContextUpdateJob), is(false));
assertThat(secondContextUpdateJob.equals(contextUpdateJob), is(false));
}
@Test
public void webOfTrustContextUpdateJobsAreNotEqualIfTheirContextsDiffer() {
WebOfTrustContextUpdateJob secondContextUpdateJob = webOfTrustUpdater.new WebOfTrustContextUpdateJob(ownIdentity, CONTEXT + CONTEXT);
assertThat(contextUpdateJob.equals(secondContextUpdateJob), is(false));
assertThat(secondContextUpdateJob.equals(contextUpdateJob), is(false));
}
@Test
public void webOfTrustContextUpdateJobsAreNotEqualToNull() {
assertThat(contextUpdateJob.equals(null), is(false));
}
@Test
public void addContextJobAddsTheContext() throws PluginException {
addContextJob.run();
verify(webOfTrustConnector).addContext(eq(ownIdentity), eq(CONTEXT));
verify(ownIdentity).addContext(eq(CONTEXT));
assertThat(addContextJob.waitForCompletion(), is(true));
}
@Test
public void exceptionWhileAddingAContextIsExposed() throws PluginException {
doThrow(PluginException.class).when(webOfTrustConnector).addContext(eq(ownIdentity), eq(CONTEXT));
addContextJob.run();
verify(webOfTrustConnector).addContext(eq(ownIdentity), eq(CONTEXT));
verify(ownIdentity, never()).addContext(eq(CONTEXT));
assertThat(addContextJob.waitForCompletion(), is(false));
}
@Test
public void removeContextJobRemovesTheContext() throws PluginException {
removeContextJob.run();
verify(webOfTrustConnector).removeContext(eq(ownIdentity), eq(CONTEXT));
verify(ownIdentity).removeContext(eq(CONTEXT));
assertThat(removeContextJob.waitForCompletion(), is(true));
}
@Test
public void exceptionWhileRemovingAContextIsExposed() throws PluginException {
doThrow(PluginException.class).when(webOfTrustConnector).removeContext(eq(ownIdentity), eq(CONTEXT));
removeContextJob.run();
verify(webOfTrustConnector).removeContext(eq(ownIdentity), eq(CONTEXT));
verify(ownIdentity, never()).removeContext(eq(CONTEXT));
assertThat(removeContextJob.waitForCompletion(), is(false));
}
@Test
public void settingAPropertySetsTheProperty() throws PluginException {
String propertyName = "property-name";
String propertyValue = "property-value";
SetPropertyJob setPropertyJob = webOfTrustUpdater.new SetPropertyJob(ownIdentity, propertyName, propertyValue);
setPropertyJob.run();
verify(webOfTrustConnector).setProperty(eq(ownIdentity), eq(propertyName), eq(propertyValue));
verify(ownIdentity).setProperty(eq(propertyName), eq(propertyValue));
assertThat(setPropertyJob.waitForCompletion(), is(true));
}
@Test
public void settingAPropertyToNullRemovesTheProperty() throws PluginException {
String propertyName = "property-name";
SetPropertyJob setPropertyJob = webOfTrustUpdater.new SetPropertyJob(ownIdentity, propertyName, null);
setPropertyJob.run();
verify(webOfTrustConnector).removeProperty(eq(ownIdentity), eq(propertyName));
verify(ownIdentity).removeProperty(eq(propertyName));
assertThat(setPropertyJob.waitForCompletion(), is(true));
}
@Test
public void pluginExceptionWhileSettingAPropertyIsHandled() throws PluginException {
String propertyName = "property-name";
String propertyValue = "property-value";
doThrow(PluginException.class).when(webOfTrustConnector).setProperty(eq(ownIdentity), eq(propertyName), eq(propertyValue));
SetPropertyJob setPropertyJob = webOfTrustUpdater.new SetPropertyJob(ownIdentity, propertyName, propertyValue);
setPropertyJob.run();
verify(webOfTrustConnector).setProperty(eq(ownIdentity), eq(propertyName), eq(propertyValue));
verify(ownIdentity, never()).setProperty(eq(propertyName), eq(propertyValue));
assertThat(setPropertyJob.waitForCompletion(), is(false));
}
@Test
public void setPropertyJobsWithSameClassPropertyAndValueAreEqual() {
String propertyName = "property-name";
String propertyValue = "property-value";
SetPropertyJob firstSetPropertyJob = webOfTrustUpdater.new SetPropertyJob(ownIdentity, propertyName, propertyValue);
SetPropertyJob secondSetPropertyJob = webOfTrustUpdater.new SetPropertyJob(ownIdentity, propertyName, propertyValue);
assertThat(firstSetPropertyJob, is(secondSetPropertyJob));
assertThat(secondSetPropertyJob, is(firstSetPropertyJob));
assertThat(firstSetPropertyJob.hashCode(), is(secondSetPropertyJob.hashCode()));
}
@Test
public void setPropertyJobsWithDifferentClassesAreNotEqual() {
String propertyName = "property-name";
String propertyValue = "property-value";
SetPropertyJob firstSetPropertyJob = webOfTrustUpdater.new SetPropertyJob(ownIdentity, propertyName, propertyValue);
SetPropertyJob secondSetPropertyJob = webOfTrustUpdater.new SetPropertyJob(ownIdentity, propertyName, propertyValue) {
};
assertThat(firstSetPropertyJob, not(is(secondSetPropertyJob)));
}
@Test
public void nullIsNotASetProjectJobEither() {
String propertyName = "property-name";
String propertyValue = "property-value";
SetPropertyJob setPropertyJob = webOfTrustUpdater.new SetPropertyJob(ownIdentity, propertyName, propertyValue);
assertThat(setPropertyJob, not(is((Object) null)));
}
@Test
public void setPropertyJobsWithDifferentPropertiesAreNotEqual() {
String propertyName = "property-name";
String propertyValue = "property-value";
SetPropertyJob firstSetPropertyJob = webOfTrustUpdater.new SetPropertyJob(ownIdentity, propertyName, propertyValue);
SetPropertyJob secondSetPropertyJob = webOfTrustUpdater.new SetPropertyJob(ownIdentity, propertyName + "2", propertyValue);
assertThat(firstSetPropertyJob, not(is(secondSetPropertyJob)));
}
@Test
public void setPropertyJobsWithDifferentOwnIdentitiesAreNotEqual() {
OwnIdentity otherOwnIdentity = mock(OwnIdentity.class);
String propertyName = "property-name";
String propertyValue = "property-value";
SetPropertyJob firstSetPropertyJob = webOfTrustUpdater.new SetPropertyJob(ownIdentity, propertyName, propertyValue);
SetPropertyJob secondSetPropertyJob = webOfTrustUpdater.new SetPropertyJob(otherOwnIdentity, propertyName, propertyValue);
assertThat(firstSetPropertyJob, not(is(secondSetPropertyJob)));
}
@Test
public void setTrustJobSetsTrust() throws PluginException {
SetTrustJob setTrustJob = webOfTrustUpdater.new SetTrustJob(ownIdentity, trustee, SCORE, TRUST_COMMENT);
setTrustJob.run();
verify(webOfTrustConnector).setTrust(eq(ownIdentity), eq(trustee), eq(SCORE), eq(TRUST_COMMENT));
verify(trustee).setTrust(eq(ownIdentity), eq(new Trust(SCORE, null, 0)));
assertThat(setTrustJob.waitForCompletion(), is(true));
}
@Test
public void settingNullTrustRemovesTrust() throws WebOfTrustException {
SetTrustJob setTrustJob = webOfTrustUpdater.new SetTrustJob(ownIdentity, trustee, null, TRUST_COMMENT);
setTrustJob.run();
verify(webOfTrustConnector).removeTrust(eq(ownIdentity), eq(trustee));
verify(trustee).removeTrust(eq(ownIdentity));
assertThat(setTrustJob.waitForCompletion(), is(true));
}
@Test
public void exceptionWhileSettingTrustIsCaught() throws PluginException {
doThrow(PluginException.class).when(webOfTrustConnector).setTrust(eq(ownIdentity), eq(trustee), eq(SCORE), eq(TRUST_COMMENT));
SetTrustJob setTrustJob = webOfTrustUpdater.new SetTrustJob(ownIdentity, trustee, SCORE, TRUST_COMMENT);
setTrustJob.run();
verify(webOfTrustConnector).setTrust(eq(ownIdentity), eq(trustee), eq(SCORE), eq(TRUST_COMMENT));
verify(trustee, never()).setTrust(eq(ownIdentity), eq(new Trust(SCORE, null, 0)));
assertThat(setTrustJob.waitForCompletion(), is(false));
}
@Test
public void setTrustJobsWithDifferentClassesAreNotEqual() {
SetTrustJob firstSetTrustJob = webOfTrustUpdater.new SetTrustJob(ownIdentity, trustee, SCORE, TRUST_COMMENT);
SetTrustJob secondSetTrustJob = webOfTrustUpdater.new SetTrustJob(ownIdentity, trustee, SCORE, TRUST_COMMENT) {
};
assertThat(firstSetTrustJob, not(is(secondSetTrustJob)));
assertThat(secondSetTrustJob, not(is(firstSetTrustJob)));
}
@Test
public void setTrustJobsWithDifferentTrustersAreNotEqual() {
SetTrustJob firstSetTrustJob = webOfTrustUpdater.new SetTrustJob(ownIdentity, trustee, SCORE, TRUST_COMMENT);
SetTrustJob secondSetTrustJob = webOfTrustUpdater.new SetTrustJob(mock(OwnIdentity.class), trustee, SCORE, TRUST_COMMENT);
assertThat(firstSetTrustJob, not(is(secondSetTrustJob)));
assertThat(secondSetTrustJob, not(is(firstSetTrustJob)));
}
@Test
public void setTrustJobsWithDifferentTrusteesAreNotEqual() {
SetTrustJob firstSetTrustJob = webOfTrustUpdater.new SetTrustJob(ownIdentity, trustee, SCORE, TRUST_COMMENT);
SetTrustJob secondSetTrustJob = webOfTrustUpdater.new SetTrustJob(ownIdentity, mock(Identity.class), SCORE, TRUST_COMMENT);
assertThat(firstSetTrustJob, not(is(secondSetTrustJob)));
assertThat(secondSetTrustJob, not(is(firstSetTrustJob)));
}
@Test
public void setTrustJobsWithDifferentScoreAreEqual() {
SetTrustJob firstSetTrustJob = webOfTrustUpdater.new SetTrustJob(ownIdentity, trustee, SCORE, TRUST_COMMENT);
SetTrustJob secondSetTrustJob = webOfTrustUpdater.new SetTrustJob(ownIdentity, trustee, OTHER_SCORE, TRUST_COMMENT);
assertThat(firstSetTrustJob, is(secondSetTrustJob));
assertThat(secondSetTrustJob, is(firstSetTrustJob));
assertThat(firstSetTrustJob.hashCode(), is(secondSetTrustJob.hashCode()));
}
@Test
public void setTrustJobDoesNotEqualNull() {
SetTrustJob setTrustJob = webOfTrustUpdater.new SetTrustJob(ownIdentity, trustee, SCORE, TRUST_COMMENT);
assertThat(setTrustJob, not(is((Object) null)));
}
@Test
public void toStringOfSetTrustJobContainsIdsOfTrusterAndTrustee() {
SetTrustJob setTrustJob = webOfTrustUpdater.new SetTrustJob(ownIdentity, trustee, SCORE, TRUST_COMMENT);
assertThat(setTrustJob.toString(), containsString(ownIdentity.getId()));
assertThat(setTrustJob.toString(), containsString(trustee.getId()));
}
@Test
public void webOfTrustUpdaterStopsWhenItShould() {
webOfTrustUpdater.stop();
webOfTrustUpdater.serviceRun();
}
@Test
public void webOfTrustUpdaterStopsAfterItWasStarted() {
webOfTrustUpdater.start();
webOfTrustUpdater.stop();
}
@Test
public void removePropertyRemovesProperty() throws InterruptedException, PluginException {
final CountDownLatch wotCallTriggered = new CountDownLatch(1);
doAnswer(new Answer<Void>() {
@Override
public Void answer(InvocationOnMock invocation) throws Throwable {
wotCallTriggered.countDown();
return null;
}
}).when(webOfTrustConnector).removeProperty(eq(ownIdentity), eq(PROPERTY_NAME));
webOfTrustUpdater.removeProperty(ownIdentity, PROPERTY_NAME);
webOfTrustUpdater.start();
assertThat(wotCallTriggered.await(1, SECONDS), is(true));
}
@Test
public void multipleCallsToSetPropertyAreCollapsed() throws InterruptedException, PluginException {
final CountDownLatch wotCallTriggered = new CountDownLatch(1);
doAnswer(new Answer<Void>() {
@Override
public Void answer(InvocationOnMock invocation) throws Throwable {
wotCallTriggered.countDown();
return null;
}
}).when(webOfTrustConnector).removeProperty(eq(ownIdentity), eq(PROPERTY_NAME));
webOfTrustUpdater.removeProperty(ownIdentity, PROPERTY_NAME);
webOfTrustUpdater.removeProperty(ownIdentity, PROPERTY_NAME);
webOfTrustUpdater.start();
assertThat(wotCallTriggered.await(1, SECONDS), is(true));
verify(webOfTrustConnector).removeProperty(eq(ownIdentity), eq(PROPERTY_NAME));
}
@Test
public void addContextWaitWaitsForTheContextToBeAdded() {
webOfTrustUpdater.start();
assertThat(webOfTrustUpdater.addContextWait(ownIdentity, CONTEXT), is(true));
verify(ownIdentity).addContext(eq(CONTEXT));
}
@Test
public void removeContextRemovesAContext() throws InterruptedException, PluginException {
webOfTrustUpdater.start();
final CountDownLatch removeContextTrigger = new CountDownLatch(1);
doAnswer(new Answer<Void>() {
@Override
public Void answer(InvocationOnMock invocation) throws Throwable {
removeContextTrigger.countDown();
return null;
}
}).when(ownIdentity).removeContext(eq(CONTEXT));
webOfTrustUpdater.removeContext(ownIdentity, CONTEXT);
removeContextTrigger.await(1, SECONDS);
verify(webOfTrustConnector).removeContext(eq(ownIdentity), eq(CONTEXT));
verify(ownIdentity).removeContext(eq(CONTEXT));
}
@Test
public void removeContextRequestsAreCoalesced() throws InterruptedException, PluginException {
final CountDownLatch contextRemovedTrigger = new CountDownLatch(1);
doAnswer(new Answer<Void>() {
@Override
public Void answer(InvocationOnMock invocation) throws Throwable {
contextRemovedTrigger.countDown();
return null;
}
}).when(ownIdentity).removeContext(eq(CONTEXT));
for (int i = 1; i <= 2; i++) {
/* this is so fucking volatile. */
if (i > 1) {
sleep(200);
}
new Thread(new Runnable() {
public void run() {
webOfTrustUpdater.removeContext(ownIdentity, CONTEXT);
}
}).start();
}
webOfTrustUpdater.start();
assertThat(contextRemovedTrigger.await(1, SECONDS), is(true));
verify(webOfTrustConnector).removeContext(eq(ownIdentity), eq(CONTEXT));
verify(ownIdentity).removeContext(eq(CONTEXT));
}
@Test
public void setTrustSetsTrust() throws InterruptedException, PluginException {
final CountDownLatch trustSetTrigger = new CountDownLatch(1);
doAnswer(new Answer<Void>() {
@Override
public Void answer(InvocationOnMock invocation) throws Throwable {
trustSetTrigger.countDown();
return null;
}
}).when(trustee).setTrust(eq(ownIdentity), eq(new Trust(SCORE, null, 0)));
webOfTrustUpdater.start();
webOfTrustUpdater.setTrust(ownIdentity, trustee, SCORE, TRUST_COMMENT);
assertThat(trustSetTrigger.await(1, SECONDS), is(true));
verify(trustee).setTrust(eq(ownIdentity), eq(new Trust(SCORE, null, 0)));
verify(webOfTrustConnector).setTrust(eq(ownIdentity), eq(trustee), eq(SCORE), eq(TRUST_COMMENT));
}
@Test
public void setTrustRequestsAreCoalesced() throws InterruptedException, PluginException {
final CountDownLatch trustSetTrigger = new CountDownLatch(1);
doAnswer(new Answer<Void>() {
@Override
public Void answer(InvocationOnMock invocation) throws Throwable {
trustSetTrigger.countDown();
return null;
}
}).when(trustee).setTrust(eq(ownIdentity), eq(new Trust(SCORE, null, 0)));
for (int i = 1; i <= 2; i++) {
/* this is so fucking volatile. */
if (i > 1) {
sleep(200);
}
new Thread(new Runnable() {
public void run() {
webOfTrustUpdater.setTrust(ownIdentity, trustee, SCORE, TRUST_COMMENT);
}
}).start();
}
webOfTrustUpdater.start();
assertThat(trustSetTrigger.await(1, SECONDS), is(true));
verify(trustee).setTrust(eq(ownIdentity), eq(new Trust(SCORE, null, 0)));
verify(webOfTrustConnector).setTrust(eq(ownIdentity), eq(trustee), eq(SCORE), eq(TRUST_COMMENT));
}
}