From: David ‘Bombe’ Roden Date: Thu, 21 Nov 2019 06:20:05 +0000 (+0100) Subject: 🔀 Merge next X-Git-Url: https://git.pterodactylus.net/?p=Sone.git;a=commitdiff_plain;h=refs%2Fheads%2Fadd-audio-player;hp=97fe04482ebb8a08e43294acde041c2975cbd8ee 🔀 Merge next --- diff --git a/build.gradle b/build.gradle index a9e11ca..bff4143 100644 --- a/build.gradle +++ b/build.gradle @@ -1,18 +1,13 @@ -group = 'net.pterodactylus' -version = '80' -buildscript { - ext.kotlinVersion = '1.3.41' - repositories { - mavenCentral() - } - dependencies { - classpath group: 'info.solidsoft.gradle.pitest', name: 'gradle-pitest-plugin', version: '1.4.0' - classpath group: 'org.jetbrains.kotlin', name: 'kotlin-gradle-plugin', version: kotlinVersion - classpath group: 'org.jetbrains.kotlin', name: 'kotlin-noarg', version: kotlinVersion - } +plugins { + id 'org.jetbrains.kotlin.jvm' version '1.3.50' + id 'org.jetbrains.kotlin.plugin.noarg' version '1.3.50' + id 'info.solidsoft.pitest' version '1.4.5' } +group = 'net.pterodactylus' +version = '80' + repositories { mavenCentral() maven { url "https://maven.pterodactylus.net/" } @@ -27,8 +22,6 @@ tasks.withType(JavaCompile) { options.encoding = 'UTF-8' } -apply plugin: 'kotlin' - configurations { provided { dependencies.all { dep -> @@ -95,9 +88,8 @@ jacoco { jacocoTestReport.dependsOn test -apply plugin: 'info.solidsoft.pitest' - pitest { + pitestVersion = '1.4.10' outputFormats = ['HTML', 'XML'] timestampedReports = false timeoutFactor = 3.0 @@ -129,8 +121,6 @@ task countLines { dependsOn tasks.countLinesTest } -apply plugin: 'kotlin-noarg' - noArg { annotation('net.pterodactylus.sone.main.NoArg') } diff --git a/src/main/java/net/pterodactylus/sone/core/Core.java b/src/main/java/net/pterodactylus/sone/core/Core.java index c3af217..a8d753e 100644 --- a/src/main/java/net/pterodactylus/sone/core/Core.java +++ b/src/main/java/net/pterodactylus/sone/core/Core.java @@ -88,6 +88,7 @@ import net.pterodactylus.util.service.AbstractService; import net.pterodactylus.util.thread.NamedThreadFactory; import com.google.common.annotations.VisibleForTesting; +import com.google.common.base.Stopwatch; import com.google.common.collect.FluentIterable; import com.google.common.collect.HashMultimap; import com.google.common.collect.Multimap; @@ -176,6 +177,7 @@ public class Core extends AbstractService implements SoneProvider, PostProvider, private volatile long lastConfigurationUpdate; private final MetricRegistry metricRegistry; + private final Histogram configurationSaveTimeHistogram; /** * Creates a new core. @@ -207,6 +209,7 @@ public class Core extends AbstractService implements SoneProvider, PostProvider, this.database = database; this.metricRegistry = metricRegistry; preferences = new Preferences(eventBus); + this.configurationSaveTimeHistogram = metricRegistry.histogram("configuration.save.duration", () -> new Histogram(new ExponentiallyDecayingReservoir(3000, 0))); } // @@ -631,6 +634,10 @@ public class Core extends AbstractService implements SoneProvider, PostProvider, loadSone(sone); database.storeSone(sone); sone.setStatus(SoneStatus.idle); + if (sone.getPosts().isEmpty() && sone.getReplies().isEmpty()) { + // dirty hack + lockSone(sone); + } soneInserter.start(); return sone; } @@ -1363,7 +1370,8 @@ public class Core extends AbstractService implements SoneProvider, PostProvider, synchronized (soneInserters) { for (Entry soneInserter : soneInserters.entrySet()) { soneInserter.getValue().stop(); - saveSone(soneInserter.getKey()); + Sone latestSone = getLocalSone(soneInserter.getKey().getId()); + saveSone(latestSone); } } synchronized (soneRescuers) { @@ -1542,7 +1550,9 @@ public class Core extends AbstractService implements SoneProvider, PostProvider, database.save(); /* now save it. */ + Stopwatch stopwatch = Stopwatch.createStarted(); configuration.save(); + configurationSaveTimeHistogram.update(stopwatch.elapsed(TimeUnit.MICROSECONDS)); } catch (ConfigurationException ce1) { logger.log(Level.SEVERE, "Could not store configuration!", ce1); @@ -1582,7 +1592,7 @@ public class Core extends AbstractService implements SoneProvider, PostProvider, */ @Subscribe public void ownIdentityAdded(OwnIdentityAddedEvent ownIdentityAddedEvent) { - OwnIdentity ownIdentity = ownIdentityAddedEvent.ownIdentity(); + OwnIdentity ownIdentity = ownIdentityAddedEvent.getOwnIdentity(); logger.log(Level.FINEST, String.format("Adding OwnIdentity: %s", ownIdentity)); if (ownIdentity.hasContext("Sone")) { addLocalSone(ownIdentity); @@ -1597,7 +1607,7 @@ public class Core extends AbstractService implements SoneProvider, PostProvider, */ @Subscribe public void ownIdentityRemoved(OwnIdentityRemovedEvent ownIdentityRemovedEvent) { - OwnIdentity ownIdentity = ownIdentityRemovedEvent.ownIdentity(); + OwnIdentity ownIdentity = ownIdentityRemovedEvent.getOwnIdentity(); logger.log(Level.FINEST, String.format("Removing OwnIdentity: %s", ownIdentity)); trustedIdentities.removeAll(ownIdentity); } @@ -1610,9 +1620,9 @@ public class Core extends AbstractService implements SoneProvider, PostProvider, */ @Subscribe public void identityAdded(IdentityAddedEvent identityAddedEvent) { - Identity identity = identityAddedEvent.identity(); + Identity identity = identityAddedEvent.getIdentity(); logger.log(Level.FINEST, String.format("Adding Identity: %s", identity)); - trustedIdentities.put(identityAddedEvent.ownIdentity(), identity); + trustedIdentities.put(identityAddedEvent.getOwnIdentity(), identity); addRemoteSone(identity); } @@ -1624,7 +1634,7 @@ public class Core extends AbstractService implements SoneProvider, PostProvider, */ @Subscribe public void identityUpdated(IdentityUpdatedEvent identityUpdatedEvent) { - Identity identity = identityUpdatedEvent.identity(); + Identity identity = identityUpdatedEvent.getIdentity(); final Sone sone = getRemoteSone(identity.getId()); if (sone.isLocal()) { return; @@ -1648,8 +1658,8 @@ public class Core extends AbstractService implements SoneProvider, PostProvider, */ @Subscribe public void identityRemoved(IdentityRemovedEvent identityRemovedEvent) { - OwnIdentity ownIdentity = identityRemovedEvent.ownIdentity(); - Identity identity = identityRemovedEvent.identity(); + OwnIdentity ownIdentity = identityRemovedEvent.getOwnIdentity(); + Identity identity = identityRemovedEvent.getIdentity(); trustedIdentities.remove(ownIdentity, identity); for (Entry> trustedIdentity : trustedIdentities.asMap().entrySet()) { if (trustedIdentity.getKey().equals(ownIdentity)) { diff --git a/src/main/java/net/pterodactylus/sone/core/FreenetInterface.java b/src/main/java/net/pterodactylus/sone/core/FreenetInterface.java index b3b32b3..28c8e22 100644 --- a/src/main/java/net/pterodactylus/sone/core/FreenetInterface.java +++ b/src/main/java/net/pterodactylus/sone/core/FreenetInterface.java @@ -21,7 +21,6 @@ import static freenet.keys.USK.create; import static java.lang.String.format; import static java.util.logging.Level.WARNING; import static java.util.logging.Logger.getLogger; -import static net.pterodactylus.sone.freenet.Key.routingKey; import java.io.IOException; import java.net.MalformedURLException; @@ -75,6 +74,7 @@ import freenet.support.api.Bucket; import freenet.support.api.RandomAccessBucket; import freenet.support.io.ArrayBucket; import freenet.support.io.ResumeFailedException; +import net.pterodactylus.sone.freenet.*; /** * Contains all necessary functionality for interacting with the Freenet node. @@ -255,7 +255,7 @@ public class FreenetInterface { public void registerActiveUsk(FreenetURI requestUri, USKCallback uskCallback) { try { - soneUskCallbacks.put(routingKey(requestUri), uskCallback); + soneUskCallbacks.put(FreenetURIsKt.getRoutingKeyString(requestUri), uskCallback); node.clientCore.uskManager.subscribe(create(requestUri), uskCallback, true, requestClient); } catch (MalformedURLException mue1) { @@ -267,7 +267,7 @@ public class FreenetInterface { public void registerPassiveUsk(FreenetURI requestUri, USKCallback uskCallback) { try { - soneUskCallbacks.put(routingKey(requestUri), uskCallback); + soneUskCallbacks.put(FreenetURIsKt.getRoutingKeyString(requestUri), uskCallback); node.clientCore .uskManager .subscribe(create(requestUri), uskCallback, false, requestClient); diff --git a/src/main/java/net/pterodactylus/sone/core/SoneModificationDetector.java b/src/main/java/net/pterodactylus/sone/core/SoneModificationDetector.java index 92fa3dc..e5e20d9 100644 --- a/src/main/java/net/pterodactylus/sone/core/SoneModificationDetector.java +++ b/src/main/java/net/pterodactylus/sone/core/SoneModificationDetector.java @@ -24,7 +24,7 @@ class SoneModificationDetector { private final Ticker ticker; private final LockableFingerprintProvider lockableFingerprintProvider; private final AtomicInteger insertionDelay; - private Optional lastModificationTime; + private Long lastModificationTime; private String lastInsertFingerprint; private String lastCheckFingerprint; @@ -42,18 +42,18 @@ class SoneModificationDetector { public boolean isEligibleForInsert() { if (lockableFingerprintProvider.isLocked()) { - lastModificationTime = absent(); + lastModificationTime = null; lastCheckFingerprint = ""; return false; } String fingerprint = lockableFingerprintProvider.getFingerprint(); if (fingerprint.equals(lastInsertFingerprint)) { - lastModificationTime = absent(); + lastModificationTime = null; lastCheckFingerprint = fingerprint; return false; } if (!Objects.equal(lastCheckFingerprint, fingerprint)) { - lastModificationTime = of(ticker.read()); + lastModificationTime = ticker.read(); lastCheckFingerprint = fingerprint; return false; } @@ -67,11 +67,11 @@ class SoneModificationDetector { public void setFingerprint(String fingerprint) { lastInsertFingerprint = fingerprint; lastCheckFingerprint = lastInsertFingerprint; - lastModificationTime = absent(); + lastModificationTime = null; } private boolean insertionDelayHasPassed() { - return NANOSECONDS.toSeconds(ticker.read() - lastModificationTime.get()) >= insertionDelay.get(); + return NANOSECONDS.toSeconds(ticker.read() - lastModificationTime) >= insertionDelay.get(); } public boolean isModified() { diff --git a/src/main/java/net/pterodactylus/sone/core/SoneParser.java b/src/main/java/net/pterodactylus/sone/core/SoneParser.java index d82dab5..e1fd9ff 100644 --- a/src/main/java/net/pterodactylus/sone/core/SoneParser.java +++ b/src/main/java/net/pterodactylus/sone/core/SoneParser.java @@ -33,7 +33,7 @@ public class SoneParser { @Inject public SoneParser(Database database, MetricRegistry metricRegistry) { this.database = database; - this.soneParsingDurationHistogram = metricRegistry.histogram("sone.parsing.duration", () -> new Histogram(new ExponentiallyDecayingReservoir(3000, 0))); + this.soneParsingDurationHistogram = metricRegistry.histogram("sone.parse.duration", () -> new Histogram(new ExponentiallyDecayingReservoir(3000, 0))); } @Nullable diff --git a/src/main/java/net/pterodactylus/sone/core/SoneRescuer.java b/src/main/java/net/pterodactylus/sone/core/SoneRescuer.java index adc0925..2246ac9 100644 --- a/src/main/java/net/pterodactylus/sone/core/SoneRescuer.java +++ b/src/main/java/net/pterodactylus/sone/core/SoneRescuer.java @@ -108,6 +108,18 @@ public class SoneRescuer extends AbstractService { } /** + * Sets the edition to rescue. + * + * @param edition + * The edition to rescue + * @return This Sone rescuer + */ + public SoneRescuer setEdition(long edition) { + currentEdition = edition; + return this; + } + + /** * Sets whether the last fetch was successful. * * @return {@code true} if the last fetch was successful, {@code false} @@ -123,7 +135,9 @@ public class SoneRescuer extends AbstractService { // /** - * Starts the next fetch. + * Starts the next fetch. If you want to fetch a different edition than “the + * next older one,” remember to call {@link #setEdition(long)} before + * calling this method. */ public void startNextFetch() { fetching = true; @@ -145,14 +159,13 @@ public class SoneRescuer extends AbstractService { } if (fetching) { core.lockSone(sone); - FreenetURI soneUri = sone.getRequestUri().setKeyType("SSK").setDocName("Sone-" + getNextEdition()).setMetaString(new String[] { "sone.xml" }); + FreenetURI soneUri = sone.getRequestUri().setKeyType("SSK").setDocName("Sone-" + currentEdition).setMetaString(new String[] { "sone.xml" }); System.out.println("URI: " + soneUri); Sone fetchedSone = soneDownloader.fetchSone(sone, soneUri, true); System.out.println("Sone: " + fetchedSone); lastFetchSuccessful = (fetchedSone != null); if (lastFetchSuccessful) { core.updateSone(fetchedSone, true); - currentEdition = getNextEdition(); } fetching = false; } diff --git a/src/main/java/net/pterodactylus/sone/data/impl/AlbumImpl.java b/src/main/java/net/pterodactylus/sone/data/impl/AlbumImpl.java index 3ec362f..a8768e1 100644 --- a/src/main/java/net/pterodactylus/sone/data/impl/AlbumImpl.java +++ b/src/main/java/net/pterodactylus/sone/data/impl/AlbumImpl.java @@ -17,28 +17,17 @@ package net.pterodactylus.sone.data.impl; -import static com.google.common.base.Optional.absent; -import static com.google.common.base.Optional.fromNullable; -import static com.google.common.base.Preconditions.checkArgument; -import static com.google.common.base.Preconditions.checkNotNull; -import static java.nio.charset.StandardCharsets.UTF_8; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.UUID; - -import net.pterodactylus.sone.data.Album; -import net.pterodactylus.sone.data.Image; -import net.pterodactylus.sone.data.Sone; - -import com.google.common.base.Function; -import com.google.common.base.Optional; -import com.google.common.base.Predicates; -import com.google.common.collect.Collections2; -import com.google.common.hash.Hasher; +import java.util.*; +import javax.annotation.*; + +import com.google.common.base.*; +import com.google.common.collect.*; import com.google.common.hash.Hashing; +import com.google.common.hash.*; +import net.pterodactylus.sone.data.*; + +import static com.google.common.base.Preconditions.*; +import static java.nio.charset.StandardCharsets.*; /** * Container for images that can also contain nested {@link AlbumImpl}s. @@ -258,32 +247,33 @@ public class AlbumImpl implements Album { public Modifier modify() throws IllegalStateException { // TODO: reenable check for local Sones return new Modifier() { - private Optional title = absent(); - - private Optional description = absent(); + @Nullable + private String title; + @Nullable + private String description; @Override public Modifier setTitle(String title) { - this.title = fromNullable(title); + this.title = title; return this; } @Override public Modifier setDescription(String description) { - this.description = fromNullable(description); + this.description = description; return this; } @Override public Album update() throws IllegalStateException { - if (title.isPresent() && title.get().trim().isEmpty()) { + if (title != null && title.trim().isEmpty()) { throw new AlbumTitleMustNotBeEmpty(); } - if (title.isPresent()) { - AlbumImpl.this.title = title.get(); + if (title != null) { + AlbumImpl.this.title = title; } - if (description.isPresent()) { - AlbumImpl.this.description = description.get(); + if (description != null) { + AlbumImpl.this.description = description; } return AlbumImpl.this; } diff --git a/src/main/java/net/pterodactylus/sone/data/impl/ImageImpl.java b/src/main/java/net/pterodactylus/sone/data/impl/ImageImpl.java index a54a8de..b357b6a 100644 --- a/src/main/java/net/pterodactylus/sone/data/impl/ImageImpl.java +++ b/src/main/java/net/pterodactylus/sone/data/impl/ImageImpl.java @@ -16,22 +16,14 @@ */ package net.pterodactylus.sone.data.impl; -import static com.google.common.base.Optional.absent; -import static com.google.common.base.Optional.fromNullable; -import static com.google.common.base.Optional.of; -import static com.google.common.base.Preconditions.checkNotNull; -import static com.google.common.base.Preconditions.checkState; -import static java.nio.charset.StandardCharsets.UTF_8; +import java.util.*; +import javax.annotation.*; -import java.util.UUID; +import com.google.common.hash.*; +import net.pterodactylus.sone.data.*; -import net.pterodactylus.sone.data.Album; -import net.pterodactylus.sone.data.Image; -import net.pterodactylus.sone.data.Sone; - -import com.google.common.base.Optional; -import com.google.common.hash.Hasher; -import com.google.common.hash.Hashing; +import static com.google.common.base.Preconditions.*; +import static java.nio.charset.StandardCharsets.*; /** * Container for image metadata. @@ -146,93 +138,94 @@ public class ImageImpl implements Image { public Modifier modify() throws IllegalStateException { // TODO: reenable check for local images return new Modifier() { - private Optional sone = absent(); - - private Optional creationTime = absent(); - - private Optional key = absent(); - - private Optional title = absent(); - - private Optional description = absent(); - - private Optional width = absent(); - - private Optional height = absent(); + @Nullable + private Sone sone; + @Nullable + private Long creationTime; + @Nullable + private String key; + @Nullable + private String title; + @Nullable + private String description; + @Nullable + private Integer width; + @Nullable + private Integer height; @Override public Modifier setSone(Sone sone) { - this.sone = fromNullable(sone); + this.sone = sone; return this; } @Override public Modifier setCreationTime(long creationTime) { - this.creationTime = of(creationTime); + this.creationTime = creationTime; return this; } @Override public Modifier setKey(String key) { - this.key = fromNullable(key); + this.key = key; return this; } @Override public Modifier setTitle(String title) { - this.title = fromNullable(title); + this.title = title; return this; } @Override public Modifier setDescription(String description) { - this.description = fromNullable(description); + this.description = description; return this; } @Override public Modifier setWidth(int width) { - this.width = of(width); + this.width = width; return this; } @Override public Modifier setHeight(int height) { - this.height = of(height); + this.height = height; return this; } @Override public Image update() throws IllegalStateException { - checkState(!sone.isPresent() || (ImageImpl.this.sone == null) || sone.get().equals(ImageImpl.this.sone), "can not change Sone once set"); - checkState(!creationTime.isPresent() || ((ImageImpl.this.creationTime == 0) || (ImageImpl.this.creationTime == creationTime.get())), "can not change creation time once set"); - checkState(!key.isPresent() || (ImageImpl.this.key == null) || key.get().equals(ImageImpl.this.key), "can not change key once set"); - if (title.isPresent() && title.get().trim().isEmpty()) { + checkState(sone == null || (ImageImpl.this.sone == null) || sone.equals(ImageImpl.this.sone), "can not change Sone once set"); + checkState(creationTime == null || ((ImageImpl.this.creationTime == 0) || (ImageImpl.this.creationTime == creationTime)), "can not change creation time once set"); + checkState(key == null || (ImageImpl.this.key == null) || key.equals(ImageImpl.this.key), "can not change key once set"); + if (title != null && title.trim().isEmpty()) { throw new ImageTitleMustNotBeEmpty(); } - checkState(!width.isPresent() || (ImageImpl.this.width == 0) || width.get().equals(ImageImpl.this.width), "can not change width once set"); - checkState(!height.isPresent() || (ImageImpl.this.height == 0) || height.get().equals(ImageImpl.this.height), "can not change height once set"); + checkState(width == null || (ImageImpl.this.width == 0) || width.equals(ImageImpl.this.width), "can not change width once set"); + checkState(height == null || (ImageImpl.this.height == 0) || height.equals(ImageImpl.this.height), "can not change height once set"); - if (sone.isPresent()) { - ImageImpl.this.sone = sone.get(); + if (sone != null) { + ImageImpl.this.sone = sone; } - if (creationTime.isPresent()) { - ImageImpl.this.creationTime = creationTime.get(); + if (creationTime != null) { + ImageImpl.this.creationTime = creationTime; } - if (key.isPresent()) { - ImageImpl.this.key = key.get(); + if (key != null) { + ImageImpl.this.key = key; } - if (title.isPresent()) { - ImageImpl.this.title = title.get(); + if (title != null) { + ImageImpl.this.title = title; } - if (description.isPresent()) { - ImageImpl.this.description = description.get(); + if (description != null) { + ImageImpl.this.description = description; } - if (width.isPresent()) { - ImageImpl.this.width = width.get(); + if (width != null) { + ImageImpl.this.width = width; } - if (height.isPresent()) { - ImageImpl.this.height = height.get(); + if (height != null) { + ImageImpl.this.height = height; } return ImageImpl.this; diff --git a/src/main/java/net/pterodactylus/sone/database/memory/MemoryDatabase.kt b/src/main/java/net/pterodactylus/sone/database/memory/MemoryDatabase.kt index a9459e8..857560e 100644 --- a/src/main/java/net/pterodactylus/sone/database/memory/MemoryDatabase.kt +++ b/src/main/java/net/pterodactylus/sone/database/memory/MemoryDatabase.kt @@ -41,7 +41,7 @@ import net.pterodactylus.sone.database.ImageBuilder import net.pterodactylus.sone.database.PostBuilder import net.pterodactylus.sone.database.PostDatabase import net.pterodactylus.sone.database.PostReplyBuilder -import net.pterodactylus.sone.utils.unit +import net.pterodactylus.sone.utils.* import net.pterodactylus.util.config.Configuration import net.pterodactylus.util.config.ConfigurationException import java.util.concurrent.locks.ReentrantReadWriteLock @@ -71,6 +71,8 @@ class MemoryDatabase @Inject constructor(private val configuration: Configuratio private val memoryBookmarkDatabase = MemoryBookmarkDatabase(this, configurationLoader) private val memoryFriendDatabase = MemoryFriendDatabase(configurationLoader) private val saveRateLimiter: RateLimiter = RateLimiter.create(1.0) + private val saveKnownPostsRateLimiter: RateLimiter = RateLimiter.create(1.0) + private val saveKnownPostRepliesRateLimiter: RateLimiter = RateLimiter.create(1.0) override val soneLoader get() = this::getSone @@ -314,15 +316,17 @@ class MemoryDatabase @Inject constructor(private val configuration: Configuratio } private fun saveKnownPosts() = - try { - readLock.withLock { - knownPosts.forEachIndexed { index, knownPostId -> - configuration.getStringValue("KnownPosts/$index/ID").value = knownPostId + saveKnownPostsRateLimiter.tryAcquire().ifTrue { + try { + readLock.withLock { + knownPosts.forEachIndexed { index, knownPostId -> + configuration.getStringValue("KnownPosts/$index/ID").value = knownPostId + } + configuration.getStringValue("KnownPosts/${knownPosts.size}/ID").value = null } - configuration.getStringValue("KnownPosts/${knownPosts.size}/ID").value = null + } catch (ce1: ConfigurationException) { + throw DatabaseException("Could not save database.", ce1) } - } catch (ce1: ConfigurationException) { - throw DatabaseException("Could not save database.", ce1) } private fun loadKnownPostReplies(): Unit = @@ -334,15 +338,17 @@ class MemoryDatabase @Inject constructor(private val configuration: Configuratio } private fun saveKnownPostReplies() = - try { - readLock.withLock { - knownPostReplies.forEachIndexed { index, knownPostReply -> - configuration.getStringValue("KnownReplies/$index/ID").value = knownPostReply + saveKnownPostRepliesRateLimiter.tryAcquire().ifTrue { + try { + readLock.withLock { + knownPostReplies.forEachIndexed { index, knownPostReply -> + configuration.getStringValue("KnownReplies/$index/ID").value = knownPostReply + } + configuration.getStringValue("KnownReplies/${knownPostReplies.size}/ID").value = null } - configuration.getStringValue("KnownReplies/${knownPostReplies.size}/ID").value = null + } catch (ce1: ConfigurationException) { + throw DatabaseException("Could not save database.", ce1) } - } catch (ce1: ConfigurationException) { - throw DatabaseException("Could not save database.", ce1) } } diff --git a/src/main/java/net/pterodactylus/sone/freenet/Key.java b/src/main/java/net/pterodactylus/sone/freenet/Key.java deleted file mode 100644 index 6811642..0000000 --- a/src/main/java/net/pterodactylus/sone/freenet/Key.java +++ /dev/null @@ -1,65 +0,0 @@ -package net.pterodactylus.sone.freenet; - -import static freenet.support.Base64.encode; -import static java.lang.String.format; - -import freenet.keys.FreenetURI; - -import com.google.common.annotations.VisibleForTesting; - -/** - * Encapsulates the parts of a {@link FreenetURI} that do not change while - * being converted from SSK to USK and/or back. - */ -public class Key { - - private final byte[] routingKey; - private final byte[] cryptoKey; - private final byte[] extra; - - private Key(byte[] routingKey, byte[] cryptoKey, byte[] extra) { - this.routingKey = routingKey; - this.cryptoKey = cryptoKey; - this.extra = extra; - } - - @VisibleForTesting - public String getRoutingKey() { - return encode(routingKey); - } - - @VisibleForTesting - public String getCryptoKey() { - return encode(cryptoKey); - } - - @VisibleForTesting - public String getExtra() { - return encode(extra); - } - - public FreenetURI toUsk(String docName, long edition, String... paths) { - return new FreenetURI("USK", docName, paths, routingKey, cryptoKey, - extra, edition); - } - - public FreenetURI toSsk(String docName, String... paths) { - return new FreenetURI("SSK", docName, paths, routingKey, cryptoKey, - extra); - } - - public FreenetURI toSsk(String docName, long edition, String... paths) { - return new FreenetURI("SSK", format("%s-%d", docName, edition), paths, - routingKey, cryptoKey, extra, edition); - } - - public static Key from(FreenetURI freenetURI) { - return new Key(freenetURI.getRoutingKey(), freenetURI.getCryptoKey(), - freenetURI.getExtra()); - } - - public static String routingKey(FreenetURI freenetURI) { - return from(freenetURI).getRoutingKey(); - } - -} diff --git a/src/main/java/net/pterodactylus/sone/freenet/L10nFilter.java b/src/main/java/net/pterodactylus/sone/freenet/L10nFilter.java deleted file mode 100644 index d667811..0000000 --- a/src/main/java/net/pterodactylus/sone/freenet/L10nFilter.java +++ /dev/null @@ -1,78 +0,0 @@ -/* - * Sone - L10nFilter.java - Copyright © 2010–2019 David Roden - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package net.pterodactylus.sone.freenet; - -import java.text.MessageFormat; -import java.util.ArrayList; -import java.util.List; -import java.util.Locale; -import java.util.Map; - -import javax.annotation.Nonnull; - -import net.pterodactylus.util.template.Filter; -import net.pterodactylus.util.template.TemplateContext; - -import freenet.l10n.BaseL10n; - -/** - * {@link Filter} implementation replaces {@link String} values with their - * translated equivalents. - */ -public class L10nFilter implements Filter { - - private final BaseL10n l10n; - - public L10nFilter(BaseL10n l10n) { - this.l10n = l10n; - } - - /** - * {@inheritDoc} - */ - @Override - public String format(TemplateContext templateContext, Object data, Map parameters) { - List parameterValues = getParameters(data, parameters); - String text = getText(data); - if (parameterValues.isEmpty()) { - return l10n.getString(text); - } - return new MessageFormat(l10n.getString(text), new Locale(l10n.getSelectedLanguage().shortCode)).format(parameterValues.toArray()); - } - - @Nonnull - private String getText(Object data) { - return (data instanceof L10nText) ? ((L10nText) data).getText() : String.valueOf(data); - } - - @Nonnull - private List getParameters(Object data, Map parameters) { - if (data instanceof L10nText) { - return ((L10nText) data).getParameters(); - } - List parameterValues = new ArrayList<>(); - int parameterIndex = 0; - while (parameters.containsKey(String.valueOf(parameterIndex))) { - Object value = parameters.get(String.valueOf(parameterIndex)); - parameterValues.add(value); - ++parameterIndex; - } - return parameterValues; - } - -} diff --git a/src/main/java/net/pterodactylus/sone/freenet/plugin/PluginConnector.java b/src/main/java/net/pterodactylus/sone/freenet/plugin/PluginConnector.java deleted file mode 100644 index fbaabb3..0000000 --- a/src/main/java/net/pterodactylus/sone/freenet/plugin/PluginConnector.java +++ /dev/null @@ -1,133 +0,0 @@ -/* - * Sone - PluginConnector.java - Copyright © 2010–2019 David Roden - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package net.pterodactylus.sone.freenet.plugin; - -import net.pterodactylus.sone.freenet.plugin.event.ReceivedReplyEvent; - -import com.google.common.eventbus.EventBus; -import com.google.inject.Inject; -import com.google.inject.Singleton; - -import freenet.pluginmanager.FredPluginTalker; -import freenet.pluginmanager.PluginNotFoundException; -import freenet.pluginmanager.PluginRespirator; -import freenet.pluginmanager.PluginTalker; -import freenet.support.SimpleFieldSet; -import freenet.support.api.Bucket; - -/** - * Interface for talking to other plugins. Other plugins are identified by their - * name and a unique connection identifier. - */ -@Singleton -public class PluginConnector implements FredPluginTalker { - - /** The event bus. */ - private final EventBus eventBus; - - /** The plugin respirator. */ - private final PluginRespirator pluginRespirator; - - /** - * Creates a new plugin connector. - * - * @param eventBus - * The event bus - * @param pluginRespirator - * The plugin respirator - */ - @Inject - public PluginConnector(EventBus eventBus, PluginRespirator pluginRespirator) { - this.eventBus = eventBus; - this.pluginRespirator = pluginRespirator; - } - - // - // ACTIONS - // - - /** - * Sends a request to the given plugin. - * - * @param pluginName - * The name of the plugin - * @param identifier - * The identifier of the connection - * @param fields - * The fields of the message - * @throws PluginException - * if the plugin can not be found - */ - public void sendRequest(String pluginName, String identifier, SimpleFieldSet fields) throws PluginException { - sendRequest(pluginName, identifier, fields, null); - } - - /** - * Sends a request to the given plugin. - * - * @param pluginName - * The name of the plugin - * @param identifier - * The identifier of the connection - * @param fields - * The fields of the message - * @param data - * The payload of the message (may be null) - * @throws PluginException - * if the plugin can not be found - */ - public void sendRequest(String pluginName, String identifier, SimpleFieldSet fields, Bucket data) throws PluginException { - getPluginTalker(pluginName, identifier).send(fields, data); - } - - // - // PRIVATE METHODS - // - - /** - * Returns the plugin talker for the given plugin connection. - * - * @param pluginName - * The name of the plugin - * @param identifier - * The identifier of the connection - * @return The plugin talker - * @throws PluginException - * if the plugin can not be found - */ - private PluginTalker getPluginTalker(String pluginName, String identifier) throws PluginException { - try { - return pluginRespirator.getPluginTalker(this, pluginName, identifier); - } catch (PluginNotFoundException pnfe1) { - throw new PluginException(pnfe1); - } - } - - // - // INTERFACE FredPluginTalker - // - - /** - * {@inheritDoc} - */ - @Override - public void onReply(String pluginName, String identifier, SimpleFieldSet params, Bucket data) { - eventBus.post(new ReceivedReplyEvent(this, pluginName, identifier, params, data)); - } - -} diff --git a/src/main/java/net/pterodactylus/sone/freenet/plugin/PluginException.java b/src/main/java/net/pterodactylus/sone/freenet/plugin/PluginException.java deleted file mode 100644 index e263a60..0000000 --- a/src/main/java/net/pterodactylus/sone/freenet/plugin/PluginException.java +++ /dev/null @@ -1,66 +0,0 @@ -/* - * Sone - PluginException.java - Copyright © 2010–2019 David Roden - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package net.pterodactylus.sone.freenet.plugin; - -import net.pterodactylus.sone.freenet.wot.WebOfTrustException; - -/** - * Exception that signals an error when communicating with a plugin. - */ -public class PluginException extends WebOfTrustException { - - /** - * Creates a new plugin exception. - */ - public PluginException() { - super(); - } - - /** - * Creates a new plugin exception. - * - * @param message - * The message of the exception - */ - public PluginException(String message) { - super(message); - } - - /** - * Creates a new plugin exception. - * - * @param cause - * The cause of the exception - */ - public PluginException(Throwable cause) { - super(cause); - } - - /** - * Creates a new plugin exception. - * - * @param message - * The message of the exception - * @param cause - * The cause of the exception - */ - public PluginException(String message, Throwable cause) { - super(message, cause); - } - -} diff --git a/src/main/java/net/pterodactylus/sone/freenet/plugin/event/ReceivedReplyEvent.java b/src/main/java/net/pterodactylus/sone/freenet/plugin/event/ReceivedReplyEvent.java deleted file mode 100644 index ce2ba7f..0000000 --- a/src/main/java/net/pterodactylus/sone/freenet/plugin/event/ReceivedReplyEvent.java +++ /dev/null @@ -1,115 +0,0 @@ -/* - * Sone - ReceivedReplyEvent.java - Copyright © 2013–2019 David Roden - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package net.pterodactylus.sone.freenet.plugin.event; - -import net.pterodactylus.sone.freenet.plugin.PluginConnector; -import freenet.support.SimpleFieldSet; -import freenet.support.api.Bucket; - -/** - * Event that signals that a plugin reply was received. - */ -public class ReceivedReplyEvent { - - /** The connector that received the reply. */ - private final PluginConnector pluginConnector; - - /** The name of the plugin that sent the reply. */ - private final String pluginName; - - /** The identifier of the initial request. */ - private final String identifier; - - /** The fields containing the reply. */ - private final SimpleFieldSet fieldSet; - - /** The optional reply data. */ - private final Bucket data; - - /** - * Creates a new “reply received” event. - * - * @param pluginConnector - * The connector that received the event - * @param pluginName - * The name of the plugin that sent the reply - * @param identifier - * The identifier of the initial request - * @param fieldSet - * The fields containing the reply - * @param data - * The optional data of the reply - */ - public ReceivedReplyEvent(PluginConnector pluginConnector, String pluginName, String identifier, SimpleFieldSet fieldSet, Bucket data) { - this.pluginConnector = pluginConnector; - this.pluginName = pluginName; - this.identifier = identifier; - this.fieldSet = fieldSet; - this.data = data; - } - - // - // ACCESSORS - // - - /** - * Returns the plugin connector that received the reply. - * - * @return The plugin connector that received the reply - */ - public PluginConnector pluginConnector() { - return pluginConnector; - } - - /** - * Returns the name of the plugin that sent the reply. - * - * @return The name of the plugin that sent the reply - */ - public String pluginName() { - return pluginName; - } - - /** - * Returns the identifier of the initial request. - * - * @return The identifier of the initial request - */ - public String identifier() { - return identifier; - } - - /** - * Returns the fields containing the reply. - * - * @return The fields containing the reply - */ - public SimpleFieldSet fieldSet() { - return fieldSet; - } - - /** - * Returns the optional data of the reply. - * - * @return The optional data of the reply (may be {@code null}) - */ - public Bucket data() { - return data; - } - -} diff --git a/src/main/java/net/pterodactylus/sone/freenet/wot/Context.java b/src/main/java/net/pterodactylus/sone/freenet/wot/Context.java deleted file mode 100644 index b386bdb..0000000 --- a/src/main/java/net/pterodactylus/sone/freenet/wot/Context.java +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Sone - Context.java - Copyright © 2014–2019 David Roden - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package net.pterodactylus.sone.freenet.wot; - -import javax.annotation.Nullable; - -import com.google.common.base.Function; - -/** - * Custom container for the Web of Trust context. This allows easier - * configuration of dependency injection. - */ -public class Context { - - public static final Function extractContext = new Function() { - @Nullable - @Override - public String apply(@Nullable Context context) { - return (context == null) ? null : context.getContext(); - } - }; - - private final String context; - - public Context(String context) { - this.context = context; - } - - public String getContext() { - return context; - } - -} diff --git a/src/main/java/net/pterodactylus/sone/freenet/wot/DefaultIdentity.java b/src/main/java/net/pterodactylus/sone/freenet/wot/DefaultIdentity.java deleted file mode 100644 index 0d92d61..0000000 --- a/src/main/java/net/pterodactylus/sone/freenet/wot/DefaultIdentity.java +++ /dev/null @@ -1,181 +0,0 @@ -/* - * Sone - DefaultIdentity.java - Copyright © 2010–2019 David Roden - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package net.pterodactylus.sone.freenet.wot; - -import java.util.Collection; -import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Map; -import java.util.Set; - -/** - * A Web of Trust identity. - */ -public class DefaultIdentity implements Identity { - - /** The ID of the identity. */ - private final String id; - - /** The nickname of the identity. */ - private final String nickname; - - /** The request URI of the identity. */ - private final String requestUri; - - /** The contexts of the identity. */ - private final Set contexts = Collections.synchronizedSet(new HashSet()); - - /** The properties of the identity. */ - private final Map properties = Collections.synchronizedMap(new HashMap()); - - /** Cached trust. */ - private final Map trustCache = Collections.synchronizedMap(new HashMap()); - - /** - * Creates a new identity. - * - * @param id - * The ID of the identity - * @param nickname - * The nickname of the identity - * @param requestUri - * The request URI of the identity - */ - public DefaultIdentity(String id, String nickname, String requestUri) { - this.id = id; - this.nickname = nickname; - this.requestUri = requestUri; - } - - // - // ACCESSORS - // - - @Override - public String getId() { - return id; - } - - @Override - public String getNickname() { - return nickname; - } - - @Override - public String getRequestUri() { - return requestUri; - } - - @Override - public Set getContexts() { - return Collections.unmodifiableSet(contexts); - } - - @Override - public boolean hasContext(String context) { - return contexts.contains(context); - } - - @Override - public void setContexts(Collection contexts) { - this.contexts.clear(); - this.contexts.addAll(contexts); - } - - @Override - public Identity addContext(String context) { - contexts.add(context); - return this; - } - - @Override - public Identity removeContext(String context) { - contexts.remove(context); - return this; - } - - @Override - public Map getProperties() { - return Collections.unmodifiableMap(properties); - } - - @Override - public void setProperties(Map properties) { - this.properties.clear(); - this.properties.putAll(properties); - } - - @Override - public String getProperty(String name) { - return properties.get(name); - } - - @Override - public Identity setProperty(String name, String value) { - properties.put(name, value); - return this; - } - - @Override - public Identity removeProperty(String name) { - properties.remove(name); - return this; - } - - @Override - public Trust getTrust(OwnIdentity ownIdentity) { - return trustCache.get(ownIdentity); - } - - @Override - public Identity setTrust(OwnIdentity ownIdentity, Trust trust) { - trustCache.put(ownIdentity, trust); - return this; - } - - @Override - public Identity removeTrust(OwnIdentity ownIdentity) { - trustCache.remove(ownIdentity); - return this; - } - - // - // OBJECT METHODS - // - - @Override - public int hashCode() { - return getId().hashCode(); - } - - @Override - public boolean equals(Object object) { - if (!(object instanceof Identity)) { - return false; - } - Identity identity = (Identity) object; - return identity.getId().equals(getId()); - } - - @Override - public String toString() { - return getClass().getSimpleName() + "[id=" + id + ",nickname=" + nickname + ",contexts=" + contexts + ",properties=" + properties + "]"; - } - -} diff --git a/src/main/java/net/pterodactylus/sone/freenet/wot/DefaultOwnIdentity.java b/src/main/java/net/pterodactylus/sone/freenet/wot/DefaultOwnIdentity.java deleted file mode 100644 index e2e3a74..0000000 --- a/src/main/java/net/pterodactylus/sone/freenet/wot/DefaultOwnIdentity.java +++ /dev/null @@ -1,91 +0,0 @@ -/* - * Sone - DefaultOwnIdentity.java - Copyright © 2010–2019 David Roden - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package net.pterodactylus.sone.freenet.wot; - -import static com.google.common.base.Preconditions.checkNotNull; - -/** - * An own identity is an identity that the owner of the node has full control - * over. - */ -public class DefaultOwnIdentity extends DefaultIdentity implements OwnIdentity { - - /** The insert URI of the identity. */ - private final String insertUri; - - /** - * Creates a new own identity. - * - * @param id - * The ID of the identity - * @param nickname - * The nickname of the identity - * @param requestUri - * The request URI of the identity - * @param insertUri - * The insert URI of the identity - */ - public DefaultOwnIdentity(String id, String nickname, String requestUri, String insertUri) { - super(id, nickname, requestUri); - this.insertUri = checkNotNull(insertUri); - } - - // - // ACCESSORS - // - - @Override - public String getInsertUri() { - return insertUri; - } - - @Override - public OwnIdentity addContext(String context) { - return (OwnIdentity) super.addContext(context); - } - - @Override - public OwnIdentity removeContext(String context) { - return (OwnIdentity) super.removeContext(context); - } - - @Override - public OwnIdentity setProperty(String name, String value) { - return (OwnIdentity) super.setProperty(name, value); - } - - @Override - public OwnIdentity removeProperty(String name) { - return (OwnIdentity) super.removeProperty(name); - } - - // - // OBJECT METHODS - // - - @Override - public int hashCode() { - return super.hashCode(); - } - - @Override - public boolean equals(Object object) { - return super.equals(object); - } - -} diff --git a/src/main/java/net/pterodactylus/sone/freenet/wot/Identity.java b/src/main/java/net/pterodactylus/sone/freenet/wot/Identity.java index a99aac0..3460516 100644 --- a/src/main/java/net/pterodactylus/sone/freenet/wot/Identity.java +++ b/src/main/java/net/pterodactylus/sone/freenet/wot/Identity.java @@ -31,20 +31,6 @@ import com.google.common.base.Function; */ public interface Identity { - public static final Function> TO_CONTEXTS = new Function>() { - @Override - public Set apply(Identity identity) { - return (identity == null) ? Collections.emptySet() : identity.getContexts(); - } - }; - - public static final Function> TO_PROPERTIES = new Function>() { - @Override - public Map apply(Identity input) { - return (input == null) ? Collections.emptyMap() : input.getProperties(); - } - }; - /** * Returns the ID of the identity. * @@ -97,7 +83,7 @@ public interface Identity { * @param contexts * All contexts of the identity */ - public void setContexts(Collection contexts); + public void setContexts(Set contexts); /** * Removes the given context from this identity. diff --git a/src/main/java/net/pterodactylus/sone/freenet/wot/IdentityChangeDetector.java b/src/main/java/net/pterodactylus/sone/freenet/wot/IdentityChangeDetector.java deleted file mode 100644 index 8b28011..0000000 --- a/src/main/java/net/pterodactylus/sone/freenet/wot/IdentityChangeDetector.java +++ /dev/null @@ -1,198 +0,0 @@ -/* - * Sone - IdentityChangeDetector.java - Copyright © 2013–2019 David Roden - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package net.pterodactylus.sone.freenet.wot; - -import static com.google.common.base.Optional.absent; -import static com.google.common.base.Optional.fromNullable; -import static com.google.common.base.Predicates.not; -import static com.google.common.collect.FluentIterable.from; -import static net.pterodactylus.sone.freenet.wot.Identity.TO_CONTEXTS; -import static net.pterodactylus.sone.freenet.wot.Identity.TO_PROPERTIES; - -import java.util.Collection; -import java.util.Map; -import java.util.Map.Entry; - -import com.google.common.base.Optional; -import com.google.common.base.Predicate; -import com.google.common.collect.FluentIterable; -import com.google.common.collect.ImmutableMap; - -/** - * Detects changes between two lists of {@link Identity}s. The detector can find - * added and removed identities, and for identities that exist in both list - * their contexts and properties are checked for added, removed, or (in case of - * properties) changed values. - */ -public class IdentityChangeDetector { - - private final Map oldIdentities; - private Optional onNewIdentity = absent(); - private Optional onRemovedIdentity = absent(); - private Optional onChangedIdentity = absent(); - private Optional onUnchangedIdentity = absent(); - - public IdentityChangeDetector(Collection oldIdentities) { - this.oldIdentities = convertToMap(oldIdentities); - } - - public void onNewIdentity(IdentityProcessor onNewIdentity) { - this.onNewIdentity = fromNullable(onNewIdentity); - } - - public void onRemovedIdentity(IdentityProcessor onRemovedIdentity) { - this.onRemovedIdentity = fromNullable(onRemovedIdentity); - } - - public void onChangedIdentity(IdentityProcessor onChangedIdentity) { - this.onChangedIdentity = fromNullable(onChangedIdentity); - } - - public void onUnchangedIdentity(IdentityProcessor onUnchangedIdentity) { - this.onUnchangedIdentity = fromNullable(onUnchangedIdentity); - } - - public void detectChanges(final Collection newIdentities) { - notifyForRemovedIdentities(from(oldIdentities.values()).filter(notContainedIn(newIdentities))); - notifyForNewIdentities(from(newIdentities).filter(notContainedIn(oldIdentities.values()))); - notifyForChangedIdentities(from(newIdentities).filter(containedIn(oldIdentities)).filter(hasChanged(oldIdentities))); - notifyForUnchangedIdentities(from(newIdentities).filter(containedIn(oldIdentities)).filter(not(hasChanged(oldIdentities)))); - } - - private void notifyForRemovedIdentities(Iterable identities) { - notify(onRemovedIdentity, identities); - } - - private void notifyForNewIdentities(FluentIterable newIdentities) { - notify(onNewIdentity, newIdentities); - } - - private void notifyForChangedIdentities(FluentIterable identities) { - notify(onChangedIdentity, identities); - } - - private void notifyForUnchangedIdentities(FluentIterable identities) { - notify(onUnchangedIdentity, identities); - } - - private void notify(Optional identityProcessor, Iterable identities) { - if (!identityProcessor.isPresent()) { - return; - } - for (Identity identity : identities) { - identityProcessor.get().processIdentity(identity); - } - } - - private static Predicate hasChanged(final Map oldIdentities) { - return new Predicate() { - @Override - public boolean apply(Identity identity) { - return (identity != null) && identityHasChanged(oldIdentities.get(identity.getId()), identity); - } - }; - } - - private static boolean identityHasChanged(Identity oldIdentity, Identity newIdentity) { - return identityHasNewContexts(oldIdentity, newIdentity) - || identityHasRemovedContexts(oldIdentity, newIdentity) - || identityHasNewProperties(oldIdentity, newIdentity) - || identityHasRemovedProperties(oldIdentity, newIdentity) - || identityHasChangedProperties(oldIdentity, newIdentity); - } - - private static boolean identityHasNewContexts(Identity oldIdentity, Identity newIdentity) { - return from(TO_CONTEXTS.apply(newIdentity)).anyMatch(notAContextOf(oldIdentity)); - } - - private static boolean identityHasRemovedContexts(Identity oldIdentity, Identity newIdentity) { - return from(TO_CONTEXTS.apply(oldIdentity)).anyMatch(notAContextOf(newIdentity)); - } - - private static boolean identityHasNewProperties(Identity oldIdentity, Identity newIdentity) { - return from(TO_PROPERTIES.apply(newIdentity).entrySet()).anyMatch(notAPropertyOf(oldIdentity)); - } - - private static boolean identityHasRemovedProperties(Identity oldIdentity, Identity newIdentity) { - return from(TO_PROPERTIES.apply(oldIdentity).entrySet()).anyMatch(notAPropertyOf(newIdentity)); - } - - private static boolean identityHasChangedProperties(Identity oldIdentity, Identity newIdentity) { - return from(TO_PROPERTIES.apply(oldIdentity).entrySet()).anyMatch(hasADifferentValueThanIn(newIdentity)); - } - - private static Predicate containedIn(final Map identities) { - return new Predicate() { - @Override - public boolean apply(Identity identity) { - return (identity != null) && identities.containsKey(identity.getId()); - } - }; - } - - private static Predicate notAContextOf(final Identity identity) { - return new Predicate() { - @Override - public boolean apply(String context) { - return (identity != null) && !identity.getContexts().contains(context); - } - }; - } - - private static Predicate notContainedIn(final Collection newIdentities) { - return new Predicate() { - @Override - public boolean apply(Identity identity) { - return (identity != null) && !newIdentities.contains(identity); - } - }; - } - - private static Predicate> notAPropertyOf(final Identity identity) { - return new Predicate>() { - @Override - public boolean apply(Entry property) { - return (property != null) && !identity.getProperties().containsKey(property.getKey()); - } - }; - } - - private static Predicate> hasADifferentValueThanIn(final Identity newIdentity) { - return new Predicate>() { - @Override - public boolean apply(Entry property) { - return (property != null) && !newIdentity.getProperty(property.getKey()).equals(property.getValue()); - } - }; - } - - private static Map convertToMap(Collection identities) { - ImmutableMap.Builder mapBuilder = ImmutableMap.builder(); - for (Identity identity : identities) { - mapBuilder.put(identity.getId(), identity); - } - return mapBuilder.build(); - } - - public interface IdentityProcessor { - - void processIdentity(Identity identity); - - } - -} diff --git a/src/main/java/net/pterodactylus/sone/freenet/wot/IdentityChangeDetector.kt b/src/main/java/net/pterodactylus/sone/freenet/wot/IdentityChangeDetector.kt new file mode 100644 index 0000000..deb35b1 --- /dev/null +++ b/src/main/java/net/pterodactylus/sone/freenet/wot/IdentityChangeDetector.kt @@ -0,0 +1,68 @@ +/* + * Sone - IdentityChangeDetector.java - Copyright © 2013–2019 David Roden + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.pterodactylus.sone.freenet.wot + +/** + * Detects changes between two lists of [Identity]s. The detector can find + * added and removed identities, and for identities that exist in both list + * their contexts and properties are checked for added, removed, or (in case of + * properties) changed values. + */ +class IdentityChangeDetector(oldIdentities: Collection) { + + private val oldIdentities: Map = oldIdentities.associateBy { it.id } + var onNewIdentity: IdentityProcessor? = null + var onRemovedIdentity: IdentityProcessor? = null + var onChangedIdentity: IdentityProcessor? = null + var onUnchangedIdentity: IdentityProcessor? = null + + fun detectChanges(newIdentities: Collection) { + onRemovedIdentity.notify(oldIdentities.values.filter { it !in newIdentities }) + onNewIdentity.notify(newIdentities.filter { it !in oldIdentities.values }) + onChangedIdentity.notify(newIdentities.filter { it.id in oldIdentities }.filter { identityHasChanged(oldIdentities[it.id]!!, it) }) + onUnchangedIdentity.notify(newIdentities.filter { it.id in oldIdentities }.filterNot { identityHasChanged(oldIdentities[it.id]!!, it) }) + } + + private fun identityHasChanged(oldIdentity: Identity, newIdentity: Identity?) = + identityHasNewContexts(oldIdentity, newIdentity!!) + || identityHasRemovedContexts(oldIdentity, newIdentity) + || identityHasNewProperties(oldIdentity, newIdentity) + || identityHasRemovedProperties(oldIdentity, newIdentity) + || identityHasChangedProperties(oldIdentity, newIdentity) + + private fun identityHasNewContexts(oldIdentity: Identity, newIdentity: Identity) = + newIdentity.contexts.any { it !in oldIdentity.contexts } + + private fun identityHasRemovedContexts(oldIdentity: Identity, newIdentity: Identity) = + oldIdentity.contexts.any { it !in newIdentity.contexts } + + private fun identityHasNewProperties(oldIdentity: Identity, newIdentity: Identity) = + newIdentity.properties.keys.any { it !in oldIdentity.properties } + + private fun identityHasRemovedProperties(oldIdentity: Identity, newIdentity: Identity) = + oldIdentity.properties.keys.any { it !in newIdentity.properties } + + private fun identityHasChangedProperties(oldIdentity: Identity, newIdentity: Identity) = + oldIdentity.properties.entries.any { newIdentity.properties[it.key] != it.value } + +} + +typealias IdentityProcessor = (Identity) -> Unit + +private fun IdentityProcessor?.notify(identities: Iterable) = + this?.let { identities.forEach(this::invoke) } diff --git a/src/main/java/net/pterodactylus/sone/freenet/wot/IdentityChangeEventSender.java b/src/main/java/net/pterodactylus/sone/freenet/wot/IdentityChangeEventSender.java deleted file mode 100644 index fd57c38..0000000 --- a/src/main/java/net/pterodactylus/sone/freenet/wot/IdentityChangeEventSender.java +++ /dev/null @@ -1,132 +0,0 @@ -/* - * Sone - IdentityChangeEventSender.java - Copyright © 2013–2019 David Roden - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package net.pterodactylus.sone.freenet.wot; - -import java.util.Collection; -import java.util.Map; - -import net.pterodactylus.sone.freenet.wot.IdentityChangeDetector.IdentityProcessor; -import net.pterodactylus.sone.freenet.wot.event.IdentityAddedEvent; -import net.pterodactylus.sone.freenet.wot.event.IdentityRemovedEvent; -import net.pterodactylus.sone.freenet.wot.event.IdentityUpdatedEvent; -import net.pterodactylus.sone.freenet.wot.event.OwnIdentityAddedEvent; -import net.pterodactylus.sone.freenet.wot.event.OwnIdentityRemovedEvent; - -import com.google.common.eventbus.EventBus; - -/** - * Detects changes in {@link Identity}s trusted my multiple {@link - * OwnIdentity}s. - * - * @see IdentityChangeDetector - */ -public class IdentityChangeEventSender { - - private final EventBus eventBus; - private final Map> oldIdentities; - - public IdentityChangeEventSender(EventBus eventBus, Map> oldIdentities) { - this.eventBus = eventBus; - this.oldIdentities = oldIdentities; - } - - public void detectChanges(Map> identities) { - IdentityChangeDetector identityChangeDetector = new IdentityChangeDetector(oldIdentities.keySet()); - identityChangeDetector.onNewIdentity(addNewOwnIdentityAndItsTrustedIdentities(identities)); - identityChangeDetector.onRemovedIdentity(removeOwnIdentityAndItsTrustedIdentities(oldIdentities)); - identityChangeDetector.onUnchangedIdentity(detectChangesInTrustedIdentities(identities, oldIdentities)); - identityChangeDetector.detectChanges(identities.keySet()); - } - - private IdentityProcessor addNewOwnIdentityAndItsTrustedIdentities(final Map> newIdentities) { - return new IdentityProcessor() { - @Override - public void processIdentity(Identity identity) { - eventBus.post(new OwnIdentityAddedEvent((OwnIdentity) identity)); - for (Identity newIdentity : newIdentities.get((OwnIdentity) identity)) { - eventBus.post(new IdentityAddedEvent((OwnIdentity) identity, newIdentity)); - } - } - }; - } - - private IdentityProcessor removeOwnIdentityAndItsTrustedIdentities(final Map> oldIdentities) { - return new IdentityProcessor() { - @Override - public void processIdentity(Identity identity) { - eventBus.post(new OwnIdentityRemovedEvent((OwnIdentity) identity)); - for (Identity removedIdentity : oldIdentities.get((OwnIdentity) identity)) { - eventBus.post(new IdentityRemovedEvent((OwnIdentity) identity, removedIdentity)); - } - } - }; - } - - private IdentityProcessor detectChangesInTrustedIdentities(Map> newIdentities, Map> oldIdentities) { - return new DefaultIdentityProcessor(oldIdentities, newIdentities); - } - - private class DefaultIdentityProcessor implements IdentityProcessor { - - private final Map> oldIdentities; - private final Map> newIdentities; - - public DefaultIdentityProcessor(Map> oldIdentities, Map> newIdentities) { - this.oldIdentities = oldIdentities; - this.newIdentities = newIdentities; - } - - @Override - public void processIdentity(Identity ownIdentity) { - IdentityChangeDetector identityChangeDetector = new IdentityChangeDetector(oldIdentities.get((OwnIdentity) ownIdentity)); - identityChangeDetector.onNewIdentity(notifyForAddedIdentities((OwnIdentity) ownIdentity)); - identityChangeDetector.onRemovedIdentity(notifyForRemovedIdentities((OwnIdentity) ownIdentity)); - identityChangeDetector.onChangedIdentity(notifyForChangedIdentities((OwnIdentity) ownIdentity)); - identityChangeDetector.detectChanges(newIdentities.get((OwnIdentity) ownIdentity)); - } - - private IdentityProcessor notifyForChangedIdentities(final OwnIdentity ownIdentity) { - return new IdentityProcessor() { - @Override - public void processIdentity(Identity identity) { - eventBus.post(new IdentityUpdatedEvent(ownIdentity, identity)); - } - }; - } - - private IdentityProcessor notifyForRemovedIdentities(final OwnIdentity ownIdentity) { - return new IdentityProcessor() { - @Override - public void processIdentity(Identity identity) { - eventBus.post(new IdentityRemovedEvent(ownIdentity, identity)); - } - }; - } - - private IdentityProcessor notifyForAddedIdentities(final OwnIdentity ownIdentity) { - return new IdentityProcessor() { - @Override - public void processIdentity(Identity identity) { - eventBus.post(new IdentityAddedEvent(ownIdentity, identity)); - } - }; - } - - } - -} diff --git a/src/main/java/net/pterodactylus/sone/freenet/wot/IdentityLoader.java b/src/main/java/net/pterodactylus/sone/freenet/wot/IdentityLoader.java deleted file mode 100644 index 0917130..0000000 --- a/src/main/java/net/pterodactylus/sone/freenet/wot/IdentityLoader.java +++ /dev/null @@ -1,80 +0,0 @@ -/* - * Sone - IdentityLoader.java - Copyright © 2013–2019 David Roden - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package net.pterodactylus.sone.freenet.wot; - -import static java.util.concurrent.TimeUnit.*; -import static net.pterodactylus.sone.freenet.wot.Context.*; - -import java.util.*; -import java.util.logging.*; - -import net.pterodactylus.sone.freenet.plugin.*; - -import com.google.common.base.Optional; -import com.google.common.base.*; -import com.google.inject.*; - -/** - * Loads {@link OwnIdentity}s and the {@link Identity}s they trust. - */ -public class IdentityLoader { - - private final Logger logger = Logger.getLogger(IdentityLoader.class.getName()); - private final WebOfTrustConnector webOfTrustConnector; - private final Optional context; - - public IdentityLoader(WebOfTrustConnector webOfTrustConnector) { - this(webOfTrustConnector, Optional.absent()); - } - - @Inject - public IdentityLoader(WebOfTrustConnector webOfTrustConnector, Optional context) { - this.webOfTrustConnector = webOfTrustConnector; - this.context = context; - } - - public Map> loadIdentities() throws WebOfTrustException { - Stopwatch stopwatch = Stopwatch.createStarted(); - Collection currentOwnIdentities = webOfTrustConnector.loadAllOwnIdentities(); - logger.fine("Loaded " + currentOwnIdentities.size() + " own identities in " + (stopwatch.elapsed(MILLISECONDS) / 1000.0) + "s."); - return loadTrustedIdentitiesForOwnIdentities(currentOwnIdentities); - } - - private Map> loadTrustedIdentitiesForOwnIdentities(Collection ownIdentities) throws PluginException { - Map> currentIdentities = new HashMap<>(); - - for (OwnIdentity ownIdentity : ownIdentities) { - if (identityDoesNotHaveTheCorrectContext(ownIdentity)) { - currentIdentities.put(ownIdentity, Collections.emptySet()); - continue; - } - - Stopwatch stopwatch = Stopwatch.createStarted(); - Set trustedIdentities = webOfTrustConnector.loadTrustedIdentities(ownIdentity, context.transform(extractContext)); - logger.fine("Loaded " + trustedIdentities.size() + " identities for " + ownIdentity.getNickname() + " in " + (stopwatch.elapsed(MILLISECONDS) / 1000.0) + "s."); - currentIdentities.put(ownIdentity, trustedIdentities); - } - - return currentIdentities; - } - - private boolean identityDoesNotHaveTheCorrectContext(OwnIdentity ownIdentity) { - return context.isPresent() && !ownIdentity.hasContext(context.transform(extractContext).get()); - } - -} diff --git a/src/main/java/net/pterodactylus/sone/freenet/wot/IdentityManager.java b/src/main/java/net/pterodactylus/sone/freenet/wot/IdentityManager.java deleted file mode 100644 index c0f6f1b..0000000 --- a/src/main/java/net/pterodactylus/sone/freenet/wot/IdentityManager.java +++ /dev/null @@ -1,20 +0,0 @@ -package net.pterodactylus.sone.freenet.wot; - -import java.util.Set; - -import net.pterodactylus.util.service.Service; - -import com.google.common.eventbus.EventBus; -import com.google.inject.ImplementedBy; - -/** - * Connects to a {@link WebOfTrustConnector} and sends identity events to an - * {@link EventBus}. - */ -@ImplementedBy(IdentityManagerImpl.class) -public interface IdentityManager extends Service { - - boolean isConnected(); - Set getAllOwnIdentities(); - -} diff --git a/src/main/java/net/pterodactylus/sone/freenet/wot/IdentityManagerImpl.java b/src/main/java/net/pterodactylus/sone/freenet/wot/IdentityManagerImpl.java deleted file mode 100644 index 6f46465..0000000 --- a/src/main/java/net/pterodactylus/sone/freenet/wot/IdentityManagerImpl.java +++ /dev/null @@ -1,147 +0,0 @@ -/* - * Sone - IdentityManagerImpl.java - Copyright © 2010–2019 David Roden - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package net.pterodactylus.sone.freenet.wot; - -import static java.util.logging.Logger.getLogger; - -import java.util.Collection; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Map; -import java.util.Set; -import java.util.logging.Level; -import java.util.logging.Logger; - -import net.pterodactylus.sone.freenet.plugin.PluginException; -import net.pterodactylus.util.service.AbstractService; - -import com.google.common.collect.Sets; -import com.google.common.eventbus.EventBus; -import com.google.inject.Inject; -import com.google.inject.Singleton; - -/** - * The identity manager takes care of loading and storing identities, their - * contexts, and properties. It does so in a way that does not expose errors via - * exceptions but it only logs them and tries to return sensible defaults. - *

- * It is also responsible for polling identities from the Web of Trust plugin - * and sending events to the {@link EventBus} when {@link Identity}s and - * {@link OwnIdentity}s are discovered or disappearing. - */ -@Singleton -public class IdentityManagerImpl extends AbstractService implements IdentityManager { - - /** The logger. */ - private static final Logger logger = getLogger(IdentityManagerImpl.class.getName()); - - /** The event bus. */ - private final EventBus eventBus; - - private final IdentityLoader identityLoader; - - /** The Web of Trust connector. */ - private final WebOfTrustConnector webOfTrustConnector; - - /** The currently known own identities. */ - private final Set currentOwnIdentities = Sets.newHashSet(); - - /** - * Creates a new identity manager. - * - * @param eventBus - * The event bus - * @param webOfTrustConnector - * The Web of Trust connector - */ - @Inject - public IdentityManagerImpl(EventBus eventBus, WebOfTrustConnector webOfTrustConnector, IdentityLoader identityLoader) { - super("Sone Identity Manager", false); - this.eventBus = eventBus; - this.webOfTrustConnector = webOfTrustConnector; - this.identityLoader = identityLoader; - } - - // - // ACCESSORS - // - - /** - * Returns whether the Web of Trust plugin could be reached during the last - * try. - * - * @return {@code true} if the Web of Trust plugin is connected, - * {@code false} otherwise - */ - @Override - public boolean isConnected() { - try { - webOfTrustConnector.ping(); - return true; - } catch (PluginException pe1) { - /* not connected, ignore. */ - return false; - } - } - - /** - * Returns all own identities. - * - * @return All own identities - */ - @Override - public Set getAllOwnIdentities() { - synchronized (currentOwnIdentities) { - return new HashSet<>(currentOwnIdentities); - } - } - - // - // SERVICE METHODS - // - - /** - * {@inheritDoc} - */ - @Override - protected void serviceRun() { - Map> oldIdentities = new HashMap<>(); - - while (!shouldStop()) { - try { - Map> currentIdentities = identityLoader.loadIdentities(); - - IdentityChangeEventSender identityChangeEventSender = new IdentityChangeEventSender(eventBus, oldIdentities); - identityChangeEventSender.detectChanges(currentIdentities); - - oldIdentities = currentIdentities; - - synchronized (currentOwnIdentities) { - currentOwnIdentities.clear(); - currentOwnIdentities.addAll(currentIdentities.keySet()); - } - } catch (WebOfTrustException wote1) { - logger.log(Level.WARNING, "WoT has disappeared!", wote1); - } - - /* wait a minute before checking again. */ - sleep(60 * 1000); - } - } - -} diff --git a/src/main/java/net/pterodactylus/sone/freenet/wot/Trust.java b/src/main/java/net/pterodactylus/sone/freenet/wot/Trust.java deleted file mode 100644 index 2da00f6..0000000 --- a/src/main/java/net/pterodactylus/sone/freenet/wot/Trust.java +++ /dev/null @@ -1,104 +0,0 @@ -/* - * Sone - Trust.java - Copyright © 2010–2019 David Roden - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package net.pterodactylus.sone.freenet.wot; - -import static com.google.common.base.Objects.equal; - -import com.google.common.base.Objects; - -/** - * Container class for trust in the web of trust. - */ -public class Trust { - - /** Explicitely assigned trust. */ - private final Integer explicit; - - /** Implicitely calculated trust. */ - private final Integer implicit; - - /** The distance from the owner of the trust tree. */ - private final Integer distance; - - /** - * Creates a new trust container. - * - * @param explicit - * The explicit trust - * @param implicit - * The implicit trust - * @param distance - * The distance - */ - public Trust(Integer explicit, Integer implicit, Integer distance) { - this.explicit = explicit; - this.implicit = implicit; - this.distance = distance; - } - - /** - * Returns the trust explicitely assigned to an identity. - * - * @return The explicitely assigned trust, or {@code null} if the identity - * is not in the own identity’s trust tree - */ - public Integer getExplicit() { - return explicit; - } - - /** - * Returns the implicitely assigned trust, or the calculated trust. - * - * @return The calculated trust, or {@code null} if the identity is not in - * the own identity’s trust tree - */ - public Integer getImplicit() { - return implicit; - } - - /** - * Returns the distance of the trusted identity from the trusting identity. - * - * @return The distance from the own identity, or {@code null} if the - * identity is not in the own identity’s trust tree - */ - public Integer getDistance() { - return distance; - } - - @Override - public boolean equals(Object object) { - if (!(object instanceof Trust)) { - return false; - } - Trust trust = (Trust) object; - return equal(getExplicit(), trust.getExplicit()) && equal(getImplicit(), trust.getImplicit()) && equal(getDistance(), trust.getDistance()); - } - - @Override - public int hashCode() { - return Objects.hashCode(explicit, implicit, distance); - } - - /** {@inheritDoc} */ - @Override - public String toString() { - return getClass().getName() + "[explicit=" + explicit + ",implicit=" + implicit + ",distance=" + distance + "]"; - } - -} diff --git a/src/main/java/net/pterodactylus/sone/freenet/wot/WebOfTrustConnector.java b/src/main/java/net/pterodactylus/sone/freenet/wot/WebOfTrustConnector.java deleted file mode 100644 index c1dbef9..0000000 --- a/src/main/java/net/pterodactylus/sone/freenet/wot/WebOfTrustConnector.java +++ /dev/null @@ -1,623 +0,0 @@ -/* - * Sone - WebOfTrustConnector.java - Copyright © 2010–2019 David Roden - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package net.pterodactylus.sone.freenet.wot; - -import static java.util.logging.Logger.getLogger; -import static net.pterodactylus.sone.utils.NumberParsers.parseInt; - -import java.util.HashMap; -import java.util.HashSet; -import java.util.Map; -import java.util.Set; -import java.util.concurrent.atomic.AtomicLong; -import java.util.logging.Level; -import java.util.logging.Logger; - -import net.pterodactylus.sone.freenet.plugin.PluginConnector; -import net.pterodactylus.sone.freenet.plugin.PluginException; -import net.pterodactylus.sone.freenet.plugin.event.ReceivedReplyEvent; - -import com.google.common.base.Optional; -import com.google.common.collect.MapMaker; -import com.google.common.eventbus.Subscribe; -import com.google.inject.Inject; -import com.google.inject.Singleton; - -import freenet.support.SimpleFieldSet; -import freenet.support.api.Bucket; - -/** - * Connector for the Web of Trust plugin. - */ -@Singleton -public class WebOfTrustConnector { - - /** The logger. */ - private static final Logger logger = getLogger(WebOfTrustConnector.class.getName()); - - /** The name of the WoT plugin. */ - private static final String WOT_PLUGIN_NAME = "plugins.WebOfTrust.WebOfTrust"; - - /** Counter for connection identifiers. */ - private final AtomicLong counter = new AtomicLong(); - - /** The plugin connector. */ - private final PluginConnector pluginConnector; - - /** Map for replies. */ - private final Map replies = new MapMaker().makeMap(); - - /** - * Creates a new Web of Trust connector that uses the given plugin - * connector. - * - * @param pluginConnector - * The plugin connector - */ - @Inject - public WebOfTrustConnector(PluginConnector pluginConnector) { - this.pluginConnector = pluginConnector; - } - - // - // ACTIONS - // - - /** - * Stops the web of trust connector. - */ - public void stop() { - /* does nothing. */ - } - - /** - * Loads all own identities from the Web of Trust plugin. - * - * @return All own identity - * @throws WebOfTrustException - * if the own identities can not be loaded - */ - public Set loadAllOwnIdentities() throws WebOfTrustException { - Reply reply = performRequest(SimpleFieldSetConstructor.create().put("Message", "GetOwnIdentities").get()); - SimpleFieldSet fields = reply.getFields(); - int ownIdentityCounter = -1; - Set ownIdentities = new HashSet<>(); - while (true) { - String id = fields.get("Identity" + ++ownIdentityCounter); - if (id == null) { - break; - } - String requestUri = fields.get("RequestURI" + ownIdentityCounter); - String insertUri = fields.get("InsertURI" + ownIdentityCounter); - String nickname = fields.get("Nickname" + ownIdentityCounter); - DefaultOwnIdentity ownIdentity = new DefaultOwnIdentity(id, nickname, requestUri, insertUri); - ownIdentity.setContexts(parseContexts("Contexts" + ownIdentityCounter + ".", fields)); - ownIdentity.setProperties(parseProperties("Properties" + ownIdentityCounter + ".", fields)); - ownIdentities.add(ownIdentity); - } - return ownIdentities; - } - - /** - * Loads all identities that the given identities trusts with a score of - * more than 0. - * - * @param ownIdentity - * The own identity - * @return All trusted identities - * @throws PluginException - * if an error occured talking to the Web of Trust plugin - */ - public Set loadTrustedIdentities(OwnIdentity ownIdentity) throws PluginException { - return loadTrustedIdentities(ownIdentity, Optional.absent()); - } - - /** - * Loads all identities that the given identities trusts with a score of - * more than 0 and the (optional) given context. - * - * @param ownIdentity - * The own identity - * @param context - * The context to filter, or {@code null} - * @return All trusted identities - * @throws PluginException - * if an error occured talking to the Web of Trust plugin - */ - public Set loadTrustedIdentities(OwnIdentity ownIdentity, Optional context) throws PluginException { - Reply reply = performRequest(SimpleFieldSetConstructor.create().put("Message", "GetIdentitiesByScore").put("Truster", ownIdentity.getId()).put("Selection", "+").put("Context", context.or("")).put("WantTrustValues", "true").get()); - SimpleFieldSet fields = reply.getFields(); - Set identities = new HashSet<>(); - int identityCounter = -1; - while (true) { - String id = fields.get("Identity" + ++identityCounter); - if (id == null) { - break; - } - String nickname = fields.get("Nickname" + identityCounter); - String requestUri = fields.get("RequestURI" + identityCounter); - DefaultIdentity identity = new DefaultIdentity(id, nickname, requestUri); - identity.setContexts(parseContexts("Contexts" + identityCounter + ".", fields)); - identity.setProperties(parseProperties("Properties" + identityCounter + ".", fields)); - Integer trust = parseInt(fields.get("Trust" + identityCounter), null); - int score = parseInt(fields.get("Score" + identityCounter), 0); - int rank = parseInt(fields.get("Rank" + identityCounter), 0); - identity.setTrust(ownIdentity, new Trust(trust, score, rank)); - identities.add(identity); - } - return identities; - } - - /** - * Adds the given context to the given identity. - * - * @param ownIdentity - * The identity to add the context to - * @param context - * The context to add - * @throws PluginException - * if an error occured talking to the Web of Trust plugin - */ - public void addContext(OwnIdentity ownIdentity, String context) throws PluginException { - performRequest(SimpleFieldSetConstructor.create().put("Message", "AddContext").put("Identity", ownIdentity.getId()).put("Context", context).get()); - } - - /** - * Removes the given context from the given identity. - * - * @param ownIdentity - * The identity to remove the context from - * @param context - * The context to remove - * @throws PluginException - * if an error occured talking to the Web of Trust plugin - */ - public void removeContext(OwnIdentity ownIdentity, String context) throws PluginException { - performRequest(SimpleFieldSetConstructor.create().put("Message", "RemoveContext").put("Identity", ownIdentity.getId()).put("Context", context).get()); - } - - /** - * Returns the value of the property with the given name. - * - * @param identity - * The identity whose properties to check - * @param name - * The name of the property to return - * @return The value of the property, or {@code null} if there is no value - * @throws PluginException - * if an error occured talking to the Web of Trust plugin - */ - public String getProperty(Identity identity, String name) throws PluginException { - Reply reply = performRequest(SimpleFieldSetConstructor.create().put("Message", "GetProperty").put("Identity", identity.getId()).put("Property", name).get()); - return reply.getFields().get("Property"); - } - - /** - * Sets the property with the given name to the given value. - * - * @param ownIdentity - * The identity to set the property on - * @param name - * The name of the property to set - * @param value - * The value to set - * @throws PluginException - * if an error occured talking to the Web of Trust plugin - */ - public void setProperty(OwnIdentity ownIdentity, String name, String value) throws PluginException { - performRequest(SimpleFieldSetConstructor.create().put("Message", "SetProperty").put("Identity", ownIdentity.getId()).put("Property", name).put("Value", value).get()); - } - - /** - * Removes the property with the given name. - * - * @param ownIdentity - * The identity to remove the property from - * @param name - * The name of the property to remove - * @throws PluginException - * if an error occured talking to the Web of Trust plugin - */ - public void removeProperty(OwnIdentity ownIdentity, String name) throws PluginException { - performRequest(SimpleFieldSetConstructor.create().put("Message", "RemoveProperty").put("Identity", ownIdentity.getId()).put("Property", name).get()); - } - - /** - * Returns the trust for the given identity assigned to it by the given own - * identity. - * - * @param ownIdentity - * The own identity - * @param identity - * The identity to get the trust for - * @return The trust for the given identity - * @throws PluginException - * if an error occured talking to the Web of Trust plugin - */ - public Trust getTrust(OwnIdentity ownIdentity, Identity identity) throws PluginException { - Reply getTrustReply = performRequest(SimpleFieldSetConstructor.create().put("Message", "GetIdentity").put("Truster", ownIdentity.getId()).put("Identity", identity.getId()).get()); - String trust = getTrustReply.getFields().get("Trust"); - String score = getTrustReply.getFields().get("Score"); - String rank = getTrustReply.getFields().get("Rank"); - Integer explicit = null; - Integer implicit = null; - Integer distance = null; - try { - explicit = Integer.valueOf(trust); - } catch (NumberFormatException nfe1) { - /* ignore. */ - } - try { - implicit = Integer.valueOf(score); - distance = Integer.valueOf(rank); - } catch (NumberFormatException nfe1) { - /* ignore. */ - } - return new Trust(explicit, implicit, distance); - } - - /** - * Sets the trust for the given identity. - * - * @param ownIdentity - * The trusting identity - * @param identity - * The trusted identity - * @param trust - * The amount of trust (-100 thru 100) - * @param comment - * The comment or explanation of the trust value - * @throws PluginException - * if an error occured talking to the Web of Trust plugin - */ - public void setTrust(OwnIdentity ownIdentity, Identity identity, int trust, String comment) throws PluginException { - performRequest(SimpleFieldSetConstructor.create().put("Message", "SetTrust").put("Truster", ownIdentity.getId()).put("Trustee", identity.getId()).put("Value", String.valueOf(trust)).put("Comment", comment).get()); - } - - /** - * Removes any trust assignment of the given own identity for the given - * identity. - * - * @param ownIdentity - * The own identity - * @param identity - * The identity to remove all trust for - * @throws WebOfTrustException - * if an error occurs - */ - public void removeTrust(OwnIdentity ownIdentity, Identity identity) throws WebOfTrustException { - performRequest(SimpleFieldSetConstructor.create().put("Message", "RemoveTrust").put("Truster", ownIdentity.getId()).put("Trustee", identity.getId()).get()); - } - - /** - * Pings the Web of Trust plugin. If the plugin can not be reached, a - * {@link PluginException} is thrown. - * - * @throws PluginException - * if the plugin is not loaded - */ - public void ping() throws PluginException { - performRequest(SimpleFieldSetConstructor.create().put("Message", "Ping").get()); - } - - // - // PRIVATE ACTIONS - // - - /** - * Parses the contexts from the given fields. - * - * @param prefix - * The prefix to use to access the contexts - * @param fields - * The fields to parse the contexts from - * @return The parsed contexts - */ - private static Set parseContexts(String prefix, SimpleFieldSet fields) { - Set contexts = new HashSet<>(); - int contextCounter = -1; - while (true) { - String context = fields.get(prefix + "Context" + ++contextCounter); - if (context == null) { - break; - } - contexts.add(context); - } - return contexts; - } - - /** - * Parses the properties from the given fields. - * - * @param prefix - * The prefix to use to access the properties - * @param fields - * The fields to parse the properties from - * @return The parsed properties - */ - private static Map parseProperties(String prefix, SimpleFieldSet fields) { - Map properties = new HashMap<>(); - int propertiesCounter = -1; - while (true) { - String propertyName = fields.get(prefix + "Property" + ++propertiesCounter + ".Name"); - if (propertyName == null) { - break; - } - String propertyValue = fields.get(prefix + "Property" + propertiesCounter + ".Value"); - properties.put(propertyName, propertyValue); - } - return properties; - } - - /** - * Sends a request containing the given fields and waits for the target - * message. - * - * @param fields - * The fields of the message - * @return The reply message - * @throws PluginException - * if the request could not be sent - */ - private Reply performRequest(SimpleFieldSet fields) throws PluginException { - return performRequest(fields, null); - } - - /** - * Sends a request containing the given fields and waits for the target - * message. - * - * @param fields - * The fields of the message - * @param data - * The payload of the message - * @return The reply message - * @throws PluginException - * if the request could not be sent - */ - private Reply performRequest(SimpleFieldSet fields, Bucket data) throws PluginException { - String identifier = "FCP-Command-" + System.currentTimeMillis() + "-" + counter.getAndIncrement(); - Reply reply = new Reply(); - PluginIdentifier pluginIdentifier = new PluginIdentifier(WOT_PLUGIN_NAME, identifier); - replies.put(pluginIdentifier, reply); - - logger.log(Level.FINE, String.format("Sending FCP Request: %s", fields.get("Message"))); - synchronized (reply) { - try { - pluginConnector.sendRequest(WOT_PLUGIN_NAME, identifier, fields, data); - while (reply.getFields() == null) { - try { - reply.wait(); - } catch (InterruptedException ie1) { - logger.log(Level.WARNING, String.format("Got interrupted while waiting for reply on %s.", fields.get("Message")), ie1); - } - } - } finally { - replies.remove(pluginIdentifier); - } - } - logger.log(Level.FINEST, String.format("Received FCP Response for %s: %s", fields.get("Message"), (reply.getFields() != null) ? reply.getFields().get("Message") : null)); - if ((reply.getFields() == null) || "Error".equals(reply.getFields().get("Message"))) { - throw new PluginException("Could not perform request for " + fields.get("Message")); - } - return reply; - } - - /** - * Notifies the connector that a plugin reply was received. - * - * @param receivedReplyEvent - * The event - */ - @Subscribe - public void receivedReply(ReceivedReplyEvent receivedReplyEvent) { - PluginIdentifier pluginIdentifier = new PluginIdentifier(receivedReplyEvent.pluginName(), receivedReplyEvent.identifier()); - Reply reply = replies.remove(pluginIdentifier); - if (reply == null) { - return; - } - logger.log(Level.FINEST, String.format("Received Reply from Plugin: %s", receivedReplyEvent.fieldSet().get("Message"))); - synchronized (reply) { - reply.setFields(receivedReplyEvent.fieldSet()); - reply.setData(receivedReplyEvent.data()); - reply.notify(); - } - } - - /** - * Container for the data of the reply from a plugin. - */ - private static class Reply { - - /** The fields of the reply. */ - private SimpleFieldSet fields; - - /** The payload of the reply. */ - private Bucket data; - - /** Empty constructor. */ - public Reply() { - /* do nothing. */ - } - - /** - * Returns the fields of the reply. - * - * @return The fields of the reply - */ - public SimpleFieldSet getFields() { - return fields; - } - - /** - * Sets the fields of the reply. - * - * @param fields - * The fields of the reply - */ - public void setFields(SimpleFieldSet fields) { - this.fields = fields; - } - - /** - * Returns the payload of the reply. - * - * @return The payload of the reply (may be {@code null}) - */ - @SuppressWarnings("unused") - public Bucket getData() { - return data; - } - - /** - * Sets the payload of the reply. - * - * @param data - * The payload of the reply (may be {@code null}) - */ - public void setData(Bucket data) { - this.data = data; - } - - } - - /** - * Helper method to create {@link SimpleFieldSet}s with terser code. - */ - private static class SimpleFieldSetConstructor { - - /** The field set being created. */ - private SimpleFieldSet simpleFieldSet; - - /** - * Creates a new simple field set constructor. - * - * @param shortLived - * {@code true} if the resulting simple field set should be - * short-lived, {@code false} otherwise - */ - private SimpleFieldSetConstructor(boolean shortLived) { - simpleFieldSet = new SimpleFieldSet(shortLived); - } - - // - // ACCESSORS - // - - /** - * Returns the created simple field set. - * - * @return The created simple field set - */ - public SimpleFieldSet get() { - return simpleFieldSet; - } - - /** - * Sets the field with the given name to the given value. - * - * @param name - * The name of the fleld - * @param value - * The value of the field - * @return This constructor (for method chaining) - */ - public SimpleFieldSetConstructor put(String name, String value) { - simpleFieldSet.putOverwrite(name, value); - return this; - } - - // - // ACTIONS - // - - /** - * Creates a new simple field set constructor. - * - * @return The created simple field set constructor - */ - public static SimpleFieldSetConstructor create() { - return create(true); - } - - /** - * Creates a new simple field set constructor. - * - * @param shortLived - * {@code true} if the resulting simple field set should be - * short-lived, {@code false} otherwise - * @return The created simple field set constructor - */ - public static SimpleFieldSetConstructor create(boolean shortLived) { - return new SimpleFieldSetConstructor(shortLived); - } - - } - - /** - * Container for identifying plugins. Plugins are identified by their plugin - * name and their unique identifier. - */ - private static class PluginIdentifier { - - /** The plugin name. */ - private final String pluginName; - - /** The plugin identifier. */ - private final String identifier; - - /** - * Creates a new plugin identifier. - * - * @param pluginName - * The name of the plugin - * @param identifier - * The identifier of the plugin - */ - public PluginIdentifier(String pluginName, String identifier) { - this.pluginName = pluginName; - this.identifier = identifier; - } - - // - // OBJECT METHODS - // - - /** - * {@inheritDoc} - */ - @Override - public int hashCode() { - return pluginName.hashCode() ^ identifier.hashCode(); - } - - /** - * {@inheritDoc} - */ - @Override - public boolean equals(Object object) { - if (!(object instanceof PluginIdentifier)) { - return false; - } - PluginIdentifier pluginIdentifier = (PluginIdentifier) object; - return pluginName.equals(pluginIdentifier.pluginName) && identifier.equals(pluginIdentifier.identifier); - } - - } - -} diff --git a/src/main/java/net/pterodactylus/sone/freenet/wot/WebOfTrustException.java b/src/main/java/net/pterodactylus/sone/freenet/wot/WebOfTrustException.java deleted file mode 100644 index 954fe04..0000000 --- a/src/main/java/net/pterodactylus/sone/freenet/wot/WebOfTrustException.java +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Sone - WebOfTrustException.java - Copyright © 2010–2019 David Roden - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package net.pterodactylus.sone.freenet.wot; - -/** - * Exception that signals an error processing web of trust identities, mostly - * when communicating with the web of trust plugin. - */ -public class WebOfTrustException extends Exception { - - /** - * Creates a new web of trust exception. - */ - public WebOfTrustException() { - super(); - } - - /** - * Creates a new web of trust exception. - * - * @param message - * The message of the exception - */ - public WebOfTrustException(String message) { - super(message); - } - - /** - * Creates a new web of trust exception. - * - * @param cause - * The cause of the exception - */ - public WebOfTrustException(Throwable cause) { - super(cause); - } - - /** - * Creates a new web of trust exception. - * - * @param message - * The message of the exception - * @param cause - * The cause of the exception - */ - public WebOfTrustException(String message, Throwable cause) { - super(message, cause); - } - -} diff --git a/src/main/java/net/pterodactylus/sone/freenet/wot/event/IdentityAddedEvent.java b/src/main/java/net/pterodactylus/sone/freenet/wot/event/IdentityAddedEvent.java deleted file mode 100644 index 2d7ab32..0000000 --- a/src/main/java/net/pterodactylus/sone/freenet/wot/event/IdentityAddedEvent.java +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Sone - IdentityAddedEvent.java - Copyright © 2013–2019 David Roden - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package net.pterodactylus.sone.freenet.wot.event; - -import net.pterodactylus.sone.freenet.wot.Identity; -import net.pterodactylus.sone.freenet.wot.OwnIdentity; - -/** - * Event that signals that an {@link Identity} was added. - */ -public class IdentityAddedEvent extends IdentityEvent { - - /** - * Creates a new “identity added” event. - * - * @param ownIdentity - * The own identity that added the identity - * @param identity - * The identity that was added - */ - public IdentityAddedEvent(OwnIdentity ownIdentity, Identity identity) { - super(ownIdentity, identity); - } - -} diff --git a/src/main/java/net/pterodactylus/sone/freenet/wot/event/IdentityEvent.java b/src/main/java/net/pterodactylus/sone/freenet/wot/event/IdentityEvent.java deleted file mode 100644 index 8a23ae1..0000000 --- a/src/main/java/net/pterodactylus/sone/freenet/wot/event/IdentityEvent.java +++ /dev/null @@ -1,83 +0,0 @@ -/* - * Sone - IdentityEvent.java - Copyright © 2013–2019 David Roden - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package net.pterodactylus.sone.freenet.wot.event; - -import net.pterodactylus.sone.freenet.wot.Identity; -import net.pterodactylus.sone.freenet.wot.OwnIdentity; - -/** - * Base class for {@link Identity} events. - */ -public abstract class IdentityEvent { - - /** The own identity this event relates to. */ - private final OwnIdentity ownIdentity; - - /** The identity this event is about. */ - private final Identity identity; - - /** - * Creates a new identity-based event. - * - * @param ownIdentity - * The own identity that relates to the identity - * @param identity - * The identity this event is about - */ - protected IdentityEvent(OwnIdentity ownIdentity, Identity identity) { - this.ownIdentity = ownIdentity; - this.identity = identity; - } - - // - // ACCESSORS - // - - /** - * Returns the own identity this event relates to. - * - * @return The own identity this event relates to - */ - public OwnIdentity ownIdentity() { - return ownIdentity; - } - - /** - * Returns the identity this event is about. - * - * @return The identity this event is about - */ - public Identity identity() { - return identity; - } - - @Override - public int hashCode() { - return ownIdentity().hashCode() ^ identity().hashCode(); - } - - @Override - public boolean equals(Object object) { - if ((object == null) || !object.getClass().equals(getClass())) { - return false; - } - IdentityEvent identityEvent = (IdentityEvent) object; - return ownIdentity().equals(identityEvent.ownIdentity()) && identity().equals(identityEvent.identity()); - } - -} diff --git a/src/main/java/net/pterodactylus/sone/freenet/wot/event/IdentityRemovedEvent.java b/src/main/java/net/pterodactylus/sone/freenet/wot/event/IdentityRemovedEvent.java deleted file mode 100644 index 655015e..0000000 --- a/src/main/java/net/pterodactylus/sone/freenet/wot/event/IdentityRemovedEvent.java +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Sone - IdentityRemovedEvent.java - Copyright © 2013–2019 David Roden - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package net.pterodactylus.sone.freenet.wot.event; - -import net.pterodactylus.sone.freenet.wot.Identity; -import net.pterodactylus.sone.freenet.wot.OwnIdentity; - -/** - * Event that signals that an {@link Identity} was removed. - */ -public class IdentityRemovedEvent extends IdentityEvent { - - /** - * Creates a new “identity removed” event. - * - * @param ownIdentity - * The own identity that removed the identity - * @param identity - * The identity that was removed - */ - public IdentityRemovedEvent(OwnIdentity ownIdentity, Identity identity) { - super(ownIdentity, identity); - } - -} diff --git a/src/main/java/net/pterodactylus/sone/freenet/wot/event/IdentityUpdatedEvent.java b/src/main/java/net/pterodactylus/sone/freenet/wot/event/IdentityUpdatedEvent.java deleted file mode 100644 index a71ad5b..0000000 --- a/src/main/java/net/pterodactylus/sone/freenet/wot/event/IdentityUpdatedEvent.java +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Sone - IdentityUpdatedEvent.java - Copyright © 2013–2019 David Roden - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package net.pterodactylus.sone.freenet.wot.event; - -import net.pterodactylus.sone.freenet.wot.Identity; -import net.pterodactylus.sone.freenet.wot.OwnIdentity; - -/** - * Event that signals that an {@link Identity} was updated. - */ -public class IdentityUpdatedEvent extends IdentityEvent { - - /** - * Creates a new “identity updated” event. - * - * @param ownIdentity - * The own identity that tracks the identity - * @param identity - * The identity that was updated - */ - public IdentityUpdatedEvent(OwnIdentity ownIdentity, Identity identity) { - super(ownIdentity, identity); - } - -} diff --git a/src/main/java/net/pterodactylus/sone/freenet/wot/event/OwnIdentityAddedEvent.java b/src/main/java/net/pterodactylus/sone/freenet/wot/event/OwnIdentityAddedEvent.java deleted file mode 100644 index fc34848..0000000 --- a/src/main/java/net/pterodactylus/sone/freenet/wot/event/OwnIdentityAddedEvent.java +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Sone - OwnIdentityAddedEvent.java - Copyright © 2013–2019 David Roden - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package net.pterodactylus.sone.freenet.wot.event; - -import net.pterodactylus.sone.freenet.wot.OwnIdentity; - -/** - * Event that signals that an {@link OwnIdentity} was added. - */ -public class OwnIdentityAddedEvent extends OwnIdentityEvent { - - /** - * Creates new “own identity added” event. - * - * @param ownIdentity - * The own identity that was added - */ - public OwnIdentityAddedEvent(OwnIdentity ownIdentity) { - super(ownIdentity); - } - -} diff --git a/src/main/java/net/pterodactylus/sone/freenet/wot/event/OwnIdentityEvent.java b/src/main/java/net/pterodactylus/sone/freenet/wot/event/OwnIdentityEvent.java deleted file mode 100644 index 9e88d20..0000000 --- a/src/main/java/net/pterodactylus/sone/freenet/wot/event/OwnIdentityEvent.java +++ /dev/null @@ -1,67 +0,0 @@ -/* - * Sone - OwnIdentityEvent.java - Copyright © 2013–2019 David Roden - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package net.pterodactylus.sone.freenet.wot.event; - -import net.pterodactylus.sone.freenet.wot.OwnIdentity; - -/** - * Base class for {@link OwnIdentity} events. - */ -public abstract class OwnIdentityEvent { - - /** The own identity this event is about. */ - private final OwnIdentity ownIdentity; - - /** - * Creates a new own identity-based event. - * - * @param ownIdentity - * The own identity this event is about - */ - protected OwnIdentityEvent(OwnIdentity ownIdentity) { - this.ownIdentity = ownIdentity; - } - - // - // ACCESSORS - // - - /** - * Returns the own identity this event is about. - * - * @return The own identity this event is about - */ - public OwnIdentity ownIdentity() { - return ownIdentity; - } - - @Override - public int hashCode() { - return ownIdentity().hashCode(); - } - - @Override - public boolean equals(Object object) { - if ((object == null) || !object.getClass().equals(getClass())) { - return false; - } - OwnIdentityEvent ownIdentityEvent = (OwnIdentityEvent) object; - return ownIdentity().equals(ownIdentityEvent.ownIdentity()); - } - -} diff --git a/src/main/java/net/pterodactylus/sone/freenet/wot/event/OwnIdentityRemovedEvent.java b/src/main/java/net/pterodactylus/sone/freenet/wot/event/OwnIdentityRemovedEvent.java deleted file mode 100644 index 73761b0..0000000 --- a/src/main/java/net/pterodactylus/sone/freenet/wot/event/OwnIdentityRemovedEvent.java +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Sone - OwnIdentityRemovedEvent.java - Copyright © 2013–2019 David Roden - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package net.pterodactylus.sone.freenet.wot.event; - -import net.pterodactylus.sone.freenet.wot.OwnIdentity; - -/** - * Event that signals that an {@link OwnIdentity} was removed. - */ -public class OwnIdentityRemovedEvent extends OwnIdentityEvent { - - /** - * Creates a new “own identity removed” event. - * - * @param ownIdentity - * The own identity that was removed - */ - public OwnIdentityRemovedEvent(OwnIdentity ownIdentity) { - super(ownIdentity); - } - -} diff --git a/src/main/java/net/pterodactylus/sone/notify/ListNotification.java b/src/main/java/net/pterodactylus/sone/notify/ListNotification.java deleted file mode 100644 index 6a7b086..0000000 --- a/src/main/java/net/pterodactylus/sone/notify/ListNotification.java +++ /dev/null @@ -1,199 +0,0 @@ -/* - * Sone - ListNotification.java - Copyright © 2010–2019 David Roden - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package net.pterodactylus.sone.notify; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; -import java.util.concurrent.CopyOnWriteArrayList; - -import net.pterodactylus.util.notify.TemplateNotification; -import net.pterodactylus.util.template.Template; - -/** - * Notification that maintains a list of new elements. - * - * @param - * The type of the items - */ -public class ListNotification extends TemplateNotification { - - /** The key under which to store the elements in the template. */ - private final String key; - - /** The list of new elements. */ - private final List elements = new CopyOnWriteArrayList<>(); - - /** - * Creates a new list notification. - * - * @param id - * The ID of the notification - * @param key - * The key under which to store the elements in the template - * @param template - * The template to render - */ - public ListNotification(String id, String key, Template template) { - this(id, key, template, true); - } - - /** - * Creates a new list notification. - * - * @param id - * The ID of the notification - * @param key - * The key under which to store the elements in the template - * @param template - * The template to render - * @param dismissable - * {@code true} if this notification should be dismissable by the - * user, {@code false} otherwise - */ - public ListNotification(String id, String key, Template template, boolean dismissable) { - super(id, System.currentTimeMillis(), System.currentTimeMillis(), dismissable, template); - this.key = key; - template.getInitialContext().set(key, elements); - } - - /** - * Creates a new list notification that copies its ID and the template from - * the given list notification. - * - * @param listNotification - * The list notification to copy - */ - public ListNotification(ListNotification listNotification) { - super(listNotification.getId(), listNotification.getCreatedTime(), listNotification.getLastUpdatedTime(), listNotification.isDismissable(), new Template()); - this.key = listNotification.key; - getTemplate().add(listNotification.getTemplate()); - getTemplate().getInitialContext().set(key, elements); - } - - // - // ACTIONS - // - - /** - * Returns the current list of elements. - * - * @return The current list of elements - */ - public List getElements() { - return new ArrayList<>(elements); - } - - /** - * Sets the elements to show in this notification. This method will not call - * {@link #touch()}. - * - * @param elements - * The elements to show - */ - public void setElements(Collection elements) { - this.elements.clear(); - this.elements.addAll(elements); - touch(); - } - - /** - * Returns whether there are any new elements. - * - * @return {@code true} if there are no new elements, {@code false} if there - * are new elements - */ - public boolean isEmpty() { - return elements.isEmpty(); - } - - /** - * Adds a discovered element. - * - * @param element - * The new element - */ - public void add(T element) { - elements.add(element); - touch(); - } - - /** - * Removes the given element from the list of new elements. - * - * @param element - * The element to remove - */ - public void remove(T element) { - while (elements.remove(element)) { - /* do nothing, just remove all instances of the element. */ - } - if (elements.isEmpty()) { - dismiss(); - } - touch(); - } - - // - // ABSTRACTNOTIFICATION METHODS - // - - /** - * {@inheritDoc} - */ - @Override - public void dismiss() { - super.dismiss(); - elements.clear(); - } - - // - // OBJECT METHODS - // - - /** - * {@inheritDoc} - */ - @Override - public int hashCode() { - int hashCode = super.hashCode(); - for (T element : elements) { - hashCode ^= element.hashCode(); - } - return hashCode; - } - - /** - * {@inheritDoc} - */ - @Override - public boolean equals(Object object) { - if (!(object instanceof ListNotification)) { - return false; - } - ListNotification listNotification = (ListNotification) object; - if (!super.equals(listNotification)) { - return false; - } - if (!key.equals(listNotification.key)) { - return false; - } - return elements.equals(listNotification.elements); - } - -} diff --git a/src/main/java/net/pterodactylus/sone/template/UnknownDateFilter.java b/src/main/java/net/pterodactylus/sone/template/UnknownDateFilter.java index 0d4872b..5924e5a 100644 --- a/src/main/java/net/pterodactylus/sone/template/UnknownDateFilter.java +++ b/src/main/java/net/pterodactylus/sone/template/UnknownDateFilter.java @@ -19,18 +19,18 @@ package net.pterodactylus.sone.template; import java.util.Map; +import net.pterodactylus.sone.freenet.Translation; import net.pterodactylus.util.template.Filter; import net.pterodactylus.util.template.TemplateContext; -import freenet.l10n.BaseL10n; /** * {@link Filter} implementation that replaces a {@link Long} with a value of - * {@code 0} by a {@link String} from an {@link BaseL10n l10n handler}. + * {@code 0} by a {@link String} from a {@link Translation translation}. */ public class UnknownDateFilter implements Filter { - /** The l10n handler. */ - private BaseL10n l10nHandler; + /** The translation. */ + private final Translation translation; /** The key for the text to show. */ private final String unknownKey; @@ -38,13 +38,11 @@ public class UnknownDateFilter implements Filter { /** * Creates a new unknown date filter. * - * @param l10nHandler - * The l10n handler - * @param unknownKey - * The key of the text to show + * @param translation The translation + * @param unknownKey The key of the text to show */ - public UnknownDateFilter(BaseL10n l10nHandler, String unknownKey) { - this.l10nHandler = l10nHandler; + public UnknownDateFilter(Translation translation, String unknownKey) { + this.translation = translation; this.unknownKey = unknownKey; } @@ -55,7 +53,7 @@ public class UnknownDateFilter implements Filter { public Object format(TemplateContext templateContext, Object data, Map parameters) { if (data instanceof Long) { if ((Long) data == 0) { - return l10nHandler.getString(unknownKey); + return translation.translate(unknownKey); } } return data; diff --git a/src/main/java/net/pterodactylus/sone/web/WebInterface.java b/src/main/java/net/pterodactylus/sone/web/WebInterface.java index 12526df..588ffb7 100644 --- a/src/main/java/net/pterodactylus/sone/web/WebInterface.java +++ b/src/main/java/net/pterodactylus/sone/web/WebInterface.java @@ -19,9 +19,7 @@ package net.pterodactylus.sone.web; import static com.google.common.collect.FluentIterable.from; import static java.util.logging.Logger.getLogger; -import static net.pterodactylus.util.template.TemplateParser.parse; -import java.io.StringReader; import java.util.Collection; import java.util.Collections; import java.util.HashMap; @@ -46,6 +44,7 @@ import net.pterodactylus.sone.data.Post; import net.pterodactylus.sone.data.PostReply; import net.pterodactylus.sone.data.Sone; import net.pterodactylus.sone.freenet.L10nFilter; +import net.pterodactylus.sone.freenet.Translation; import net.pterodactylus.sone.main.Loaders; import net.pterodactylus.sone.main.PluginHomepage; import net.pterodactylus.sone.main.PluginVersion; @@ -107,7 +106,6 @@ import net.pterodactylus.util.web.TemplatePage; import freenet.clients.http.SessionManager; import freenet.clients.http.SessionManager.Session; import freenet.clients.http.ToadletContext; -import freenet.l10n.BaseL10n; import com.codahale.metrics.*; import com.google.common.base.Optional; @@ -160,6 +158,7 @@ public class WebInterface implements SessionProvider { private final PageToadletRegistry pageToadletRegistry; private final MetricRegistry metricRegistry; + private final Translation translation; /** The “new Sone” notification. */ private final ListNotification newSoneNotification; @@ -211,7 +210,7 @@ public class WebInterface implements SessionProvider { ParserFilter parserFilter, ShortenFilter shortenFilter, RenderFilter renderFilter, LinkedElementRenderFilter linkedElementRenderFilter, - PageToadletRegistry pageToadletRegistry, MetricRegistry metricRegistry) { + PageToadletRegistry pageToadletRegistry, MetricRegistry metricRegistry, Translation translation, L10nFilter l10nFilter) { this.sonePlugin = sonePlugin; this.loaders = loaders; this.listNotificationFilter = listNotificationFilter; @@ -225,9 +224,10 @@ public class WebInterface implements SessionProvider { this.linkedElementRenderFilter = linkedElementRenderFilter; this.pageToadletRegistry = pageToadletRegistry; this.metricRegistry = metricRegistry; + this.l10nFilter = l10nFilter; + this.translation = translation; formPassword = sonePlugin.pluginRespirator().getToadletContainer().getFormPassword(); soneTextParser = new SoneTextParser(getCore(), getCore()); - l10nFilter = new L10nFilter(getL10n()); this.templateContextFactory = templateContextFactory; templateContextFactory.addTemplateObject("webInterface", this); @@ -381,13 +381,8 @@ public class WebInterface implements SessionProvider { return listNotificationFilter.filterNotifications(notificationManager.getNotifications(), currentSone); } - /** - * Returns the l10n helper of the node. - * - * @return The node’s l10n helper - */ - public BaseL10n getL10n() { - return sonePlugin.l10n().getBase(); + public Translation getTranslation() { + return translation; } /** diff --git a/src/main/java/net/pterodactylus/sone/web/page/PageToadlet.java b/src/main/java/net/pterodactylus/sone/web/page/PageToadlet.java index 2b22377..08c935b 100644 --- a/src/main/java/net/pterodactylus/sone/web/page/PageToadlet.java +++ b/src/main/java/net/pterodactylus/sone/web/page/PageToadlet.java @@ -34,7 +34,6 @@ import freenet.clients.http.SessionManager; import freenet.clients.http.Toadlet; import freenet.clients.http.ToadletContext; import freenet.clients.http.ToadletContextClosedException; -import freenet.l10n.NodeL10n; import freenet.support.MultiValueTable; import freenet.support.api.HTTPRequest; @@ -107,7 +106,7 @@ public class PageToadlet extends Toadlet implements LinkEnabledCallback, LinkFil * if the toadlet context is closed */ public void handleMethodGET(URI uri, HTTPRequest httpRequest, ToadletContext toadletContext) throws IOException, ToadletContextClosedException { - handleRequest(new FreenetRequest(uri, Method.GET, httpRequest, toadletContext, NodeL10n.getBase(), sessionManager)); + handleRequest(new FreenetRequest(uri, Method.GET, httpRequest, toadletContext, sessionManager)); } /** @@ -125,7 +124,7 @@ public class PageToadlet extends Toadlet implements LinkEnabledCallback, LinkFil * if the toadlet context is closed */ public void handleMethodPOST(URI uri, HTTPRequest httpRequest, ToadletContext toadletContext) throws IOException, ToadletContextClosedException { - handleRequest(new FreenetRequest(uri, Method.POST, httpRequest, toadletContext, NodeL10n.getBase(), sessionManager)); + handleRequest(new FreenetRequest(uri, Method.POST, httpRequest, toadletContext, sessionManager)); } /** diff --git a/src/main/kotlin/net/pterodactylus/sone/freenet/BaseL10nTranslation.kt b/src/main/kotlin/net/pterodactylus/sone/freenet/BaseL10nTranslation.kt new file mode 100644 index 0000000..1b41f70 --- /dev/null +++ b/src/main/kotlin/net/pterodactylus/sone/freenet/BaseL10nTranslation.kt @@ -0,0 +1,16 @@ +package net.pterodactylus.sone.freenet + +import freenet.l10n.* +import java.util.* + +/** + * [Translation] implementation based on Fred’s [BaseL10n]. + */ +class BaseL10nTranslation(private val baseL10n: BaseL10n) : Translation { + + override val currentLocale: Locale + get() = Locale(baseL10n.selectedLanguage.shortCode) + + override fun translate(key: String): String = baseL10n.getString(key) + +} diff --git a/src/main/kotlin/net/pterodactylus/sone/freenet/FreenetURIs.kt b/src/main/kotlin/net/pterodactylus/sone/freenet/FreenetURIs.kt new file mode 100644 index 0000000..3a6d43c --- /dev/null +++ b/src/main/kotlin/net/pterodactylus/sone/freenet/FreenetURIs.kt @@ -0,0 +1,6 @@ +package net.pterodactylus.sone.freenet + +import freenet.keys.* +import freenet.support.Base64.* + +val FreenetURI.routingKeyString: String get() = encode(routingKey) diff --git a/src/main/kotlin/net/pterodactylus/sone/freenet/L10nFilter.kt b/src/main/kotlin/net/pterodactylus/sone/freenet/L10nFilter.kt new file mode 100644 index 0000000..b48bfec --- /dev/null +++ b/src/main/kotlin/net/pterodactylus/sone/freenet/L10nFilter.kt @@ -0,0 +1,51 @@ +/* + * Sone - L10nFilter.java - Copyright © 2010–2019 David Roden + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.pterodactylus.sone.freenet + +import net.pterodactylus.util.template.* +import java.text.* + +/** + * [Filter] implementation replaces [String] values with their + * translated equivalents. + */ +class L10nFilter(private val translation: Translation) : Filter { + + override fun format(templateContext: TemplateContext?, data: Any?, parameters: Map?): String { + val parameterValues = getParameters(data, parameters) + val text = getText(data) + return if (parameterValues.isEmpty()) { + translation.translate(text) + } else + MessageFormat(translation.translate(text), translation.currentLocale).format(parameterValues.toTypedArray()) + } + + private fun getText(data: Any?) = (data as? L10nText)?.text ?: data.toString() + + private fun getParameters(data: Any?, parameters: Map?) = + if (data is L10nText) + data.parameters + else + (parameters ?: emptyMap()).let { params -> + generateSequence(0) { it + 1 } + .takeWhile { it.toString() in params } + .map { params[it.toString()] } + .toList() + } + +} diff --git a/src/main/kotlin/net/pterodactylus/sone/freenet/Translation.kt b/src/main/kotlin/net/pterodactylus/sone/freenet/Translation.kt new file mode 100644 index 0000000..78919a9 --- /dev/null +++ b/src/main/kotlin/net/pterodactylus/sone/freenet/Translation.kt @@ -0,0 +1,38 @@ +/** + * Sone - Translation.kt - Copyright © 2019 David ‘Bombe’ Roden + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.pterodactylus.sone.freenet + +import java.util.* + +/** + * Facade for Fred’s [freenet.l10n.BaseL10n] object. + */ +interface Translation { + + /** The currently selected locale. */ + val currentLocale: Locale + + /** + * Returns the translated string for the given key, defaulting to `""`. + * + * @param key The key to return the translated string for + * @return The translated string, or `""` if there is no translation + */ + fun translate(key: String): String + +} diff --git a/src/main/kotlin/net/pterodactylus/sone/freenet/plugin/FredPluginConnector.kt b/src/main/kotlin/net/pterodactylus/sone/freenet/plugin/FredPluginConnector.kt new file mode 100644 index 0000000..98849c4 --- /dev/null +++ b/src/main/kotlin/net/pterodactylus/sone/freenet/plugin/FredPluginConnector.kt @@ -0,0 +1,35 @@ +/* Fred’s plugin stuff is mostly deprecated. ¯\_(ツ)_/¯ */ +@file:Suppress("DEPRECATION") + +package net.pterodactylus.sone.freenet.plugin + +import freenet.pluginmanager.* +import freenet.support.* +import freenet.support.api.* +import kotlinx.coroutines.* +import kotlinx.coroutines.channels.* +import javax.inject.* + +/** + * [PluginConnector] implementation that uses a [PluginRespiratorFacade] and coroutines to send + * a request to another plugin and receive a reply. + */ +class FredPluginConnector @Inject constructor(private val pluginRespiratorFacade: PluginRespiratorFacade) : PluginConnector { + + override suspend fun sendRequest(pluginName: String, fields: SimpleFieldSet, data: Bucket?): PluginReply { + val receivedReply = Channel() + val responseReceiver = FredPluginTalker { _, _, responseFields, responseData -> + GlobalScope.launch { + receivedReply.send(PluginReply(responseFields, responseData)) + } + } + try { + val pluginTalker = pluginRespiratorFacade.getPluginTalker(responseReceiver, pluginName, "") + pluginTalker.send(fields, data) + return receivedReply.receive() + } catch (e: PluginNotFoundException) { + throw PluginException(cause = e) + } + } + +} diff --git a/src/main/kotlin/net/pterodactylus/sone/freenet/plugin/PluginConnector.kt b/src/main/kotlin/net/pterodactylus/sone/freenet/plugin/PluginConnector.kt new file mode 100644 index 0000000..16c506f --- /dev/null +++ b/src/main/kotlin/net/pterodactylus/sone/freenet/plugin/PluginConnector.kt @@ -0,0 +1,43 @@ +/* + * Sone - PluginConnector.kt - Copyright © 2010–2019 David Roden + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.pterodactylus.sone.freenet.plugin + +import freenet.support.* +import freenet.support.api.* + +/** + * Interface for talking to other plugins. Other plugins are identified by their + * name and a unique connection identifier. + */ +interface PluginConnector { + + /** + * Sends a message to another plugin running in the same node. + * + * @param pluginName The fully qualified name of the plugin + * @param fields The message being sent + * @param data Optional data + * @return The reply from the plugin + * @throws PluginException if the plugin identified by [pluginName] does not exist + */ + @Throws(PluginException::class) + suspend fun sendRequest(pluginName: String, fields: SimpleFieldSet, data: Bucket? = null): PluginReply + +} + +data class PluginReply(val fields: SimpleFieldSet, val data: Bucket?) diff --git a/src/main/kotlin/net/pterodactylus/sone/freenet/plugin/PluginException.kt b/src/main/kotlin/net/pterodactylus/sone/freenet/plugin/PluginException.kt new file mode 100644 index 0000000..8515f0c --- /dev/null +++ b/src/main/kotlin/net/pterodactylus/sone/freenet/plugin/PluginException.kt @@ -0,0 +1,25 @@ +/* + * Sone - PluginException.java - Copyright © 2010–2019 David Roden + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.pterodactylus.sone.freenet.plugin + +import net.pterodactylus.sone.freenet.wot.* + +/** + * Exception that signals an error when communicating with a plugin. + */ +class PluginException(message: String? = null, cause: Throwable? = null) : WebOfTrustException(message, cause) diff --git a/src/main/kotlin/net/pterodactylus/sone/freenet/plugin/PluginRespiratorFacade.kt b/src/main/kotlin/net/pterodactylus/sone/freenet/plugin/PluginRespiratorFacade.kt new file mode 100644 index 0000000..3ec52ce --- /dev/null +++ b/src/main/kotlin/net/pterodactylus/sone/freenet/plugin/PluginRespiratorFacade.kt @@ -0,0 +1,66 @@ +/** + * Sone - PluginRespiratorFacade.kt - Copyright © 2019 David ‘Bombe’ Roden + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +/* Yes, this handle Fred-based stuff that’s mostly deprecated. */ +@file:Suppress("DEPRECATION") + +package net.pterodactylus.sone.freenet.plugin + +import freenet.pluginmanager.* +import freenet.support.* +import freenet.support.api.* +import javax.inject.* + +/** + * Facade for the only method of a [plugin respirator][PluginRespirator] that Sone actually uses, + * for easier testing. + */ +interface PluginRespiratorFacade { + + @Throws(PluginNotFoundException::class) + fun getPluginTalker(pluginTalker: FredPluginTalker, pluginName: String, identifier: String): PluginTalkerFacade + +} + +/** + * Facade for a [plugin talker][PluginTalker], for easier testing. + */ +interface PluginTalkerFacade { + + fun send(pluginParameters: SimpleFieldSet, data: Bucket?) + +} + +/** + * Fred-based [PluginRespiratorFacade] implementation that proxies the given real [PluginRespirator]. + */ +class FredPluginRespiratorFacade @Inject constructor(private val pluginRespirator: PluginRespirator) : PluginRespiratorFacade { + + override fun getPluginTalker(pluginTalker: FredPluginTalker, pluginName: String, identifier: String) = + FredPluginTalkerFacade(pluginRespirator.getPluginTalker(pluginTalker, pluginName, identifier)) + +} + +/** + * Fred-based [PluginTalkerFacade] implementation that proxies the given real [PluginTalker]. + */ +class FredPluginTalkerFacade(private val pluginTalker: PluginTalker) : PluginTalkerFacade { + + override fun send(pluginParameters: SimpleFieldSet, data: Bucket?) = + pluginTalker.send(pluginParameters, data) + +} diff --git a/src/main/kotlin/net/pterodactylus/sone/freenet/wot/Context.kt b/src/main/kotlin/net/pterodactylus/sone/freenet/wot/Context.kt new file mode 100644 index 0000000..37d7b61 --- /dev/null +++ b/src/main/kotlin/net/pterodactylus/sone/freenet/wot/Context.kt @@ -0,0 +1,24 @@ +/* + * Sone - Context.java - Copyright © 2014–2019 David Roden + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.pterodactylus.sone.freenet.wot + +/** + * Custom container for the Web of Trust context. This allows easier + * configuration of dependency injection. + */ +class Context(val context: String) diff --git a/src/main/kotlin/net/pterodactylus/sone/freenet/wot/DefaultIdentity.kt b/src/main/kotlin/net/pterodactylus/sone/freenet/wot/DefaultIdentity.kt new file mode 100644 index 0000000..fe84584 --- /dev/null +++ b/src/main/kotlin/net/pterodactylus/sone/freenet/wot/DefaultIdentity.kt @@ -0,0 +1,109 @@ +/* + * Sone - DefaultIdentity.java - Copyright © 2010–2019 David Roden + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.pterodactylus.sone.freenet.wot + +import java.util.Collections.* + +/** + * A Web of Trust identity. + */ +open class DefaultIdentity(private val id: String, private val nickname: String?, private val requestUri: String) : Identity { + + private val contexts = mutableSetOf().synchronized() + private val properties = mutableMapOf().synchronized() + private val trustCache = mutableMapOf().synchronized() + + override fun getId() = id + override fun getNickname() = nickname + override fun getRequestUri() = requestUri + override fun getContexts() = synchronized(contexts) { contexts.toSet() } + + override fun hasContext(context: String) = context in contexts + + override fun setContexts(contexts: Set) { + synchronized(this.contexts) { + this.contexts.clear() + this.contexts.addAll(contexts) + } + } + + override fun addContext(context: String): Identity = apply { + synchronized(this.contexts) { + contexts += context + } + } + + override fun removeContext(context: String): Identity = apply { + synchronized(this.contexts) { + contexts -= context + } + } + + override fun getProperties() = synchronized(properties) { properties.toMap() } + + override fun setProperties(properties: Map) { + synchronized(this.properties) { + this.properties.clear() + this.properties.putAll(properties) + } + } + + override fun getProperty(name: String) = synchronized(properties) { properties[name] } + + override fun setProperty(name: String, value: String): Identity = apply { + synchronized(properties) { + properties[name] = value + } + } + + override fun removeProperty(name: String): Identity = apply { + synchronized(properties) { + properties -= name + } + } + + override fun getTrust(ownIdentity: OwnIdentity) = synchronized(trustCache) { + trustCache[ownIdentity] + } + + override fun setTrust(ownIdentity: OwnIdentity, trust: Trust) = apply { + synchronized(trustCache) { + trustCache[ownIdentity] = trust + } + } + + override fun removeTrust(ownIdentity: OwnIdentity) = apply { + synchronized(trustCache) { + trustCache -= ownIdentity + } + } + + override fun hashCode() = id.hashCode() + + override fun equals(other: Any?) = if (other !is Identity) { + false + } else { + other.id == getId() + } + + override fun toString() = "${javaClass.simpleName}[id=$id,nickname=$nickname,contexts=$contexts,properties=$properties]" + +} + +private fun Set.synchronized(): MutableSet = synchronizedSet(this) +private fun Map.synchronized(): MutableMap = synchronizedMap(this) diff --git a/src/main/kotlin/net/pterodactylus/sone/freenet/wot/DefaultOwnIdentity.kt b/src/main/kotlin/net/pterodactylus/sone/freenet/wot/DefaultOwnIdentity.kt new file mode 100644 index 0000000..f330c0a --- /dev/null +++ b/src/main/kotlin/net/pterodactylus/sone/freenet/wot/DefaultOwnIdentity.kt @@ -0,0 +1,38 @@ +/* + * Sone - DefaultOwnIdentity.java - Copyright © 2010–2019 David Roden + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.pterodactylus.sone.freenet.wot + +/** + * An own identity is an identity that the owner of the node has full control + * over. + */ +class DefaultOwnIdentity(id: String, nickname: String, requestUri: String, private val insertUri: String) : DefaultIdentity(id, nickname, requestUri), OwnIdentity { + + override fun getInsertUri(): String { + return insertUri + } + + override fun addContext(context: String) = super.addContext(context) as OwnIdentity + + override fun removeContext(context: String) = super.removeContext(context) as OwnIdentity + + override fun setProperty(name: String, value: String) = super.setProperty(name, value) as OwnIdentity + + override fun removeProperty(name: String) = super.removeProperty(name) as OwnIdentity + +} diff --git a/src/main/kotlin/net/pterodactylus/sone/freenet/wot/IdentityChangeEventSender.kt b/src/main/kotlin/net/pterodactylus/sone/freenet/wot/IdentityChangeEventSender.kt new file mode 100644 index 0000000..5ffffa3 --- /dev/null +++ b/src/main/kotlin/net/pterodactylus/sone/freenet/wot/IdentityChangeEventSender.kt @@ -0,0 +1,63 @@ +/* + * Sone - IdentityChangeEventSender.java - Copyright © 2013–2019 David Roden + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.pterodactylus.sone.freenet.wot + +import com.google.common.eventbus.* +import net.pterodactylus.sone.freenet.wot.event.* + +/** + * Detects changes in [Identity]s trusted by multiple [OwnIdentity]s. + * + * @see IdentityChangeDetector + */ +class IdentityChangeEventSender(private val eventBus: EventBus, private val oldIdentities: Map>) { + + fun detectChanges(identities: Map>) { + val identityChangeDetector = IdentityChangeDetector(oldIdentities.keys) + identityChangeDetector.onNewIdentity = addNewOwnIdentityAndItsTrustedIdentities(identities) + identityChangeDetector.onRemovedIdentity = removeOwnIdentityAndItsTrustedIdentities(oldIdentities) + identityChangeDetector.onUnchangedIdentity = detectChangesInTrustedIdentities(identities, oldIdentities) + identityChangeDetector.detectChanges(identities.keys) + } + + private fun addNewOwnIdentityAndItsTrustedIdentities(newIdentities: Map>) = + { identity: Identity -> + eventBus.post(OwnIdentityAddedEvent(identity as OwnIdentity)) + newIdentities[identity] + ?.map { IdentityAddedEvent(identity, it) } + ?.forEach(eventBus::post) ?: Unit + } + + private fun removeOwnIdentityAndItsTrustedIdentities(oldIdentities: Map>) = + { identity: Identity -> + eventBus.post(OwnIdentityRemovedEvent(identity as OwnIdentity)) + oldIdentities[identity] + ?.map { IdentityRemovedEvent(identity, it) } + ?.forEach(eventBus::post) ?: Unit + } + + private fun detectChangesInTrustedIdentities(newIdentities: Map>, oldIdentities: Map>) = + { ownIdentity: Identity -> + val identityChangeDetector = IdentityChangeDetector(oldIdentities[ownIdentity as OwnIdentity]!!) + identityChangeDetector.onNewIdentity = { eventBus.post(IdentityAddedEvent(ownIdentity, it)) } + identityChangeDetector.onRemovedIdentity = { eventBus.post(IdentityRemovedEvent(ownIdentity, it)) } + identityChangeDetector.onChangedIdentity = { eventBus.post(IdentityUpdatedEvent(ownIdentity, it)) } + identityChangeDetector.detectChanges(newIdentities[ownIdentity]!!) + } + +} diff --git a/src/main/kotlin/net/pterodactylus/sone/freenet/wot/IdentityLoader.kt b/src/main/kotlin/net/pterodactylus/sone/freenet/wot/IdentityLoader.kt new file mode 100644 index 0000000..f9019d0 --- /dev/null +++ b/src/main/kotlin/net/pterodactylus/sone/freenet/wot/IdentityLoader.kt @@ -0,0 +1,64 @@ +/* + * Sone - IdentityLoader.java - Copyright © 2013–2019 David Roden + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.pterodactylus.sone.freenet.wot + +import com.google.common.base.* +import com.google.inject.* +import net.pterodactylus.sone.freenet.plugin.* +import java.util.concurrent.TimeUnit.* +import java.util.logging.* + +/** + * Loads [OwnIdentity]s and the [Identity]s they trust. + */ +class IdentityLoader @Inject constructor(private val webOfTrustConnector: WebOfTrustConnector, private val context: Context? = null) { + + private val logger: Logger = Logger.getLogger(IdentityLoader::class.java.name) + + @Throws(WebOfTrustException::class) + fun loadIdentities() = + time({ stopwatch, identities -> "Loaded ${identities.size} own identities in ${stopwatch.elapsed(MILLISECONDS) / 1000.0}s." }) { + webOfTrustConnector.loadAllOwnIdentities() + }.let(this::loadTrustedIdentitiesForOwnIdentities) + + @Throws(PluginException::class) + private fun loadTrustedIdentitiesForOwnIdentities(ownIdentities: Collection) = + ownIdentities + .also { logger.fine { "Getting trusted identities for ${it.size} own identities..." } } + .associateWith { ownIdentity -> + logger.fine { "Getting trusted identities for $ownIdentity..." } + if (ownIdentity.doesNotHaveCorrectContext()) { + logger.fine { "Skipping $ownIdentity because of incorrect context." } + emptySet() + } else { + logger.fine { "Loading trusted identities for $ownIdentity from WoT..." } + time({ stopwatch, identities -> "Loaded ${identities.size} identities for ${ownIdentity.nickname} in ${stopwatch.elapsed(MILLISECONDS) / 1000.0}s." }) { + webOfTrustConnector.loadTrustedIdentities(ownIdentity, context?.context) + } + } + } + + private fun OwnIdentity.doesNotHaveCorrectContext() = + context?.let { it.context !in contexts } ?: false + + private fun time(logMessage: (Stopwatch, R) -> String, loader: () -> R) = + Stopwatch.createStarted().let { stopwatch -> + loader().also { logger.fine(logMessage(stopwatch, it)) } + } + +} diff --git a/src/main/kotlin/net/pterodactylus/sone/freenet/wot/IdentityManager.kt b/src/main/kotlin/net/pterodactylus/sone/freenet/wot/IdentityManager.kt new file mode 100644 index 0000000..e90af91 --- /dev/null +++ b/src/main/kotlin/net/pterodactylus/sone/freenet/wot/IdentityManager.kt @@ -0,0 +1,18 @@ +package net.pterodactylus.sone.freenet.wot + +import net.pterodactylus.util.service.Service + +import com.google.common.eventbus.EventBus +import com.google.inject.ImplementedBy + +/** + * Connects to a [WebOfTrustConnector] and sends identity events to an + * [EventBus]. + */ +@ImplementedBy(IdentityManagerImpl::class) +interface IdentityManager : Service { + + val isConnected: Boolean + val allOwnIdentities: Set + +} diff --git a/src/main/kotlin/net/pterodactylus/sone/freenet/wot/IdentityManagerImpl.kt b/src/main/kotlin/net/pterodactylus/sone/freenet/wot/IdentityManagerImpl.kt new file mode 100644 index 0000000..cb7d0bd --- /dev/null +++ b/src/main/kotlin/net/pterodactylus/sone/freenet/wot/IdentityManagerImpl.kt @@ -0,0 +1,91 @@ +/* + * Sone - IdentityManagerImpl.java - Copyright © 2010–2019 David Roden + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.pterodactylus.sone.freenet.wot + +import com.google.common.eventbus.* +import com.google.inject.* +import net.pterodactylus.util.service.* +import java.util.concurrent.TimeUnit.* +import java.util.logging.* +import java.util.logging.Logger.* + +/** + * The identity manager takes care of loading and storing identities, their + * contexts, and properties. It does so in a way that does not expose errors via + * exceptions but it only logs them and tries to return sensible defaults. + * + * + * It is also responsible for polling identities from the Web of Trust plugin + * and sending events to the [EventBus] when [Identity]s and + * [OwnIdentity]s are discovered or disappearing. + */ +@Singleton +class IdentityManagerImpl @Inject constructor( + private val eventBus: EventBus, + private val webOfTrustConnector: WebOfTrustConnector, + private val identityLoader: IdentityLoader +) : AbstractService("Sone Identity Manager", false), IdentityManager { + + private val currentOwnIdentities = mutableSetOf() + + override val isConnected: Boolean + get() = notThrowing { webOfTrustConnector.ping() } + + override val allOwnIdentities: Set + get() = synchronized(currentOwnIdentities) { + currentOwnIdentities.toSet() + } + + override fun serviceRun() { + var oldIdentities = mapOf>() + + while (!shouldStop()) { + try { + val currentIdentities = identityLoader.loadIdentities() + + val identityChangeEventSender = IdentityChangeEventSender(eventBus, oldIdentities) + identityChangeEventSender.detectChanges(currentIdentities) + + oldIdentities = currentIdentities + + synchronized(currentOwnIdentities) { + currentOwnIdentities.clear() + currentOwnIdentities.addAll(currentIdentities.keys) + } + } catch (wote1: WebOfTrustException) { + logger.log(Level.WARNING, "WoT has disappeared!", wote1) + } catch (e: Exception) { + logger.log(Level.SEVERE, "Uncaught exception in IdentityManager thread!", e) + } + + /* wait a minute before checking again. */ + sleep(SECONDS.toMillis(60)) + } + } + +} + +private val logger: Logger = getLogger(IdentityManagerImpl::class.java.name) + +private fun notThrowing(action: () -> Unit): Boolean = + try { + action() + true + } catch (e: Exception) { + false + } diff --git a/src/main/kotlin/net/pterodactylus/sone/freenet/wot/PluginWebOfTrustConnector.kt b/src/main/kotlin/net/pterodactylus/sone/freenet/wot/PluginWebOfTrustConnector.kt new file mode 100644 index 0000000..fae90ae --- /dev/null +++ b/src/main/kotlin/net/pterodactylus/sone/freenet/wot/PluginWebOfTrustConnector.kt @@ -0,0 +1,134 @@ +/* + * Sone - WebOfTrustConnector.java - Copyright © 2010–2019 David Roden + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.pterodactylus.sone.freenet.wot + +import com.google.inject.* +import freenet.support.* +import kotlinx.coroutines.* +import net.pterodactylus.sone.freenet.* +import net.pterodactylus.sone.freenet.plugin.* +import java.lang.String.* +import java.util.logging.* +import java.util.logging.Logger +import java.util.logging.Logger.* + +/** + * Connector for the Web of Trust plugin. + */ +class PluginWebOfTrustConnector @Inject constructor(private val pluginConnector: PluginConnector) : WebOfTrustConnector { + + private val logger: Logger = getLogger(PluginWebOfTrustConnector::class.java.name) + + @Throws(PluginException::class) + override fun loadAllOwnIdentities(): Set = + performRequest(SimpleFieldSetBuilder().put("Message", "GetOwnIdentities").get()) + .fields + .parseIdentities { parseOwnIdentity(it) } + + @Throws(PluginException::class) + override fun loadTrustedIdentities(ownIdentity: OwnIdentity, context: String?): Set = + performRequest(SimpleFieldSetBuilder().put("Message", "GetIdentitiesByScore").put("Truster", ownIdentity.id).put("Selection", "+").put("Context", context ?: "").put("WantTrustValues", "true").get()) + .fields + .parseIdentities { parseTrustedIdentity(it, ownIdentity) } + + @Throws(PluginException::class) + override fun addContext(ownIdentity: OwnIdentity, context: String) { + performRequest(SimpleFieldSetBuilder().put("Message", "AddContext").put("Identity", ownIdentity.id).put("Context", context).get()) + } + + @Throws(PluginException::class) + override fun removeContext(ownIdentity: OwnIdentity, context: String) { + performRequest(SimpleFieldSetBuilder().put("Message", "RemoveContext").put("Identity", ownIdentity.id).put("Context", context).get()) + } + + override fun setProperty(ownIdentity: OwnIdentity, name: String, value: String) { + performRequest(SimpleFieldSetBuilder().put("Message", "SetProperty").put("Identity", ownIdentity.id).put("Property", name).put("Value", value).get()) + } + + override fun removeProperty(ownIdentity: OwnIdentity, name: String) { + performRequest(SimpleFieldSetBuilder().put("Message", "RemoveProperty").put("Identity", ownIdentity.id).put("Property", name).get()) + } + + override fun getTrust(ownIdentity: OwnIdentity, identity: Identity) = + performRequest(SimpleFieldSetBuilder().put("Message", "GetIdentity").put("Truster", ownIdentity.id).put("Identity", identity.id).get()) + .fields + .parseTrust() + + override fun setTrust(ownIdentity: OwnIdentity, identity: Identity, trust: Int, comment: String) { + performRequest(SimpleFieldSetBuilder().put("Message", "SetTrust").put("Truster", ownIdentity.id).put("Trustee", identity.id).put("Value", trust.toString()).put("Comment", comment).get()) + } + + override fun removeTrust(ownIdentity: OwnIdentity, identity: Identity) { + performRequest(SimpleFieldSetBuilder().put("Message", "RemoveTrust").put("Truster", ownIdentity.id).put("Trustee", identity.id).get()) + } + + override fun ping() { + performRequest(SimpleFieldSetBuilder().put("Message", "Ping").get()) + } + + private fun performRequest(fields: SimpleFieldSet): PluginReply { + logger.log(Level.FINE, format("Sending FCP Request: %s", fields.get("Message"))) + return runBlocking { + pluginConnector.sendRequest(WOT_PLUGIN_NAME, fields).also { + logger.log(Level.FINEST, format("Received FCP Response for %s: %s", fields.get("Message"), it.fields.get("Message"))) + if ("Error" == it.fields.get("Message")) { + throw PluginException("Could not perform request for " + fields.get("Message")) + } + } + } + } + +} + +private const val WOT_PLUGIN_NAME = "plugins.WebOfTrust.WebOfTrust" + +private fun SimpleFieldSet.parseIdentities(parser: SimpleFieldSet.(Int) -> I) = + scanPrefix { "Identity$it" } + .map { parser(this, it) } + .toSet() + +private fun SimpleFieldSet.parseOwnIdentity(index: Int) = + DefaultOwnIdentity(get("Identity$index"), get("Nickname$index"), get("RequestURI$index"), get("InsertURI$index")) + .setContextsAndProperties(this@parseOwnIdentity, index) + +private fun SimpleFieldSet.parseTrustedIdentity(index: Int, ownIdentity: OwnIdentity) = + DefaultIdentity(get("Identity$index"), get("Nickname$index"), get("RequestURI$index")) + .setContextsAndProperties(this@parseTrustedIdentity, index) + .apply { setTrust(ownIdentity, this@parseTrustedIdentity.parseTrust(index.toString())) } + +private fun I.setContextsAndProperties(simpleFieldSet: SimpleFieldSet, index: Int) = apply { + contexts = simpleFieldSet.contexts("Contexts$index.") + properties = simpleFieldSet.properties("Properties$index.") +} + +private fun SimpleFieldSet.parseTrust(index: String = "") = + Trust(get("Trust$index")?.toIntOrNull(), get("Score$index")?.toIntOrNull(), get("Rank$index")?.toIntOrNull()) + +private fun SimpleFieldSet.contexts(prefix: String) = + scanPrefix { "${prefix}Context$it" } + .map { get("${prefix}Context$it") } + .toSet() + +private fun SimpleFieldSet.properties(prefix: String) = + scanPrefix { "${prefix}Property${it}.Name" } + .map { get("${prefix}Property${it}.Name") to get("${prefix}Property${it}.Value") } + .toMap() + +private fun SimpleFieldSet.scanPrefix(prefix: (Int) -> String) = + generateSequence(0, Int::inc) + .takeWhile { get(prefix(it)) != null } diff --git a/src/main/kotlin/net/pterodactylus/sone/freenet/wot/Trust.kt b/src/main/kotlin/net/pterodactylus/sone/freenet/wot/Trust.kt new file mode 100644 index 0000000..9242de9 --- /dev/null +++ b/src/main/kotlin/net/pterodactylus/sone/freenet/wot/Trust.kt @@ -0,0 +1,23 @@ +/* + * Sone - Trust.java - Copyright © 2010–2019 David Roden + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.pterodactylus.sone.freenet.wot + +/** + * Container class for trust in the web of trust. + */ +data class Trust(val explicit: Int?, val implicit: Int?, val distance: Int?) diff --git a/src/main/kotlin/net/pterodactylus/sone/freenet/wot/WebOfTrustConnector.kt b/src/main/kotlin/net/pterodactylus/sone/freenet/wot/WebOfTrustConnector.kt new file mode 100644 index 0000000..8488f16 --- /dev/null +++ b/src/main/kotlin/net/pterodactylus/sone/freenet/wot/WebOfTrustConnector.kt @@ -0,0 +1,121 @@ +package net.pterodactylus.sone.freenet.wot + +import net.pterodactylus.sone.freenet.plugin.* + +/** + * Connector for the web of trust plugin. + */ +interface WebOfTrustConnector { + + /** + * Loads all own identities from the Web of Trust plugin. + * + * @return All own identity + * @throws WebOfTrustException if the own identities can not be loaded + */ + @Throws(WebOfTrustException::class) + fun loadAllOwnIdentities(): Set + + /** + * Loads all identities that the given identities trusts with a score of + * more than 0 and the (optional) given context. + * + * @param ownIdentity The own identity + * @param context The context to filter, or `null` + * @return All trusted identities + * @throws PluginException if an error occured talking to the Web of Trust plugin + */ + @Throws(PluginException::class) + fun loadTrustedIdentities(ownIdentity: OwnIdentity, context: String? = null): Set + + /** + * Adds the given context to the given identity. + * + * @param ownIdentity The identity to add the context to + * @param context The context to add + * @throws PluginException if an error occured talking to the Web of Trust plugin + */ + @Throws(PluginException::class) + fun addContext(ownIdentity: OwnIdentity, context: String) + + /** + * Removes the given context from the given identity. + * + * @param ownIdentity The identity to remove the context from + * @param context The context to remove + * @throws PluginException if an error occured talking to the Web of Trust plugin + */ + @Throws(PluginException::class) + fun removeContext(ownIdentity: OwnIdentity, context: String) + + /** + * Sets the property with the given name to the given value. + * + * @param ownIdentity The identity to set the property on + * @param name The name of the property to set + * @param value The value to set + * @throws PluginException if an error occured talking to the Web of Trust plugin + */ + @Throws(PluginException::class) + fun setProperty(ownIdentity: OwnIdentity, name: String, value: String) + + /** + * Removes the property with the given name. + * + * @param ownIdentity The identity to remove the property from + * @param name The name of the property to remove + * @throws PluginException if an error occured talking to the Web of Trust plugin + */ + @Throws(PluginException::class) + fun removeProperty(ownIdentity: OwnIdentity, name: String) + + /** + * Returns the trust for the given identity assigned to it by the given own + * identity. + * + * @param ownIdentity The own identity + * @param identity The identity to get the trust for + * @return The trust for the given identity + * @throws PluginException if an error occured talking to the Web of Trust plugin + */ + @Throws(PluginException::class) + fun getTrust(ownIdentity: OwnIdentity, identity: Identity): Trust + + /** + * Sets the trust for the given identity. + * + * @param ownIdentity The trusting identity + * @param identity The trusted identity + * @param trust The amount of trust (-100 thru 100) + * @param comment The comment or explanation of the trust value + * @throws PluginException if an error occured talking to the Web of Trust plugin + */ + @Throws(PluginException::class) + fun setTrust(ownIdentity: OwnIdentity, identity: Identity, trust: Int, comment: String) + + /** + * Removes any trust assignment of the given own identity for the given + * identity. + * + * @param ownIdentity The own identity + * @param identity The identity to remove all trust for + * @throws WebOfTrustException if an error occurs + */ + @Throws(WebOfTrustException::class) + fun removeTrust(ownIdentity: OwnIdentity, identity: Identity) + + /** + * Pings the Web of Trust plugin. If the plugin can not be reached, a + * [PluginException] is thrown. + * + * @throws PluginException if the plugin is not loaded + */ + @Throws(PluginException::class) + fun ping() + + /** + * Stops the web of trust connector. + */ + fun stop() = Unit + +} diff --git a/src/main/kotlin/net/pterodactylus/sone/freenet/wot/WebOfTrustException.kt b/src/main/kotlin/net/pterodactylus/sone/freenet/wot/WebOfTrustException.kt new file mode 100644 index 0000000..a3ba9aa --- /dev/null +++ b/src/main/kotlin/net/pterodactylus/sone/freenet/wot/WebOfTrustException.kt @@ -0,0 +1,24 @@ +/* + * Sone - WebOfTrustException.java - Copyright © 2010–2019 David Roden + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.pterodactylus.sone.freenet.wot + +/** + * Exception that signals an error processing web of trust identities, mostly + * when communicating with the web of trust plugin. + */ +open class WebOfTrustException(message: String? = null, cause: Throwable?) : Exception(message, cause) diff --git a/src/main/kotlin/net/pterodactylus/sone/freenet/wot/event/IdentityAddedEvent.kt b/src/main/kotlin/net/pterodactylus/sone/freenet/wot/event/IdentityAddedEvent.kt new file mode 100644 index 0000000..9fb2bbf --- /dev/null +++ b/src/main/kotlin/net/pterodactylus/sone/freenet/wot/event/IdentityAddedEvent.kt @@ -0,0 +1,25 @@ +/* + * Sone - IdentityAddedEvent.java - Copyright © 2013–2019 David Roden + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.pterodactylus.sone.freenet.wot.event + +import net.pterodactylus.sone.freenet.wot.* + +/** + * Event that signals that an [Identity] was added. + */ +data class IdentityAddedEvent(val ownIdentity: OwnIdentity, val identity: Identity) diff --git a/src/main/kotlin/net/pterodactylus/sone/freenet/wot/event/IdentityRemovedEvent.kt b/src/main/kotlin/net/pterodactylus/sone/freenet/wot/event/IdentityRemovedEvent.kt new file mode 100644 index 0000000..f8c9cd0 --- /dev/null +++ b/src/main/kotlin/net/pterodactylus/sone/freenet/wot/event/IdentityRemovedEvent.kt @@ -0,0 +1,26 @@ +/* + * Sone - IdentityRemovedEvent.java - Copyright © 2013–2019 David Roden + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.pterodactylus.sone.freenet.wot.event + +import net.pterodactylus.sone.freenet.wot.Identity +import net.pterodactylus.sone.freenet.wot.OwnIdentity + +/** + * Event that signals that an [Identity] was removed. + */ +data class IdentityRemovedEvent(val ownIdentity: OwnIdentity, val identity: Identity) diff --git a/src/main/kotlin/net/pterodactylus/sone/freenet/wot/event/IdentityUpdatedEvent.kt b/src/main/kotlin/net/pterodactylus/sone/freenet/wot/event/IdentityUpdatedEvent.kt new file mode 100644 index 0000000..40557d3 --- /dev/null +++ b/src/main/kotlin/net/pterodactylus/sone/freenet/wot/event/IdentityUpdatedEvent.kt @@ -0,0 +1,25 @@ +/* + * Sone - IdentityUpdatedEvent.java - Copyright © 2013–2019 David Roden + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.pterodactylus.sone.freenet.wot.event + +import net.pterodactylus.sone.freenet.wot.* + +/** + * Event that signals that an [Identity] was updated. + */ +data class IdentityUpdatedEvent(val ownIdentity: OwnIdentity, val identity: Identity) diff --git a/src/main/kotlin/net/pterodactylus/sone/freenet/wot/event/OwnIdentityAddedEvent.kt b/src/main/kotlin/net/pterodactylus/sone/freenet/wot/event/OwnIdentityAddedEvent.kt new file mode 100644 index 0000000..6f76564 --- /dev/null +++ b/src/main/kotlin/net/pterodactylus/sone/freenet/wot/event/OwnIdentityAddedEvent.kt @@ -0,0 +1,25 @@ +/* + * Sone - OwnIdentityAddedEvent.java - Copyright © 2013–2019 David Roden + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.pterodactylus.sone.freenet.wot.event + +import net.pterodactylus.sone.freenet.wot.OwnIdentity + +/** + * Event that signals that an [OwnIdentity] was added. + */ +data class OwnIdentityAddedEvent(val ownIdentity: OwnIdentity) diff --git a/src/main/kotlin/net/pterodactylus/sone/freenet/wot/event/OwnIdentityRemovedEvent.kt b/src/main/kotlin/net/pterodactylus/sone/freenet/wot/event/OwnIdentityRemovedEvent.kt new file mode 100644 index 0000000..7b0e3fe --- /dev/null +++ b/src/main/kotlin/net/pterodactylus/sone/freenet/wot/event/OwnIdentityRemovedEvent.kt @@ -0,0 +1,25 @@ +/* + * Sone - OwnIdentityRemovedEvent.java - Copyright © 2013–2019 David Roden + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.pterodactylus.sone.freenet.wot.event + +import net.pterodactylus.sone.freenet.wot.* + +/** + * Event that signals that an [OwnIdentity] was removed. + */ +data class OwnIdentityRemovedEvent(val ownIdentity: OwnIdentity) diff --git a/src/main/kotlin/net/pterodactylus/sone/main/FreenetModule.kt b/src/main/kotlin/net/pterodactylus/sone/main/FreenetModule.kt index 9b5fa2e..1438c2a 100644 --- a/src/main/kotlin/net/pterodactylus/sone/main/FreenetModule.kt +++ b/src/main/kotlin/net/pterodactylus/sone/main/FreenetModule.kt @@ -5,6 +5,7 @@ import freenet.client.* import freenet.clients.http.* import freenet.node.* import freenet.pluginmanager.* +import net.pterodactylus.sone.freenet.plugin.* import javax.inject.Provider import javax.inject.Singleton @@ -14,8 +15,9 @@ import javax.inject.Singleton class FreenetModule(private val pluginRespirator: PluginRespirator) : Module { override fun configure(binder: Binder): Unit = binder.run { - bind(PluginRespirator::class.java).toProvider(Provider { pluginRespirator }) - pluginRespirator.node!!.let { node -> bind(Node::class.java).toProvider(Provider { node }) } + bind(PluginRespiratorFacade::class.java).toProvider(Provider { FredPluginRespiratorFacade(pluginRespirator) }).`in`(Singleton::class.java) + bind(PluginConnector::class.java).to(FredPluginConnector::class.java).`in`(Singleton::class.java) + bind(Node::class.java).toProvider(Provider { pluginRespirator.node }) bind(HighLevelSimpleClient::class.java).toProvider(Provider { pluginRespirator.hlSimpleClient!! }) bind(ToadletContainer::class.java).toProvider(Provider { pluginRespirator.toadletContainer }) bind(PageMaker::class.java).toProvider(Provider { pluginRespirator.pageMaker }) diff --git a/src/main/kotlin/net/pterodactylus/sone/main/SoneModule.kt b/src/main/kotlin/net/pterodactylus/sone/main/SoneModule.kt index dcb5b3b..97b342a 100644 --- a/src/main/kotlin/net/pterodactylus/sone/main/SoneModule.kt +++ b/src/main/kotlin/net/pterodactylus/sone/main/SoneModule.kt @@ -10,6 +10,7 @@ import com.google.inject.spi.* import freenet.l10n.* import net.pterodactylus.sone.database.* import net.pterodactylus.sone.database.memory.* +import net.pterodactylus.sone.freenet.* import net.pterodactylus.sone.freenet.wot.* import net.pterodactylus.util.config.* import net.pterodactylus.util.config.ConfigurationException @@ -50,9 +51,10 @@ open class SoneModule(private val sonePlugin: SonePlugin, private val eventBus: bind(PluginYear::class.java).toInstance(PluginYear(sonePlugin.year)) bind(PluginHomepage::class.java).toInstance(PluginHomepage(sonePlugin.homepage)) bind(Database::class.java).to(MemoryDatabase::class.java).`in`(Singleton::class.java) - bind(BaseL10n::class.java).toInstance(sonePlugin.l10n().base) + bind(Translation::class.java).toInstance(BaseL10nTranslation(sonePlugin.l10n().base)) loaders?.let { bind(Loaders::class.java).toInstance(it) } bind(MetricRegistry::class.java).`in`(Singleton::class.java) + bind(WebOfTrustConnector::class.java).to(PluginWebOfTrustConnector::class.java).`in`(Singleton::class.java) bindListener(Matchers.any(), object : TypeListener { override fun hear(typeLiteral: TypeLiteral, typeEncounter: TypeEncounter) { diff --git a/src/main/kotlin/net/pterodactylus/sone/notify/ListNotification.kt b/src/main/kotlin/net/pterodactylus/sone/notify/ListNotification.kt new file mode 100644 index 0000000..9ee328e --- /dev/null +++ b/src/main/kotlin/net/pterodactylus/sone/notify/ListNotification.kt @@ -0,0 +1,95 @@ +/* + * Sone - ListNotification.java - Copyright © 2010–2019 David Roden + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.pterodactylus.sone.notify + +import net.pterodactylus.util.notify.* +import net.pterodactylus.util.template.* +import java.lang.System.* +import java.util.concurrent.* + +/** + * Notification that maintains a list of elements. + * + * @param + * The type of the items + */ +class ListNotification : TemplateNotification { + + private val key: String + private val realElements = CopyOnWriteArrayList() + + val elements: List get() = realElements.toList() + + val isEmpty + get() = elements.isEmpty() + + @JvmOverloads + constructor(id: String, key: String, template: Template, dismissable: Boolean = true) : super(id, currentTimeMillis(), currentTimeMillis(), dismissable, template) { + this.key = key + template.initialContext.set(key, realElements) + } + + constructor(listNotification: ListNotification) : super(listNotification.id, listNotification.createdTime, listNotification.lastUpdatedTime, listNotification.isDismissable, Template()) { + this.key = listNotification.key + template.add(listNotification.template) + template.initialContext.set(key, realElements) + } + + fun setElements(elements: Collection) { + realElements.clear() + realElements.addAll(elements.distinct()) + touch() + } + + fun add(element: T) { + if (element !in realElements) { + realElements.add(element) + touch() + } + } + + fun remove(element: T) { + while (realElements.remove(element)) { + /* do nothing, just remove all instances of the element. */ + } + if (realElements.isEmpty()) { + dismiss() + } + touch() + } + + override fun dismiss() { + super.dismiss() + realElements.clear() + } + + override fun hashCode() = + realElements.fold(super.hashCode()) { hash, element -> hash xor element.hashCode() } + + override fun equals(other: Any?): Boolean { + if (other !is ListNotification<*>) { + return false + } + val listNotification = other as ListNotification<*>? + if (!super.equals(listNotification)) { + return false + } + return (key == listNotification.key) && (realElements == listNotification.realElements) + } + +} diff --git a/src/main/kotlin/net/pterodactylus/sone/template/HistogramRenderer.kt b/src/main/kotlin/net/pterodactylus/sone/template/HistogramRenderer.kt new file mode 100644 index 0000000..dba32b2 --- /dev/null +++ b/src/main/kotlin/net/pterodactylus/sone/template/HistogramRenderer.kt @@ -0,0 +1,46 @@ +package net.pterodactylus.sone.template + +import com.codahale.metrics.* +import net.pterodactylus.sone.utils.* +import net.pterodactylus.util.template.* + +/** + * [Filter] that renders a [Histogram] as a table row. + */ +class HistogramRenderer : Filter { + + override fun format(templateContext: TemplateContext, data: Any?, parameters: Map?): Any? { + templateContext["metricName"] = (parameters?.get("name") as String?)?.dotToCamel()?.let { "Page.Metrics.$it.Title" } + (data as? Histogram)?.snapshot?.run { + templateContext["count"] = data.count + templateContext["min"] = min + templateContext["max"] = max + templateContext["mean"] = mean + templateContext["median"] = median + templateContext["percentile75"] = get75thPercentile() + templateContext["percentile95"] = get95thPercentile() + templateContext["percentile98"] = get98thPercentile() + templateContext["percentile99"] = get99thPercentile() + templateContext["percentile999"] = get999thPercentile() + } + return template.render(templateContext) + } + +} + +private val template = """ + <% metricName|l10n|html> + <% count|html> + <% min|duration scale=='μs'|html> + <% max|duration scale=='μs'|html> + <% mean|duration scale=='μs'|html> + <% median|duration scale=='μs'|html> + <% percentile75|duration scale=='μs'|html> + <% percentile95|duration scale=='μs'|html> + <% percentile98|duration scale=='μs'|html> + <% percentile99|duration scale=='μs'|html> + <% percentile999|duration scale=='μs'|html> +""".asTemplate() + +private fun String.dotToCamel() = + split(".").joinToString("", transform = String::capitalize) diff --git a/src/main/kotlin/net/pterodactylus/sone/utils/Booleans.kt b/src/main/kotlin/net/pterodactylus/sone/utils/Booleans.kt index bfcb319..49deb87 100644 --- a/src/main/kotlin/net/pterodactylus/sone/utils/Booleans.kt +++ b/src/main/kotlin/net/pterodactylus/sone/utils/Booleans.kt @@ -9,3 +9,11 @@ fun Boolean.ifTrue(block: () -> R): R? = if (this) block() else null * Returns the value of [block] if `this` is false, returns `null` otherwise. */ fun Boolean.ifFalse(block: () -> R): R? = if (!this) block() else null + +/** + * Returns `this` but runs the given block if `this` is `false`. + * + * @param block The block to run if `this` is `false` + * @return `this` + */ +fun Boolean.onFalse(block: () -> Unit): Boolean = this.also { if (!this) block() } diff --git a/src/main/kotlin/net/pterodactylus/sone/web/WebInterfaceModule.kt b/src/main/kotlin/net/pterodactylus/sone/web/WebInterfaceModule.kt index 283b1ea..c996e30 100644 --- a/src/main/kotlin/net/pterodactylus/sone/web/WebInterfaceModule.kt +++ b/src/main/kotlin/net/pterodactylus/sone/web/WebInterfaceModule.kt @@ -1,7 +1,6 @@ package net.pterodactylus.sone.web import com.google.inject.* -import freenet.l10n.* import freenet.support.api.* import net.pterodactylus.sone.core.* import net.pterodactylus.sone.data.* @@ -73,6 +72,7 @@ class WebInterfaceModule : AbstractModule() { addFilter("unique", UniqueElementFilter()) addFilter("mod", ModFilter()) addFilter("paginate", PaginationFilter()) + addFilter("render-histogram", HistogramRenderer()) addProvider(TemplateProvider.TEMPLATE_CONTEXT_PROVIDER) addProvider(loaders.templateProvider) @@ -99,8 +99,8 @@ class WebInterfaceModule : AbstractModule() { ProfileAccessor(core) @Provides - fun getL10nFilter(l10n: BaseL10n) = - L10nFilter(l10n) + fun getL10nFilter(translation: Translation) = + L10nFilter(translation) @Provides fun getParserFilter(core: Core, soneTextParser: SoneTextParser) = @@ -115,8 +115,8 @@ class WebInterfaceModule : AbstractModule() { LinkedElementsFilter(elementLoader) @Provides - fun getUnknownDateFilter(l10n: BaseL10n) = - UnknownDateFilter(l10n, "View.Sone.Text.UnknownDate") + fun getUnknownDateFilter(translation: Translation) = + UnknownDateFilter(translation, "View.Sone.Text.UnknownDate") @Provides fun getImageLinkFilter(core: Core) = diff --git a/src/main/kotlin/net/pterodactylus/sone/web/ajax/GetTranslationAjaxPage.kt b/src/main/kotlin/net/pterodactylus/sone/web/ajax/GetTranslationAjaxPage.kt index 41188ea..e2c41b6 100644 --- a/src/main/kotlin/net/pterodactylus/sone/web/ajax/GetTranslationAjaxPage.kt +++ b/src/main/kotlin/net/pterodactylus/sone/web/ajax/GetTranslationAjaxPage.kt @@ -16,6 +16,6 @@ class GetTranslationAjaxPage @Inject constructor(webInterface: WebInterface) : J override fun createJsonObject(request: FreenetRequest) = createSuccessJsonObject() - .put("value", webInterface.l10n.getString(request.parameters["key"])) + .put("value", webInterface.translation.translate(request.parameters["key"] ?: "")) } diff --git a/src/main/kotlin/net/pterodactylus/sone/web/page/FreenetRequest.kt b/src/main/kotlin/net/pterodactylus/sone/web/page/FreenetRequest.kt index 54c8f7f..65b45d9 100644 --- a/src/main/kotlin/net/pterodactylus/sone/web/page/FreenetRequest.kt +++ b/src/main/kotlin/net/pterodactylus/sone/web/page/FreenetRequest.kt @@ -19,8 +19,8 @@ package net.pterodactylus.sone.web.page import freenet.clients.http.* import freenet.clients.http.SessionManager.* -import freenet.l10n.* import freenet.support.api.* +import net.pterodactylus.sone.freenet.* import net.pterodactylus.util.web.* import java.net.* import java.util.UUID.* @@ -28,7 +28,6 @@ import java.util.UUID.* open class FreenetRequest(uri: URI, method: Method, val httpRequest: HTTPRequest, val toadletContext: ToadletContext, - val l10n: BaseL10n, val sessionManager: SessionManager ) : Request(uri, method) { diff --git a/src/main/kotlin/net/pterodactylus/sone/web/page/SoneRequest.kt b/src/main/kotlin/net/pterodactylus/sone/web/page/SoneRequest.kt index d3eed37..703f953 100644 --- a/src/main/kotlin/net/pterodactylus/sone/web/page/SoneRequest.kt +++ b/src/main/kotlin/net/pterodactylus/sone/web/page/SoneRequest.kt @@ -1,16 +1,16 @@ package net.pterodactylus.sone.web.page import freenet.clients.http.* -import freenet.l10n.* import freenet.support.api.* import net.pterodactylus.sone.core.* import net.pterodactylus.sone.web.* import net.pterodactylus.util.web.* import java.net.* -class SoneRequest(uri: URI, method: Method, httpRequest: HTTPRequest, toadletContext: ToadletContext, l10n: BaseL10n, sessionManager: SessionManager, - val core: Core, - val webInterface: WebInterface -) : FreenetRequest(uri, method, httpRequest, toadletContext, l10n, sessionManager) +class SoneRequest(uri: URI, method: Method, httpRequest: HTTPRequest, toadletContext: ToadletContext, sessionManager: SessionManager, + val core: Core, + val webInterface: WebInterface +) : FreenetRequest(uri, method, httpRequest, toadletContext, sessionManager) -fun FreenetRequest.toSoneRequest(core: Core, webInterface: WebInterface) = SoneRequest(uri, method, httpRequest, toadletContext, l10n, sessionManager, core, webInterface) +fun FreenetRequest.toSoneRequest(core: Core, webInterface: WebInterface) = + SoneRequest(uri, method, httpRequest, toadletContext, sessionManager, core, webInterface) diff --git a/src/main/kotlin/net/pterodactylus/sone/web/pages/MetricsPage.kt b/src/main/kotlin/net/pterodactylus/sone/web/pages/MetricsPage.kt index 236848d..837e2c8 100644 --- a/src/main/kotlin/net/pterodactylus/sone/web/pages/MetricsPage.kt +++ b/src/main/kotlin/net/pterodactylus/sone/web/pages/MetricsPage.kt @@ -13,25 +13,7 @@ import javax.inject.* class MetricsPage @Inject constructor(webInterface: WebInterface, loaders: Loaders, templateRenderer: TemplateRenderer, private val metricsRegistry: MetricRegistry) : SoneTemplatePage(webInterface, loaders, templateRenderer, "Page.Metrics.Title") { override fun handleRequest(soneRequest: SoneRequest, templateContext: TemplateContext) { - addHistogram(templateContext, "sone.parsing.duration", "soneParsingDuration") - addHistogram(templateContext, "sone.insert.duration", "soneInsertDuration") - } - - private fun addHistogram(templateContext: TemplateContext, metricName: String, variablePrefix: String) { - metricsRegistry.histogram(metricName).also { histogram -> - templateContext["${variablePrefix}Count"] = histogram.count - histogram.snapshot.also { snapshot -> - templateContext["${variablePrefix}Min"] = snapshot.min - templateContext["${variablePrefix}Max"] = snapshot.max - templateContext["${variablePrefix}Median"] = snapshot.median - templateContext["${variablePrefix}Mean"] = snapshot.mean - templateContext["${variablePrefix}Percentile75"] = snapshot.get75thPercentile() - templateContext["${variablePrefix}Percentile95"] = snapshot.get95thPercentile() - templateContext["${variablePrefix}Percentile98"] = snapshot.get98thPercentile() - templateContext["${variablePrefix}Percentile99"] = snapshot.get99thPercentile() - templateContext["${variablePrefix}Percentile999"] = snapshot.get999thPercentile() - } - } + templateContext["histograms"] = metricsRegistry.histograms } } diff --git a/src/main/kotlin/net/pterodactylus/sone/web/pages/RescuePage.kt b/src/main/kotlin/net/pterodactylus/sone/web/pages/RescuePage.kt index b93d09a..2ed4940 100644 --- a/src/main/kotlin/net/pterodactylus/sone/web/pages/RescuePage.kt +++ b/src/main/kotlin/net/pterodactylus/sone/web/pages/RescuePage.kt @@ -21,10 +21,15 @@ class RescuePage @Inject constructor(webInterface: WebInterface, loaders: Loader val soneRescuer = soneRequest.core.getSoneRescuer(currentSone) templateContext["soneRescuer"] = soneRescuer if (soneRequest.isPOST) { + soneRequest.parameters["edition", 9]?.toIntOrNull()?.also { + if (it > -1) { + soneRescuer.setEdition(it.toLong()) + } + } if (soneRequest.parameters["fetch", 8] == "true") { soneRescuer.startNextFetch() } - redirectTo("rescue.html") + throw RedirectException("rescue.html") } } diff --git a/src/main/kotlin/net/pterodactylus/sone/web/pages/SoneTemplatePage.kt b/src/main/kotlin/net/pterodactylus/sone/web/pages/SoneTemplatePage.kt index c905ed2..9734a03 100644 --- a/src/main/kotlin/net/pterodactylus/sone/web/pages/SoneTemplatePage.kt +++ b/src/main/kotlin/net/pterodactylus/sone/web/pages/SoneTemplatePage.kt @@ -2,6 +2,7 @@ package net.pterodactylus.sone.web.pages import freenet.clients.http.* import net.pterodactylus.sone.data.* +import net.pterodactylus.sone.freenet.* import net.pterodactylus.sone.main.* import net.pterodactylus.sone.utils.* import net.pterodactylus.sone.web.* @@ -20,11 +21,12 @@ open class SoneTemplatePage( templateRenderer: TemplateRenderer, private val pageTitleKey: String? = null, private val requiresLogin: Boolean = false, - private val pageTitle: (FreenetRequest) -> String = { pageTitleKey?.let(webInterface.l10n::getString) ?: "" } + private val pageTitle: (FreenetRequest) -> String = { pageTitleKey?.let(webInterface.translation::translate) ?: "" } ) : FreenetTemplatePage(templateRenderer, loaders, "noPermission.html") { private val core = webInterface.core private val sessionProvider: SessionProvider = webInterface + protected val translation: Translation = webInterface.translation protected fun getCurrentSone(toadletContext: ToadletContext, createSession: Boolean = true) = sessionProvider.getCurrentSone(toadletContext, createSession) @@ -89,7 +91,7 @@ open class SoneTemplatePage( private val String.urlEncode: String get() = URLEncoder.encode(this, "UTF-8") override fun isEnabled(toadletContext: ToadletContext) = - isEnabled(SoneRequest(toadletContext.uri, Method.GET, HTTPRequestImpl(toadletContext.uri, "GET"), toadletContext, webInterface.l10n, webInterface.sessionManager, core, webInterface)) + isEnabled(SoneRequest(toadletContext.uri, Method.GET, HTTPRequestImpl(toadletContext.uri, "GET"), toadletContext, webInterface.sessionManager, core, webInterface)) open fun isEnabled(soneRequest: SoneRequest) = when { requiresLogin && getCurrentSone(soneRequest.toadletContext) == null -> false diff --git a/src/main/kotlin/net/pterodactylus/sone/web/pages/UploadImagePage.kt b/src/main/kotlin/net/pterodactylus/sone/web/pages/UploadImagePage.kt index 392b272..0021c6b 100644 --- a/src/main/kotlin/net/pterodactylus/sone/web/pages/UploadImagePage.kt +++ b/src/main/kotlin/net/pterodactylus/sone/web/pages/UploadImagePage.kt @@ -33,7 +33,7 @@ class UploadImagePage @Inject constructor(webInterface: WebInterface, loaders: L val bytes = uploadedFile.data.use { it.toByteArray() } val bufferedImage = bytes.toImage() if (bufferedImage == null) { - templateContext["messages"] = soneRequest.l10n.getString("Page.UploadImage.Error.InvalidImage") + templateContext["messages"] = translation.translate("Page.UploadImage.Error.InvalidImage") return } diff --git a/src/main/kotlin/net/pterodactylus/sone/web/pages/ViewSonePage.kt b/src/main/kotlin/net/pterodactylus/sone/web/pages/ViewSonePage.kt index a498d24..efaee3f 100644 --- a/src/main/kotlin/net/pterodactylus/sone/web/pages/ViewSonePage.kt +++ b/src/main/kotlin/net/pterodactylus/sone/web/pages/ViewSonePage.kt @@ -51,7 +51,7 @@ class ViewSonePage @Inject constructor(webInterface: WebInterface, loaders: Load override fun getPageTitle(soneRequest: SoneRequest): String = soneRequest.parameters["sone"]!!.let(soneRequest.core::getSone)?.let { sone -> - "${SoneAccessor.getNiceName(sone)} - ${soneRequest.l10n.getString("Page.ViewSone.Title")}" - } ?: soneRequest.l10n.getString("Page.ViewSone.Page.TitleWithoutSone") + "${SoneAccessor.getNiceName(sone)} - ${translation.translate("Page.ViewSone.Title")}" + } ?: translation.translate("Page.ViewSone.Page.TitleWithoutSone") } diff --git a/src/main/resources/i18n/sone.de.properties b/src/main/resources/i18n/sone.de.properties index 7e42167..a3a95d6 100644 --- a/src/main/resources/i18n/sone.de.properties +++ b/src/main/resources/i18n/sone.de.properties @@ -305,7 +305,7 @@ Page.Rescue.Text.Fetching=Die Sonerettung versucht gerade, Version {0} Ihrer Son Page.Rescue.Text.Fetched=Die Sonerettung hat Version {0} Ihrer Sone herunter geladen. Bitte überprüfen Sie Ihre Nachrichten, Antworten und Ihr Profile. Bei Gefallen können Sie die Sone einfach entsperren. Page.Rescue.Text.FetchedLast=Die Sonerettung hat die letzte verfügbare Version Ihrer Sone herunter geladen. Wenn bis jetzt keine Version dabei war, die Sie wiederherstellen möchten, haben Sie jetzt kein Glück. Page.Rescue.Text.NotFetched=Die Sonerettung konnte Version {0} Ihrer Sone nicht herunter laden. Bitte versuchen Sie erneut, Version {0} herunter zu laden, oder versuchen Sie die nächstältere Version. -Page.Rescue.Label.NextEdition=Nächste Version: +Page.Rescue.Label.NextEdition=Nächste Version Page.Rescue.Button.Fetch=Version herunter laden Page.NoPermission.Title=Unberechtigter Zugriff - Sone @@ -331,6 +331,12 @@ Page.Invalid.Title=Ungültige Aktion ausgeführt - Sone Page.Invalid.Page.Title=Ungültige Aktion ausgeführt Page.Invalid.Text=Eine ungültige Aktion wurde ausgeführt, oder eine gültige Aktion hatte ungültige Parameter. Bitte kehren Sie zur {link}Hauptseite{/link} zurück und versuchen Sie Ihre Aktion erneut. Wenn der Fehler weiterhin besteht, haben Sie wahrscheinlich einen Programmierfehler gefunden. +Page.Metrics.Title=Metriken +Page.Metrics.Page.Title=Metriken +Page.Metrics.SoneInsertDuration.Title=Hochladedauer einer Sone +Page.Metrics.SoneParseDuration.Title=Parsdauer einer Sone +Page.Metrics.ConfigurationSavingDuration.Title=Speicherdauer der Konfiguration + View.Search.Button.Search=Suchen View.CreateSone.Text.WotIdentityRequired=Um eine Sone anzulegen, brauchen Sie eine Identität aus dem {link}„Web of Trust“ Plugin{/link}. diff --git a/src/main/resources/i18n/sone.en.properties b/src/main/resources/i18n/sone.en.properties index 41fa10c..a23730c 100644 --- a/src/main/resources/i18n/sone.en.properties +++ b/src/main/resources/i18n/sone.en.properties @@ -307,7 +307,7 @@ Page.Rescue.Text.Fetching=The Sone Rescuer is currently fetching edition {0} of Page.Rescue.Text.Fetched=The Sone Rescuer has downloaded edition {0} of your Sone. Please check your posts, replies, and profile. If you like what the current Sone contains, just unlock it. Page.Rescue.Text.FetchedLast=The Sone rescuer has downloaded the last available edition. If it did not manage to restore your Sone you are probably out of luck now. Page.Rescue.Text.NotFetched=The Sone Rescuer could not download edition {0} of your Sone. Please either try again with edition {0}, or try the next older edition. -Page.Rescue.Label.NextEdition=Next edition: +Page.Rescue.Label.NextEdition=Next edition Page.Rescue.Button.Fetch=Fetch edition Page.NoPermission.Title=Unauthorized Access - Sone @@ -336,7 +336,8 @@ Page.Invalid.Text=An invalid action was performed, or the action was valid but t Page.Metrics.Title=Metrics Page.Metrics.Page.Title=Metrics Page.Metrics.SoneInsertDuration.Title=Sone Insert Duration -Page.Metrics.SoneParsingDuration.Title=Sone Parsing Duration +Page.Metrics.SoneParseDuration.Title=Sone Parse Duration +Page.Metrics.ConfigurationSaveDuration.Title=Configuration Save Duration View.Search.Button.Search=Search diff --git a/src/main/resources/i18n/sone.es.properties b/src/main/resources/i18n/sone.es.properties index f5072c8..8a6e200 100644 --- a/src/main/resources/i18n/sone.es.properties +++ b/src/main/resources/i18n/sone.es.properties @@ -305,7 +305,7 @@ Page.Rescue.Text.Fetching=El modo rescate está obteniendo actualmente la edici Page.Rescue.Text.Fetched=El modo rescate ha descargado la edición {0} de tu Sone. Por favor, comprueba tus publicaciones, respuestas y perfil. Si te gusta lo que tiene el Sone actual, desbloquealo. Page.Rescue.Text.FetchedLast=El rescatador de Sone ha descargado la última versión disponibe. Si no ha conseguido restaurar tu Sone probablemente no te queda suerte. Page.Rescue.Text.NotFetched=El rescatador de Sone no ha podido descargar la edición {0} de tu Sone. Por favor, vuelve ha intentarlo con la edición {0}, o prueba con la siguiente versión antigua. -Page.Rescue.Label.NextEdition=Siguiente edición: +Page.Rescue.Label.NextEdition=Siguiente edición Page.Rescue.Button.Fetch=Obtener edición Page.NoPermission.Title=Acceso desautorizado - Sone @@ -331,6 +331,12 @@ Page.Invalid.Title=Acción invalida realizada - Sone Page.Invalid.Page.Title=Acción invalida realizada Page.Invalid.Text=Se ha realizado una acción inválida, o la acción era válida pero los parámetros no. Por favor, vuelve al {link}índice{/link} e intentalo de nuevo. Si el error persiste, probablemente hayas encontrado un bug. +Page.Metrics.Title=Metrics +Page.Metrics.Page.Title=Metrics +Page.Metrics.SoneInsertDuration.Title=Sone Insert Duration +Page.Metrics.SoneParseDuration.Title=Sone Parse Duration +Page.Metrics.ConfigurationSaveDuration.Title=Configuration Save Duration + View.Search.Button.Search=Buscar View.CreateSone.Text.WotIdentityRequired=Para crear un Sone necesitas una identidad del {link}plugin Web of Trust{/link}. @@ -471,4 +477,4 @@ Notification.Mention.Text=Has sido mencionado en las siguientes publicaciones: Notification.SoneIsInserting.Text=Tu Sone sone://{0} está siendo insertado. Notification.SoneIsInserted.Text=Tu Sone sone://{0} ha sido insertado en {1,number} {1,choice,0#segundos|1#segundo|1 - +

- +
- +
<%/if> <%/if> diff --git a/src/main/resources/templates/include/viewReply.html b/src/main/resources/templates/include/viewReply.html index 3a5e59a..99c02e6 100644 --- a/src/main/resources/templates/include/viewReply.html +++ b/src/main/resources/templates/include/viewReply.html @@ -71,19 +71,19 @@ - +
- +
- +
<%/if> <%/if> diff --git a/src/main/resources/templates/metrics.html b/src/main/resources/templates/metrics.html index beaded8..5707476 100644 --- a/src/main/resources/templates/metrics.html +++ b/src/main/resources/templates/metrics.html @@ -8,45 +8,22 @@ Metric - Count - Min - Max - Mean - Median - 75% - 95% - 98% - 99% - 99.9% + Count + Min + Max + Mean + Median + 75% + 95% + 98% + 99% + 99.9% - - <%= Page.Metrics.SoneInsertDuration.Title|l10n|html> - <% soneInsertDurationCount|html> - <% soneInsertDurationMin|duration scale=="μs"|html> - <% soneInsertDurationMax|duration scale=="μs"|html> - <% soneInsertDurationMean|duration scale=="μs"|html> - <% soneInsertDurationMedian|duration scale=="μs"|html> - <% soneInsertDurationPercentile75|duration scale=="μs"|html> - <% soneInsertDurationPercentile95|duration scale=="μs"|html> - <% soneInsertDurationPercentile98|duration scale=="μs"|html> - <% soneInsertDurationPercentile99|duration scale=="μs"|html> - <% soneInsertDurationPercentile999|duration scale=="μs"|html> - - - <%= Page.Metrics.SoneParsingDuration.Title|l10n|html> - <% soneParsingDurationCount|html> - <% soneParsingDurationMin|duration scale=="μs"|html> - <% soneParsingDurationMax|duration scale=="μs"|html> - <% soneParsingDurationMean|duration scale=="μs"|html> - <% soneParsingDurationMedian|duration scale=="μs"|html> - <% soneParsingDurationPercentile75|duration scale=="μs"|html> - <% soneParsingDurationPercentile95|duration scale=="μs"|html> - <% soneParsingDurationPercentile98|duration scale=="μs"|html> - <% soneParsingDurationPercentile99|duration scale=="μs"|html> - <% soneParsingDurationPercentile999|duration scale=="μs"|html> - + <%foreach histograms histogram> + <% histogram.value|render-histogram name=histogram.key> + <%/foreach> diff --git a/src/main/resources/templates/rescue.html b/src/main/resources/templates/rescue.html index d711881..e08edc3 100644 --- a/src/main/resources/templates/rescue.html +++ b/src/main/resources/templates/rescue.html @@ -16,10 +16,9 @@ <%/if>
-
- <%= Page.Rescue.Label.NextEdition|l10n|html>: <%soneRescuer.nextEdition> - -
+ + +
<%else> <%if soneRescuer.lastFetchSuccessful> diff --git a/src/test/java/net/pterodactylus/sone/core/SoneRescuerTest.java b/src/test/java/net/pterodactylus/sone/core/SoneRescuerTest.java new file mode 100644 index 0000000..ff49a4c --- /dev/null +++ b/src/test/java/net/pterodactylus/sone/core/SoneRescuerTest.java @@ -0,0 +1,134 @@ +package net.pterodactylus.sone.core; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.doAnswer; +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 net.pterodactylus.sone.data.Sone; + +import freenet.keys.FreenetURI; + +import org.junit.Before; +import org.junit.Test; +import org.mockito.invocation.InvocationOnMock; +import org.mockito.stubbing.Answer; + +/** + * Unit test for {@link SoneRescuer}. + */ +public class SoneRescuerTest { + + private static final long CURRENT_EDITION = 12L; + private static final long SOME_OTHER_EDITION = 15L; + private final Core core = mock(Core.class); + private final SoneDownloader soneDownloader = mock(SoneDownloader.class); + private final Sone sone = mock(Sone.class); + private SoneRescuer soneRescuer; + + @Before + public void setupSone() { + FreenetURI soneUri = mock(FreenetURI.class); + when(soneUri.getEdition()).thenReturn(CURRENT_EDITION); + when(sone.getRequestUri()).thenReturn(soneUri); + } + + @Before + public void setupSoneRescuer() { + soneRescuer = new SoneRescuer(core, soneDownloader, sone); + } + + @Test + public void newSoneRescuerIsNotFetchingAnything() { + assertThat(soneRescuer.isFetching(), is(false)); + } + + @Test + public void newSoneRescuerStartsAtCurrentEditionOfSone() { + assertThat(soneRescuer.getCurrentEdition(), is(CURRENT_EDITION)); + } + + @Test + public void newSoneRescuerHasANextEditionToGet() { + assertThat(soneRescuer.hasNextEdition(), is(true)); + } + + @Test + public void soneRescuerDoesNotHaveANextEditionIfCurrentEditionIsZero() { + when(sone.getRequestUri().getEdition()).thenReturn(0L); + soneRescuer = new SoneRescuer(core, soneDownloader, sone); + assertThat(soneRescuer.hasNextEdition(), is(false)); + } + + @Test + public void nextEditionIsOneSmallerThanTheCurrentEdition() { + assertThat(soneRescuer.getNextEdition(), is(CURRENT_EDITION - 1)); + } + + @Test + public void currentEditionCanBeSet() { + soneRescuer.setEdition(SOME_OTHER_EDITION); + assertThat(soneRescuer.getCurrentEdition(), is(SOME_OTHER_EDITION)); + } + + @Test + public void lastFetchOfANewSoneRescuerWasSuccessful() { + assertThat(soneRescuer.isLastFetchSuccessful(), is(true)); + } + + @Test + public void mainLoopStopsWhenItShould() { + soneRescuer.stop(); + soneRescuer.serviceRun(); + } + + @Test + public void successfulInsert() { + final Sone fetchedSone = mock(Sone.class); + returnUriOnInsert(fetchedSone); + soneRescuer.startNextFetch(); + soneRescuer.serviceRun(); + verify(core).lockSone(eq(sone)); + verify(core).updateSone(eq(fetchedSone), eq(true)); + assertThat(soneRescuer.isLastFetchSuccessful(), is(true)); + assertThat(soneRescuer.isFetching(), is(false)); + } + + @Test + public void nonSuccessfulInsertIsRecognized() { + returnUriOnInsert(null); + soneRescuer.startNextFetch(); + soneRescuer.serviceRun(); + verify(core).lockSone(eq(sone)); + verify(core, never()).updateSone(any(Sone.class), eq(true)); + assertThat(soneRescuer.isLastFetchSuccessful(), is(false)); + assertThat(soneRescuer.isFetching(), is(false)); + } + + private void returnUriOnInsert(final Sone fetchedSone) { + FreenetURI keyWithMetaStrings = setupFreenetUri(); + doAnswer(new Answer() { + @Override + public Sone answer(InvocationOnMock invocation) throws Throwable { + soneRescuer.stop(); + return fetchedSone; + } + }).when(soneDownloader).fetchSone(eq(sone), eq(keyWithMetaStrings), eq(true)); + } + + private FreenetURI setupFreenetUri() { + FreenetURI sskKey = mock(FreenetURI.class); + FreenetURI keyWithDocName = mock(FreenetURI.class); + FreenetURI keyWithMetaStrings = mock(FreenetURI.class); + when(keyWithDocName.setMetaString(eq(new String[] { "sone.xml" }))).thenReturn(keyWithMetaStrings); + when(sskKey.setDocName(eq("Sone-" + CURRENT_EDITION))).thenReturn(keyWithDocName); + when(sone.getRequestUri().setKeyType(eq("SSK"))).thenReturn(sskKey); + return keyWithMetaStrings; + } + +} diff --git a/src/test/java/net/pterodactylus/sone/freenet/KeyTest.java b/src/test/java/net/pterodactylus/sone/freenet/KeyTest.java deleted file mode 100644 index 80e1e45..0000000 --- a/src/test/java/net/pterodactylus/sone/freenet/KeyTest.java +++ /dev/null @@ -1,67 +0,0 @@ -package net.pterodactylus.sone.freenet; - -import static freenet.support.Base64.encode; -import static net.pterodactylus.sone.freenet.Key.from; -import static net.pterodactylus.sone.freenet.Key.routingKey; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.is; - -import java.net.MalformedURLException; - -import freenet.keys.FreenetURI; - -import org.junit.Test; - -/** - * Unit test for {@link Key}. - */ -public class KeyTest { - - private final FreenetURI uri; - private final Key key; - - public KeyTest() throws MalformedURLException { - uri = new FreenetURI( - "SSK@NfUYvxDwU9vqb2mh-qdT~DYJ6U0XNbxMGGoLe0aCHJs,Miglsgix0VR56ZiPl4NgjnUd~UdrnHqIvXJ3KKHmxmI,AQACAAE/some-site-12/foo/bar.html"); - key = from(uri); - } - - @Test - public void keyCanBeCreatedFromFreenetUri() throws MalformedURLException { - assertThat(key.getRoutingKey(), - is("NfUYvxDwU9vqb2mh-qdT~DYJ6U0XNbxMGGoLe0aCHJs")); - assertThat(key.getCryptoKey(), - is("Miglsgix0VR56ZiPl4NgjnUd~UdrnHqIvXJ3KKHmxmI")); - assertThat(key.getExtra(), is("AQACAAE")); - } - - @Test - public void keyCanBeConvertedToUsk() throws MalformedURLException { - FreenetURI uskUri = key.toUsk("other-site", 15, "some", "path.html"); - assertThat(uskUri.toString(), - is("USK@NfUYvxDwU9vqb2mh-qdT~DYJ6U0XNbxMGGoLe0aCHJs,Miglsgix0VR56ZiPl4NgjnUd~UdrnHqIvXJ3KKHmxmI,AQACAAE/other-site/15/some/path.html")); - } - - @Test - public void keyCanBeConvertedToSskWithoutEdition() - throws MalformedURLException { - FreenetURI uskUri = key.toSsk("other-site", "some", "path.html"); - assertThat(uskUri.toString(), - is("SSK@NfUYvxDwU9vqb2mh-qdT~DYJ6U0XNbxMGGoLe0aCHJs,Miglsgix0VR56ZiPl4NgjnUd~UdrnHqIvXJ3KKHmxmI,AQACAAE/other-site/some/path.html")); - } - - @Test - public void keyCanBeConvertedToSskWithEdition() - throws MalformedURLException { - FreenetURI uskUri = key.toSsk("other-site", 15, "some", "path.html"); - assertThat(uskUri.toString(), - is("SSK@NfUYvxDwU9vqb2mh-qdT~DYJ6U0XNbxMGGoLe0aCHJs,Miglsgix0VR56ZiPl4NgjnUd~UdrnHqIvXJ3KKHmxmI,AQACAAE/other-site-15/some/path.html")); - } - - @Test - public void routingKeyIsExtractCorrectly() { - assertThat(routingKey(uri), - is("NfUYvxDwU9vqb2mh-qdT~DYJ6U0XNbxMGGoLe0aCHJs")); - } - -} diff --git a/src/test/java/net/pterodactylus/sone/freenet/wot/DefaultIdentityTest.java b/src/test/java/net/pterodactylus/sone/freenet/wot/DefaultIdentityTest.java deleted file mode 100644 index 9d59570..0000000 --- a/src/test/java/net/pterodactylus/sone/freenet/wot/DefaultIdentityTest.java +++ /dev/null @@ -1,150 +0,0 @@ -/* - * Sone - DefaultIdentityTest.java - Copyright © 2013–2019 David Roden - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package net.pterodactylus.sone.freenet.wot; - -import static com.google.common.collect.ImmutableMap.of; -import static java.util.Arrays.asList; -import static net.pterodactylus.sone.test.Matchers.matchesRegex; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.containsInAnyOrder; -import static org.hamcrest.Matchers.empty; -import static org.hamcrest.Matchers.hasEntry; -import static org.hamcrest.Matchers.is; -import static org.hamcrest.Matchers.not; -import static org.hamcrest.Matchers.nullValue; -import static org.hamcrest.collection.IsIterableContainingInOrder.contains; -import static org.mockito.Mockito.mock; - -import java.util.Collections; - -import org.junit.Test; - -/** - * Unit test for {@link DefaultIdentity}. - */ -public class DefaultIdentityTest { - - protected final DefaultIdentity identity = createIdentity(); - - protected DefaultIdentity createIdentity() { - return new DefaultIdentity("Id", "Nickname", "RequestURI"); - } - - @Test - public void identityCanBeCreated() { - assertThat(identity.getId(), is("Id")); - assertThat(identity.getNickname(), is("Nickname")); - assertThat(identity.getRequestUri(), is("RequestURI")); - assertThat(identity.getContexts(), empty()); - assertThat(identity.getProperties(), is(Collections.emptyMap())); - } - - @Test - public void contextsAreAddedCorrectly() { - identity.addContext("Test"); - assertThat(identity.getContexts(), contains("Test")); - assertThat(identity.hasContext("Test"), is(true)); - } - - @Test - public void contextsAreRemovedCorrectly() { - identity.addContext("Test"); - identity.removeContext("Test"); - assertThat(identity.getContexts(), empty()); - assertThat(identity.hasContext("Test"), is(false)); - } - - @Test - public void contextsAreSetCorrectlyInBulk() { - identity.addContext("Test"); - identity.setContexts(asList("Test1", "Test2")); - assertThat(identity.getContexts(), containsInAnyOrder("Test1", "Test2")); - assertThat(identity.hasContext("Test"), is(false)); - assertThat(identity.hasContext("Test1"), is(true)); - assertThat(identity.hasContext("Test2"), is(true)); - } - - @Test - public void propertiesAreAddedCorrectly() { - identity.setProperty("Key", "Value"); - assertThat(identity.getProperties().size(), is(1)); - assertThat(identity.getProperties(), hasEntry("Key", "Value")); - assertThat(identity.getProperty("Key"), is("Value")); - } - - @Test - public void propertiesAreRemovedCorrectly() { - identity.setProperty("Key", "Value"); - identity.removeProperty("Key"); - assertThat(identity.getProperties(), is(Collections.emptyMap())); - assertThat(identity.getProperty("Key"), nullValue()); - } - - @Test - public void propertiesAreSetCorrectlyInBulk() { - identity.setProperty("Key", "Value"); - identity.setProperties(of("Key1", "Value1", "Key2", "Value2")); - assertThat(identity.getProperties().size(), is(2)); - assertThat(identity.getProperty("Key"), nullValue()); - assertThat(identity.getProperty("Key1"), is("Value1")); - assertThat(identity.getProperty("Key2"), is("Value2")); - } - - @Test - public void trustRelationshipsAreAddedCorrectly() { - OwnIdentity ownIdentity = mock(OwnIdentity.class); - Trust trust = mock(Trust.class); - identity.setTrust(ownIdentity, trust); - assertThat(identity.getTrust(ownIdentity), is(trust)); - } - - @Test - public void trustRelationshipsAreRemovedCorrectly() { - OwnIdentity ownIdentity = mock(OwnIdentity.class); - Trust trust = mock(Trust.class); - identity.setTrust(ownIdentity, trust); - identity.removeTrust(ownIdentity); - assertThat(identity.getTrust(ownIdentity), nullValue()); - } - - @Test - public void identitiesWithTheSameIdAreEqual() { - DefaultIdentity identity2 = new DefaultIdentity("Id", "Nickname2", "RequestURI2"); - assertThat(identity2, is(identity)); - assertThat(identity, is(identity2)); - } - - @Test - public void twoEqualIdentitiesHaveTheSameHashCode() { - DefaultIdentity identity2 = new DefaultIdentity("Id", "Nickname2", "RequestURI2"); - assertThat(identity.hashCode(), is(identity2.hashCode())); - } - - @Test - public void nullDoesNotMatchAnIdentity() { - assertThat(identity, not(is((Object) null))); - } - - @Test - public void toStringContainsIdAndNickname() { - String identityString = identity.toString(); - assertThat(identityString, matchesRegex(".*\\bId\\b.*")); - assertThat(identityString, matchesRegex(".*\\bNickname\\b.*")); - } - -} diff --git a/src/test/java/net/pterodactylus/sone/freenet/wot/DefaultOwnIdentityTest.java b/src/test/java/net/pterodactylus/sone/freenet/wot/DefaultOwnIdentityTest.java deleted file mode 100644 index 138ced3..0000000 --- a/src/test/java/net/pterodactylus/sone/freenet/wot/DefaultOwnIdentityTest.java +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Sone - DefaultOwnIdentityTest.java - Copyright © 2013–2019 David Roden - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package net.pterodactylus.sone.freenet.wot; - -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.is; - -import org.junit.Test; - -/** - * Unit test for {@link DefaultOwnIdentity}. - */ -public class DefaultOwnIdentityTest extends DefaultIdentityTest { - - @Override - protected DefaultIdentity createIdentity() { - return new DefaultOwnIdentity("Id", "Nickname", "RequestURI", "InsertURI"); - } - - @Test - public void ownIdentityCanBeCreated() { - assertThat(((OwnIdentity) identity).getInsertUri(), is("InsertURI")); - } - -} diff --git a/src/test/java/net/pterodactylus/sone/freenet/wot/Identities.java b/src/test/java/net/pterodactylus/sone/freenet/wot/Identities.java deleted file mode 100644 index 7ed2b59..0000000 --- a/src/test/java/net/pterodactylus/sone/freenet/wot/Identities.java +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Sone - Identities.java - Copyright © 2013–2019 David Roden - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package net.pterodactylus.sone.freenet.wot; - -import java.util.Collection; -import java.util.Map; - -/** - * Creates {@link Identity}s and {@link OwnIdentity}s. - */ -public class Identities { - - public static OwnIdentity createOwnIdentity(String id, Collection contexts, Map properties) { - DefaultOwnIdentity ownIdentity = new DefaultOwnIdentity(id, "Nickname" + id, "Request" + id, "Insert" + id); - setContextsAndPropertiesOnIdentity(ownIdentity, contexts, properties); - return ownIdentity; - } - - public static Identity createIdentity(String id, Collection contexts, Map properties) { - DefaultIdentity identity = new DefaultIdentity(id, "Nickname" + id, "Request" + id); - setContextsAndPropertiesOnIdentity(identity, contexts, properties); - return identity; - } - - private static void setContextsAndPropertiesOnIdentity(Identity identity, Collection contexts, Map properties) { - identity.setContexts(contexts); - identity.setProperties(properties); - } - -} diff --git a/src/test/java/net/pterodactylus/sone/freenet/wot/IdentityChangeDetectorTest.java b/src/test/java/net/pterodactylus/sone/freenet/wot/IdentityChangeDetectorTest.java deleted file mode 100644 index e1eb1c8..0000000 --- a/src/test/java/net/pterodactylus/sone/freenet/wot/IdentityChangeDetectorTest.java +++ /dev/null @@ -1,188 +0,0 @@ -/* - * Sone - IdentityChangeDetectorTest.java - Copyright © 2013–2019 David Roden - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package net.pterodactylus.sone.freenet.wot; - -import static com.google.common.collect.ImmutableMap.of; -import static com.google.common.collect.Lists.newArrayList; -import static java.util.Arrays.asList; -import static net.pterodactylus.sone.freenet.wot.Identities.createIdentity; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.containsInAnyOrder; -import static org.hamcrest.Matchers.empty; - -import java.util.Collection; - -import net.pterodactylus.sone.freenet.wot.IdentityChangeDetector.IdentityProcessor; - -import org.junit.Before; -import org.junit.Test; - -/** - * Unit test for {@link IdentityChangeDetector}. - */ -public class IdentityChangeDetectorTest { - - private final IdentityChangeDetector identityChangeDetector = new IdentityChangeDetector(createOldIdentities()); - private final Collection newIdentities = newArrayList(); - private final Collection removedIdentities = newArrayList(); - private final Collection changedIdentities = newArrayList(); - private final Collection unchangedIdentities = newArrayList(); - - @Before - public void setup() { - identityChangeDetector.onNewIdentity(new IdentityProcessor() { - @Override - public void processIdentity(Identity identity) { - newIdentities.add(identity); - } - }); - identityChangeDetector.onRemovedIdentity(new IdentityProcessor() { - @Override - public void processIdentity(Identity identity) { - removedIdentities.add(identity); - } - }); - identityChangeDetector.onChangedIdentity(new IdentityProcessor() { - @Override - public void processIdentity(Identity identity) { - changedIdentities.add(identity); - } - }); - identityChangeDetector.onUnchangedIdentity(new IdentityProcessor() { - @Override - public void processIdentity(Identity identity) { - unchangedIdentities.add(identity); - } - }); - } - - @Test - public void noDifferencesAreDetectedWhenSendingTheOldIdentitiesAgain() { - identityChangeDetector.detectChanges(createOldIdentities()); - assertThat(newIdentities, empty()); - assertThat(removedIdentities, empty()); - assertThat(changedIdentities, empty()); - assertThat(unchangedIdentities, containsInAnyOrder(createIdentity1(), createIdentity2(), createIdentity3())); - } - - @Test - public void detectThatAnIdentityWasRemoved() { - identityChangeDetector.detectChanges(asList(createIdentity1(), createIdentity3())); - assertThat(newIdentities, empty()); - assertThat(removedIdentities, containsInAnyOrder(createIdentity2())); - assertThat(changedIdentities, empty()); - assertThat(unchangedIdentities, containsInAnyOrder(createIdentity1(), createIdentity3())); - } - - @Test - public void detectThatAnIdentityWasAdded() { - identityChangeDetector.detectChanges(asList(createIdentity1(), createIdentity2(), createIdentity3(), createIdentity4())); - assertThat(newIdentities, containsInAnyOrder(createIdentity4())); - assertThat(removedIdentities, empty()); - assertThat(changedIdentities, empty()); - assertThat(unchangedIdentities, containsInAnyOrder(createIdentity1(), createIdentity2(), createIdentity3())); - } - - @Test - public void detectThatAContextWasRemoved() { - Identity identity2 = createIdentity2(); - identity2.removeContext("Context C"); - identityChangeDetector.detectChanges(asList(createIdentity1(), identity2, createIdentity3())); - assertThat(newIdentities, empty()); - assertThat(removedIdentities, empty()); - assertThat(changedIdentities, containsInAnyOrder(identity2)); - assertThat(unchangedIdentities, containsInAnyOrder(createIdentity1(), createIdentity3())); - } - - @Test - public void detectThatAContextWasAdded() { - Identity identity2 = createIdentity2(); - identity2.addContext("Context C1"); - identityChangeDetector.detectChanges(asList(createIdentity1(), identity2, createIdentity3())); - assertThat(newIdentities, empty()); - assertThat(removedIdentities, empty()); - assertThat(changedIdentities, containsInAnyOrder(identity2)); - assertThat(unchangedIdentities, containsInAnyOrder(createIdentity1(), createIdentity3())); - } - - @Test - public void detectThatAPropertyWasRemoved() { - Identity identity1 = createIdentity1(); - identity1.removeProperty("Key A"); - identityChangeDetector.detectChanges(asList(identity1, createIdentity2(), createIdentity3())); - assertThat(newIdentities, empty()); - assertThat(removedIdentities, empty()); - assertThat(changedIdentities, containsInAnyOrder(identity1)); - assertThat(unchangedIdentities, containsInAnyOrder(createIdentity2(), createIdentity3())); - } - - @Test - public void detectThatAPropertyWasAdded() { - Identity identity3 = createIdentity3(); - identity3.setProperty("Key A", "Value A"); - identityChangeDetector.detectChanges(asList(createIdentity1(), createIdentity2(), identity3)); - assertThat(newIdentities, empty()); - assertThat(removedIdentities, empty()); - assertThat(changedIdentities, containsInAnyOrder(identity3)); - assertThat(unchangedIdentities, containsInAnyOrder(createIdentity1(), createIdentity2())); - } - - @Test - public void detectThatAPropertyWasChanged() { - Identity identity3 = createIdentity3(); - identity3.setProperty("Key E", "Value F"); - identityChangeDetector.detectChanges(asList(createIdentity1(), createIdentity2(), identity3)); - assertThat(newIdentities, empty()); - assertThat(removedIdentities, empty()); - assertThat(changedIdentities, containsInAnyOrder(identity3)); - assertThat(unchangedIdentities, containsInAnyOrder(createIdentity1(), createIdentity2())); - } - - @Test - public void noRemovedIdentitiesAreDetectedWithoutAnIdentityProcessor() { - identityChangeDetector.onRemovedIdentity(null); - identityChangeDetector.detectChanges(asList(createIdentity1(), createIdentity3())); - } - - @Test - public void noAddedIdentitiesAreDetectedWithoutAnIdentityProcessor() { - identityChangeDetector.onNewIdentity(null); - identityChangeDetector.detectChanges(asList(createIdentity1(), createIdentity2(), createIdentity3(), createIdentity4())); - } - - private static Collection createOldIdentities() { - return asList(createIdentity1(), createIdentity2(), createIdentity3()); - } - - private static Identity createIdentity1() { - return createIdentity("Test1", asList("Context A", "Context B"), of("Key A", "Value A", "Key B", "Value B")); - } - - private static Identity createIdentity2() { - return createIdentity("Test2", asList("Context C", "Context D"), of("Key C", "Value C", "Key D", "Value D")); - } - - private static Identity createIdentity3() { - return createIdentity("Test3", asList("Context E", "Context F"), of("Key E", "Value E", "Key F", "Value F")); - } - - private static Identity createIdentity4() { - return createIdentity("Test4", asList("Context G", "Context H"), of("Key G", "Value G", "Key H", "Value H")); - } - -} diff --git a/src/test/java/net/pterodactylus/sone/freenet/wot/IdentityChangeEventSenderTest.java b/src/test/java/net/pterodactylus/sone/freenet/wot/IdentityChangeEventSenderTest.java deleted file mode 100644 index ff442a4..0000000 --- a/src/test/java/net/pterodactylus/sone/freenet/wot/IdentityChangeEventSenderTest.java +++ /dev/null @@ -1,91 +0,0 @@ -/* - * Sone - IdentityChangeEventSenderTest.java - Copyright © 2013–2019 David Roden - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package net.pterodactylus.sone.freenet.wot; - -import static com.google.common.collect.ImmutableMap.of; -import static java.util.Arrays.asList; -import static net.pterodactylus.sone.freenet.wot.Identities.createIdentity; -import static net.pterodactylus.sone.freenet.wot.Identities.createOwnIdentity; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; - -import java.util.Collection; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import net.pterodactylus.sone.freenet.wot.event.IdentityAddedEvent; -import net.pterodactylus.sone.freenet.wot.event.IdentityRemovedEvent; -import net.pterodactylus.sone.freenet.wot.event.IdentityUpdatedEvent; -import net.pterodactylus.sone.freenet.wot.event.OwnIdentityAddedEvent; -import net.pterodactylus.sone.freenet.wot.event.OwnIdentityRemovedEvent; - -import com.google.common.eventbus.EventBus; -import org.junit.Test; - -/** - * Unit test for {@link IdentityChangeEventSender}. - */ -public class IdentityChangeEventSenderTest { - - private final EventBus eventBus = mock(EventBus.class); - private final List ownIdentities = asList( - createOwnIdentity("O1", asList("Test"), of("KeyA", "ValueA")), - createOwnIdentity("O2", asList("Test2"), of("KeyB", "ValueB")), - createOwnIdentity("O3", asList("Test3"), of("KeyC", "ValueC")) - ); - private final List identities = asList( - createIdentity("I1", Collections.emptyList(), Collections.emptyMap()), - createIdentity("I2", Collections.emptyList(), Collections.emptyMap()), - createIdentity("I3", Collections.emptyList(), Collections.emptyMap()), - createIdentity("I2", asList("Test"), Collections.emptyMap()) - ); - private final IdentityChangeEventSender identityChangeEventSender = new IdentityChangeEventSender(eventBus, createOldIdentities()); - - @Test - public void addingAnOwnIdentityIsDetectedAndReportedCorrectly() { - Map> newIdentities = createNewIdentities(); - identityChangeEventSender.detectChanges(newIdentities); - verify(eventBus).post(eq(new OwnIdentityRemovedEvent(ownIdentities.get(0)))); - verify(eventBus).post(eq(new IdentityRemovedEvent(ownIdentities.get(0), identities.get(0)))); - verify(eventBus).post(eq(new IdentityRemovedEvent(ownIdentities.get(0), identities.get(1)))); - verify(eventBus).post(eq(new OwnIdentityAddedEvent(ownIdentities.get(2)))); - verify(eventBus).post(eq(new IdentityAddedEvent(ownIdentities.get(2), identities.get(1)))); - verify(eventBus).post(eq(new IdentityAddedEvent(ownIdentities.get(2), identities.get(2)))); - verify(eventBus).post(eq(new IdentityRemovedEvent(ownIdentities.get(1), identities.get(0)))); - verify(eventBus).post(eq(new IdentityAddedEvent(ownIdentities.get(1), identities.get(2)))); - verify(eventBus).post(eq(new IdentityUpdatedEvent(ownIdentities.get(1), identities.get(1)))); - } - - private Map> createNewIdentities() { - Map> oldIdentities = new HashMap<>(); - oldIdentities.put(ownIdentities.get(1), asList(identities.get(3), identities.get(2))); - oldIdentities.put(ownIdentities.get(2), asList(identities.get(1), identities.get(2))); - return oldIdentities; - } - - private Map> createOldIdentities() { - Map> oldIdentities = new HashMap<>(); - oldIdentities.put(ownIdentities.get(0), asList(identities.get(0), identities.get(1))); - oldIdentities.put(ownIdentities.get(1), asList(identities.get(0), identities.get(1))); - return oldIdentities; - } - -} diff --git a/src/test/java/net/pterodactylus/sone/freenet/wot/IdentityLoaderTest.java b/src/test/java/net/pterodactylus/sone/freenet/wot/IdentityLoaderTest.java deleted file mode 100644 index 8744c15..0000000 --- a/src/test/java/net/pterodactylus/sone/freenet/wot/IdentityLoaderTest.java +++ /dev/null @@ -1,154 +0,0 @@ -/* - * Sone - IdentityLoaderTest.java - Copyright © 2013–2019 David Roden - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package net.pterodactylus.sone.freenet.wot; - -import static com.google.common.base.Optional.of; -import static com.google.common.collect.Lists.newArrayList; -import static com.google.common.collect.Sets.newHashSet; -import static java.util.Arrays.asList; -import static java.util.Collections.emptySet; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.containsInAnyOrder; -import static org.hamcrest.Matchers.hasSize; -import static org.hamcrest.Matchers.is; -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.verify; -import static org.mockito.Mockito.when; - -import java.util.Collection; -import java.util.Collections; -import java.util.List; -import java.util.Map; -import java.util.Set; - -import com.google.common.base.Optional; -import com.google.common.collect.ImmutableMap; -import org.hamcrest.Matchers; -import org.junit.Before; -import org.junit.Test; - -/** - * Unit test for {@link IdentityLoader}. - */ -public class IdentityLoaderTest { - - private final WebOfTrustConnector webOfTrustConnector = mock(WebOfTrustConnector.class); - private final IdentityLoader identityLoader = new IdentityLoader(webOfTrustConnector, of(new Context("Test"))); - private final IdentityLoader identityLoaderWithoutContext = new IdentityLoader(webOfTrustConnector); - - @Before - public void setup() throws WebOfTrustException { - List ownIdentities = createOwnIdentities(); - when(webOfTrustConnector.loadAllOwnIdentities()).thenReturn(newHashSet(ownIdentities)); - when(webOfTrustConnector.loadTrustedIdentities(eq(ownIdentities.get(0)), any(Optional.class))).thenReturn(createTrustedIdentitiesForFirstOwnIdentity()); - when(webOfTrustConnector.loadTrustedIdentities(eq(ownIdentities.get(1)), any(Optional.class))).thenReturn(createTrustedIdentitiesForSecondOwnIdentity()); - when(webOfTrustConnector.loadTrustedIdentities(eq(ownIdentities.get(2)), any(Optional.class))).thenReturn(createTrustedIdentitiesForThirdOwnIdentity()); - when(webOfTrustConnector.loadTrustedIdentities(eq(ownIdentities.get(3)), any(Optional.class))).thenReturn(createTrustedIdentitiesForFourthOwnIdentity()); - } - - private List createOwnIdentities() { - return newArrayList( - createOwnIdentity("O1", "ON1", "OR1", "OI1", asList("Test", "Test2"), ImmutableMap.of("KeyA", "ValueA", "KeyB", "ValueB")), - createOwnIdentity("O2", "ON2", "OR2", "OI2", asList("Test"), ImmutableMap.of("KeyC", "ValueC")), - createOwnIdentity("O3", "ON3", "OR3", "OI3", asList("Test2"), ImmutableMap.of("KeyE", "ValueE", "KeyD", "ValueD")), - createOwnIdentity("O4", "ON4", "OR$", "OI4", asList("Test"), ImmutableMap.of("KeyA", "ValueA", "KeyD", "ValueD")) - ); - } - - private Set createTrustedIdentitiesForFirstOwnIdentity() { - return newHashSet( - createIdentity("I11", "IN11", "IR11", asList("Test"), ImmutableMap.of("KeyA", "ValueA")) - ); - } - - private Set createTrustedIdentitiesForSecondOwnIdentity() { - return newHashSet( - createIdentity("I21", "IN21", "IR21", asList("Test", "Test2"), ImmutableMap.of("KeyB", "ValueB")) - ); - } - - private Set createTrustedIdentitiesForThirdOwnIdentity() { - return newHashSet( - createIdentity("I31", "IN31", "IR31", asList("Test", "Test3"), ImmutableMap.of("KeyC", "ValueC")) - ); - } - - private Set createTrustedIdentitiesForFourthOwnIdentity() { - return emptySet(); - } - - private OwnIdentity createOwnIdentity(String id, String nickname, String requestUri, String insertUri, List contexts, ImmutableMap properties) { - OwnIdentity ownIdentity = new DefaultOwnIdentity(id, nickname, requestUri, insertUri); - ownIdentity.setContexts(contexts); - ownIdentity.setProperties(properties); - return ownIdentity; - } - - private Identity createIdentity(String id, String nickname, String requestUri, List contexts, ImmutableMap properties) { - Identity identity = new DefaultIdentity(id, nickname, requestUri); - identity.setContexts(contexts); - identity.setProperties(properties); - return identity; - } - - @Test - public void loadingIdentities() throws WebOfTrustException { - List ownIdentities = createOwnIdentities(); - Map> identities = identityLoader.loadIdentities(); - verify(webOfTrustConnector).loadAllOwnIdentities(); - verify(webOfTrustConnector).loadTrustedIdentities(eq(ownIdentities.get(0)), eq(of("Test"))); - verify(webOfTrustConnector).loadTrustedIdentities(eq(ownIdentities.get(1)), eq(of("Test"))); - verify(webOfTrustConnector, never()).loadTrustedIdentities(eq(ownIdentities.get(2)), any(Optional.class)); - verify(webOfTrustConnector).loadTrustedIdentities(eq(ownIdentities.get(3)), eq(of("Test"))); - assertThat(identities.keySet(), hasSize(4)); - assertThat(identities.keySet(), containsInAnyOrder(ownIdentities.get(0), ownIdentities.get(1), ownIdentities.get(2), ownIdentities.get(3))); - verifyIdentitiesForOwnIdentity(identities, ownIdentities.get(0), createTrustedIdentitiesForFirstOwnIdentity()); - verifyIdentitiesForOwnIdentity(identities, ownIdentities.get(1), createTrustedIdentitiesForSecondOwnIdentity()); - verifyIdentitiesForOwnIdentity(identities, ownIdentities.get(2), Collections.emptySet()); - verifyIdentitiesForOwnIdentity(identities, ownIdentities.get(3), createTrustedIdentitiesForFourthOwnIdentity()); - } - - @Test - public void loadingIdentitiesWithoutContext() throws WebOfTrustException { - List ownIdentities = createOwnIdentities(); - Map> identities = identityLoaderWithoutContext.loadIdentities(); - verify(webOfTrustConnector).loadAllOwnIdentities(); - verify(webOfTrustConnector).loadTrustedIdentities(eq(ownIdentities.get(0)), eq(Optional.absent())); - verify(webOfTrustConnector).loadTrustedIdentities(eq(ownIdentities.get(1)), eq(Optional.absent())); - verify(webOfTrustConnector).loadTrustedIdentities(eq(ownIdentities.get(2)), eq(Optional.absent())); - verify(webOfTrustConnector).loadTrustedIdentities(eq(ownIdentities.get(3)), eq(Optional.absent())); - assertThat(identities.keySet(), hasSize(4)); - OwnIdentity firstOwnIdentity = ownIdentities.get(0); - OwnIdentity secondOwnIdentity = ownIdentities.get(1); - OwnIdentity thirdOwnIdentity = ownIdentities.get(2); - OwnIdentity fourthOwnIdentity = ownIdentities.get(3); - assertThat(identities.keySet(), containsInAnyOrder(firstOwnIdentity, secondOwnIdentity, thirdOwnIdentity, fourthOwnIdentity)); - verifyIdentitiesForOwnIdentity(identities, firstOwnIdentity, createTrustedIdentitiesForFirstOwnIdentity()); - verifyIdentitiesForOwnIdentity(identities, secondOwnIdentity, createTrustedIdentitiesForSecondOwnIdentity()); - verifyIdentitiesForOwnIdentity(identities, thirdOwnIdentity, createTrustedIdentitiesForThirdOwnIdentity()); - verifyIdentitiesForOwnIdentity(identities, fourthOwnIdentity, createTrustedIdentitiesForFourthOwnIdentity()); - } - - private void verifyIdentitiesForOwnIdentity(Map> identities, OwnIdentity ownIdentity, Set trustedIdentities) { - assertThat(identities.get(ownIdentity), Matchers.>is(trustedIdentities)); - } - -} diff --git a/src/test/java/net/pterodactylus/sone/freenet/wot/IdentityManagerTest.java b/src/test/java/net/pterodactylus/sone/freenet/wot/IdentityManagerTest.java deleted file mode 100644 index 45e95db..0000000 --- a/src/test/java/net/pterodactylus/sone/freenet/wot/IdentityManagerTest.java +++ /dev/null @@ -1,37 +0,0 @@ -package net.pterodactylus.sone.freenet.wot; - -import static com.google.common.base.Optional.of; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.is; -import static org.mockito.Mockito.doThrow; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; - -import net.pterodactylus.sone.freenet.plugin.PluginException; - -import com.google.common.eventbus.EventBus; -import org.junit.Test; - -/** - * Unit test for {@link IdentityManagerImpl}. - */ -public class IdentityManagerTest { - - private final EventBus eventBus = mock(EventBus.class); - private final WebOfTrustConnector webOfTrustConnector = mock(WebOfTrustConnector.class); - private final IdentityManager identityManager = new IdentityManagerImpl(eventBus, webOfTrustConnector, new IdentityLoader(webOfTrustConnector, of(new Context("Test")))); - - @Test - public void identityManagerPingsWotConnector() throws PluginException { - assertThat(identityManager.isConnected(), is(true)); - verify(webOfTrustConnector).ping(); - } - - @Test - public void disconnectedWotConnectorIsRecognized() throws PluginException { - doThrow(PluginException.class).when(webOfTrustConnector).ping(); - assertThat(identityManager.isConnected(), is(false)); - verify(webOfTrustConnector).ping(); - } - -} diff --git a/src/test/java/net/pterodactylus/sone/freenet/wot/IdentityManagerTest.kt b/src/test/java/net/pterodactylus/sone/freenet/wot/IdentityManagerTest.kt new file mode 100644 index 0000000..3768efd --- /dev/null +++ b/src/test/java/net/pterodactylus/sone/freenet/wot/IdentityManagerTest.kt @@ -0,0 +1,33 @@ +package net.pterodactylus.sone.freenet.wot + +import com.google.common.eventbus.* +import net.pterodactylus.sone.freenet.plugin.* +import net.pterodactylus.sone.test.* +import org.hamcrest.MatcherAssert.* +import org.hamcrest.Matchers.* +import org.junit.* +import org.mockito.Mockito.* + +/** + * Unit test for [IdentityManagerImpl]. + */ +class IdentityManagerTest { + + private val eventBus = mock() + private val webOfTrustConnector = mock() + private val identityManager = IdentityManagerImpl(eventBus, webOfTrustConnector, IdentityLoader(webOfTrustConnector, Context("Test"))) + + @Test + fun identityManagerPingsWotConnector() { + assertThat(identityManager.isConnected, equalTo(true)) + verify(webOfTrustConnector).ping() + } + + @Test + fun disconnectedWotConnectorIsRecognized() { + doThrow(PluginException::class.java).whenever(webOfTrustConnector).ping() + assertThat(identityManager.isConnected, equalTo(false)) + verify(webOfTrustConnector).ping() + } + +} diff --git a/src/test/java/net/pterodactylus/sone/freenet/wot/event/IdentityEventTest.java b/src/test/java/net/pterodactylus/sone/freenet/wot/event/IdentityEventTest.java deleted file mode 100644 index 4fa5bc9..0000000 --- a/src/test/java/net/pterodactylus/sone/freenet/wot/event/IdentityEventTest.java +++ /dev/null @@ -1,52 +0,0 @@ -package net.pterodactylus.sone.freenet.wot.event; - -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.is; -import static org.hamcrest.Matchers.not; -import static org.mockito.Mockito.mock; - -import net.pterodactylus.sone.freenet.wot.Identity; -import net.pterodactylus.sone.freenet.wot.OwnIdentity; - -import org.junit.Test; - -/** - * Unit test for {@link IdentityEvent}. - */ -public class IdentityEventTest { - - private final OwnIdentity ownIdentity = mock(OwnIdentity.class); - private final Identity identity = mock(Identity.class); - private final IdentityEvent identityEvent = createIdentityEvent(ownIdentity, identity); - - private IdentityEvent createIdentityEvent(final OwnIdentity ownIdentity, final Identity identity) { - return new IdentityEvent(ownIdentity, identity) { - }; - } - - @Test - public void identityEventRetainsIdentities() { - assertThat(identityEvent.ownIdentity(), is(ownIdentity)); - assertThat(identityEvent.identity(), is(identity)); - } - - @Test - public void eventsWithTheSameIdentityHaveTheSameHashCode() { - IdentityEvent secondIdentityEvent = createIdentityEvent(ownIdentity, identity); - assertThat(identityEvent.hashCode(), is(secondIdentityEvent.hashCode())); - } - - @Test - public void eventsWithTheSameIdentitiesAreEqual() { - IdentityEvent secondIdentityEvent = createIdentityEvent(ownIdentity, identity); - assertThat(identityEvent, is(secondIdentityEvent)); - assertThat(secondIdentityEvent, is(identityEvent)); - } - - @Test - public void nullDoesNotEqualIdentityEvent() { - assertThat(identityEvent, not(is((Object) null))); - } - - -} diff --git a/src/test/java/net/pterodactylus/sone/freenet/wot/event/OwnIdentityEventTest.java b/src/test/java/net/pterodactylus/sone/freenet/wot/event/OwnIdentityEventTest.java deleted file mode 100644 index 3d27c34..0000000 --- a/src/test/java/net/pterodactylus/sone/freenet/wot/event/OwnIdentityEventTest.java +++ /dev/null @@ -1,46 +0,0 @@ -package net.pterodactylus.sone.freenet.wot.event; - -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.is; -import static org.hamcrest.Matchers.not; -import static org.mockito.Mockito.mock; - -import net.pterodactylus.sone.freenet.wot.OwnIdentity; - -import org.junit.Test; - -/** - * Unit test for {@link OwnIdentityEvent}. - */ -public class OwnIdentityEventTest { - - private final OwnIdentity ownIdentity = mock(OwnIdentity.class); - private final OwnIdentityEvent ownIdentityEvent = createOwnIdentityEvent(ownIdentity); - - @Test - public void eventRetainsOwnIdentity() { - assertThat(ownIdentityEvent.ownIdentity(), is(ownIdentity)); - } - - protected OwnIdentityEvent createOwnIdentityEvent(final OwnIdentity ownIdentity) { - return new OwnIdentityEvent(ownIdentity) { - }; - } - - @Test - public void twoOwnIdentityEventsWithTheSameIdentityHaveTheSameHashCode() { - OwnIdentityEvent secondOwnIdentityEvent = createOwnIdentityEvent(ownIdentity); - assertThat(secondOwnIdentityEvent.hashCode(), is(ownIdentityEvent.hashCode())); - } - - @Test - public void ownIdentityEventDoesNotMatchNull() { - assertThat(ownIdentityEvent, not(is((Object) null))); - } - - @Test - public void ownIdentityEventDoesNotMatchObjectWithADifferentClass() { - assertThat(ownIdentityEvent, not(is(new Object()))); - } - -} diff --git a/src/test/java/net/pterodactylus/sone/main/DebugLoadersTest.java b/src/test/java/net/pterodactylus/sone/main/DebugLoadersTest.java index 7a84978..72f0bf7 100644 --- a/src/test/java/net/pterodactylus/sone/main/DebugLoadersTest.java +++ b/src/test/java/net/pterodactylus/sone/main/DebugLoadersTest.java @@ -23,7 +23,6 @@ import net.pterodactylus.util.web.Response; import freenet.clients.http.SessionManager; import freenet.clients.http.ToadletContext; -import freenet.l10n.BaseL10n; import freenet.support.api.HTTPRequest; import com.google.common.base.Charsets; @@ -41,7 +40,6 @@ public class DebugLoadersTest { @Rule public final TemporaryFolder temporaryFolder = new TemporaryFolder(); - private final BaseL10n l10n = mock(BaseL10n.class); private final StringWriter stringWriter = new StringWriter(); private final TemplateContext templateContext = new TemplateContext(); private Loaders loaders; @@ -72,7 +70,7 @@ public class DebugLoadersTest { HTTPRequest httpRequest = mock(HTTPRequest.class); ToadletContext toadletContext = mock(ToadletContext.class); SessionManager sessionManager = mock(SessionManager.class); - FreenetRequest request = new FreenetRequest(uri, method, httpRequest, toadletContext, l10n, sessionManager); + FreenetRequest request = new FreenetRequest(uri, method, httpRequest, toadletContext, sessionManager); OutputStream outputStream = new ByteArrayOutputStream(); Response response = new Response(outputStream); page.handleRequest(request, response); diff --git a/src/test/java/net/pterodactylus/sone/main/DefaultLoadersTest.java b/src/test/java/net/pterodactylus/sone/main/DefaultLoadersTest.java index dc51479..d91e9e3 100644 --- a/src/test/java/net/pterodactylus/sone/main/DefaultLoadersTest.java +++ b/src/test/java/net/pterodactylus/sone/main/DefaultLoadersTest.java @@ -23,7 +23,6 @@ import net.pterodactylus.util.web.Response; import freenet.clients.http.SessionManager; import freenet.clients.http.ToadletContext; -import freenet.l10n.BaseL10n; import freenet.support.api.HTTPRequest; import org.junit.Test; @@ -33,7 +32,6 @@ import org.junit.Test; */ public class DefaultLoadersTest { - private final BaseL10n l10n = mock(BaseL10n.class); private final Loaders loaders = new DefaultLoaders(); private final StringWriter stringWriter = new StringWriter(); private final TemplateContext templateContext = new TemplateContext(); @@ -53,7 +51,7 @@ public class DefaultLoadersTest { HTTPRequest httpRequest = mock(HTTPRequest.class); ToadletContext toadletContext = mock(ToadletContext.class); SessionManager sessionManager = mock(SessionManager.class); - FreenetRequest request = new FreenetRequest(uri, method, httpRequest, toadletContext, l10n, sessionManager); + FreenetRequest request = new FreenetRequest(uri, method, httpRequest, toadletContext, sessionManager); OutputStream outputStream = new ByteArrayOutputStream(); Response response = new Response(outputStream); staticPage.handleRequest(request, response); diff --git a/src/test/java/net/pterodactylus/sone/notify/ListNotificationTest.java b/src/test/java/net/pterodactylus/sone/notify/ListNotificationTest.java deleted file mode 100644 index 5e40f81..0000000 --- a/src/test/java/net/pterodactylus/sone/notify/ListNotificationTest.java +++ /dev/null @@ -1,120 +0,0 @@ -package net.pterodactylus.sone.notify; - -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.emptyIterable; -import static org.hamcrest.Matchers.is; -import static org.hamcrest.Matchers.not; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; -import static org.mockito.hamcrest.MockitoHamcrest.argThat; - -import java.util.Arrays; - -import net.pterodactylus.util.notify.NotificationListener; -import net.pterodactylus.util.template.Template; -import net.pterodactylus.util.template.TemplateContext; - -import org.hamcrest.Matchers; -import org.junit.Test; - -/** - * Unit test for {@link ListNotification}. - */ -public class ListNotificationTest { - - private static final String ID = "notification-id"; - private static final String KEY = "element-key"; - private static final String OTHER_KEY = "other-key"; - - private final Template template = mock(Template.class); - private final TemplateContext templateInitialContext = mock(TemplateContext.class); - private ListNotification listNotification; - - public ListNotificationTest() { - when(template.getInitialContext()).thenReturn(templateInitialContext); - listNotification = new ListNotification<>(ID, KEY, template); - } - - @Test - public void creatingAListNotificationSetsEmptyIterableOnElementKeyInTemplateContext() { - verify(templateInitialContext).set(eq(KEY), argThat(emptyIterable())); - } - - @Test - public void newListNotificationHasNoElement() { - assertThat(listNotification.getElements(), emptyIterable()); - } - - @Test - public void newListNotificationIsEmpty() { - assertThat(listNotification.isEmpty(), is(true)); - } - - @Test - public void listNotificationRetainsSetElements() { - listNotification.setElements(Arrays.asList("a", "b", "c")); - assertThat(listNotification.getElements(), Matchers.contains("a", "b", "c")); - } - - @Test - public void listNotificationRetainsAddedElements() { - listNotification.add("a"); - listNotification.add("b"); - listNotification.add("c"); - assertThat(listNotification.getElements(), Matchers.contains("a", "b", "c")); - } - - @Test - public void listNotificationRemovesCorrectElement() { - listNotification.setElements(Arrays.asList("a", "b", "c")); - listNotification.remove("b"); - assertThat(listNotification.getElements(), Matchers.contains("a", "c")); - } - - @Test - public void removingTheLastElementDismissesTheNotification() { - NotificationListener notificationListener = mock(NotificationListener.class); - listNotification.addNotificationListener(notificationListener); - listNotification.add("a"); - listNotification.remove("a"); - verify(notificationListener).notificationDismissed(listNotification); - } - - @Test - public void dismissingTheListNotificationRemovesAllElements() { - listNotification.setElements(Arrays.asList("a", "b", "c")); - listNotification.dismiss(); - assertThat(listNotification.getElements(), emptyIterable()); - } - - @Test - public void listNotificationWithDifferentElementsIsNotEqual() { - ListNotification secondNotification = new ListNotification(ID, KEY, template); - listNotification.add("a"); - secondNotification.add("b"); - assertThat(listNotification, not(is(secondNotification))); - } - - @Test - public void listNotificationWithDifferentKeyIsNotEqual() { - ListNotification secondNotification = new ListNotification(ID, OTHER_KEY, template); - assertThat(listNotification, not(is(secondNotification))); - } - - @Test - public void copiedNotificationsHaveTheSameHashCode() { - ListNotification secondNotification = new ListNotification(listNotification); - listNotification.add("a"); - secondNotification.add("a"); - listNotification.setLastUpdateTime(secondNotification.getLastUpdatedTime()); - assertThat(listNotification.hashCode(), is(secondNotification.hashCode())); - } - - @Test - public void listNotificationIsNotEqualToOtherObjects() { - assertThat(listNotification, not(is(new Object()))); - } - -} diff --git a/src/test/java/net/pterodactylus/sone/test/Matchers.java b/src/test/java/net/pterodactylus/sone/test/Matchers.java index d04da0f..5d2d641 100644 --- a/src/test/java/net/pterodactylus/sone/test/Matchers.java +++ b/src/test/java/net/pterodactylus/sone/test/Matchers.java @@ -22,6 +22,8 @@ import static java.util.regex.Pattern.compile; import java.io.IOException; import java.io.InputStream; +import javax.annotation.*; + import net.pterodactylus.sone.data.Album; import net.pterodactylus.sone.data.Image; import net.pterodactylus.sone.data.Post; @@ -91,8 +93,7 @@ public class Matchers { }; } - public static Matcher isPost(String postId, long time, - String text, Optional recipient) { + public static Matcher isPost(String postId, long time, String text, @Nullable String recipient) { return new PostMatcher(postId, time, text, recipient); } @@ -229,10 +230,10 @@ public class Matchers { private final String postId; private final long time; private final String text; - private final Optional recipient; + @Nullable + private final String recipient; - private PostMatcher(String postId, long time, String text, - Optional recipient) { + private PostMatcher(String postId, long time, String text, @Nullable String recipient) { this.postId = postId; this.time = time; this.text = text; @@ -257,15 +258,15 @@ public class Matchers { .appendValue(text); return false; } - if (recipient.isPresent()) { + if (recipient != null) { if (!post.getRecipientId().isPresent()) { mismatchDescription.appendText( "Recipient not present"); return false; } - if (!post.getRecipientId().get().equals(recipient.get())) { + if (!post.getRecipientId().get().equals(recipient)) { mismatchDescription.appendText("Recipient is not ") - .appendValue(recipient.get()); + .appendValue(recipient); return false; } } else { @@ -283,9 +284,9 @@ public class Matchers { .appendValue(postId); description.appendText(", created at @").appendValue(time); description.appendText(", text ").appendValue(text); - if (recipient.isPresent()) { + if (recipient != null) { description.appendText(", directed at ") - .appendValue(recipient.get()); + .appendValue(recipient); } } diff --git a/src/test/kotlin/net/pterodactylus/sone/core/ConfigurationSoneParserTest.kt b/src/test/kotlin/net/pterodactylus/sone/core/ConfigurationSoneParserTest.kt index 6f93cd4..90e3fa6 100644 --- a/src/test/kotlin/net/pterodactylus/sone/core/ConfigurationSoneParserTest.kt +++ b/src/test/kotlin/net/pterodactylus/sone/core/ConfigurationSoneParserTest.kt @@ -95,8 +95,8 @@ class ConfigurationSoneParserTest { val postBuilderFactory = createPostBuilderFactory() val posts = configurationSoneParser.parsePosts(postBuilderFactory) assertThat(posts, containsInAnyOrder( - isPost("P0", 1000L, "T0", absent()), - isPost("P1", 1001L, "T1", of("1234567890123456789012345678901234567890123")) + isPost("P0", 1000L, "T0", null), + isPost("P1", 1001L, "T1", "1234567890123456789012345678901234567890123") )) } @@ -149,7 +149,7 @@ class ConfigurationSoneParserTest { fun postWithInvalidRecipientIdIsRecognized() { setupPostWithInvalidRecipientId() val posts = configurationSoneParser.parsePosts(createPostBuilderFactory()) - assertThat(posts, contains(isPost("P0", 1000L, "T0", absent()))) + assertThat(posts, contains(isPost("P0", 1000L, "T0", null))) } private fun setupPostWithInvalidRecipientId() { diff --git a/src/test/kotlin/net/pterodactylus/sone/core/DefaultElementLoaderTest.kt b/src/test/kotlin/net/pterodactylus/sone/core/DefaultElementLoaderTest.kt index 0e6a0c0..fb9217e 100644 --- a/src/test/kotlin/net/pterodactylus/sone/core/DefaultElementLoaderTest.kt +++ b/src/test/kotlin/net/pterodactylus/sone/core/DefaultElementLoaderTest.kt @@ -4,13 +4,11 @@ import com.google.common.base.Ticker import com.google.common.io.ByteStreams import freenet.keys.FreenetURI import net.pterodactylus.sone.core.FreenetInterface.BackgroundFetchCallback -import net.pterodactylus.sone.test.capture -import net.pterodactylus.sone.test.mock +import net.pterodactylus.sone.test.* import org.hamcrest.MatcherAssert.assertThat import org.hamcrest.Matchers.equalTo import org.mockito.ArgumentMatchers.any import org.mockito.ArgumentMatchers.eq -import org.mockito.Mockito.`when` import org.mockito.Mockito.times import org.mockito.Mockito.verify import java.io.ByteArrayOutputStream @@ -183,7 +181,7 @@ class DefaultElementLoaderTest { elementLoader.loadElement(IMAGE_ID) verify(freenetInterface).startFetch(eq(freenetURI), callback.capture()) callback.value.failed(freenetURI) - `when`(ticker.read()).thenReturn(TimeUnit.MINUTES.toNanos(31)) + whenever(ticker.read()).thenReturn(TimeUnit.MINUTES.toNanos(31)) val linkedElement = elementLoader.loadElement(IMAGE_ID) assertThat(linkedElement.failed, equalTo(false)) assertThat(linkedElement.loading, equalTo(true)) diff --git a/src/test/kotlin/net/pterodactylus/sone/core/FreenetInterfaceTest.kt b/src/test/kotlin/net/pterodactylus/sone/core/FreenetInterfaceTest.kt index cdf0dfb..6647434 100644 --- a/src/test/kotlin/net/pterodactylus/sone/core/FreenetInterfaceTest.kt +++ b/src/test/kotlin/net/pterodactylus/sone/core/FreenetInterfaceTest.kt @@ -88,7 +88,7 @@ class FreenetInterfaceTest { @Before fun setupCallbackCaptorAndUskManager() { - doNothing().`when`(uskManager).subscribe(any(USK::class.java), callbackCaptor.capture(), anyBoolean(), any(RequestClient::class.java)) + doNothing().whenever(uskManager).subscribe(any(USK::class.java), callbackCaptor.capture(), anyBoolean(), any(RequestClient::class.java)) } @Test diff --git a/src/test/kotlin/net/pterodactylus/sone/core/ImageInserterTest.kt b/src/test/kotlin/net/pterodactylus/sone/core/ImageInserterTest.kt index 39a11dc..60bb7e2 100644 --- a/src/test/kotlin/net/pterodactylus/sone/core/ImageInserterTest.kt +++ b/src/test/kotlin/net/pterodactylus/sone/core/ImageInserterTest.kt @@ -37,7 +37,7 @@ class ImageInserterTest { @Test fun `exception when inserting image is ignored`() { - doThrow(SoneException::class.java).`when`(freenetInterface).insertImage(eq(temporaryImage), eq(image), any(InsertToken::class.java)) + doThrow(SoneException::class.java).whenever(freenetInterface).insertImage(eq(temporaryImage), eq(image), any(InsertToken::class.java)) imageInserter.insertImage(temporaryImage, image) verify(freenetInterface).insertImage(eq(temporaryImage), eq(image), any(InsertToken::class.java)) } diff --git a/src/test/kotlin/net/pterodactylus/sone/core/SoneInserterTest.kt b/src/test/kotlin/net/pterodactylus/sone/core/SoneInserterTest.kt index 8b061a7..2f90250 100644 --- a/src/test/kotlin/net/pterodactylus/sone/core/SoneInserterTest.kt +++ b/src/test/kotlin/net/pterodactylus/sone/core/SoneInserterTest.kt @@ -104,7 +104,7 @@ class SoneInserterTest { doAnswer { soneInserter.stop() null - }.`when`(core).touchConfiguration() + }.whenever(core).touchConfiguration() soneInserter.serviceRun() val soneEvents = ArgumentCaptor.forClass(SoneEvent::class.java) verify(freenetInterface).insertDirectory(eq(insertUri), any>(), eq("index.html")) @@ -251,7 +251,7 @@ class SoneInserterTest { doAnswer { soneInserter.stop() null - }.`when`(core).touchConfiguration() + }.whenever(core).touchConfiguration() soneInserter.serviceRun() val histogram = metricRegistry.histogram("sone.insert.duration") assertThat(histogram.count, equalTo(1L)) diff --git a/src/test/kotlin/net/pterodactylus/sone/core/SoneParserTest.kt b/src/test/kotlin/net/pterodactylus/sone/core/SoneParserTest.kt index 6d9d4b8..336d855 100644 --- a/src/test/kotlin/net/pterodactylus/sone/core/SoneParserTest.kt +++ b/src/test/kotlin/net/pterodactylus/sone/core/SoneParserTest.kt @@ -401,7 +401,7 @@ class SoneParserTest { fun `unsuccessful parsing does not add a histogram entry`() { val inputStream = javaClass.getResourceAsStream("sone-parser-with-invalid-image-height.xml") assertThat(soneParser.parseSone(sone, inputStream), nullValue()) - val histogram = metricRegistry.histogram("sone.parsing.duration") + val histogram = metricRegistry.histogram("sone.parse.duration") assertThat(histogram.count, equalTo(0L)) } @@ -409,7 +409,7 @@ class SoneParserTest { fun `successful parsing adds histogram entry`() { val inputStream = javaClass.getResourceAsStream("sone-parser-without-images.xml") assertThat(soneParser.parseSone(sone, inputStream), notNullValue()) - val histogram = metricRegistry.histogram("sone.parsing.duration") + val histogram = metricRegistry.histogram("sone.parse.duration") assertThat(histogram.count, equalTo(1L)) } diff --git a/src/test/kotlin/net/pterodactylus/sone/core/SoneRescuerTest.kt b/src/test/kotlin/net/pterodactylus/sone/core/SoneRescuerTest.kt deleted file mode 100644 index a74b874..0000000 --- a/src/test/kotlin/net/pterodactylus/sone/core/SoneRescuerTest.kt +++ /dev/null @@ -1,112 +0,0 @@ -package net.pterodactylus.sone.core - -import freenet.keys.* -import net.pterodactylus.sone.data.* -import net.pterodactylus.sone.test.* -import org.hamcrest.MatcherAssert.* -import org.hamcrest.Matchers.equalTo -import org.junit.* -import org.mockito.ArgumentMatchers.any -import org.mockito.ArgumentMatchers.eq -import org.mockito.Mockito.doAnswer -import org.mockito.Mockito.never -import org.mockito.Mockito.verify - -/** - * Unit test for [SoneRescuer]. - */ -class SoneRescuerTest { - - private val core = mock() - private val soneDownloader = mock() - private val sone = mock().apply { - val soneUri = mock() - whenever(soneUri.edition).thenReturn(currentEdition) - whenever(requestUri).thenReturn(soneUri) - } - private val soneRescuer = SoneRescuer(core, soneDownloader, sone) - - @Test - fun newSoneRescuerIsNotFetchingAnything() { - assertThat(soneRescuer.isFetching, equalTo(false)) - } - - @Test - fun newSoneRescuerStartsAtCurrentEditionOfSone() { - assertThat(soneRescuer.currentEdition, equalTo(currentEdition)) - } - - @Test - fun newSoneRescuerHasANextEditionToGet() { - assertThat(soneRescuer.hasNextEdition(), equalTo(true)) - } - - @Test - fun soneRescuerDoesNotHaveANextEditionIfCurrentEditionIsZero() { - whenever(sone.requestUri.edition).thenReturn(0L) - val soneRescuer = SoneRescuer(core, soneDownloader, sone) - assertThat(soneRescuer.hasNextEdition(), equalTo(false)) - } - - @Test - fun nextEditionIsOneSmallerThanTheCurrentEdition() { - assertThat(soneRescuer.nextEdition, equalTo(currentEdition - 1)) - } - - @Test - fun lastFetchOfANewSoneRescuerWasSuccessful() { - assertThat(soneRescuer.isLastFetchSuccessful, equalTo(true)) - } - - @Test - fun mainLoopStopsWhenItShould() { - soneRescuer.stop() - soneRescuer.serviceRun() - } - - @Test - fun successfulInsert() { - val fetchedSone = mock() - returnUriOnInsert(fetchedSone) - soneRescuer.startNextFetch() - soneRescuer.serviceRun() - verify(core).lockSone(eq(sone)) - verify(core).updateSone(eq(fetchedSone), eq(true)) - assertThat(soneRescuer.isLastFetchSuccessful, equalTo(true)) - assertThat(soneRescuer.isFetching, equalTo(false)) - assertThat(soneRescuer.currentEdition, equalTo(currentEdition - 1)) - } - - @Test - fun nonSuccessfulInsertIsRecognized() { - returnUriOnInsert(null) - soneRescuer.startNextFetch() - soneRescuer.serviceRun() - verify(core).lockSone(eq(sone)) - verify(core, never()).updateSone(any(Sone::class.java), eq(true)) - assertThat(soneRescuer.isLastFetchSuccessful, equalTo(false)) - assertThat(soneRescuer.isFetching, equalTo(false)) - assertThat(soneRescuer.currentEdition, equalTo(currentEdition)) - } - - private fun returnUriOnInsert(fetchedSone: Sone?) { - val keyWithMetaStrings = setupFreenetUri() - doAnswer { - soneRescuer.stop() - fetchedSone - }.`when`(soneDownloader).fetchSone(eq(sone), eq(keyWithMetaStrings), eq(true)) - } - - private fun setupFreenetUri(): FreenetURI { - val sskKey = mock() - val keyWithDocName = mock() - val keyWithMetaStrings = mock() - whenever(keyWithDocName.setMetaString(eq(arrayOf("sone.xml")))).thenReturn(keyWithMetaStrings) - whenever(sskKey.setDocName(eq("Sone-" + (currentEdition - 1)))).thenReturn(keyWithDocName) - whenever(sone.requestUri.setKeyType(eq("SSK"))).thenReturn(sskKey) - return keyWithMetaStrings - } - -} - -private const val currentEdition = 12L diff --git a/src/test/kotlin/net/pterodactylus/sone/database/memory/MemoryDatabaseTest.kt b/src/test/kotlin/net/pterodactylus/sone/database/memory/MemoryDatabaseTest.kt index 4d7a105..5d4fe3f 100644 --- a/src/test/kotlin/net/pterodactylus/sone/database/memory/MemoryDatabaseTest.kt +++ b/src/test/kotlin/net/pterodactylus/sone/database/memory/MemoryDatabaseTest.kt @@ -50,8 +50,8 @@ class MemoryDatabaseTest { @Test fun `stored sone is made available`() { storeSone() - assertThat(memoryDatabase.getPost("post1"), isPost("post1", 1000L, "post1", absent())) - assertThat(memoryDatabase.getPost("post2"), isPost("post2", 2000L, "post2", of(RECIPIENT_ID))) + assertThat(memoryDatabase.getPost("post1"), isPost("post1", 1000L, "post1", null)) + assertThat(memoryDatabase.getPost("post2"), isPost("post2", 2000L, "post2", RECIPIENT_ID)) assertThat(memoryDatabase.getPost("post3"), nullValue()) assertThat(memoryDatabase.getPostReply("reply1"), isPostReply("reply1", "post1", 3000L, "reply1")) assertThat(memoryDatabase.getPostReply("reply2"), isPostReply("reply2", "post2", 4000L, "reply2")) @@ -417,6 +417,28 @@ class MemoryDatabaseTest { verify(configuration.getStringValue("KnownPosts/0/ID"), times(1)).value = null } + @Test + @Dirty("the rate limiter should be mocked") + fun `setting posts as knows twice in a row only saves the database once`() { + prepareConfigurationValues() + val post = mock() + whenever(post.id).thenReturn("post-id") + memoryDatabase.setPostKnown(post, true) + memoryDatabase.setPostKnown(post, true) + verify(configuration, times(1)).getStringValue("KnownPosts/1/ID") + } + + @Test + @Dirty("the rate limiter should be mocked") + fun `setting post replies as knows twice in a row only saves the database once`() { + prepareConfigurationValues() + val postReply = mock() + whenever(postReply.id).thenReturn("post-reply-id") + memoryDatabase.setPostReplyKnown(postReply, true) + memoryDatabase.setPostReplyKnown(postReply, true) + verify(configuration, times(1)).getStringValue("KnownReplies/1/ID") + } + } private const val SONE_ID = "sone" diff --git a/src/test/kotlin/net/pterodactylus/sone/freenet/BaseL10nTranslationTest.kt b/src/test/kotlin/net/pterodactylus/sone/freenet/BaseL10nTranslationTest.kt new file mode 100644 index 0000000..a188d47 --- /dev/null +++ b/src/test/kotlin/net/pterodactylus/sone/freenet/BaseL10nTranslationTest.kt @@ -0,0 +1,30 @@ +package net.pterodactylus.sone.freenet + +import freenet.l10n.* +import net.pterodactylus.sone.test.* +import org.hamcrest.MatcherAssert.* +import org.hamcrest.Matchers.* +import org.junit.* +import java.util.* + +/** + * Test for [BaseL10nTranslation]. + */ +class BaseL10nTranslationTest { + + private val baseL10n = mock() + private val translation = BaseL10nTranslation(baseL10n) + + @Test + fun `translate method is facade for the correct method`() { + whenever(baseL10n.getString("test")).thenReturn("answer") + assertThat(translation.translate("test"), equalTo("answer")) + } + + @Test + fun `language exposes correct short code`() { + whenever(baseL10n.selectedLanguage).thenReturn(BaseL10n.LANGUAGE.ENGLISH) + assertThat(translation.currentLocale, equalTo(Locale.ENGLISH)) + } + +} diff --git a/src/test/kotlin/net/pterodactylus/sone/freenet/FreenetURIsTest.kt b/src/test/kotlin/net/pterodactylus/sone/freenet/FreenetURIsTest.kt new file mode 100644 index 0000000..ca1fc6e --- /dev/null +++ b/src/test/kotlin/net/pterodactylus/sone/freenet/FreenetURIsTest.kt @@ -0,0 +1,24 @@ +package net.pterodactylus.sone.freenet + +import freenet.keys.* +import org.hamcrest.MatcherAssert.* +import org.hamcrest.Matchers.* +import org.junit.* + +/** + * Unit test for [Key]. + */ +class FreenetURIsTest { + + private val uri = FreenetURI("SSK@$routingKey,$cryptoKey,$extra/some-site-12/foo/bar.html") + + @Test + fun routingKeyIsExtractCorrectly() { + assertThat(uri.routingKeyString, equalTo(routingKey)) + } + +} + +private const val routingKey = "NfUYvxDwU9vqb2mh-qdT~DYJ6U0XNbxMGGoLe0aCHJs" +private const val cryptoKey = "Miglsgix0VR56ZiPl4NgjnUd~UdrnHqIvXJ3KKHmxmI" +private const val extra = "AQACAAE" diff --git a/src/test/kotlin/net/pterodactylus/sone/freenet/L10nFilterTest.kt b/src/test/kotlin/net/pterodactylus/sone/freenet/L10nFilterTest.kt index 66b1ab1..358d106 100644 --- a/src/test/kotlin/net/pterodactylus/sone/freenet/L10nFilterTest.kt +++ b/src/test/kotlin/net/pterodactylus/sone/freenet/L10nFilterTest.kt @@ -1,60 +1,51 @@ package net.pterodactylus.sone.freenet -import freenet.l10n.BaseL10n -import freenet.l10n.BaseL10n.LANGUAGE.ENGLISH -import net.pterodactylus.sone.test.mock -import net.pterodactylus.sone.test.whenever -import net.pterodactylus.util.template.TemplateContext -import org.hamcrest.MatcherAssert.assertThat -import org.hamcrest.Matchers.equalTo -import org.junit.Before -import org.junit.Test -import org.mockito.ArgumentMatchers.anyString +import org.hamcrest.MatcherAssert.* +import org.hamcrest.Matchers.* +import org.junit.* +import java.util.* +import kotlin.collections.* /** * Unit test for [L10nFilter]. */ class L10nFilterTest { - private val l10n = mock() - private val filter = L10nFilter(l10n) - private val templateContext = mock() private val translations = mutableMapOf() - - @Before - fun setupL10n() { - whenever(l10n.selectedLanguage).thenReturn(ENGLISH) - whenever(l10n.getString(anyString())).then { translations[it.arguments[0]] } + private val translation = object : Translation { + override val currentLocale = Locale.ENGLISH + override fun translate(key: String): String = translations[key] ?: "" } + private val filter = L10nFilter(translation) @Test fun `translation without parameters returns translated string`() { translations["data"] = "translated data" - assertThat(filter.format(templateContext, "data", emptyMap()), equalTo("translated data")) + assertThat(filter.format(null, "data", emptyMap()), equalTo("translated data")) } @Test fun `translation with parameters returned translated string`() { translations["data"] = "translated {0,number} {1}" - assertThat(filter.format(templateContext, "data", mapOf("0" to 4.5, "1" to "data")), equalTo("translated 4.5 data")) + assertThat(filter.format(null, "data", mapOf("0" to 4.5, "1" to "data")), equalTo("translated 4.5 data")) } @Test fun `filter processes l10n text without parameters correctly`() { translations["data"] = "translated data" - assertThat(filter.format(templateContext, L10nText("data"), emptyMap()), equalTo("translated data")) + assertThat(filter.format(null, L10nText("data"), emptyMap()), equalTo("translated data")) } @Test fun `filter processes l10n text with parameters correctly`() { translations["data"] = "translated {0,number} {1}" - assertThat(filter.format(templateContext, L10nText("data", listOf(4.5, "data")), emptyMap()), equalTo("translated 4.5 data")) + assertThat(filter.format(null, L10nText("data", listOf(4.5, "data")), emptyMap()), equalTo("translated 4.5 data")) } @Test fun `filter does not replace values if there are no parameters`() { translations["data"] = "{link}" - assertThat(filter.format(templateContext, "data", emptyMap()), equalTo("{link}")) + assertThat(filter.format(null, "data", emptyMap()), equalTo("{link}")) } } diff --git a/src/test/kotlin/net/pterodactylus/sone/freenet/plugin/FredPluginConnectorTest.kt b/src/test/kotlin/net/pterodactylus/sone/freenet/plugin/FredPluginConnectorTest.kt new file mode 100644 index 0000000..1eaa91a --- /dev/null +++ b/src/test/kotlin/net/pterodactylus/sone/freenet/plugin/FredPluginConnectorTest.kt @@ -0,0 +1,77 @@ +/** + * Sone - FredPluginConnectorTest.kt - Copyright © 2019 David ‘Bombe’ Roden + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +/* Fred-based plugin stuff is mostly deprecated. ¯\_(ツ)_/¯ */ +@file:Suppress("DEPRECATION") + +package net.pterodactylus.sone.freenet.plugin + +import freenet.pluginmanager.* +import freenet.support.* +import freenet.support.api.* +import freenet.support.io.* +import kotlinx.coroutines.* +import net.pterodactylus.sone.freenet.* +import org.hamcrest.MatcherAssert.* +import org.hamcrest.Matchers.* +import org.junit.* +import org.junit.rules.* +import kotlin.concurrent.* + +class FredPluginConnectorTest { + + @Rule + @JvmField + val expectedException = ExpectedException.none()!! + + @Test + fun `connector throws exception if plugin can not be found`() = runBlocking { + val pluginConnector = FredPluginConnector(pluginRespiratorFacade) + expectedException.expect(PluginException::class.java) + pluginConnector.sendRequest("wrong.plugin", requestFields, requestData) + Unit + } + + @Test + fun `connector returns correct fields and data`() = runBlocking { + val pluginConnector = FredPluginConnector(pluginRespiratorFacade) + val reply = pluginConnector.sendRequest("test.plugin", requestFields, requestData) + assertThat(reply.fields, equalTo(responseFields)) + assertThat(reply.data, equalTo(responseData)) + } + +} + +private val requestFields = SimpleFieldSetBuilder().put("foo", "bar").get() +private val requestData: Bucket? = ArrayBucket(byteArrayOf(1, 2)) +private val responseFields = SimpleFieldSetBuilder().put("baz", "quo").get() +private val responseData: Bucket? = ArrayBucket(byteArrayOf(3, 4)) + +private val pluginRespiratorFacade = object : PluginRespiratorFacade { + override fun getPluginTalker(pluginTalker: FredPluginTalker, pluginName: String, identifier: String) = + if (pluginName == "test.plugin") { + object : PluginTalkerFacade { + override fun send(pluginParameters: SimpleFieldSet, data: Bucket?) { + if ((pluginParameters == requestFields) && (data == requestData)) { + thread { pluginTalker.onReply(pluginName, identifier, responseFields, responseData) } + } + } + } + } else { + throw PluginNotFoundException() + } +} diff --git a/src/test/kotlin/net/pterodactylus/sone/freenet/plugin/PluginRespiratorFacadeTest.kt b/src/test/kotlin/net/pterodactylus/sone/freenet/plugin/PluginRespiratorFacadeTest.kt new file mode 100644 index 0000000..4e44d5d --- /dev/null +++ b/src/test/kotlin/net/pterodactylus/sone/freenet/plugin/PluginRespiratorFacadeTest.kt @@ -0,0 +1,46 @@ +package net.pterodactylus.sone.freenet.plugin + +import freenet.pluginmanager.* +import freenet.support.* +import freenet.support.api.* +import freenet.support.io.* +import net.pterodactylus.sone.freenet.* +import net.pterodactylus.sone.test.* +import org.hamcrest.MatcherAssert.* +import org.hamcrest.Matchers.* +import org.mockito.ArgumentMatchers.* +import kotlin.test.* + +/** + * Unit test for [FredPluginRespiratorFacade] and [FredPluginTalkerFacade]. + */ +@Suppress("DEPRECATION") +class PluginRespiratorFacadeTest { + + @Test + fun `respirator facade creates correct plugin talker facade`() { + val pluginTalkerSendParameters = mutableListOf() + val originalPluginTalker = mock().apply { + whenever(send(any(), any())).then { invocation -> + pluginTalkerSendParameters += PluginTalkerSendParameters(invocation.getArgument(0), invocation.getArgument(1)) + Unit + } + } + val fredPluginTalker = FredPluginTalker { _, _, _, _ -> } + val pluginRespirator = mock().apply { + whenever(getPluginTalker(fredPluginTalker, "test.plugin", "test-request-1")).thenReturn(originalPluginTalker) + } + val pluginRespiratorFacade = FredPluginRespiratorFacade(pluginRespirator) + val pluginTalker = pluginRespiratorFacade.getPluginTalker(fredPluginTalker, "test.plugin", "test-request-1") + pluginTalker.send(fields, data) + assertThat(pluginTalkerSendParameters, hasSize(1)) + assertThat(pluginTalkerSendParameters[0].parameter, equalTo(fields)) + assertThat(pluginTalkerSendParameters[0].data, equalTo(data)) + } + +} + +private val fields = SimpleFieldSetBuilder().put("foo", "bar").get() +private val data: Bucket? = ArrayBucket(byteArrayOf(1, 2)) + +private data class PluginTalkerSendParameters(val parameter: SimpleFieldSet, val data: Bucket?) diff --git a/src/test/kotlin/net/pterodactylus/sone/freenet/wot/DefaultIdentityTest.kt b/src/test/kotlin/net/pterodactylus/sone/freenet/wot/DefaultIdentityTest.kt new file mode 100644 index 0000000..2cd7bda --- /dev/null +++ b/src/test/kotlin/net/pterodactylus/sone/freenet/wot/DefaultIdentityTest.kt @@ -0,0 +1,142 @@ +/* + * Sone - DefaultIdentityTest.java - Copyright © 2013–2019 David Roden + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.pterodactylus.sone.freenet.wot + +import com.google.common.collect.ImmutableMap.* +import net.pterodactylus.sone.test.* +import net.pterodactylus.sone.test.Matchers.* +import org.hamcrest.MatcherAssert.* +import org.hamcrest.Matchers.containsInAnyOrder +import org.hamcrest.Matchers.empty +import org.hamcrest.Matchers.equalTo +import org.hamcrest.Matchers.hasEntry +import org.hamcrest.Matchers.not +import org.hamcrest.Matchers.nullValue +import org.hamcrest.collection.IsIterableContainingInOrder.contains +import org.junit.* + +/** + * Unit test for [DefaultIdentity]. + */ +open class DefaultIdentityTest { + + protected open val identity = DefaultIdentity("Id", "Nickname", "RequestURI") + + @Test + fun `identity can be created`() { + assertThat(identity.id, equalTo("Id")) + assertThat(identity.nickname, equalTo("Nickname")) + assertThat(identity.requestUri, equalTo("RequestURI")) + assertThat(identity.contexts, empty()) + assertThat(identity.properties, equalTo(emptyMap())) + } + + @Test + fun `contexts are added correctly`() { + identity.addContext("Test") + assertThat(identity.contexts, contains("Test")) + assertThat(identity.hasContext("Test"), equalTo(true)) + } + + @Test + fun `contexts are removed correctly`() { + identity.addContext("Test") + identity.removeContext("Test") + assertThat(identity.contexts, empty()) + assertThat(identity.hasContext("Test"), equalTo(false)) + } + + @Test + fun `contexts are set correctly in bulk`() { + identity.addContext("Test") + identity.contexts = setOf("Test1", "Test2") + assertThat(identity.contexts, containsInAnyOrder("Test1", "Test2")) + assertThat(identity.hasContext("Test"), equalTo(false)) + assertThat(identity.hasContext("Test1"), equalTo(true)) + assertThat(identity.hasContext("Test2"), equalTo(true)) + } + + @Test + fun `properties are added correctly`() { + identity.setProperty("Key", "Value") + assertThat(identity.properties.size, equalTo(1)) + assertThat(identity.properties, hasEntry("Key", "Value")) + assertThat(identity.getProperty("Key"), equalTo("Value")) + } + + @Test + fun `properties are removed correctly`() { + identity.setProperty("Key", "Value") + identity.removeProperty("Key") + assertThat(identity.properties, equalTo(emptyMap())) + assertThat(identity.getProperty("Key"), nullValue()) + } + + @Test + fun `properties are set correctly in bulk`() { + identity.setProperty("Key", "Value") + identity.properties = of("Key1", "Value1", "Key2", "Value2") + assertThat(identity.properties.size, equalTo(2)) + assertThat(identity.getProperty("Key"), nullValue()) + assertThat(identity.getProperty("Key1"), equalTo("Value1")) + assertThat(identity.getProperty("Key2"), equalTo("Value2")) + } + + @Test + fun `trust relationships are added correctly`() { + val ownIdentity = mock() + val trust = mock() + identity.setTrust(ownIdentity, trust) + assertThat(identity.getTrust(ownIdentity), equalTo(trust)) + } + + @Test + fun `trust relationships are removed correctly`() { + val ownIdentity = mock() + val trust = mock() + identity.setTrust(ownIdentity, trust) + identity.removeTrust(ownIdentity) + assertThat(identity.getTrust(ownIdentity), nullValue()) + } + + @Test + fun `identities with the same id are equal`() { + val identity2 = DefaultIdentity("Id", "Nickname2", "RequestURI2") + assertThat(identity2, equalTo(identity)) + assertThat(identity, equalTo(identity2)) + } + + @Test + fun `two equal identities have the same hash code`() { + val identity2 = DefaultIdentity("Id", "Nickname2", "RequestURI2") + assertThat(identity.hashCode(), equalTo(identity2.hashCode())) + } + + @Test + fun `null does not match an identity`() { + assertThat(identity, not(equalTo(null as Any?))) + } + + @Test + fun `toString() contains id and nickname`() { + val identityString = identity.toString() + assertThat(identityString, matchesRegex(".*\\bId\\b.*")) + assertThat(identityString, matchesRegex(".*\\bNickname\\b.*")) + } + +} diff --git a/src/test/kotlin/net/pterodactylus/sone/freenet/wot/DefaultOwnIdentityTest.kt b/src/test/kotlin/net/pterodactylus/sone/freenet/wot/DefaultOwnIdentityTest.kt new file mode 100644 index 0000000..593b157 --- /dev/null +++ b/src/test/kotlin/net/pterodactylus/sone/freenet/wot/DefaultOwnIdentityTest.kt @@ -0,0 +1,36 @@ +/* + * Sone - DefaultOwnIdentityTest.java - Copyright © 2013–2019 David Roden + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.pterodactylus.sone.freenet.wot + +import org.hamcrest.MatcherAssert.* +import org.hamcrest.Matchers.* +import org.junit.* + +/** + * Unit test for [DefaultOwnIdentity]. + */ +class DefaultOwnIdentityTest : DefaultIdentityTest() { + + override val identity = DefaultOwnIdentity("Id", "Nickname", "RequestURI", "InsertURI") + + @Test + fun `own identity can be created`() { + assertThat((identity as OwnIdentity).insertUri, equalTo("InsertURI")) + } + +} diff --git a/src/test/kotlin/net/pterodactylus/sone/freenet/wot/Identities.kt b/src/test/kotlin/net/pterodactylus/sone/freenet/wot/Identities.kt new file mode 100644 index 0000000..9d74b65 --- /dev/null +++ b/src/test/kotlin/net/pterodactylus/sone/freenet/wot/Identities.kt @@ -0,0 +1,35 @@ +/* + * Sone - Identities.java - Copyright © 2013–2019 David Roden + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.pterodactylus.sone.freenet.wot + +fun createOwnIdentity(id: String, contexts: Set, vararg properties: Pair): OwnIdentity { + val ownIdentity = DefaultOwnIdentity(id, "Nickname$id", "Request$id", "Insert$id") + setContextsAndPropertiesOnIdentity(ownIdentity, contexts, mapOf(*properties)) + return ownIdentity +} + +fun createIdentity(id: String, contexts: Set, vararg properties: Pair): Identity { + val identity = DefaultIdentity(id, "Nickname$id", "Request$id") + setContextsAndPropertiesOnIdentity(identity, contexts, mapOf(*properties)) + return identity +} + +private fun setContextsAndPropertiesOnIdentity(identity: Identity, contexts: Set, properties: Map) { + identity.contexts = contexts + identity.properties = properties +} diff --git a/src/test/kotlin/net/pterodactylus/sone/freenet/wot/IdentityChangeDetectorTest.kt b/src/test/kotlin/net/pterodactylus/sone/freenet/wot/IdentityChangeDetectorTest.kt new file mode 100644 index 0000000..e9f8092 --- /dev/null +++ b/src/test/kotlin/net/pterodactylus/sone/freenet/wot/IdentityChangeDetectorTest.kt @@ -0,0 +1,154 @@ +/* + * Sone - IdentityChangeDetectorTest.java - Copyright © 2013–2019 David Roden + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.pterodactylus.sone.freenet.wot + +import org.hamcrest.MatcherAssert.* +import org.hamcrest.Matchers.* +import org.junit.* + +/** + * Unit test for [IdentityChangeDetector]. + */ +class IdentityChangeDetectorTest { + + private val identityChangeDetector = IdentityChangeDetector(createOldIdentities()) + private val newIdentities = mutableListOf() + private val removedIdentities = mutableListOf() + private val changedIdentities = mutableListOf() + private val unchangedIdentities = mutableListOf() + + @Before + fun setup() { + identityChangeDetector.onNewIdentity = { identity -> newIdentities.add(identity) } + identityChangeDetector.onRemovedIdentity = { identity -> removedIdentities.add(identity) } + identityChangeDetector.onChangedIdentity = { identity -> changedIdentities.add(identity) } + identityChangeDetector.onUnchangedIdentity = { identity -> unchangedIdentities.add(identity) } + } + + @Test + fun `no differences are detected when sending the old identities again`() { + identityChangeDetector.detectChanges(createOldIdentities()) + assertThat(newIdentities, empty()) + assertThat(removedIdentities, empty()) + assertThat(changedIdentities, empty()) + assertThat(unchangedIdentities, containsInAnyOrder(createIdentity1(), createIdentity2(), createIdentity3())) + } + + @Test + fun `detect that an identity was removed`() { + identityChangeDetector.detectChanges(listOf(createIdentity1(), createIdentity3())) + assertThat(newIdentities, empty()) + assertThat(removedIdentities, containsInAnyOrder(createIdentity2())) + assertThat(changedIdentities, empty()) + assertThat(unchangedIdentities, containsInAnyOrder(createIdentity1(), createIdentity3())) + } + + @Test + fun `detect that an identity was added`() { + identityChangeDetector.detectChanges(listOf(createIdentity1(), createIdentity2(), createIdentity3(), createIdentity4())) + assertThat(newIdentities, containsInAnyOrder(createIdentity4())) + assertThat(removedIdentities, empty()) + assertThat(changedIdentities, empty()) + assertThat(unchangedIdentities, containsInAnyOrder(createIdentity1(), createIdentity2(), createIdentity3())) + } + + @Test + fun `detect that a context was removed`() { + val identity2 = createIdentity2() + identity2.removeContext("Context C") + identityChangeDetector.detectChanges(listOf(createIdentity1(), identity2, createIdentity3())) + assertThat(newIdentities, empty()) + assertThat(removedIdentities, empty()) + assertThat(changedIdentities, containsInAnyOrder(identity2)) + assertThat(unchangedIdentities, containsInAnyOrder(createIdentity1(), createIdentity3())) + } + + @Test + fun `detect that a context was added`() { + val identity2 = createIdentity2() + identity2.addContext("Context C1") + identityChangeDetector.detectChanges(listOf(createIdentity1(), identity2, createIdentity3())) + assertThat(newIdentities, empty()) + assertThat(removedIdentities, empty()) + assertThat(changedIdentities, containsInAnyOrder(identity2)) + assertThat(unchangedIdentities, containsInAnyOrder(createIdentity1(), createIdentity3())) + } + + @Test + fun `detect that a property was removed`() { + val identity1 = createIdentity1() + identity1.removeProperty("Key A") + identityChangeDetector.detectChanges(listOf(identity1, createIdentity2(), createIdentity3())) + assertThat(newIdentities, empty()) + assertThat(removedIdentities, empty()) + assertThat(changedIdentities, containsInAnyOrder(identity1)) + assertThat(unchangedIdentities, containsInAnyOrder(createIdentity2(), createIdentity3())) + } + + @Test + fun `detect that a property was added`() { + val identity3 = createIdentity3() + identity3.setProperty("Key A", "Value A") + identityChangeDetector.detectChanges(listOf(createIdentity1(), createIdentity2(), identity3)) + assertThat(newIdentities, empty()) + assertThat(removedIdentities, empty()) + assertThat(changedIdentities, containsInAnyOrder(identity3)) + assertThat(unchangedIdentities, containsInAnyOrder(createIdentity1(), createIdentity2())) + } + + @Test + fun `detect that a property was changed`() { + val identity3 = createIdentity3() + identity3.setProperty("Key E", "Value F") + identityChangeDetector.detectChanges(listOf(createIdentity1(), createIdentity2(), identity3)) + assertThat(newIdentities, empty()) + assertThat(removedIdentities, empty()) + assertThat(changedIdentities, containsInAnyOrder(identity3)) + assertThat(unchangedIdentities, containsInAnyOrder(createIdentity1(), createIdentity2())) + } + + @Test + fun `no removed identities are detected without an identity processor`() { + identityChangeDetector.onRemovedIdentity = null + identityChangeDetector.detectChanges(listOf(createIdentity1(), createIdentity3())) + assertThat(removedIdentities, empty()) + } + + @Test + fun `no added identities are detected without an identity processor`() { + identityChangeDetector.onNewIdentity = null + identityChangeDetector.detectChanges(listOf(createIdentity1(), createIdentity2(), createIdentity3(), createIdentity4())) + assertThat(newIdentities, empty()) + } + + private fun createOldIdentities() = + listOf(createIdentity1(), createIdentity2(), createIdentity3()) + + private fun createIdentity1() = + createIdentity("Test1", setOf("Context A", "Context B"), "Key A" to "Value A", "Key B" to "Value B") + + private fun createIdentity2() = + createIdentity("Test2", setOf("Context C", "Context D"), "Key C" to "Value C", "Key D" to "Value D") + + private fun createIdentity3() = + createIdentity("Test3", setOf("Context E", "Context F"), "Key E" to "Value E", "Key F" to "Value F") + + private fun createIdentity4() = + createIdentity("Test4", setOf("Context G", "Context H"), "Key G" to "Value G", "Key H" to "Value H") + +} diff --git a/src/test/kotlin/net/pterodactylus/sone/freenet/wot/IdentityChangeEventSenderTest.kt b/src/test/kotlin/net/pterodactylus/sone/freenet/wot/IdentityChangeEventSenderTest.kt new file mode 100644 index 0000000..19fdda0 --- /dev/null +++ b/src/test/kotlin/net/pterodactylus/sone/freenet/wot/IdentityChangeEventSenderTest.kt @@ -0,0 +1,71 @@ +/* + * Sone - IdentityChangeEventSenderTest.java - Copyright © 2013–2019 David Roden + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.pterodactylus.sone.freenet.wot + +import com.google.common.eventbus.* +import net.pterodactylus.sone.freenet.wot.event.* +import net.pterodactylus.sone.test.* +import org.junit.* +import org.mockito.ArgumentMatchers.eq +import org.mockito.Mockito.verify + +/** + * Unit test for [IdentityChangeEventSender]. + */ +class IdentityChangeEventSenderTest { + + private val eventBus = mock() + private val ownIdentities = listOf( + createOwnIdentity("O1", setOf("Test"), "KeyA" to "ValueA"), + createOwnIdentity("O2", setOf("Test2"), "KeyB" to "ValueB"), + createOwnIdentity("O3", setOf("Test3"), "KeyC" to "ValueC") + ) + private val identities = listOf( + createIdentity("I1", setOf()), + createIdentity("I2", setOf()), + createIdentity("I3", setOf()), + createIdentity("I2", setOf("Test")) + ) + private val identityChangeEventSender = IdentityChangeEventSender(eventBus, createOldIdentities()) + + @Test + fun addingAnOwnIdentityIsDetectedAndReportedCorrectly() { + val newIdentities = createNewIdentities() + identityChangeEventSender.detectChanges(newIdentities) + verify(eventBus).post(eq(OwnIdentityRemovedEvent(ownIdentities[0]))) + verify(eventBus).post(eq(IdentityRemovedEvent(ownIdentities[0], identities[0]))) + verify(eventBus).post(eq(IdentityRemovedEvent(ownIdentities[0], identities[1]))) + verify(eventBus).post(eq(OwnIdentityAddedEvent(ownIdentities[2]))) + verify(eventBus).post(eq(IdentityAddedEvent(ownIdentities[2], identities[1]))) + verify(eventBus).post(eq(IdentityAddedEvent(ownIdentities[2], identities[2]))) + verify(eventBus).post(eq(IdentityRemovedEvent(ownIdentities[1], identities[0]))) + verify(eventBus).post(eq(IdentityAddedEvent(ownIdentities[1], identities[2]))) + verify(eventBus).post(eq(IdentityUpdatedEvent(ownIdentities[1], identities[1]))) + } + + private fun createNewIdentities() = mapOf( + ownIdentities[1] to listOf(identities[3], identities[2]), + ownIdentities[2] to listOf(identities[1], identities[2]) + ) + + private fun createOldIdentities() = mapOf( + ownIdentities[0] to listOf(identities[0], identities[1]), + ownIdentities[1] to listOf(identities[0], identities[1]) + ) + +} diff --git a/src/test/kotlin/net/pterodactylus/sone/freenet/wot/IdentityLoaderTest.kt b/src/test/kotlin/net/pterodactylus/sone/freenet/wot/IdentityLoaderTest.kt new file mode 100644 index 0000000..780288d --- /dev/null +++ b/src/test/kotlin/net/pterodactylus/sone/freenet/wot/IdentityLoaderTest.kt @@ -0,0 +1,118 @@ +/* + * Sone - IdentityLoaderTest.java - Copyright © 2013–2019 David Roden + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.pterodactylus.sone.freenet.wot + +import org.hamcrest.MatcherAssert.* +import org.hamcrest.Matchers.* +import org.junit.* + +/** + * Unit test for [IdentityLoader]. + */ +class IdentityLoaderTest { + + private val ownIdentities = createOwnIdentities() + private val webOfTrustConnector = object : TestWebOfTrustConnector() { + override fun loadAllOwnIdentities() = ownIdentities.toSet() + override fun loadTrustedIdentities(ownIdentity: OwnIdentity, context: String?) = + when (ownIdentity) { + ownIdentities[0] -> createTrustedIdentitiesForFirstOwnIdentity() + ownIdentities[1] -> createTrustedIdentitiesForSecondOwnIdentity() + ownIdentities[2] -> createTrustedIdentitiesForThirdOwnIdentity() + ownIdentities[3] -> createTrustedIdentitiesForFourthOwnIdentity() + else -> throw RuntimeException() + } + } + + @Test + fun loadingIdentities() { + val identityLoader = IdentityLoader(webOfTrustConnector, Context("Test")) + val identities = identityLoader.loadIdentities() + assertThat(identities.keys, hasSize(4)) + assertThat(identities.keys, containsInAnyOrder(ownIdentities[0], ownIdentities[1], ownIdentities[2], ownIdentities[3])) + verifyIdentitiesForOwnIdentity(identities, ownIdentities[0], createTrustedIdentitiesForFirstOwnIdentity()) + verifyIdentitiesForOwnIdentity(identities, ownIdentities[1], createTrustedIdentitiesForSecondOwnIdentity()) + verifyIdentitiesForOwnIdentity(identities, ownIdentities[2], emptySet()) + verifyIdentitiesForOwnIdentity(identities, ownIdentities[3], createTrustedIdentitiesForFourthOwnIdentity()) + } + + @Test + fun loadingIdentitiesWithoutContext() { + val identityLoaderWithoutContext = IdentityLoader(webOfTrustConnector) + val identities = identityLoaderWithoutContext.loadIdentities() + assertThat(identities.keys, hasSize(4)) + assertThat(identities.keys, containsInAnyOrder(ownIdentities[0], ownIdentities[1], ownIdentities[2], ownIdentities[3])) + verifyIdentitiesForOwnIdentity(identities, ownIdentities[0], createTrustedIdentitiesForFirstOwnIdentity()) + verifyIdentitiesForOwnIdentity(identities, ownIdentities[1], createTrustedIdentitiesForSecondOwnIdentity()) + verifyIdentitiesForOwnIdentity(identities, ownIdentities[2], createTrustedIdentitiesForThirdOwnIdentity()) + verifyIdentitiesForOwnIdentity(identities, ownIdentities[3], createTrustedIdentitiesForFourthOwnIdentity()) + } + + private fun verifyIdentitiesForOwnIdentity(identities: Map>, ownIdentity: OwnIdentity, trustedIdentities: Set) { + assertThat(identities[ownIdentity], equalTo>(trustedIdentities)) + } + +} + +private fun createOwnIdentities() = listOf( + createOwnIdentity("O1", "ON1", "OR1", "OI1", setOf("Test", "Test2"), mapOf("KeyA" to "ValueA", "KeyB" to "ValueB")), + createOwnIdentity("O2", "ON2", "OR2", "OI2", setOf("Test"), mapOf("KeyC" to "ValueC")), + createOwnIdentity("O3", "ON3", "OR3", "OI3", setOf("Test2"), mapOf("KeyE" to "ValueE", "KeyD" to "ValueD")), + createOwnIdentity("O4", "ON4", "OR$", "OI4", setOf("Test"), mapOf("KeyA" to "ValueA", "KeyD" to "ValueD")) +) + +private fun createTrustedIdentitiesForFirstOwnIdentity() = setOf( + createIdentity("I11", "IN11", "IR11", setOf("Test"), mapOf("KeyA" to "ValueA")) +) + +private fun createTrustedIdentitiesForSecondOwnIdentity() = setOf( + createIdentity("I21", "IN21", "IR21", setOf("Test", "Test2"), mapOf("KeyB" to "ValueB")) +) + +private fun createTrustedIdentitiesForThirdOwnIdentity() = setOf( + createIdentity("I31", "IN31", "IR31", setOf("Test", "Test3"), mapOf("KeyC" to "ValueC")) +) + +private fun createTrustedIdentitiesForFourthOwnIdentity(): Set = emptySet() + +private fun createOwnIdentity(id: String, nickname: String, requestUri: String, insertUri: String, contexts: Set, properties: Map): OwnIdentity = + DefaultOwnIdentity(id, nickname, requestUri, insertUri).apply { + setContexts(contexts) + this.properties = properties + } + +private fun createIdentity(id: String, nickname: String, requestUri: String, contexts: Set, properties: Map): Identity = + DefaultIdentity(id, nickname, requestUri).apply { + setContexts(contexts) + this.properties = properties + } + +private open class TestWebOfTrustConnector : WebOfTrustConnector { + + override fun loadAllOwnIdentities() = emptySet() + override fun loadTrustedIdentities(ownIdentity: OwnIdentity, context: String?) = emptySet() + override fun addContext(ownIdentity: OwnIdentity, context: String) = Unit + override fun removeContext(ownIdentity: OwnIdentity, context: String) = Unit + override fun setProperty(ownIdentity: OwnIdentity, name: String, value: String) = Unit + override fun removeProperty(ownIdentity: OwnIdentity, name: String) = Unit + override fun getTrust(ownIdentity: OwnIdentity, identity: Identity) = Trust(null, null, null) + override fun setTrust(ownIdentity: OwnIdentity, identity: Identity, trust: Int, comment: String) = Unit + override fun removeTrust(ownIdentity: OwnIdentity, identity: Identity) = Unit + override fun ping() = Unit + +} diff --git a/src/test/kotlin/net/pterodactylus/sone/freenet/wot/PluginWebOfTrustConnectorTest.kt b/src/test/kotlin/net/pterodactylus/sone/freenet/wot/PluginWebOfTrustConnectorTest.kt new file mode 100644 index 0000000..1af177f --- /dev/null +++ b/src/test/kotlin/net/pterodactylus/sone/freenet/wot/PluginWebOfTrustConnectorTest.kt @@ -0,0 +1,331 @@ +package net.pterodactylus.sone.freenet.wot + +import freenet.support.* +import freenet.support.api.* +import net.pterodactylus.sone.freenet.* +import net.pterodactylus.sone.freenet.plugin.* +import net.pterodactylus.sone.test.* +import org.hamcrest.* +import org.hamcrest.MatcherAssert.* +import org.hamcrest.Matchers.* +import org.hamcrest.core.* +import kotlin.test.* + +/** + * Unit test for [PluginWebOfTrustConnector]. + */ +class PluginWebOfTrustConnectorTest { + + private val ownIdentity = DefaultOwnIdentity("id", "nickname", "requestUri", "insertUri") + private val identity = DefaultIdentity("id-a", "alpha", "url://alpha") + + @Test + fun `wot plugin can be pinged`() { + createPluginConnector("Ping") + .connect { ping() } + } + + @Test + fun `own identities are returned correctly`() { + val ownIdentities = createPluginConnector("GetOwnIdentities") { + put("Identity0", "id-0") + put("RequestURI0", "request-uri-0") + put("InsertURI0", "insert-uri-0") + put("Nickname0", "nickname-0") + put("Contexts0.Context0", "id-0-context-0") + put("Properties0.Property0.Name", "id-0-property-0-name") + put("Properties0.Property0.Value", "id-0-property-0-value") + put("Identity1", "id-1") + put("RequestURI1", "request-uri-1") + put("InsertURI1", "insert-uri-1") + put("Nickname1", "nickname-1") + put("Contexts1.Context0", "id-1-context-0") + put("Properties1.Property0.Name", "id-1-property-0-name") + put("Properties1.Property0.Value", "id-1-property-0-value") + }.connect { loadAllOwnIdentities() } + assertThat(ownIdentities, containsInAnyOrder( + isOwnIdentity("id-0", "nickname-0", "request-uri-0", "insert-uri-0", contains("id-0-context-0"), hasEntry("id-0-property-0-name", "id-0-property-0-value")), + isOwnIdentity("id-1", "nickname-1", "request-uri-1", "insert-uri-1", contains("id-1-context-0"), hasEntry("id-1-property-0-name", "id-1-property-0-value")) + )) + } + + @Test + fun `trusted identities are requested with correct own identity`() { + createPluginConnector("GetIdentitiesByScore", hasField("Truster", equalTo("id"))) + .connect { loadTrustedIdentities(ownIdentity) } + } + + @Test + fun `trusted identities are requested with correct selection parameter`() { + createPluginConnector("GetIdentitiesByScore", hasField("Selection", equalTo("+"))) + .connect { loadTrustedIdentities(ownIdentity) } + } + + @Test + fun `trusted identities are requested with empty context if null context requested`() { + createPluginConnector("GetIdentitiesByScore", hasField("Context", equalTo(""))) + .connect { loadTrustedIdentities(ownIdentity) } + } + + @Test + fun `trusted identities are requested with context if context requested`() { + createPluginConnector("GetIdentitiesByScore", hasField("Context", equalTo("TestContext"))) + .connect { loadTrustedIdentities(ownIdentity, "TestContext") } + } + + @Test + fun `trusted identities are requested with trust values`() { + createPluginConnector("GetIdentitiesByScore", hasField("WantTrustValues", equalTo("true"))) + .connect { loadTrustedIdentities(ownIdentity) } + } + + @Test + fun `empty list of trusted identities is returned correctly`() { + val trustedIdentities = createPluginConnector("GetIdentitiesByScore") + .connect { loadTrustedIdentities(ownIdentity) } + assertThat(trustedIdentities, empty()) + } + + @Test + fun `trusted identities without context, properties, or trust value are returned correctly`() { + val trustedIdentities = createPluginConnector("GetIdentitiesByScore") { + put("Identity0", "id0") + put("Nickname0", "nickname0") + put("RequestURI0", "request-uri0") + put("Identity1", "id1") + put("Nickname1", "nickname1") + put("RequestURI1", "request-uri1") + }.connect { loadTrustedIdentities(ownIdentity) } + assertThat(trustedIdentities, contains( + allOf( + isIdentity("id0", "nickname0", "request-uri0", empty(), isEmptyMap()), + isTrusted(ownIdentity, isTrust(null, null, null)) + ), + allOf( + isIdentity("id1", "nickname1", "request-uri1", empty(), isEmptyMap()), + isTrusted(ownIdentity, isTrust(null, null, null)) + ) + )) + } + + @Test + fun `trusted identity without nickname is returned correctly`() { + val trustedIdentities = createPluginConnector("GetIdentitiesByScore") { + put("Identity0", "id0") + put("RequestURI0", "request-uri0") + }.connect { loadTrustedIdentities(ownIdentity) } + assertThat(trustedIdentities, contains( + allOf( + isIdentity("id0", null, "request-uri0", empty(), isEmptyMap()), + isTrusted(ownIdentity, isTrust(null, null, null)) + ) + )) + } + + @Test + fun `trusted identity with contexts is returned correctly`() { + val trustedIdentities = createPluginConnector("GetIdentitiesByScore") { + put("Identity0", "id0") + put("Nickname0", "nickname0") + put("RequestURI0", "request-uri0") + put("Contexts0.Context0", "Context0") + put("Contexts0.Context1", "Context1") + }.connect { loadTrustedIdentities(ownIdentity) } + assertThat(trustedIdentities, contains( + isIdentity("id0", "nickname0", "request-uri0", containsInAnyOrder("Context0", "Context1"), isEmptyMap()) + )) + } + + @Test + fun `trusted identity with properties is returned correctly`() { + val trustedIdentities = createPluginConnector("GetIdentitiesByScore") { + put("Identity0", "id0") + put("Nickname0", "nickname0") + put("RequestURI0", "request-uri0") + put("Properties0.Property0.Name", "foo") + put("Properties0.Property0.Value", "bar") + put("Properties0.Property1.Name", "baz") + put("Properties0.Property1.Value", "quo") + }.connect { loadTrustedIdentities(ownIdentity) } + assertThat(trustedIdentities, contains( + isIdentity("id0", "nickname0", "request-uri0", empty(), allOf(hasEntry("foo", "bar"), hasEntry("baz", "quo"))) + )) + } + + @Test + fun `trusted identity with trust value is returned correctly`() { + val trustedIdentities = createPluginConnector("GetIdentitiesByScore") { + put("Identity0", "id0") + put("Nickname0", "nickname0") + put("RequestURI0", "request-uri0") + put("Trust0", "12") + put("Score0", "34") + put("Rank0", "56") + }.connect { loadTrustedIdentities(ownIdentity) } + assertThat(trustedIdentities, contains( + allOf( + isIdentity("id0", "nickname0", "request-uri0", empty(), isEmptyMap()), + isTrusted(ownIdentity, isTrust(12, 34, 56)) + ) + )) + } + + @Test + fun `adding a context sends the correct own identity id`() { + createPluginConnector("AddContext", hasField("Identity", equalTo(ownIdentity.id))) + .connect { addContext(ownIdentity, "TestContext") } + } + + @Test + fun `adding a context sends the correct context`() { + createPluginConnector("AddContext", hasField("Context", equalTo("TestContext"))) + .connect { addContext(ownIdentity, "TestContext") } + } + + @Test + fun `removing a context sends the correct own identity id`() { + createPluginConnector("RemoveContext", hasField("Identity", equalTo(ownIdentity.id))) + .connect { removeContext(ownIdentity, "TestContext") } + } + + @Test + fun `removing a context sends the correct context`() { + createPluginConnector("RemoveContext", hasField("Context", equalTo("TestContext"))) + .connect { removeContext(ownIdentity, "TestContext") } + } + + @Test + fun `setting a property sends the correct identity id`() { + createPluginConnector("SetProperty", hasField("Identity", equalTo(ownIdentity.id))) + .connect { setProperty(ownIdentity, "TestProperty", "TestValue") } + } + + @Test + fun `setting a property sends the correct property name`() { + createPluginConnector("SetProperty", hasField("Property", equalTo("TestProperty"))) + .connect { setProperty(ownIdentity, "TestProperty", "TestValue") } + } + + @Test + fun `setting a property sends the correct property value`() { + createPluginConnector("SetProperty", hasField("Value", equalTo("TestValue"))) + .connect { setProperty(ownIdentity, "TestProperty", "TestValue") } + } + + @Test + fun `removing a property sends the correct identity id`() { + createPluginConnector("RemoveProperty", hasField("Identity", equalTo(ownIdentity.id))) + .connect { removeProperty(ownIdentity, "TestProperty") } + } + + @Test + fun `removing a property sends the correct property name`() { + createPluginConnector("RemoveProperty", hasField("Property", equalTo("TestProperty"))) + .connect { removeProperty(ownIdentity, "TestProperty") } + } + + @Test + fun `getting trust sends correct own identity id`() { + createPluginConnector("GetIdentity", hasField("Truster", equalTo(ownIdentity.id))) + .connect { getTrust(ownIdentity, identity) } + } + + @Test + fun `getting trust sends correct identity id`() { + createPluginConnector("GetIdentity", hasField("Identity", equalTo(identity.id))) + .connect { getTrust(ownIdentity, identity) } + } + + @Test + fun `getting trust returns correct trust values`() { + val trust = createPluginConnector("GetIdentity", hasField("Identity", equalTo(identity.id))) { + put("Trust", "12") + put("Score", "34") + put("Rank", "56") + }.connect { getTrust(ownIdentity, identity) } + assertThat(trust, isTrust(12, 34, 56)) + } + + @Test + fun `getting trust reads incorrect numbers for trust as null`() { + val trust = createPluginConnector("GetIdentity", hasField("Identity", equalTo(identity.id))) { + put("Trust", "incorrect") + put("Score", "34") + put("Rank", "56") + }.connect { getTrust(ownIdentity, identity) } + assertThat(trust, isTrust(null, 34, 56)) + } + + @Test + fun `getting trust reads incorrect numbers for score as null`() { + val trust = createPluginConnector("GetIdentity", hasField("Identity", equalTo(identity.id))) { + put("Trust", "12") + put("Score", "incorrect") + put("Rank", "56") + }.connect { getTrust(ownIdentity, identity) } + assertThat(trust, isTrust(12, null, 56)) + } + + @Test + fun `getting trust reads incorrect numbers for rank as null`() { + val trust = createPluginConnector("GetIdentity", hasField("Identity", equalTo(identity.id))) { + put("Trust", "12") + put("Score", "34") + put("Rank", "incorrect") + }.connect { getTrust(ownIdentity, identity) } + assertThat(trust, isTrust(12, 34, null)) + } + + @Test + fun `setting trust sends correct own identity id`() { + createPluginConnector("SetTrust", hasField("Truster", equalTo(ownIdentity.id))) + .connect { setTrust(ownIdentity, identity, 123, "Test Trust") } + } + + @Test + fun `setting trust sends correct identity id`() { + createPluginConnector("SetTrust", hasField("Trustee", equalTo(identity.id))) + .connect { setTrust(ownIdentity, identity, 123, "Test Trust") } + } + + @Test + fun `setting trust sends correct trust value`() { + createPluginConnector("SetTrust", hasField("Value", equalTo("123"))) + .connect { setTrust(ownIdentity, identity, 123, "Test Trust") } + } + + @Test + fun `setting trust sends correct comment`() { + createPluginConnector("SetTrust", hasField("Comment", equalTo("Test Trust"))) + .connect { setTrust(ownIdentity, identity, 123, "Test Trust") } + } + + @Test + fun `removing trust sends correct own identity id`() { + createPluginConnector("RemoveTrust", hasField("Truster", equalTo(ownIdentity.id))) + .connect { removeTrust(ownIdentity, identity) } + } + + @Test + fun `removing trust sends correct identity id`() { + createPluginConnector("RemoveTrust", hasField("Trustee", equalTo(identity.id))) + .connect { removeTrust(ownIdentity, identity) } + } + +} + +private fun PluginConnector.connect(block: PluginWebOfTrustConnector.() -> R) = + PluginWebOfTrustConnector(this).let(block) + +fun createPluginConnector(message: String, fieldsMatcher: Matcher = IsAnything(), build: SimpleFieldSetBuilder.() -> Unit = {}) = + object : PluginConnector { + override suspend fun sendRequest(pluginName: String, fields: SimpleFieldSet, data: Bucket?) = + if ((pluginName != wotPluginName) || (fields.get("Message") != message)) { + throw PluginException() + } else { + assertThat(fields, fieldsMatcher) + PluginReply(SimpleFieldSetBuilder().apply(build).get(), null) + } + } + +private const val wotPluginName = "plugins.WebOfTrust.WebOfTrust" diff --git a/src/test/kotlin/net/pterodactylus/sone/main/FreenetModuleTest.kt b/src/test/kotlin/net/pterodactylus/sone/main/FreenetModuleTest.kt index 2f55d52..e94e34e 100644 --- a/src/test/kotlin/net/pterodactylus/sone/main/FreenetModuleTest.kt +++ b/src/test/kotlin/net/pterodactylus/sone/main/FreenetModuleTest.kt @@ -5,10 +5,13 @@ import freenet.client.* import freenet.clients.http.* import freenet.node.* import freenet.pluginmanager.* +import net.pterodactylus.sone.freenet.plugin.* import net.pterodactylus.sone.test.* import org.hamcrest.MatcherAssert.* import org.hamcrest.Matchers.* import org.junit.* +import org.junit.rules.* +import org.mockito.* import org.mockito.Mockito.* /** @@ -16,6 +19,10 @@ import org.mockito.Mockito.* */ class FreenetModuleTest { + @Rule + @JvmField + val expectedException = ExpectedException.none()!! + private val sessionManager = mock() private val pluginRespirator = deepMock().apply { whenever(getSessionManager("Sone")).thenReturn(sessionManager) @@ -34,13 +41,9 @@ class FreenetModuleTest { } @Test - fun `plugin respirator is returned correctly`() { - assertThat(injector.getInstance(), sameInstance(pluginRespirator)) - } - - @Test - fun `plugin respirator is returned as singleton`() { - verifySingletonInstance() + fun `plugin respirator is not bound`() { + expectedException.expect(Exception::class.java) + injector.getInstance() } @Test @@ -90,8 +93,30 @@ class FreenetModuleTest { } @Test - fun `page maker is returned as singleten`() { + fun `page maker is returned as singleton`() { verifySingletonInstance() } + @Test + fun `plugin respirator facade is returned correctly`() { + val pluginRespiratorFacade = injector.getInstance() + pluginRespiratorFacade.getPluginTalker(mock(), "test.plugin", "test-request-1") + verify(pluginRespirator).getPluginTalker(any(), ArgumentMatchers.eq("test.plugin"), ArgumentMatchers.eq("test-request-1")) + } + + @Test + fun `plugin respirator facade is returned as singleton`() { + verifySingletonInstance() + } + + @Test + fun `plugin connector is returned correctly`() { + assertThat(injector.getInstance(), notNullValue()) + } + + @Test + fun `plugin connector facade is returned as singleton`() { + verifySingletonInstance() + } + } diff --git a/src/test/kotlin/net/pterodactylus/sone/main/SoneModuleTest.kt b/src/test/kotlin/net/pterodactylus/sone/main/SoneModuleTest.kt index 31b232d..e95955c 100644 --- a/src/test/kotlin/net/pterodactylus/sone/main/SoneModuleTest.kt +++ b/src/test/kotlin/net/pterodactylus/sone/main/SoneModuleTest.kt @@ -10,6 +10,8 @@ import freenet.pluginmanager.* import net.pterodactylus.sone.core.* import net.pterodactylus.sone.database.* import net.pterodactylus.sone.database.memory.* +import net.pterodactylus.sone.freenet.* +import net.pterodactylus.sone.freenet.plugin.* import net.pterodactylus.sone.freenet.wot.* import net.pterodactylus.sone.test.* import net.pterodactylus.util.config.* @@ -41,7 +43,8 @@ class SoneModuleTest { createInjector( SoneModule(sonePlugin, EventBus()), FreenetInterface::class.isProvidedByDeepMock(), - PluginRespirator::class.isProvidedByDeepMock() + PluginRespiratorFacade::class.isProvidedByDeepMock(), + PluginConnector::class.isProvidedByDeepMock() ) } @@ -147,8 +150,8 @@ class SoneModuleTest { } @Test - fun `base l10n is bound correctly`() { - assertThat(injector.getInstance(), sameInstance(l10n.base)) + fun `translation is bound correctly`() { + assertThat(injector.getInstance(), notNullValue()) } @Test @@ -198,7 +201,8 @@ class SoneModuleTest { val injector = createInjector( SoneModule(sonePlugin, eventBus), FreenetInterface::class.isProvidedByDeepMock(), - PluginRespirator::class.isProvidedByDeepMock() + PluginRespiratorFacade::class.isProvidedByDeepMock(), + PluginConnector::class.isProvidedByDeepMock() ) val core = injector.getInstance() verify(eventBus).register(core) @@ -216,4 +220,16 @@ class SoneModuleTest { assertThat(firstMetricRegistry, sameInstance(secondMetricRegistry)) } + @Test + fun `wot connector can be created`() { + assertThat(injector.getInstance(), notNullValue()) + } + + @Test + fun `wot connector is created as singleton`() { + val firstWebOfTrustConnector = injector.getInstance() + val secondWebOfTrustConnector = injector.getInstance() + assertThat(firstWebOfTrustConnector, sameInstance(secondWebOfTrustConnector)) + } + } diff --git a/src/test/kotlin/net/pterodactylus/sone/notify/ListNotificationTest.kt b/src/test/kotlin/net/pterodactylus/sone/notify/ListNotificationTest.kt new file mode 100644 index 0000000..4c82035 --- /dev/null +++ b/src/test/kotlin/net/pterodactylus/sone/notify/ListNotificationTest.kt @@ -0,0 +1,125 @@ +package net.pterodactylus.sone.notify + +import net.pterodactylus.util.notify.* +import net.pterodactylus.util.template.* +import org.hamcrest.MatcherAssert.* +import org.hamcrest.Matchers.* +import org.junit.* +import java.util.concurrent.atomic.* + +/** + * Unit test for [ListNotification]. + */ +class ListNotificationTest { + + private val template = Template() + private val listNotification = ListNotification(ID, KEY, template) + + @Test + fun `creating a list notification sets empty iterable on element key in template context`() { + assertThat(template.initialContext.get(KEY) as Iterable<*>, emptyIterable()) + } + + @Test + @Suppress("UNCHECKED_CAST") + fun `list in template context gets updated when elements are added`() { + listNotification.add("a") + listNotification.add("b") + assertThat(template.initialContext.get(KEY) as Iterable, contains("a", "b")) + } + + @Test + fun `new list notification has no element`() { + assertThat(listNotification.elements, emptyIterable()) + } + + @Test + fun `new list notification is empty`() { + assertThat(listNotification.isEmpty, equalTo(true)) + } + + @Test + fun `list notification retains set elements`() { + listNotification.setElements(listOf("a", "b", "c")) + assertThat(listNotification.elements, contains("a", "b", "c")) + } + + @Test + fun `list notification deduplicates set elements`() { + listNotification.setElements(listOf("a", "b", "a")) + assertThat(listNotification.elements, contains("a", "b")) + } + + @Test + fun `list notification retains added elements`() { + listNotification.add("a") + listNotification.add("b") + listNotification.add("c") + assertThat(listNotification.elements, contains("a", "b", "c")) + } + + @Test + fun `list notification deduplicates elements`() { + listNotification.add("a") + listNotification.add("b") + listNotification.add("a") + assertThat(listNotification.elements, contains("a", "b")) + } + + @Test + fun `list notification removes correct element`() { + listNotification.setElements(listOf("a", "b", "c")) + listNotification.remove("b") + assertThat(listNotification.elements, contains("a", "c")) + } + + @Test + fun `removing the last element dismisses the notification`() { + val notificationDismissed = AtomicBoolean() + val notificationListener = NotificationListener { notificationDismissed.set(it == listNotification) } + listNotification.addNotificationListener(notificationListener) + listNotification.add("a") + listNotification.remove("a") + assertThat(notificationDismissed.get(), equalTo(true)) + } + + @Test + fun `dismissing the list notification removes all elements`() { + listNotification.setElements(listOf("a", "b", "c")) + listNotification.dismiss() + assertThat(listNotification.elements, emptyIterable()) + } + + @Test + fun `list notification with different elements is not equal`() { + val secondNotification = ListNotification(ID, KEY, template) + listNotification.add("a") + secondNotification.add("b") + assertThat(listNotification, not(equalTo(secondNotification))) + } + + @Test + fun `list notification with different key is not equal`() { + val secondNotification = ListNotification(ID, OTHER_KEY, template) + assertThat(listNotification, not(equalTo(secondNotification))) + } + + @Test + fun `copied notifications have the same hash code`() { + val secondNotification = ListNotification(listNotification) + listNotification.add("a") + secondNotification.add("a") + listNotification.setLastUpdateTime(secondNotification.lastUpdatedTime) + assertThat(listNotification.hashCode(), equalTo(secondNotification.hashCode())) + } + + @Test + fun `list notification is not equal to other objects`() { + assertThat(listNotification, not(equalTo(Any()))) + } + +} + +private const val ID = "notification-id" +private const val KEY = "element-key" +private const val OTHER_KEY = "other-key" diff --git a/src/test/kotlin/net/pterodactylus/sone/template/HistogramRendererTest.kt b/src/test/kotlin/net/pterodactylus/sone/template/HistogramRendererTest.kt new file mode 100644 index 0000000..cc450d6 --- /dev/null +++ b/src/test/kotlin/net/pterodactylus/sone/template/HistogramRendererTest.kt @@ -0,0 +1,204 @@ +/** + * Sone - HistogramRendererTest.kt - Copyright © 2019 David ‘Bombe’ Roden + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.pterodactylus.sone.template + +import com.codahale.metrics.* +import net.pterodactylus.sone.freenet.* +import net.pterodactylus.util.template.* +import org.hamcrest.MatcherAssert.* +import org.hamcrest.Matchers.* +import org.jsoup.* +import org.jsoup.nodes.* +import org.junit.* +import java.util.* + +/** + * Unit test for [HistogramRenderer]. + */ +class HistogramRendererTest { + + private val translation = object : Translation { + override val currentLocale = Locale.ENGLISH + override fun translate(key: String) = "Metric Name".takeIf { key == "Page.Metrics.TestHistogram.Title" } ?: "" + } + private val metricRenderer = HistogramRenderer() + private val templateContext = TemplateContext().apply { + addFilter("html", HtmlFilter()) + addFilter("duration", DurationFormatFilter()) + addFilter("l10n", L10nFilter(translation)) + } + + @Test + fun `histogram is rendered as table row`() { + createAndVerifyTableRow { + assertThat(it.nodeName(), equalTo("tr")) + } + } + + @Test + fun `histogram has eleven columns`() { + createAndVerifyTableRow { + assertThat(it.getElementsByTag("td"), hasSize(11)) + } + } + + @Test + fun `first column contains translated metric name`() { + createAndVerifyTableRow(mapOf("name" to "test.histogram")) { + assertThat(it.getElementsByTag("td")[0].text(), equalTo("Metric Name")) + } + } + + @Test + fun `second column is numeric`() { + verifyColumnIsNumeric(1) + } + + @Test + fun `second column contains count`() { + createAndVerifyTableRow { + assertThat(it.getElementsByTag("td")[1].text(), equalTo("2001")) + } + } + + @Test + fun `third column is numeric`() { + verifyColumnIsNumeric(2) + } + + @Test + fun `third column contains min value`() { + createAndVerifyTableRow { + assertThat(it.getElementsByTag("td")[2].text(), equalTo("2.0ms")) + } + } + + @Test + fun `fourth column is numeric`() { + verifyColumnIsNumeric(3) + } + + @Test + fun `fourth column contains max value`() { + createAndVerifyTableRow { + assertThat(it.getElementsByTag("td")[3].text(), equalTo("998.0ms")) + } + } + + @Test + fun `fifth column is numeric`() { + verifyColumnIsNumeric(4) + } + + @Test + fun `fifth column contains mean value`() { + createAndVerifyTableRow { + assertThat(it.getElementsByTag("td")[4].text(), equalTo("492.7ms")) + } + } + + @Test + fun `sixth column is numeric`() { + verifyColumnIsNumeric(5) + } + + @Test + fun `sixth column contains median value`() { + createAndVerifyTableRow { + assertThat(it.getElementsByTag("td")[5].text(), equalTo("483.6ms")) + } + } + + @Test + fun `seventh column is numeric`() { + verifyColumnIsNumeric(6) + } + + @Test + fun `seventh column contains 75th percentile`() { + createAndVerifyTableRow { + assertThat(it.getElementsByTag("td")[6].text(), equalTo("740.9ms")) + } + } + + @Test + fun `eighth column is numeric`() { + verifyColumnIsNumeric(7) + } + + @Test + fun `eighth column contains 95th percentile`() { + createAndVerifyTableRow { + assertThat(it.getElementsByTag("td")[7].text(), equalTo("940.9ms")) + } + } + + @Test + fun `ninth column is numeric`() { + verifyColumnIsNumeric(8) + } + + @Test + fun `ninth column contains 98th percentile`() { + createAndVerifyTableRow { + assertThat(it.getElementsByTag("td")[8].text(), equalTo("975.6ms")) + } + } + + @Test + fun `tenth column is numeric`() { + verifyColumnIsNumeric(9) + } + + @Test + fun `tenth column contains 99th percentile`() { + createAndVerifyTableRow { + assertThat(it.getElementsByTag("td")[9].text(), equalTo("991.6ms")) + } + } + + @Test + fun `eleventh column is numeric`() { + verifyColumnIsNumeric(10) + } + + @Test + fun `eleventh column contains 99,9th percentile`() { + createAndVerifyTableRow { + assertThat(it.getElementsByTag("td")[10].text(), equalTo("998.0ms")) + } + } + + private fun createAndVerifyTableRow(parameters: Map? = null, verify: (Element) -> Unit) = + metricRenderer.format(templateContext, histogram, parameters) + .let { "$it
" } + .let(Jsoup::parseBodyFragment) + .getElementById("t").child(0).child(0) + .let(verify) + + private fun verifyColumnIsNumeric(column: Int) = + createAndVerifyTableRow { + assertThat(it.getElementsByTag("td")[column].classNames(), hasItem("numeric")) + } + +} + +private val random = Random(1) +private val histogram = MetricRegistry().histogram("test.histogram") { Histogram(SlidingWindowReservoir(1028)) }.apply { + (0..2000).map { random.nextInt(1_000_000) }.forEach(this::update) +} diff --git a/src/test/kotlin/net/pterodactylus/sone/template/LinkedElementsFilterTest.kt b/src/test/kotlin/net/pterodactylus/sone/template/LinkedElementsFilterTest.kt index 1e83b85..32e8587 100644 --- a/src/test/kotlin/net/pterodactylus/sone/template/LinkedElementsFilterTest.kt +++ b/src/test/kotlin/net/pterodactylus/sone/template/LinkedElementsFilterTest.kt @@ -10,7 +10,7 @@ import net.pterodactylus.sone.data.SoneOptions.LoadExternalContent.MANUALLY_TRUS import net.pterodactylus.sone.data.SoneOptions.LoadExternalContent.TRUSTED import net.pterodactylus.sone.freenet.wot.OwnIdentity import net.pterodactylus.sone.freenet.wot.Trust -import net.pterodactylus.sone.test.mock +import net.pterodactylus.sone.test.* import net.pterodactylus.sone.text.FreenetLinkPart import net.pterodactylus.sone.text.LinkPart import net.pterodactylus.sone.text.Part @@ -21,7 +21,6 @@ import org.hamcrest.Matchers.contains import org.hamcrest.Matchers.emptyIterable import org.junit.Before import org.junit.Test -import org.mockito.Mockito.`when` /** * Unit test for [LinkedElementsFilter]. @@ -44,14 +43,14 @@ class LinkedElementsFilterTest { @Before fun setupSone() { - `when`(sone.options).thenReturn(DefaultSoneOptions()) + whenever(sone.options).thenReturn(DefaultSoneOptions()) } @Before fun setupImageLoader() { - `when`(imageLoader.loadElement("KSK@link")).thenReturn(LinkedElement("KSK@link", failed = true)) - `when`(imageLoader.loadElement("KSK@loading.png")).thenReturn(LinkedElement("KSK@loading.png", loading = true)) - `when`(imageLoader.loadElement("KSK@link.png")).thenReturn(LinkedElement("KSK@link.png")) + whenever(imageLoader.loadElement("KSK@link")).thenReturn(LinkedElement("KSK@link", failed = true)) + whenever(imageLoader.loadElement("KSK@loading.png")).thenReturn(LinkedElement("KSK@loading.png", loading = true)) + whenever(imageLoader.loadElement("KSK@link.png")).thenReturn(LinkedElement("KSK@link.png")) } @Test @@ -89,7 +88,7 @@ class LinkedElementsFilterTest { fun `filter finds images if the remote sone is local`() { sone.options.loadLinkedImages = MANUALLY_TRUSTED templateContext.set("currentSone", sone) - `when`(remoteSone.isLocal).thenReturn(true) + whenever(remoteSone.isLocal).thenReturn(true) parameters["sone"] = remoteSone verifyThatImagesArePresent() } @@ -106,7 +105,7 @@ class LinkedElementsFilterTest { fun `filter does not find images if local sone requires manual trust and remote sone has only implicit trust`() { sone.options.loadLinkedImages = MANUALLY_TRUSTED templateContext.set("currentSone", sone) - `when`(remoteSone.identity.getTrust(this.sone.identity as OwnIdentity)).thenReturn(Trust(null, 100, null)) + whenever(remoteSone.identity.getTrust(this.sone.identity as OwnIdentity)).thenReturn(Trust(null, 100, null)) parameters["sone"] = remoteSone verifyThatImagesAreNotPresent() } @@ -115,7 +114,7 @@ class LinkedElementsFilterTest { fun `filter does not find images if local sone requires manual trust and remote sone has explicit trust of zero`() { sone.options.loadLinkedImages = MANUALLY_TRUSTED templateContext.set("currentSone", sone) - `when`(remoteSone.identity.getTrust(this.sone.identity as OwnIdentity)).thenReturn(Trust(0, null, null)) + whenever(remoteSone.identity.getTrust(this.sone.identity as OwnIdentity)).thenReturn(Trust(0, null, null)) parameters["sone"] = remoteSone verifyThatImagesAreNotPresent() } @@ -124,7 +123,7 @@ class LinkedElementsFilterTest { fun `filter finds images if local sone requires manual trust and remote sone has explicit trust of one`() { sone.options.loadLinkedImages = MANUALLY_TRUSTED templateContext.set("currentSone", sone) - `when`(remoteSone.identity.getTrust(this.sone.identity as OwnIdentity)).thenReturn(Trust(1, null, null)) + whenever(remoteSone.identity.getTrust(this.sone.identity as OwnIdentity)).thenReturn(Trust(1, null, null)) parameters["sone"] = remoteSone verifyThatImagesArePresent() } @@ -140,7 +139,7 @@ class LinkedElementsFilterTest { @Test fun `filter finds images if local sone requires following and remote sone is followed`() { sone.options.loadLinkedImages = FOLLOWED - `when`(sone.hasFriend("remote-id")).thenReturn(true) + whenever(sone.hasFriend("remote-id")).thenReturn(true) templateContext["currentSone"] = sone parameters["sone"] = remoteSone verifyThatImagesArePresent() @@ -158,7 +157,7 @@ class LinkedElementsFilterTest { fun `filter finds images if following is required and remote sone is a local sone`() { sone.options.loadLinkedImages = FOLLOWED templateContext["currentSone"] = sone - `when`(remoteSone.isLocal).thenReturn(true) + whenever(remoteSone.isLocal).thenReturn(true) parameters["sone"] = remoteSone verifyThatImagesArePresent() } @@ -175,7 +174,7 @@ class LinkedElementsFilterTest { fun `filter does not find images if any trust is required and remote sone has implicit trust of zero`() { sone.options.loadLinkedImages = TRUSTED templateContext["currentSone"] = sone - `when`(remoteSone.identity.getTrust(sone.identity as OwnIdentity)).thenReturn(Trust(null, 0, null)) + whenever(remoteSone.identity.getTrust(sone.identity as OwnIdentity)).thenReturn(Trust(null, 0, null)) parameters["sone"] = remoteSone verifyThatImagesAreNotPresent() } @@ -184,7 +183,7 @@ class LinkedElementsFilterTest { fun `filter finds images if any trust is required and remote sone has implicit trust of one`() { sone.options.loadLinkedImages = TRUSTED templateContext["currentSone"] = sone - `when`(remoteSone.identity.getTrust(sone.identity as OwnIdentity)).thenReturn(Trust(null, 1, null)) + whenever(remoteSone.identity.getTrust(sone.identity as OwnIdentity)).thenReturn(Trust(null, 1, null)) parameters["sone"] = remoteSone verifyThatImagesArePresent() } @@ -193,7 +192,7 @@ class LinkedElementsFilterTest { fun `filter does not find images if any trust is required and remote sone has explicit trust of zero but implicit trust of one`() { sone.options.loadLinkedImages = TRUSTED templateContext["currentSone"] = sone - `when`(remoteSone.identity.getTrust(sone.identity as OwnIdentity)).thenReturn(Trust(0, 1, null)) + whenever(remoteSone.identity.getTrust(sone.identity as OwnIdentity)).thenReturn(Trust(0, 1, null)) parameters["sone"] = remoteSone verifyThatImagesAreNotPresent() } @@ -202,7 +201,7 @@ class LinkedElementsFilterTest { fun `filter finds images if any trust is required and remote sone has explicit trust of one but no implicit trust`() { sone.options.loadLinkedImages = TRUSTED templateContext["currentSone"] = sone - `when`(remoteSone.identity.getTrust(sone.identity as OwnIdentity)).thenReturn(Trust(1, null, null)) + whenever(remoteSone.identity.getTrust(sone.identity as OwnIdentity)).thenReturn(Trust(1, null, null)) parameters["sone"] = remoteSone verifyThatImagesArePresent() } @@ -211,7 +210,7 @@ class LinkedElementsFilterTest { fun `filter finds images if any trust is required and remote sone is a local sone`() { sone.options.loadLinkedImages = TRUSTED templateContext["currentSone"] = sone - `when`(remoteSone.isLocal).thenReturn(true) + whenever(remoteSone.isLocal).thenReturn(true) parameters["sone"] = remoteSone verifyThatImagesArePresent() } @@ -238,9 +237,9 @@ class LinkedElementsFilterTest { private fun createSone(id: String = "sone-id"): Sone { val sone = mock() - `when`(sone.id).thenReturn(id) - `when`(sone.options).thenReturn(DefaultSoneOptions()) - `when`(sone.identity).thenReturn(mock()) + whenever(sone.id).thenReturn(id) + whenever(sone.options).thenReturn(DefaultSoneOptions()) + whenever(sone.identity).thenReturn(mock()) return sone } diff --git a/src/test/kotlin/net/pterodactylus/sone/template/ParserFilterTest.kt b/src/test/kotlin/net/pterodactylus/sone/template/ParserFilterTest.kt index 0e44568..5864a6a 100644 --- a/src/test/kotlin/net/pterodactylus/sone/template/ParserFilterTest.kt +++ b/src/test/kotlin/net/pterodactylus/sone/template/ParserFilterTest.kt @@ -3,20 +3,17 @@ package net.pterodactylus.sone.template import com.google.inject.Guice import net.pterodactylus.sone.core.Core import net.pterodactylus.sone.data.Sone -import net.pterodactylus.sone.test.getInstance -import net.pterodactylus.sone.test.isProvidedByMock -import net.pterodactylus.sone.test.mock +import net.pterodactylus.sone.test.* import net.pterodactylus.sone.text.SoneTextParser import net.pterodactylus.sone.text.SoneTextParserContext import net.pterodactylus.util.template.TemplateContext import org.hamcrest.MatcherAssert.assertThat -import org.hamcrest.Matchers.`is` +import org.hamcrest.Matchers.equalTo import org.hamcrest.Matchers.emptyIterable import org.hamcrest.Matchers.notNullValue import org.hamcrest.Matchers.sameInstance import org.junit.Test import org.mockito.ArgumentCaptor.forClass -import org.mockito.Mockito.`when` import org.mockito.Mockito.eq import org.mockito.Mockito.verify @@ -38,8 +35,8 @@ class ParserFilterTest { private fun setupSone(identity: String): Sone { val sone = mock() - `when`(sone.id).thenReturn(identity) - `when`(core.getSone(identity)).thenReturn(sone) + whenever(sone.id).thenReturn(identity) + whenever(core.getSone(identity)).thenReturn(sone) return sone } @@ -63,7 +60,7 @@ class ParserFilterTest { filter.format(templateContext, "text", parameters) val context = forClass(SoneTextParserContext::class.java) verify(soneTextParser).parse(eq("text") ?: "", context.capture()) - assertThat(context.value.postingSone, `is`(sone)) + assertThat(context.value.postingSone, equalTo(sone)) } @Test diff --git a/src/test/kotlin/net/pterodactylus/sone/template/ProfileAccessorTest.kt b/src/test/kotlin/net/pterodactylus/sone/template/ProfileAccessorTest.kt index a2c4ad7..4513ce0 100644 --- a/src/test/kotlin/net/pterodactylus/sone/template/ProfileAccessorTest.kt +++ b/src/test/kotlin/net/pterodactylus/sone/template/ProfileAccessorTest.kt @@ -17,7 +17,7 @@ import net.pterodactylus.sone.test.mock import net.pterodactylus.sone.test.whenever import net.pterodactylus.util.template.TemplateContext import org.hamcrest.MatcherAssert.assertThat -import org.hamcrest.Matchers.`is` +import org.hamcrest.Matchers.equalTo import org.hamcrest.Matchers.nullValue import org.junit.Before import org.junit.Test @@ -86,7 +86,7 @@ class ProfileAccessorTest { @Test fun `avatar ID is returned if profile belongs to local sone`() { whenever(remoteSone.isLocal).thenReturn(true) - assertThat(accessor.get(templateContext, profile, "avatar"), `is`("avatar-id")) + assertThat(accessor.get(templateContext, profile, "avatar"), equalTo("avatar-id")) } @Test @@ -98,14 +98,14 @@ class ProfileAccessorTest { @Test fun `avatar ID is returned if sone is configure to always show avatars`() { currentSone.options.showCustomAvatars = ALWAYS - assertThat(accessor.get(templateContext, profile, "avatar"), `is`("avatar-id")) + assertThat(accessor.get(templateContext, profile, "avatar"), equalTo("avatar-id")) } @Test fun `avatar ID is returned if sone is configure to show avatars of followed sones and remote sone is followed`() { currentSone.options.showCustomAvatars = FOLLOWED whenever(currentSone.hasFriend("remote-sone")).thenReturn(true) - assertThat(accessor.get(templateContext, profile, "avatar"), `is`("avatar-id")) + assertThat(accessor.get(templateContext, profile, "avatar"), equalTo("avatar-id")) } @Test @@ -142,7 +142,7 @@ class ProfileAccessorTest { fun `avatar ID is returned if sone is configure to show avatars based on manual trust and explicit trust is one`() { currentSone.options.showCustomAvatars = MANUALLY_TRUSTED setTrust(Trust(1, null, null)) - assertThat(accessor.get(templateContext, profile, "avatar"), `is`("avatar-id")) + assertThat(accessor.get(templateContext, profile, "avatar"), equalTo("avatar-id")) } @Test @@ -156,7 +156,7 @@ class ProfileAccessorTest { fun `avatar ID is returned if sone is configure to show avatars based on trust and explicit trust is one`() { currentSone.options.showCustomAvatars = TRUSTED setTrust(Trust(1, null, null)) - assertThat(accessor.get(templateContext, profile, "avatar"), `is`("avatar-id")) + assertThat(accessor.get(templateContext, profile, "avatar"), equalTo("avatar-id")) } @Test @@ -177,12 +177,12 @@ class ProfileAccessorTest { fun `avatar ID is returned if sone is configure to show avatars based on trust and implicit trust is one`() { currentSone.options.showCustomAvatars = TRUSTED setTrust(Trust(0, 1, null)) - assertThat(accessor.get(templateContext, profile, "avatar"), `is`("avatar-id")) + assertThat(accessor.get(templateContext, profile, "avatar"), equalTo("avatar-id")) } @Test fun `accessing other members uses reflection accessor`() { - assertThat(accessor.get(templateContext, profile, "hashCode"), `is`(profile.hashCode())) + assertThat(accessor.get(templateContext, profile, "hashCode"), equalTo(profile.hashCode())) } } diff --git a/src/test/kotlin/net/pterodactylus/sone/template/ShortenFilterTest.kt b/src/test/kotlin/net/pterodactylus/sone/template/ShortenFilterTest.kt index c6990a7..efed7a3 100644 --- a/src/test/kotlin/net/pterodactylus/sone/template/ShortenFilterTest.kt +++ b/src/test/kotlin/net/pterodactylus/sone/template/ShortenFilterTest.kt @@ -2,7 +2,7 @@ package net.pterodactylus.sone.template import net.pterodactylus.sone.data.Profile import net.pterodactylus.sone.data.Sone -import net.pterodactylus.sone.test.mock +import net.pterodactylus.sone.test.* import net.pterodactylus.sone.text.FreenetLinkPart import net.pterodactylus.sone.text.Part import net.pterodactylus.sone.text.PlainTextPart @@ -10,7 +10,6 @@ import net.pterodactylus.sone.text.SonePart import org.hamcrest.MatcherAssert.assertThat import org.hamcrest.Matchers.contains import org.junit.Test -import org.mockito.Mockito.`when` /** * Unit test for [ShortenFilter]. @@ -76,7 +75,7 @@ class ShortenFilterTest { @Test fun `sone parts are added but their length is ignored`() { val sone = mock() - `when`(sone.profile).thenReturn(Profile(sone)) + whenever(sone.profile).thenReturn(Profile(sone)) assertThat(shortenParts(15, 10, SonePart(sone), PlainTextPart("This is a long text.")), contains( SonePart(sone), PlainTextPart("This is a …") @@ -86,7 +85,7 @@ class ShortenFilterTest { @Test fun `additional sone parts are ignored`() { val sone = mock() - `when`(sone.profile).thenReturn(Profile(sone)) + whenever(sone.profile).thenReturn(Profile(sone)) assertThat(shortenParts(15, 10, PlainTextPart("This is a long text."), SonePart(sone)), contains( PlainTextPart("This is a …") )) diff --git a/src/test/kotlin/net/pterodactylus/sone/template/UnknownDateFilterTest.kt b/src/test/kotlin/net/pterodactylus/sone/template/UnknownDateFilterTest.kt index f18033e..b1c2cc5 100644 --- a/src/test/kotlin/net/pterodactylus/sone/template/UnknownDateFilterTest.kt +++ b/src/test/kotlin/net/pterodactylus/sone/template/UnknownDateFilterTest.kt @@ -1,30 +1,32 @@ package net.pterodactylus.sone.template -import freenet.l10n.BaseL10n -import net.pterodactylus.sone.test.mock -import net.pterodactylus.sone.test.whenever -import org.hamcrest.MatcherAssert.assertThat -import org.hamcrest.Matchers.equalTo -import org.junit.Test +import net.pterodactylus.sone.freenet.* +import net.pterodactylus.sone.test.* +import org.hamcrest.MatcherAssert.* +import org.hamcrest.Matchers.* +import org.junit.* +import java.util.* /** * Unit test for [UnknownDateFilter]. */ class UnknownDateFilterTest { - private val baseL10n = mock() + private val translation = object : Translation { + override val currentLocale = Locale.ENGLISH + override fun translate(key: String) = if (key == unknownKey) "translated" else "" + } private val unknownKey = "unknown.key" - private val filter = UnknownDateFilter(baseL10n, unknownKey) + private val filter = UnknownDateFilter(translation, unknownKey) @Test fun `filter returns given object for non-longs`() { - val someObject = Any() + val someObject = Any() assertThat(filter.format(null, someObject, null), equalTo(someObject)) } @Test fun `filter returns translated value of unknown key if zero is given`() { - whenever(baseL10n.getString(unknownKey)).thenReturn("translated") assertThat(filter.format(null, 0L, null), equalTo("translated")) } diff --git a/src/test/kotlin/net/pterodactylus/sone/test/Matchers.kt b/src/test/kotlin/net/pterodactylus/sone/test/Matchers.kt index c084d35..7fc9428 100644 --- a/src/test/kotlin/net/pterodactylus/sone/test/Matchers.kt +++ b/src/test/kotlin/net/pterodactylus/sone/test/Matchers.kt @@ -1,7 +1,11 @@ package net.pterodactylus.sone.test +import freenet.support.* +import net.pterodactylus.sone.freenet.wot.* +import net.pterodactylus.sone.utils.* import net.pterodactylus.util.web.* import org.hamcrest.* +import org.hamcrest.Matchers.* fun hasHeader(name: String, value: String) = object : TypeSafeDiagnosingMatcher
() { override fun matchesSafely(item: Header, mismatchDescription: Description) = @@ -19,3 +23,123 @@ fun compare(value: T, comparison: (T) -> Boolean, onError: (T) -> Unit false.takeUnless { comparison(value) } ?.also { onError(value) } +fun isEmptyMap() = object : TypeSafeDiagnosingMatcher>() { + override fun describeTo(description: Description) { + description.appendText("empty map") + } + + override fun matchesSafely(item: Map, mismatchDescription: Description) = + item.isEmpty().onFalse { + mismatchDescription.appendText("was ").appendValue(item) + } +} + +fun isTrust(trust: Int?, score: Int?, rank: Int?) = + AttributeMatcher("trust") + .addAttribute("trust", trust, Trust::explicit) + .addAttribute("score", score, Trust::implicit) + .addAttribute("rank", rank, Trust::distance) + +fun isTrusted(ownIdentity: OwnIdentity, trust: Matcher) = object : TypeSafeDiagnosingMatcher() { + override fun matchesSafely(item: Identity, mismatchDescription: Description) = + item.getTrust(ownIdentity)?.let { foundTrust -> + trust.matches(foundTrust).onFalse { + trust.describeMismatch(foundTrust, mismatchDescription) + } + } ?: { + mismatchDescription.appendText("not trusted") + false + }() + + override fun describeTo(description: Description) { + description + .appendText("trusted by ").appendValue(ownIdentity) + .appendText(" with ").appendValue(trust) + } +} + +fun isIdentity(id: String, nickname: String?, requestUri: String, contexts: Matcher>, properties: Matcher>) = + AttributeMatcher("identity") + .addAttribute("id", id, Identity::getId) + .addAttribute("nickname", nickname, Identity::getNickname) + .addAttribute("requestUri", requestUri, Identity::getRequestUri) + .addAttribute("contexts", Identity::getContexts, contexts) + .addAttribute("properties", Identity::getProperties, properties) + +fun isOwnIdentity(id: String, nickname: String, requestUri: String, insertUri: String, contexts: Matcher>, properties: Matcher>) = + AttributeMatcher("own identity") + .addAttribute("id", id, OwnIdentity::getId) + .addAttribute("nickname", nickname, OwnIdentity::getNickname) + .addAttribute("request uri", requestUri, OwnIdentity::getRequestUri) + .addAttribute("insert uri", insertUri, OwnIdentity::getInsertUri) + .addAttribute("contexts", OwnIdentity::getContexts, contexts) + .addAttribute("properties", OwnIdentity::getProperties, properties) + +fun hasField(name: String, valueMatcher: Matcher) = object : TypeSafeDiagnosingMatcher() { + override fun matchesSafely(item: SimpleFieldSet, mismatchDescription: Description) = + valueMatcher.matches(item.get(name)).onFalse { + valueMatcher.describeMismatch(item, mismatchDescription) + } + + override fun describeTo(description: Description) { + description + .appendText("simple field set with key ").appendValue(name) + .appendText(", value ").appendValue(valueMatcher) + } +} + +/** + * [TypeSafeDiagnosingMatcher] implementation that aims to cut down boilerplate on verifying the attributes + * of typical container objects. + */ +class AttributeMatcher(private val objectName: String) : TypeSafeDiagnosingMatcher() { + + private data class AttributeToMatch( + val name: String, + val getter: (T) -> V, + val matcher: Matcher + ) + + private val attributesToMatch = mutableListOf>() + + /** + * Adds an attribute to check for equality, returning `this`. + */ + fun addAttribute(name: String, expected: V, getter: (T) -> V): AttributeMatcher = apply { + attributesToMatch.add(AttributeToMatch(name, getter, describedAs("$name %0", equalTo(expected), expected))) + } + + /** + * Adds an attribute to check with the given [hamcrest matcher][Matcher]. + */ + fun addAttribute(name: String, getter: (T) -> V, matcher: Matcher) = apply { + attributesToMatch.add(AttributeToMatch(name, getter, matcher)) + } + + override fun describeTo(description: Description) { + attributesToMatch.forEachIndexed { index, attributeToMatch -> + if (index == 0) { + description.appendText("$objectName with ") + } else { + description.appendText(", ") + } + attributeToMatch.matcher.describeTo(description) + } + } + + override fun matchesSafely(item: T, mismatchDescription: Description): Boolean = + attributesToMatch.fold(true) { matches, attributeToMatch -> + if (!matches) { + false + } else { + if (!attributeToMatch.matcher.matches(attributeToMatch.getter(item))) { + mismatchDescription.appendText("but ${attributeToMatch.name} ") + attributeToMatch.matcher.describeMismatch(attributeToMatch.getter(item), mismatchDescription) + false + } else { + true + } + } + } + +} diff --git a/src/test/kotlin/net/pterodactylus/sone/test/Mockotlin.kt b/src/test/kotlin/net/pterodactylus/sone/test/Mockotlin.kt index 85b86d5..4d345b3 100644 --- a/src/test/kotlin/net/pterodactylus/sone/test/Mockotlin.kt +++ b/src/test/kotlin/net/pterodactylus/sone/test/Mockotlin.kt @@ -3,7 +3,7 @@ package net.pterodactylus.sone.test import com.google.inject.Module import org.mockito.* import org.mockito.invocation.InvocationOnMock -import org.mockito.stubbing.OngoingStubbing +import org.mockito.stubbing.* inline fun mock(): T = Mockito.mock(T::class.java)!! inline fun mockBuilder(): T = Mockito.mock(T::class.java, Mockito.RETURNS_SELF)!! @@ -20,6 +20,7 @@ inline fun bindMock(): Module = Module { it!!.bind(T::class.java).toInstance(mock()) } inline fun whenever(methodCall: T) = Mockito.`when`(methodCall)!! +inline fun Stubber.whenever(mock: T) = `when`(mock)!! inline fun OngoingStubbing.thenReturnMock(): OngoingStubbing = this.thenReturn(mock()) diff --git a/src/test/kotlin/net/pterodactylus/sone/text/FreenetLinkPartTest.kt b/src/test/kotlin/net/pterodactylus/sone/text/FreenetLinkPartTest.kt index fcf6e67..aeba2c7 100644 --- a/src/test/kotlin/net/pterodactylus/sone/text/FreenetLinkPartTest.kt +++ b/src/test/kotlin/net/pterodactylus/sone/text/FreenetLinkPartTest.kt @@ -1,7 +1,7 @@ package net.pterodactylus.sone.text import org.hamcrest.MatcherAssert.assertThat -import org.hamcrest.Matchers.`is` +import org.hamcrest.Matchers.equalTo import org.junit.Test /** @@ -11,7 +11,7 @@ class FreenetLinkPartTest { @Test fun linkIsUsedAsTitleIfNoTextIsGiven() { - assertThat(FreenetLinkPart("link", "text", true).title, `is`("link")) + assertThat(FreenetLinkPart("link", "text", true).title, equalTo("link")) } } diff --git a/src/test/kotlin/net/pterodactylus/sone/text/LinkPartTest.kt b/src/test/kotlin/net/pterodactylus/sone/text/LinkPartTest.kt index 373a848..e38343c 100644 --- a/src/test/kotlin/net/pterodactylus/sone/text/LinkPartTest.kt +++ b/src/test/kotlin/net/pterodactylus/sone/text/LinkPartTest.kt @@ -1,7 +1,7 @@ package net.pterodactylus.sone.text import org.hamcrest.MatcherAssert.assertThat -import org.hamcrest.Matchers.`is` +import org.hamcrest.Matchers.equalTo import org.junit.Test /** @@ -11,7 +11,7 @@ class LinkPartTest { @Test fun linkIsUsedAsTitleIfNoTitleIsGiven() { - assertThat(LinkPart("link", "text").title, `is`("link")) + assertThat(LinkPart("link", "text").title, equalTo("link")) } } diff --git a/src/test/kotlin/net/pterodactylus/sone/text/SonePartTest.kt b/src/test/kotlin/net/pterodactylus/sone/text/SonePartTest.kt index 31fca48..ef1b1c0 100644 --- a/src/test/kotlin/net/pterodactylus/sone/text/SonePartTest.kt +++ b/src/test/kotlin/net/pterodactylus/sone/text/SonePartTest.kt @@ -1,12 +1,10 @@ package net.pterodactylus.sone.text -import net.pterodactylus.sone.data.Profile import net.pterodactylus.sone.data.Sone -import net.pterodactylus.sone.test.mock +import net.pterodactylus.sone.test.* import org.hamcrest.MatcherAssert.assertThat -import org.hamcrest.Matchers.`is` +import org.hamcrest.Matchers.equalTo import org.junit.Test -import org.mockito.Mockito.`when` /** * Unit test for [SonePart]. @@ -16,15 +14,15 @@ class SonePartTest { private val sone = mock() init { - `when`(sone.profile).thenReturn(mock()) - `when`(sone.name).thenReturn("sone") + whenever(sone.profile).thenReturn(mock()) + whenever(sone.name).thenReturn("sone") } private val part = SonePart(sone) @Test fun textIsConstructedFromSonesNiceName() { - assertThat(part.text, `is`("sone")) + assertThat(part.text, equalTo("sone")) } } diff --git a/src/test/kotlin/net/pterodactylus/sone/utils/BooleansTest.kt b/src/test/kotlin/net/pterodactylus/sone/utils/BooleansTest.kt index 56627c3..a1b6da7 100644 --- a/src/test/kotlin/net/pterodactylus/sone/utils/BooleansTest.kt +++ b/src/test/kotlin/net/pterodactylus/sone/utils/BooleansTest.kt @@ -1,9 +1,8 @@ package net.pterodactylus.sone.utils -import org.hamcrest.MatcherAssert.assertThat -import org.hamcrest.Matchers.equalTo -import org.hamcrest.Matchers.nullValue -import org.junit.Test +import org.hamcrest.MatcherAssert.* +import org.hamcrest.Matchers.* +import kotlin.test.* /** * Unit test for [Booleans]. @@ -30,4 +29,24 @@ class BooleansTest { assertThat(true.ifFalse { true }, nullValue()) } + @Test + fun `onFalse returns true on true`() { + assertThat(true.onFalse {}, equalTo(true)) + } + + @Test + fun `onFalse returns false on false`() { + assertThat(false.onFalse {}, equalTo(false)) + } + + @Test + fun `onFalse is not executed on true`() { + assertThat(true.onFalse { throw RuntimeException() }, equalTo(true)) + } + + @Test(expected = RuntimeException::class) + fun `onFalse is executed on false`() { + false.onFalse { throw RuntimeException() } + } + } diff --git a/src/test/kotlin/net/pterodactylus/sone/web/WebInterfaceModuleTest.kt b/src/test/kotlin/net/pterodactylus/sone/web/WebInterfaceModuleTest.kt index ec09b20..14427ef 100644 --- a/src/test/kotlin/net/pterodactylus/sone/web/WebInterfaceModuleTest.kt +++ b/src/test/kotlin/net/pterodactylus/sone/web/WebInterfaceModuleTest.kt @@ -20,17 +20,21 @@ import net.pterodactylus.util.web.* import org.hamcrest.MatcherAssert.* import org.hamcrest.Matchers.* import org.junit.* +import java.util.* import kotlin.test.Test class WebInterfaceModuleTest { private val webInterfaceModule = WebInterfaceModule() - private val l10n = mock() private val loaders = mock() + private val translation = object : Translation { + override val currentLocale = Locale.ENGLISH + override fun translate(key: String) = if (key == "View.Sone.Text.UnknownDate") "unknown" else key + } private val additionalModules = arrayOf( Core::class.isProvidedByMock(), SoneProvider::class.isProvidedByMock(), - BaseL10n::class.isProvidedBy(l10n), + Translation::class.isProvidedBy(translation), SoneTextParser::class.isProvidedByMock(), ElementLoader::class.isProvidedByMock(), Loaders::class.isProvidedBy(loaders), @@ -191,7 +195,6 @@ class WebInterfaceModuleTest { @Test fun `unknown date filter uses correct l10n key`() { - whenever(l10n.getString("View.Sone.Text.UnknownDate")).thenReturn("unknown") assertThat(getFilter("unknown")!!.format(null, 0L, emptyMap()), equalTo("unknown")) } @@ -240,6 +243,11 @@ class WebInterfaceModuleTest { verifyFilter("paginate") } + @Test + fun `template context histogram renderer`() { + verifyFilter("render-histogram") + } + private inline fun verifyFilter(name: String) { assertThat(getFilter(name), instanceOf(F::class.java)) } diff --git a/src/test/kotlin/net/pterodactylus/sone/web/ajax/TestObjects.kt b/src/test/kotlin/net/pterodactylus/sone/web/ajax/TestObjects.kt index a42e5ba..f902533 100644 --- a/src/test/kotlin/net/pterodactylus/sone/web/ajax/TestObjects.kt +++ b/src/test/kotlin/net/pterodactylus/sone/web/ajax/TestObjects.kt @@ -3,7 +3,6 @@ package net.pterodactylus.sone.web.ajax import com.fasterxml.jackson.databind.ObjectMapper import com.google.common.eventbus.EventBus import freenet.clients.http.ToadletContext -import freenet.l10n.BaseL10n import freenet.support.SimpleReadOnlyArrayBucket import freenet.support.api.HTTPRequest import net.pterodactylus.sone.core.Core @@ -20,6 +19,7 @@ import net.pterodactylus.sone.data.Sone import net.pterodactylus.sone.data.Sone.SoneStatus import net.pterodactylus.sone.data.Sone.SoneStatus.idle import net.pterodactylus.sone.data.SoneOptions.DefaultSoneOptions +import net.pterodactylus.sone.freenet.* import net.pterodactylus.sone.test.deepMock import net.pterodactylus.sone.test.get import net.pterodactylus.sone.test.mock @@ -32,7 +32,7 @@ import net.pterodactylus.util.template.TemplateContextFactory import net.pterodactylus.util.web.Method.GET import net.pterodactylus.util.web.Method.POST import org.mockito.ArgumentMatchers -import java.util.NoSuchElementException +import java.util.* import javax.naming.SizeLimitExceededException /** @@ -44,7 +44,6 @@ open class TestObjects { val webInterface = mock() var formPassword = "form-password" - val l10n = mock() val core = mock() val eventBus = mock() val preferences = Preferences(eventBus) @@ -74,6 +73,11 @@ open class TestObjects { val images = mutableMapOf() val translations = mutableMapOf() + private val translation = object : Translation { + override val currentLocale = Locale.ENGLISH + override fun translate(key: String) = translations[key] ?: "" + } + init { whenever(webInterface.templateContextFactory).thenReturn(TemplateContextFactory()) whenever(webInterface.getCurrentSone(ArgumentMatchers.eq(toadletContext), ArgumentMatchers.anyBoolean())).thenReturn(currentSone) @@ -85,9 +89,7 @@ open class TestObjects { whenever(webInterface.getNotification(ArgumentMatchers.anyString())).then { notifications[it[0]].asOptional() } whenever(webInterface.getNewPosts(currentSone)).thenAnswer { newPosts.values } whenever(webInterface.getNewReplies(currentSone)).thenAnswer { newReplies.values } - whenever(webInterface.l10n).thenReturn(l10n) - - whenever(l10n.getString(ArgumentMatchers.anyString())).then { translations[it[0]] } + whenever(webInterface.translation).thenReturn(translation) whenever(core.preferences).thenReturn(preferences) whenever(core.updateChecker).thenReturn(updateChecker) diff --git a/src/test/kotlin/net/pterodactylus/sone/web/page/FreenetRequestTest.kt b/src/test/kotlin/net/pterodactylus/sone/web/page/FreenetRequestTest.kt index e05ad45..3295bdd 100644 --- a/src/test/kotlin/net/pterodactylus/sone/web/page/FreenetRequestTest.kt +++ b/src/test/kotlin/net/pterodactylus/sone/web/page/FreenetRequestTest.kt @@ -2,7 +2,6 @@ package net.pterodactylus.sone.web.page import freenet.clients.http.* import freenet.clients.http.SessionManager.* -import freenet.l10n.* import freenet.support.api.* import net.pterodactylus.sone.test.* import net.pterodactylus.util.web.* @@ -19,9 +18,8 @@ class FreenetRequestTest { private val method = Method.GET private val httpRequest = mock(HTTPRequest::class.java) private val toadletContext = mock(ToadletContext::class.java) - private val l10n = mock() private val sessionManager = mock() - private val request = FreenetRequest(uri, method, httpRequest, toadletContext, l10n, sessionManager) + private val request = FreenetRequest(uri, method, httpRequest, toadletContext, sessionManager) @Test fun `uri is retained correctly`() { @@ -44,11 +42,6 @@ class FreenetRequestTest { } @Test - fun `l10n is retained correctly`() { - assertThat(request.l10n, equalTo(l10n)) - } - - @Test fun `null is returned if no session exists`() { assertThat(request.existingSession, nullValue()) } diff --git a/src/test/kotlin/net/pterodactylus/sone/web/page/SoneRequestTest.kt b/src/test/kotlin/net/pterodactylus/sone/web/page/SoneRequestTest.kt index 50c6ce7..5be6ff1 100644 --- a/src/test/kotlin/net/pterodactylus/sone/web/page/SoneRequestTest.kt +++ b/src/test/kotlin/net/pterodactylus/sone/web/page/SoneRequestTest.kt @@ -19,11 +19,10 @@ class SoneRequestTest { private val method = Method.GET private val httpRequest = Mockito.mock(HTTPRequest::class.java) private val toadletContext = Mockito.mock(ToadletContext::class.java) - private val l10n = mock() private val sessionManager = mock() private val core = mock() private val webInterface = mock() - private val soneRequest = SoneRequest(uri, method, httpRequest, toadletContext, l10n, sessionManager, core, webInterface) + private val soneRequest = SoneRequest(uri, method, httpRequest, toadletContext, sessionManager, core, webInterface) @Test fun `freenet request properties are retained correctly`() { @@ -31,7 +30,6 @@ class SoneRequestTest { assertThat(soneRequest.method, equalTo(method)) assertThat(soneRequest.httpRequest, equalTo(httpRequest)) assertThat(soneRequest.toadletContext, equalTo(toadletContext)) - assertThat(soneRequest.l10n, equalTo(l10n)) assertThat(soneRequest.sessionManager, equalTo(sessionManager)) } @@ -47,13 +45,12 @@ class SoneRequestTest { @Test fun `freenet request is wrapped correctly`() { - val freenetRequest = FreenetRequest(uri, method, httpRequest, toadletContext, l10n, sessionManager) + val freenetRequest = FreenetRequest(uri, method, httpRequest, toadletContext, sessionManager) val wrappedSoneRequest = freenetRequest.toSoneRequest(core, webInterface) assertThat(wrappedSoneRequest.uri, equalTo(uri)) assertThat(wrappedSoneRequest.method, equalTo(method)) assertThat(wrappedSoneRequest.httpRequest, equalTo(httpRequest)) assertThat(wrappedSoneRequest.toadletContext, equalTo(toadletContext)) - assertThat(wrappedSoneRequest.l10n, equalTo(l10n)) assertThat(wrappedSoneRequest.sessionManager, equalTo(sessionManager)) assertThat(wrappedSoneRequest.core, sameInstance(core)) assertThat(wrappedSoneRequest.webInterface, sameInstance(webInterface)) diff --git a/src/test/kotlin/net/pterodactylus/sone/web/pages/DeleteSonePageTest.kt b/src/test/kotlin/net/pterodactylus/sone/web/pages/DeleteSonePageTest.kt index 21cfdfc..5bcdd6a 100644 --- a/src/test/kotlin/net/pterodactylus/sone/web/pages/DeleteSonePageTest.kt +++ b/src/test/kotlin/net/pterodactylus/sone/web/pages/DeleteSonePageTest.kt @@ -26,7 +26,7 @@ class DeleteSonePageTest : WebPageTest(::DeleteSonePage) { @Test fun `page returns correct title`() { - whenever(l10n.getString("Page.DeleteSone.Title")).thenReturn("delete sone page") + addTranslation("Page.DeleteSone.Title", "delete sone page") assertThat(page.getPageTitle(soneRequest), equalTo("delete sone page")) } diff --git a/src/test/kotlin/net/pterodactylus/sone/web/pages/DismissNotificationPageTest.kt b/src/test/kotlin/net/pterodactylus/sone/web/pages/DismissNotificationPageTest.kt index e8583df..7893210 100644 --- a/src/test/kotlin/net/pterodactylus/sone/web/pages/DismissNotificationPageTest.kt +++ b/src/test/kotlin/net/pterodactylus/sone/web/pages/DismissNotificationPageTest.kt @@ -28,7 +28,7 @@ class DismissNotificationPageTest : WebPageTest(::DismissNotificationPage) { @Test fun `page returns correct title`() { - whenever(l10n.getString("Page.DismissNotification.Title")).thenReturn("dismiss notification page") + addTranslation("Page.DismissNotification.Title", "dismiss notification page") assertThat(page.getPageTitle(soneRequest), equalTo("dismiss notification page")) } diff --git a/src/test/kotlin/net/pterodactylus/sone/web/pages/DistrustPageTest.kt b/src/test/kotlin/net/pterodactylus/sone/web/pages/DistrustPageTest.kt index d706e05..df16a53 100644 --- a/src/test/kotlin/net/pterodactylus/sone/web/pages/DistrustPageTest.kt +++ b/src/test/kotlin/net/pterodactylus/sone/web/pages/DistrustPageTest.kt @@ -26,7 +26,7 @@ class DistrustPageTest : WebPageTest(::DistrustPage) { @Test fun `page returns correct title`() { - whenever(l10n.getString("Page.Distrust.Title")).thenReturn("distrust page title") + addTranslation("Page.Distrust.Title", "distrust page title") assertThat(page.getPageTitle(soneRequest), equalTo("distrust page title")) } diff --git a/src/test/kotlin/net/pterodactylus/sone/web/pages/EditAlbumPageTest.kt b/src/test/kotlin/net/pterodactylus/sone/web/pages/EditAlbumPageTest.kt index 4d435b4..72e814b 100644 --- a/src/test/kotlin/net/pterodactylus/sone/web/pages/EditAlbumPageTest.kt +++ b/src/test/kotlin/net/pterodactylus/sone/web/pages/EditAlbumPageTest.kt @@ -45,7 +45,7 @@ class EditAlbumPageTest : WebPageTest(::EditAlbumPage) { @Test fun `page returns correct title`() { - whenever(l10n.getString("Page.EditAlbum.Title")).thenReturn("edit album page") + addTranslation("Page.EditAlbum.Title", "edit album page") assertThat(page.getPageTitle(soneRequest), equalTo("edit album page")) } diff --git a/src/test/kotlin/net/pterodactylus/sone/web/pages/EditImagePageTest.kt b/src/test/kotlin/net/pterodactylus/sone/web/pages/EditImagePageTest.kt index 0961622..e43551c 100644 --- a/src/test/kotlin/net/pterodactylus/sone/web/pages/EditImagePageTest.kt +++ b/src/test/kotlin/net/pterodactylus/sone/web/pages/EditImagePageTest.kt @@ -43,7 +43,7 @@ class EditImagePageTest : WebPageTest(::EditImagePage) { @Test fun `page returns correct title`() { - whenever(l10n.getString("Page.EditImage.Title")).thenReturn("edit image page title") + addTranslation("Page.EditImage.Title", "edit image page title") assertThat(page.getPageTitle(soneRequest), equalTo("edit image page title")) } diff --git a/src/test/kotlin/net/pterodactylus/sone/web/pages/EditProfileFieldPageTest.kt b/src/test/kotlin/net/pterodactylus/sone/web/pages/EditProfileFieldPageTest.kt index 7731fa2..4a84644 100644 --- a/src/test/kotlin/net/pterodactylus/sone/web/pages/EditProfileFieldPageTest.kt +++ b/src/test/kotlin/net/pterodactylus/sone/web/pages/EditProfileFieldPageTest.kt @@ -35,7 +35,7 @@ class EditProfileFieldPageTest : WebPageTest(::EditProfileFieldPage) { @Test fun `page returns correct title`() { - whenever(l10n.getString("Page.EditProfileField.Title")).thenReturn("edit profile field title") + addTranslation("Page.EditProfileField.Title", "edit profile field title") assertThat(page.getPageTitle(soneRequest), equalTo("edit profile field title")) } diff --git a/src/test/kotlin/net/pterodactylus/sone/web/pages/EditProfilePageTest.kt b/src/test/kotlin/net/pterodactylus/sone/web/pages/EditProfilePageTest.kt index a31f04c..74c3851 100644 --- a/src/test/kotlin/net/pterodactylus/sone/web/pages/EditProfilePageTest.kt +++ b/src/test/kotlin/net/pterodactylus/sone/web/pages/EditProfilePageTest.kt @@ -46,7 +46,7 @@ class EditProfilePageTest : WebPageTest(::EditProfilePage) { @Test fun `page returns correct title`() { - whenever(l10n.getString("Page.EditProfile.Title")).thenReturn("edit profile page title") + addTranslation("Page.EditProfile.Title", "edit profile page title") assertThat(page.getPageTitle(soneRequest), equalTo("edit profile page title")) } diff --git a/src/test/kotlin/net/pterodactylus/sone/web/pages/FollowSonePageTest.kt b/src/test/kotlin/net/pterodactylus/sone/web/pages/FollowSonePageTest.kt index ba1dc42..3873434 100644 --- a/src/test/kotlin/net/pterodactylus/sone/web/pages/FollowSonePageTest.kt +++ b/src/test/kotlin/net/pterodactylus/sone/web/pages/FollowSonePageTest.kt @@ -30,7 +30,7 @@ class FollowSonePageTest : WebPageTest(::FollowSonePage) { @Test fun `page returns correct title`() { - whenever(l10n.getString("Page.FollowSone.Title")).thenReturn("follow sone page title") + addTranslation("Page.FollowSone.Title", "follow sone page title") assertThat(page.getPageTitle(soneRequest), equalTo("follow sone page title")) } diff --git a/src/test/kotlin/net/pterodactylus/sone/web/pages/ImageBrowserPageTest.kt b/src/test/kotlin/net/pterodactylus/sone/web/pages/ImageBrowserPageTest.kt index a0e9372..1533f4b 100644 --- a/src/test/kotlin/net/pterodactylus/sone/web/pages/ImageBrowserPageTest.kt +++ b/src/test/kotlin/net/pterodactylus/sone/web/pages/ImageBrowserPageTest.kt @@ -26,7 +26,7 @@ class ImageBrowserPageTest : WebPageTest(::ImageBrowserPage) { @Test fun `page returns correct title`() { - whenever(l10n.getString("Page.ImageBrowser.Title")).thenReturn("image browser page title") + addTranslation("Page.ImageBrowser.Title", "image browser page title") assertThat(page.getPageTitle(soneRequest), equalTo("image browser page title")) } diff --git a/src/test/kotlin/net/pterodactylus/sone/web/pages/IndexPageTest.kt b/src/test/kotlin/net/pterodactylus/sone/web/pages/IndexPageTest.kt index 9104127..8c86365 100644 --- a/src/test/kotlin/net/pterodactylus/sone/web/pages/IndexPageTest.kt +++ b/src/test/kotlin/net/pterodactylus/sone/web/pages/IndexPageTest.kt @@ -34,7 +34,7 @@ class IndexPageTest : WebPageTest({ webInterface, loaders, templateRenderer -> I @Test fun `page returns correct title`() { - whenever(l10n.getString("Page.Index.Title")).thenReturn("index page title") + addTranslation("Page.Index.Title", "index page title") assertThat(page.getPageTitle(soneRequest), equalTo("index page title")) } diff --git a/src/test/kotlin/net/pterodactylus/sone/web/pages/KnownSonesPageTest.kt b/src/test/kotlin/net/pterodactylus/sone/web/pages/KnownSonesPageTest.kt index 37e0e95..14cae74 100644 --- a/src/test/kotlin/net/pterodactylus/sone/web/pages/KnownSonesPageTest.kt +++ b/src/test/kotlin/net/pterodactylus/sone/web/pages/KnownSonesPageTest.kt @@ -73,7 +73,7 @@ class KnownSonesPageTest : WebPageTest(::KnownSonesPage) { @Test fun `page returns correct title`() { - whenever(l10n.getString("Page.KnownSones.Title")).thenReturn("known sones page title") + addTranslation("Page.KnownSones.Title", "known sones page title") assertThat(page.getPageTitle(soneRequest), equalTo("known sones page title")) } diff --git a/src/test/kotlin/net/pterodactylus/sone/web/pages/MetricsPageTest.kt b/src/test/kotlin/net/pterodactylus/sone/web/pages/MetricsPageTest.kt index d805f65..9e2c492 100644 --- a/src/test/kotlin/net/pterodactylus/sone/web/pages/MetricsPageTest.kt +++ b/src/test/kotlin/net/pterodactylus/sone/web/pages/MetricsPageTest.kt @@ -62,37 +62,16 @@ class MetricsPageTest : WebPageTest() { } @Test - fun `metrics page lists stats about sone parsing durations`() { - createHistogram("sone.parsing.duration") + @Suppress("UNCHECKED_CAST") + fun `metrics page stores histograms in template context`() { + createHistogram("sone.random.duration2") + createHistogram("sone.random.duration1") page.handleRequest(soneRequest, templateContext) - verifyHistogram("soneParsingDuration") - } - - @Test - fun `metrice pags lists stats about sone insert durations`() { - createHistogram("sone.insert.duration") - page.handleRequest(soneRequest, templateContext) - verifyHistogram("soneInsertDuration") - } - - @Test - fun `metrics page delivers correct histogram size`() { - val histogram = metricRegistry.histogram("sone.parsing.duration") - (0..4000).forEach(histogram::update) - page.handleRequest(soneRequest, templateContext) - assertThat(templateContext["soneParsingDurationCount"] as Long, equalTo(4001L)) - } - - private fun verifyHistogram(name: String) { - assertThat(templateContext["${name}Count"] as Long, equalTo(5L)) - assertThat(templateContext["${name}Min"] as Long, equalTo(1L)) - assertThat(templateContext["${name}Max"] as Long, equalTo(10L)) - assertThat(templateContext["${name}Median"] as Double, equalTo(8.0)) - assertThat(templateContext["${name}Percentile75"] as Double, equalTo(9.0)) - assertThat(templateContext["${name}Percentile95"] as Double, equalTo(10.0)) - assertThat(templateContext["${name}Percentile98"] as Double, equalTo(10.0)) - assertThat(templateContext["${name}Percentile99"] as Double, equalTo(10.0)) - assertThat(templateContext["${name}Percentile999"] as Double, equalTo(10.0)) + val histograms = templateContext["histograms"] as Map + assertThat(histograms.entries.map { it.key to it.value }, containsInAnyOrder( + "sone.random.duration1" to metricRegistry.histogram("sone.random.duration1"), + "sone.random.duration2" to metricRegistry.histogram("sone.random.duration2") + )) } private fun createHistogram(name: String) = metricRegistry.histogram(name).run { diff --git a/src/test/kotlin/net/pterodactylus/sone/web/pages/RescuePageTest.kt b/src/test/kotlin/net/pterodactylus/sone/web/pages/RescuePageTest.kt index 85e1f6b..d11352f 100644 --- a/src/test/kotlin/net/pterodactylus/sone/web/pages/RescuePageTest.kt +++ b/src/test/kotlin/net/pterodactylus/sone/web/pages/RescuePageTest.kt @@ -54,10 +54,33 @@ class RescuePageTest : WebPageTest(::RescuePage) { } @Test - fun `post request with fetch starts next fetch`() { + fun `post request with fetch and invalid edition starts next fetch`() { setMethod(POST) addHttpRequestPart("fetch", "true") verifyRedirect("rescue.html") { + verify(soneRescuer, never()).setEdition(anyLong()) + verify(soneRescuer).startNextFetch() + } + } + + @Test + fun `post request with fetch and valid edition sets edition and starts next fetch`() { + setMethod(POST) + addHttpRequestPart("fetch", "true") + addHttpRequestPart("edition", "123") + verifyRedirect("rescue.html") { + verify(soneRescuer).setEdition(123L) + verify(soneRescuer).startNextFetch() + } + } + + @Test + fun `post request with negative edition will not set edition`() { + setMethod(POST) + addHttpRequestPart("fetch", "true") + addHttpRequestPart("edition", "-123") + verifyRedirect("rescue.html") { + verify(soneRescuer, never()).setEdition(anyLong()) verify(soneRescuer).startNextFetch() } } diff --git a/src/test/kotlin/net/pterodactylus/sone/web/pages/SoneTemplatePageTest.kt b/src/test/kotlin/net/pterodactylus/sone/web/pages/SoneTemplatePageTest.kt index 8e29df1..cffd478 100644 --- a/src/test/kotlin/net/pterodactylus/sone/web/pages/SoneTemplatePageTest.kt +++ b/src/test/kotlin/net/pterodactylus/sone/web/pages/SoneTemplatePageTest.kt @@ -31,7 +31,7 @@ class SoneTemplatePageTest : WebPageTest({ webInterface, loaders, templateRender @Test fun `page title is retrieved from l10n if page title key is given`() { SoneTemplatePage(webInterface, loaders, templateRenderer, pageTitleKey = "page.title", requiresLogin = false).let { page -> - whenever(l10n.getString("page.title")).thenReturn("Page Title") + addTranslation("page.title", "Page Title") assertThat(page.getPageTitle(soneRequest), equalTo("Page Title")) } } diff --git a/src/test/kotlin/net/pterodactylus/sone/web/pages/WebPageTest.kt b/src/test/kotlin/net/pterodactylus/sone/web/pages/WebPageTest.kt index 61d1a03..07ae727 100644 --- a/src/test/kotlin/net/pterodactylus/sone/web/pages/WebPageTest.kt +++ b/src/test/kotlin/net/pterodactylus/sone/web/pages/WebPageTest.kt @@ -6,6 +6,7 @@ import freenet.support.* import freenet.support.api.* import net.pterodactylus.sone.core.* import net.pterodactylus.sone.data.* +import net.pterodactylus.sone.freenet.* import net.pterodactylus.sone.freenet.wot.* import net.pterodactylus.sone.main.* import net.pterodactylus.sone.test.deepMock @@ -26,6 +27,7 @@ import org.mockito.ArgumentMatchers.eq import java.io.* import java.net.* import java.nio.charset.* +import java.util.* import kotlin.text.Charsets.UTF_8 /** @@ -40,7 +42,6 @@ open class WebPageTest(pageSupplier: (WebInterface, Loaders, TemplateRenderer) - val core = webInterface.core val eventBus = mock() val preferences = Preferences(eventBus) - val l10n = webInterface.l10n!! val sessionManager = mock() open val page by lazy { pageSupplier(webInterface, loaders, templateRenderer) } @@ -49,7 +50,6 @@ open class WebPageTest(pageSupplier: (WebInterface, Loaders, TemplateRenderer) - val freenetRequest = mock() init { - whenever(freenetRequest.l10n).thenReturn(l10n) whenever(freenetRequest.sessionManager).thenReturn(sessionManager) whenever(freenetRequest.uri).thenReturn(mock()) } @@ -77,12 +77,16 @@ open class WebPageTest(pageSupplier: (WebInterface, Loaders, TemplateRenderer) - private val notifications = mutableMapOf() private val translations = mutableMapOf() + private val translation = object : Translation { + override val currentLocale = Locale.ENGLISH + override fun translate(key: String) = translations[key] ?: key + } + init { setupCore() setupWebInterface() setupHttpRequest() setupFreenetRequest() - setupTranslations() } private fun setupCore() { @@ -108,6 +112,7 @@ open class WebPageTest(pageSupplier: (WebInterface, Loaders, TemplateRenderer) - whenever(webInterface.getCurrentSoneWithoutCreatingSession(eq(toadletContext))).thenReturn(currentSone) whenever(webInterface.getNotifications(currentSone)).then { notifications.values } whenever(webInterface.getNotification(anyString())).then { notifications[it[0]].asOptional() } + whenever(webInterface.translation).thenReturn(translation) } private fun setupHttpRequest() { @@ -147,10 +152,6 @@ open class WebPageTest(pageSupplier: (WebInterface, Loaders, TemplateRenderer) - whenever(freenetRequest.toadletContext).thenReturn(toadletContext) } - private fun setupTranslations() { - whenever(l10n.getString(anyString())).then { translations[it[0]] ?: it[0] } - } - fun setMethod(method: Method) { whenever(httpRequest.method).thenReturn(method.name) whenever(freenetRequest.method).thenReturn(method)