⬆️ Update to latest Kotlin version
authorDavid ‘Bombe’ Roden <bombe@pterodactylus.net>
Fri, 5 Dec 2025 15:52:08 +0000 (16:52 +0100)
committerDavid ‘Bombe’ Roden <bombe@pterodactylus.net>
Fri, 5 Dec 2025 17:04:39 +0000 (18:04 +0100)
The Kotlin compiler now enforces the nullability annotations of jSpecify, so
it’s time to clean up this mess!

* Most packages now have a packing-info.java which declares the whole package
  to be @NullMarked.
* Redundant annotations have been removed, missing annotations have been added.
* Some null values have been replaced with empty values (in production code) or
  mocks (in tests).
* Some redundant code has been removed.

46 files changed:
build.gradle
src/main/java/net/pterodactylus/rhynodge/Filter.java
src/main/java/net/pterodactylus/rhynodge/State.java
src/main/java/net/pterodactylus/rhynodge/actions/package-info.java [new file with mode: 0644]
src/main/java/net/pterodactylus/rhynodge/filters/BlacklistFilter.java
src/main/java/net/pterodactylus/rhynodge/filters/ComicSiteFilter.java
src/main/java/net/pterodactylus/rhynodge/filters/EpisodeFilter.java
src/main/java/net/pterodactylus/rhynodge/filters/ExtractUrlFilter.java
src/main/java/net/pterodactylus/rhynodge/filters/HtmlFilter.java
src/main/java/net/pterodactylus/rhynodge/filters/HttpQueryFilter.java
src/main/java/net/pterodactylus/rhynodge/filters/TorrentSiteFilter.java
src/main/java/net/pterodactylus/rhynodge/filters/comics/package-info.java [new file with mode: 0644]
src/main/java/net/pterodactylus/rhynodge/filters/package-info.java [new file with mode: 0644]
src/main/java/net/pterodactylus/rhynodge/filters/torrents/TorrentzEuFilter.java
src/main/java/net/pterodactylus/rhynodge/filters/torrents/package-info.java [new file with mode: 0644]
src/main/java/net/pterodactylus/rhynodge/package-info.java
src/main/java/net/pterodactylus/rhynodge/queries/HttpQuery.java
src/main/java/net/pterodactylus/rhynodge/queries/package-info.java [new file with mode: 0644]
src/main/java/net/pterodactylus/rhynodge/states/AbstractState.java
src/main/java/net/pterodactylus/rhynodge/states/ComicState.java
src/main/java/net/pterodactylus/rhynodge/states/EpisodeState.java
src/main/java/net/pterodactylus/rhynodge/states/FailedState.java
src/main/java/net/pterodactylus/rhynodge/states/FileState.java
src/main/java/net/pterodactylus/rhynodge/states/HtmlState.java
src/main/java/net/pterodactylus/rhynodge/states/HttpState.java
src/main/java/net/pterodactylus/rhynodge/states/OutputState.java
src/main/java/net/pterodactylus/rhynodge/states/StringState.java
src/main/java/net/pterodactylus/rhynodge/states/TorrentState.java
src/main/java/net/pterodactylus/rhynodge/states/package-info.java [new file with mode: 0644]
src/main/java/net/pterodactylus/rhynodge/watchers/KickAssTorrentsEpisodeWatcher.java
src/main/java/net/pterodactylus/rhynodge/watchers/KickAssTorrentsWatcher.java
src/main/java/net/pterodactylus/rhynodge/watchers/PirateBayEpisodeWatcher.java
src/main/java/net/pterodactylus/rhynodge/watchers/PirateBayWatcher.java
src/main/java/net/pterodactylus/rhynodge/watchers/TorrentHoundEpisodeWatcher.java
src/main/java/net/pterodactylus/rhynodge/watchers/TorrentHoundWatcher.java
src/main/java/net/pterodactylus/rhynodge/watchers/TorrentzEuEpisodeWatcher.java
src/main/java/net/pterodactylus/rhynodge/watchers/TorrentzEuWatcher.java
src/main/java/net/pterodactylus/rhynodge/watchers/package-info.java [new file with mode: 0644]
src/main/kotlin/net/pterodactylus/rhynodge/filters/webpages/savoy/MovieExtractor.kt
src/test/java/net/pterodactylus/rhynodge/filters/ComicSiteFilterTest.java
src/test/java/net/pterodactylus/rhynodge/filters/EpisodeFilterTest.java
src/test/java/net/pterodactylus/rhynodge/filters/package-info.java [new file with mode: 0644]
src/test/kotlin/net/pterodactylus/rhynodge/engine/ReactionRunnerTest.kt
src/test/kotlin/net/pterodactylus/rhynodge/filters/webpages/epicgames/FreeGamesStateTest.kt
src/test/kotlin/net/pterodactylus/rhynodge/filters/webpages/savoy/MovieStateTest.kt
src/test/kotlin/net/pterodactylus/rhynodge/states/ComicStateTest.kt

index 965087d..7f547d9 100644 (file)
@@ -1,5 +1,5 @@
 buildscript {
-    ext.kotlinVersion = '1.9.20'
+    ext.kotlinVersion = '2.2.21'
 
     repositories {
         mavenCentral()
@@ -11,7 +11,7 @@ buildscript {
 
 plugins {
     id 'java'
-    id('org.jetbrains.kotlin.jvm') version '1.9.20'
+    id('org.jetbrains.kotlin.jvm') version "$kotlinVersion"
 }
 
 apply plugin: "jacoco"
index d1ac741..3351c01 100644 (file)
@@ -17,9 +17,6 @@
 
 package net.pterodactylus.rhynodge;
 
-import org.jspecify.annotations.NonNull;
-import org.jspecify.annotations.Nullable;
-
 /**
  * Defines a filter that runs between {@link Query}s and {@link Merger}s and
  * can be used to convert a {@link State} into another {@link State}. This can
@@ -41,7 +38,6 @@ public interface Filter {
         *            The state to convert
         * @return The new state
         */
-       @NonNull
-       State filter(@Nullable State state);
+       State filter(State state);
 
 }
index aa755be..b7e5739 100644 (file)
@@ -18,7 +18,7 @@
 package net.pterodactylus.rhynodge;
 
 import net.pterodactylus.rhynodge.output.Output;
-import org.jspecify.annotations.NonNull;
+import org.jspecify.annotations.Nullable;
 
 /**
  * Defines the current state of a system.
@@ -92,9 +92,9 @@ public interface State {
         * @return An exception that occured, may be {@code null} in case an
         *         exception can not be meaningfully returned
         */
+       @Nullable
        Throwable exception();
 
-       @NonNull
        Output output(Reaction reaction);
 
 }
diff --git a/src/main/java/net/pterodactylus/rhynodge/actions/package-info.java b/src/main/java/net/pterodactylus/rhynodge/actions/package-info.java
new file mode 100644 (file)
index 0000000..320c9c6
--- /dev/null
@@ -0,0 +1,4 @@
+@NullMarked
+package net.pterodactylus.rhynodge.actions;
+
+import org.jspecify.annotations.NullMarked;
index 538e259..7e7ff14 100644 (file)
@@ -28,8 +28,6 @@ import net.pterodactylus.rhynodge.states.FailedState;
 import net.pterodactylus.rhynodge.states.TorrentState;
 import net.pterodactylus.rhynodge.states.TorrentState.TorrentFile;
 
-import org.jspecify.annotations.NonNull;
-
 /**
  * Filter for {@link TorrentState}s that removes all {@link TorrentFile}s whose
  * names match a list of bad words.
@@ -44,9 +42,8 @@ public class BlacklistFilter implements Filter {
                this.filterWords = filterWords;
        }
 
-       @NonNull
        @Override
-       public State filter(@NonNull State state) {
+       public State filter(State state) {
                if (!state.success()) {
                        return FailedState.from(state);
                }
index 726f6b8..beec233 100644 (file)
@@ -32,7 +32,6 @@ import net.pterodactylus.rhynodge.states.FailedState;
 import net.pterodactylus.rhynodge.states.HtmlState;
 
 import org.jsoup.nodes.Document;
-import org.jspecify.annotations.NonNull;
 
 import static net.pterodactylus.rhynodge.utils.Preconditions.checkArgument;
 
@@ -44,9 +43,8 @@ import static net.pterodactylus.rhynodge.utils.Preconditions.checkArgument;
  */
 public abstract class ComicSiteFilter implements Filter {
 
-       @NonNull
        @Override
-       public State filter(@NonNull State state) {
+       public State filter(State state) {
                checkArgument(state instanceof HtmlState, "state must be an HTML state");
 
                /* initialize states: */
index 901d470..ebf878e 100644 (file)
@@ -42,7 +42,6 @@ import net.pterodactylus.rhynodge.states.TorrentState.TorrentFile;
 
 import org.apache.logging.log4j.LogManager;
 import org.apache.logging.log4j.Logger;
-import org.jspecify.annotations.NonNull;
 
 import static net.pterodactylus.rhynodge.utils.Preconditions.checkState;
 
@@ -66,9 +65,8 @@ public class EpisodeFilter implements Filter {
        /**
         * {@inheritDoc}
         */
-       @NonNull
        @Override
-       public State filter(@NonNull State state) {
+       public State filter(State state) {
                if (!state.success()) {
                        return FailedState.from(state);
                }
index 5b9a5f2..e8dde7e 100644 (file)
@@ -25,7 +25,6 @@ import net.pterodactylus.rhynodge.states.HtmlState;
 import net.pterodactylus.rhynodge.states.StringState;
 
 import org.jsoup.nodes.Document;
-import org.jspecify.annotations.NonNull;
 
 import static net.pterodactylus.rhynodge.utils.Preconditions.checkArgument;
 
@@ -36,9 +35,8 @@ import static net.pterodactylus.rhynodge.utils.Preconditions.checkArgument;
  */
 public abstract class ExtractUrlFilter implements Filter {
 
-       @NonNull
        @Override
-       public State filter(@NonNull State state) {
+       public State filter(State state) {
                checkArgument(state instanceof HtmlState, "state must be an HTML state");
 
                HtmlState htmlState = (HtmlState) state;
index cae172b..9c83745 100644 (file)
@@ -27,7 +27,6 @@ import org.apache.logging.log4j.LogManager;
 import org.apache.logging.log4j.Logger;
 import org.jsoup.Jsoup;
 import org.jsoup.nodes.Document;
-import org.jspecify.annotations.NonNull;
 
 import static net.pterodactylus.rhynodge.utils.Preconditions.checkState;
 
@@ -43,9 +42,8 @@ public class HtmlFilter implements Filter {
        /**
         * {@inheritDoc}
         */
-       @NonNull
        @Override
-       public State filter(@NonNull State state) {
+       public State filter(State state) {
                if (!state.success()) {
                        return FailedState.from(state);
                }
index ce54ce3..9d4a5d3 100644 (file)
@@ -23,8 +23,6 @@ import net.pterodactylus.rhynodge.queries.HttpQuery;
 import net.pterodactylus.rhynodge.states.HttpState;
 import net.pterodactylus.rhynodge.states.StringState;
 
-import org.jspecify.annotations.NonNull;
-
 import static net.pterodactylus.rhynodge.utils.Preconditions.checkArgument;
 
 /**
@@ -36,9 +34,8 @@ import static net.pterodactylus.rhynodge.utils.Preconditions.checkArgument;
  */
 public class HttpQueryFilter implements Filter {
 
-       @NonNull
        @Override
-       public State filter(@NonNull State state) {
+       public State filter(State state) {
                checkArgument(state instanceof StringState, "state must be a String state");
 
                StringState stringState = (StringState) state;
index 6afa407..37d6d01 100644 (file)
@@ -17,7 +17,6 @@
 
 package net.pterodactylus.rhynodge.filters;
 
-import java.io.UnsupportedEncodingException;
 import java.net.URI;
 import java.net.URISyntaxException;
 import java.net.URLEncoder;
@@ -33,8 +32,8 @@ import net.pterodactylus.rhynodge.states.TorrentState.TorrentFile;
 import org.jsoup.nodes.Document;
 import org.jsoup.nodes.Element;
 import org.jsoup.select.Elements;
-import org.jspecify.annotations.NonNull;
 
+import static java.nio.charset.StandardCharsets.UTF_8;
 import static net.pterodactylus.rhynodge.utils.Preconditions.checkState;
 
 /**
@@ -49,9 +48,8 @@ public abstract class TorrentSiteFilter implements Filter {
        /**
         * {@inheritDoc}
         */
-       @NonNull
        @Override
-       public State filter(@NonNull State state) {
+       public State filter(State state) {
                if (!state.success()) {
                        return FailedState.from(state);
                }
@@ -73,7 +71,7 @@ public abstract class TorrentSiteFilter implements Filter {
                        int leechCount = extractLeechCount(dataRow);
                        try {
                                if ((downloadUri != null) && (downloadUri.length() > 0)) {
-                                       downloadUri = new URI(((HtmlState) state).uri()).resolve(URLEncoder.encode(downloadUri, "UTF-8").replace("%2F", "/")).toString();
+                                       downloadUri = new URI(((HtmlState) state).uri()).resolve(URLEncoder.encode(downloadUri, UTF_8).replace("%2F", "/")).toString();
                                } else {
                                        downloadUri = null;
                                }
@@ -81,8 +79,6 @@ public abstract class TorrentSiteFilter implements Filter {
                                torrentState.addTorrentFile(torrentFile);
                        } catch (URISyntaxException use1) {
                                /* ignore; if uri was wrong, we wouldn’t be here. */
-                       } catch (UnsupportedEncodingException uee1) {
-                               /* ignore, all JVMs can do UTF-8. */
                        }
                }
 
diff --git a/src/main/java/net/pterodactylus/rhynodge/filters/comics/package-info.java b/src/main/java/net/pterodactylus/rhynodge/filters/comics/package-info.java
new file mode 100644 (file)
index 0000000..d603e6f
--- /dev/null
@@ -0,0 +1,4 @@
+@NullMarked
+package net.pterodactylus.rhynodge.filters.comics;
+
+import org.jspecify.annotations.NullMarked;
diff --git a/src/main/java/net/pterodactylus/rhynodge/filters/package-info.java b/src/main/java/net/pterodactylus/rhynodge/filters/package-info.java
new file mode 100644 (file)
index 0000000..f04b606
--- /dev/null
@@ -0,0 +1,4 @@
+@NullMarked
+package net.pterodactylus.rhynodge.filters;
+
+import org.jspecify.annotations.NullMarked;
index 2b071c0..89a0723 100644 (file)
@@ -31,12 +31,12 @@ public class TorrentzEuFilter extends TorrentSiteFilter {
 
        @Override
        protected String extractMagnetUri(Element dataRow) {
-               return null;
+               return "";
        }
 
        @Override
        protected String extractDownloadUri(Element dataRow) {
-               return null;
+               return "";
        }
 
        @Override
diff --git a/src/main/java/net/pterodactylus/rhynodge/filters/torrents/package-info.java b/src/main/java/net/pterodactylus/rhynodge/filters/torrents/package-info.java
new file mode 100644 (file)
index 0000000..537938b
--- /dev/null
@@ -0,0 +1,4 @@
+@NullMarked
+package net.pterodactylus.rhynodge.filters.torrents;
+
+import org.jspecify.annotations.NullMarked;
index a58164d..a32f487 100644 (file)
@@ -23,5 +23,7 @@
  * previous state to define that action.
  */
 
+@NullMarked
 package net.pterodactylus.rhynodge;
 
+import org.jspecify.annotations.NullMarked;
index c1c315f..d068a1f 100644 (file)
@@ -35,6 +35,7 @@ import org.apache.http.client.protocol.ResponseContentEncoding;
 import org.apache.http.impl.client.HttpClientBuilder;
 import org.apache.http.params.HttpConnectionParams;
 import org.apache.http.util.EntityUtils;
+import org.jspecify.annotations.Nullable;
 
 /**
  * {@link Query} that performs an HTTP GET request to a fixed uri.
@@ -44,14 +45,14 @@ import org.apache.http.util.EntityUtils;
 public class HttpQuery implements Query {
 
        private final String uri;
-       private final String proxyHost;
+       private final @Nullable String proxyHost;
        private final int proxyPort;
 
        public HttpQuery(String uri) {
                this(uri, null, -1);
        }
 
-       public HttpQuery(String uri, String proxyHost, int proxyPort) {
+       public HttpQuery(String uri, @Nullable String proxyHost, int proxyPort) {
                this.uri = uri;
                this.proxyHost = proxyHost;
                this.proxyPort = proxyPort;
diff --git a/src/main/java/net/pterodactylus/rhynodge/queries/package-info.java b/src/main/java/net/pterodactylus/rhynodge/queries/package-info.java
new file mode 100644 (file)
index 0000000..48dc31e
--- /dev/null
@@ -0,0 +1,4 @@
+@NullMarked
+package net.pterodactylus.rhynodge.queries;
+
+import org.jspecify.annotations.NullMarked;
index 81e6a05..87cade6 100644 (file)
@@ -28,7 +28,6 @@ import net.pterodactylus.rhynodge.output.Output;
 
 import com.fasterxml.jackson.annotation.JsonProperty;
 import com.fasterxml.jackson.annotation.JsonTypeInfo;
-import org.jspecify.annotations.NonNull;
 import org.jspecify.annotations.Nullable;
 
 import static java.util.Collections.emptyMap;
@@ -54,7 +53,7 @@ public abstract class AbstractState implements State {
        private boolean triggered = false;
 
        /** The optional exception that occured while retrieving the state. */
-       private final Throwable exception;
+       private final @Nullable Throwable exception;
 
        /** The number of consecutive failures. */
        @JsonProperty
@@ -103,7 +102,7 @@ public abstract class AbstractState implements State {
         * @param exception
         *            The exception that occured while retrieving the state
         */
-       protected AbstractState(Throwable exception) {
+       protected AbstractState(@Nullable Throwable exception) {
                this(Clock.systemUTC(), exception);
        }
 
@@ -113,7 +112,7 @@ public abstract class AbstractState implements State {
         * @param clock The clock for generating {@link #time}
         * @param exception The exception that occured while retrieving the state
         */
-       protected AbstractState(Clock clock, Throwable exception) {
+       protected AbstractState(Clock clock, @Nullable Throwable exception) {
                this(clock, false, true, exception);
        }
 
@@ -126,7 +125,7 @@ public abstract class AbstractState implements State {
         * @param exception
         *            The exception that occured while retrieving the state
         */
-       protected AbstractState(boolean success, boolean empty, Throwable exception) {
+       protected AbstractState(boolean success, boolean empty, @Nullable Throwable exception) {
                this(Clock.systemUTC(), success, empty, exception);
        }
 
@@ -138,7 +137,7 @@ public abstract class AbstractState implements State {
         *              otherwise
         * @param exception The exception that occured while retrieving the state
         */
-       protected AbstractState(Clock clock, boolean success, boolean empty, Throwable exception) {
+       protected AbstractState(Clock clock, boolean success, boolean empty, @Nullable Throwable exception) {
                this.time = clock.millis();
                this.success = success;
                this.empty = empty;
@@ -199,12 +198,12 @@ public abstract class AbstractState implements State {
        /**
         * {@inheritDoc}
         */
+       @Nullable
        @Override
        public Throwable exception() {
                return exception;
        }
 
-       @NonNull
        @Override
        public Output output(Reaction reaction) {
                return new DefaultOutput(summary(reaction))
@@ -212,15 +211,12 @@ public abstract class AbstractState implements State {
                                .addText("text/html", htmlText());
        }
 
-       @NonNull
        protected String summary(Reaction reaction) {
                return reaction.name();
        }
 
-       @NonNull
        protected abstract String plainText();
 
-       @Nullable
        protected String htmlText() {
                var tagConsumer = createHTML(false, false);
                var div = new DIV(emptyMap(), tagConsumer);
index ad4874a..a08e9bf 100644 (file)
@@ -28,8 +28,6 @@ import java.util.Set;
 import kotlinx.html.FlowContent;
 import net.pterodactylus.rhynodge.Reaction;
 import net.pterodactylus.rhynodge.states.ComicState.Comic;
-import org.jspecify.annotations.NonNull;
-import org.jspecify.annotations.Nullable;
 
 import static java.lang.String.format;
 import static kotlinx.html.Gen_consumer_tagsKt.html;
@@ -90,13 +88,11 @@ public class ComicState extends AbstractState implements Iterable<Comic> {
                return format("ComicState[comics=%s]", comics());
        }
 
-       @NonNull
        @Override
        protected String summary(Reaction reaction) {
                return format("New Comic found for “%s!”", reaction.name());
        }
 
-       @NonNull
        @Override
        protected String plainText() {
                StringBuilder text = new StringBuilder();
@@ -117,7 +113,6 @@ public class ComicState extends AbstractState implements Iterable<Comic> {
                return text.toString();
        }
 
-       @Nullable
        @Override
        protected String htmlText() {
                List<Comic> latestComics = new ArrayList<>(comics());
index e266212..48f2dca 100644 (file)
@@ -36,7 +36,7 @@ import net.pterodactylus.rhynodge.states.EpisodeState.Episode;
 import net.pterodactylus.rhynodge.states.TorrentState.TorrentFile;
 
 import com.fasterxml.jackson.annotation.JsonProperty;
-import org.jspecify.annotations.NonNull;
+import org.jspecify.annotations.Nullable;
 
 import static java.util.stream.Collectors.groupingBy;
 import static java.util.stream.Collectors.toList;
@@ -116,7 +116,6 @@ public class EpisodeState extends AbstractState implements Iterable<Episode> {
                return Collections.unmodifiableCollection(episodes);
        }
 
-       @NonNull
        @Override
        protected String summary(Reaction reaction) {
                if (!newEpisodes.isEmpty()) {
@@ -128,7 +127,6 @@ public class EpisodeState extends AbstractState implements Iterable<Episode> {
                return String.format("%d changed Torrent(s) for “%s!”", changedEpisodes.size(), reaction.name());
        }
 
-       @NonNull
        @Override
        protected String plainText() {
                StringBuilder stringBuilder = new StringBuilder();
@@ -179,7 +177,6 @@ public class EpisodeState extends AbstractState implements Iterable<Episode> {
                return stringBuilder.toString();
        }
 
-       @NonNull
        @Override
        protected String htmlText() {
                var tagConsumer = createHTML(false, false);
@@ -199,7 +196,7 @@ public class EpisodeState extends AbstractState implements Iterable<Episode> {
                                                th(row, null, null, wrapper(th -> th.text("Download")));
                                        }))));
                                        tbody(table, null, wrapper(tableBody -> {
-                                               var lastEpisode = new AtomicReference<Episode>();
+                                               var lastEpisode = new AtomicReference<@Nullable Episode>();
                                                for (Map.Entry<Integer, List<Episode>> seasonEntry : episodes.stream().sorted(Comparator.<Episode>naturalOrder().reversed()).collect(groupingBy(Episode::season, LinkedHashMap::new, toList())).entrySet()) {
                                                        for (Episode episode : seasonEntry.getValue()) {
                                                                for (TorrentFile torrentFile : episode) {
index e29e526..dd04dcd 100644 (file)
@@ -23,7 +23,7 @@ import java.io.StringWriter;
 import java.io.Writer;
 
 import net.pterodactylus.rhynodge.State;
-import org.jspecify.annotations.NonNull;
+import org.jspecify.annotations.Nullable;
 
 /**
  * {@link State} implementation that signals failure.
@@ -48,7 +48,7 @@ public class FailedState extends AbstractState {
         * @param exception
         *            The exception of the state
         */
-       public FailedState(Throwable exception) {
+       public FailedState(@Nullable Throwable exception) {
                super(exception);
        }
 
@@ -57,7 +57,6 @@ public class FailedState extends AbstractState {
                return true;
        }
 
-       @NonNull
        @Override
        protected String plainText() {
                if (exception() == null) {
index 463278c..29ba819 100644 (file)
@@ -18,7 +18,6 @@
 package net.pterodactylus.rhynodge.states;
 
 import net.pterodactylus.rhynodge.State;
-import org.jspecify.annotations.NonNull;
 
 /**
  * A {@link State} that contains information about a file.
@@ -120,7 +119,6 @@ public class FileState extends AbstractState {
                return modificationTime;
        }
 
-       @NonNull
        @Override
        protected String plainText() {
                return toString();
index 644d5d0..6a2d49c 100644 (file)
@@ -20,8 +20,6 @@ package net.pterodactylus.rhynodge.states;
 import net.pterodactylus.rhynodge.State;
 
 import org.jsoup.nodes.Document;
-import org.jspecify.annotations.NonNull;
-import org.jspecify.annotations.Nullable;
 
 /**
  * {@link State} implementation that contains a parsed HTML {@link Document}.
@@ -76,14 +74,12 @@ public class HtmlState extends AbstractState {
                return document;
        }
 
-       @NonNull
        @Override
        protected String plainText() {
                //noinspection ConstantConditions
                return htmlText();
        }
 
-       @Nullable
        @Override
        protected String htmlText() {
                return document.toString();
index 5e62d73..78c7558 100644 (file)
@@ -27,7 +27,6 @@ import net.pterodactylus.rhynodge.queries.HttpQuery;
 import org.apache.http.HeaderElement;
 import org.apache.http.NameValuePair;
 import org.apache.http.message.BasicHeaderValueParser;
-import org.jspecify.annotations.NonNull;
 
 /**
  * {@link State} that contains the results of an {@link HttpQuery}.
@@ -127,7 +126,6 @@ public class HttpState extends AbstractState {
                }
        }
 
-       @NonNull
        @Override
        protected String plainText() {
                return content();
index 617ea27..fa4cbab 100644 (file)
@@ -3,8 +3,6 @@ package net.pterodactylus.rhynodge.states;
 import java.util.Optional;
 
 import net.pterodactylus.rhynodge.State;
-import org.jspecify.annotations.NonNull;
-import org.jspecify.annotations.Nullable;
 
 /**
  * {@link State} implementation that can expose itself as plain text and/or
@@ -31,16 +29,14 @@ public class OutputState extends AbstractState {
                return !plainTextOutput.isPresent() && !htmlOutput.isPresent();
        }
 
-       @NonNull
        @Override
        protected String plainText() {
                return plainTextOutput.orElse("");
        }
 
-       @Nullable
        @Override
        protected String htmlText() {
-               return htmlOutput.orElse(null);
+               return htmlOutput.orElse("");
        }
 
 }
index be089db..aac1818 100644 (file)
@@ -17,8 +17,6 @@
 
 package net.pterodactylus.rhynodge.states;
 
-import org.jspecify.annotations.NonNull;
-
 /**
  * A {@link net.pterodactylus.rhynodge.State} that stores a single {@link
  * String} value.
@@ -54,7 +52,6 @@ public class StringState extends AbstractState {
                return value.isEmpty();
        }
 
-       @NonNull
        @Override
        protected String plainText() {
                return value;
index 245c7cd..b2bc2f0 100644 (file)
@@ -30,16 +30,12 @@ import java.util.Set;
 
 import net.pterodactylus.rhynodge.Reaction;
 import net.pterodactylus.rhynodge.State;
-import net.pterodactylus.rhynodge.output.DefaultOutput;
-import net.pterodactylus.rhynodge.output.Output;
 import net.pterodactylus.rhynodge.states.TorrentState.TorrentFile;
 
 import com.fasterxml.jackson.annotation.JsonProperty;
 import org.apache.commons.lang3.StringEscapeUtils;
 import org.apache.http.NameValuePair;
 import org.apache.http.client.utils.URLEncodedUtils;
-import org.jspecify.annotations.NonNull;
-import org.jspecify.annotations.Nullable;
 
 import static java.lang.String.format;
 
@@ -114,13 +110,11 @@ public class TorrentState extends AbstractState implements Iterable<TorrentFile>
                return this;
        }
 
-       @NonNull
        @Override
        protected String summary(Reaction reaction) {
                return format("Found %d new Torrent(s) for “%s!”", newTorrentFiles.size(), reaction.name());
        }
 
-       @NonNull
        @Override
        protected String plainText() {
                StringBuilder plainText = new StringBuilder();
@@ -140,7 +134,6 @@ public class TorrentState extends AbstractState implements Iterable<TorrentFile>
                return plainText.toString();
        }
 
-       @Nullable
        @Override
        protected String htmlText() {
                StringBuilder htmlBuilder = new StringBuilder();
@@ -262,7 +255,7 @@ public class TorrentState extends AbstractState implements Iterable<TorrentFile>
                 */
                @SuppressWarnings("unused")
                private TorrentFile() {
-                       this(null, null, null, null, 0, 0, 0);
+                       this("", "", "", "", 0, 0, 0);
                }
 
                /**
diff --git a/src/main/java/net/pterodactylus/rhynodge/states/package-info.java b/src/main/java/net/pterodactylus/rhynodge/states/package-info.java
new file mode 100644 (file)
index 0000000..4647af9
--- /dev/null
@@ -0,0 +1,4 @@
+@NullMarked
+package net.pterodactylus.rhynodge.states;
+
+import org.jspecify.annotations.NullMarked;
index 777601a..8506a7c 100644 (file)
@@ -17,9 +17,9 @@
 
 package net.pterodactylus.rhynodge.watchers;
 
+import static java.nio.charset.StandardCharsets.UTF_8;
 import static net.pterodactylus.rhynodge.filters.BlacklistFilter.createDefaultBlacklistFilter;
 
-import java.io.UnsupportedEncodingException;
 import java.net.URLEncoder;
 import java.util.List;
 
@@ -63,12 +63,7 @@ public class KickAssTorrentsEpisodeWatcher extends DefaultWatcher {
         * @return The query of the watcher
         */
        private static Query createHttpQuery(String searchTerms) {
-               try {
-                       return new HttpQuery("https://kat.cr/usearch/" + URLEncoder.encode(searchTerms, "UTF-8") + "/?field=time_add&sorder=desc");
-               } catch (UnsupportedEncodingException uee1) {
-                       /* will not happen. */
-                       return null;
-               }
+               return new HttpQuery("https://kat.cr/usearch/" + URLEncoder.encode(searchTerms, UTF_8) + "/?field=time_add&sorder=desc");
        }
 
        /**
index aaa1e11..c4c076b 100644 (file)
@@ -17,9 +17,9 @@
 
 package net.pterodactylus.rhynodge.watchers;
 
+import static java.nio.charset.StandardCharsets.UTF_8;
 import static net.pterodactylus.rhynodge.filters.BlacklistFilter.createDefaultBlacklistFilter;
 
-import java.io.UnsupportedEncodingException;
 import java.net.URLEncoder;
 import java.util.List;
 
@@ -61,12 +61,7 @@ public class KickAssTorrentsWatcher extends DefaultWatcher {
         * @return The query of the watcher
         */
        private static Query createHttpQuery(String searchTerms) {
-               try {
-                       return new HttpQuery("https://kat.cr/usearch/" + URLEncoder.encode(searchTerms, "UTF-8") + "/?field=time_add&sorder=desc");
-               } catch (UnsupportedEncodingException uee1) {
-                       /* will not happen. */
-                       return null;
-               }
+               return new HttpQuery("https://kat.cr/usearch/" + URLEncoder.encode(searchTerms, UTF_8) + "/?field=time_add&sorder=desc");
        }
 
        /**
index 79d23ce..251d6a7 100644 (file)
@@ -17,9 +17,9 @@
 
 package net.pterodactylus.rhynodge.watchers;
 
+import static java.nio.charset.StandardCharsets.UTF_8;
 import static net.pterodactylus.rhynodge.filters.BlacklistFilter.createDefaultBlacklistFilter;
 
-import java.io.UnsupportedEncodingException;
 import java.net.URLEncoder;
 import java.util.List;
 
@@ -64,15 +64,10 @@ public class PirateBayEpisodeWatcher extends DefaultWatcher {
        //
 
        private static Query createHttpQuery(String searchTerms, String proxyHost, int proxyPort) {
-               try {
-                       HttpQuery hiddenServiceQuery = new HttpQuery("http://uj3wazyk5u4hnvtk.onion/search/" + URLEncoder.encode(searchTerms, "UTF-8") + "/0/3/0", proxyHost, proxyPort);
-                       HttpQuery torQuery = new HttpQuery("http://thepiratebay.org/search/" + URLEncoder.encode(searchTerms, "UTF-8") + "/0/3/0", proxyHost, proxyPort);
-                       HttpQuery plainInternetQuery = new HttpQuery("http://thepiratebay.org/search/" + URLEncoder.encode(searchTerms, "UTF-8") + "/0/3/0");
-                       return new FallbackQuery(hiddenServiceQuery, torQuery, plainInternetQuery);
-               } catch (UnsupportedEncodingException uee1) {
-                       /* will not happen. */
-                       return null;
-               }
+               HttpQuery hiddenServiceQuery = new HttpQuery("http://uj3wazyk5u4hnvtk.onion/search/" + URLEncoder.encode(searchTerms, UTF_8) + "/0/3/0", proxyHost, proxyPort);
+               HttpQuery torQuery = new HttpQuery("http://thepiratebay.org/search/" + URLEncoder.encode(searchTerms, UTF_8) + "/0/3/0", proxyHost, proxyPort);
+               HttpQuery plainInternetQuery = new HttpQuery("http://thepiratebay.org/search/" + URLEncoder.encode(searchTerms, UTF_8) + "/0/3/0");
+               return new FallbackQuery(hiddenServiceQuery, torQuery, plainInternetQuery);
        }
 
        /**
index 9c080d8..8e369df 100644 (file)
@@ -17,9 +17,9 @@
 
 package net.pterodactylus.rhynodge.watchers;
 
+import static java.nio.charset.StandardCharsets.UTF_8;
 import static net.pterodactylus.rhynodge.filters.BlacklistFilter.createDefaultBlacklistFilter;
 
-import java.io.UnsupportedEncodingException;
 import java.net.URLEncoder;
 import java.util.List;
 
@@ -70,15 +70,10 @@ public class PirateBayWatcher extends DefaultWatcher {
         * @return The query of the watcher
         */
        private static Query createHttpQuery(String searchTerms, String proxyHost, int proxyPort) {
-               try {
-                       HttpQuery hiddenServiceQuery = new HttpQuery("http://uj3wazyk5u4hnvtk.onion/search/" + URLEncoder.encode(searchTerms, "UTF-8") + "/0/3/0", proxyHost, proxyPort);
-                       HttpQuery torQuery = new HttpQuery("http://thepiratebay.org/search/" + URLEncoder.encode(searchTerms, "UTF-8") + "/0/3/0", proxyHost, proxyPort);
-                       HttpQuery plainInternetQuery = new HttpQuery("http://thepiratebay.org/search/" + URLEncoder.encode(searchTerms, "UTF-8") + "/0/3/0");
-                       return new FallbackQuery(hiddenServiceQuery, torQuery, plainInternetQuery);
-               } catch (UnsupportedEncodingException uee1) {
-                       /* will not happen. */
-                       return null;
-               }
+               HttpQuery hiddenServiceQuery = new HttpQuery("http://uj3wazyk5u4hnvtk.onion/search/" + URLEncoder.encode(searchTerms, UTF_8) + "/0/3/0", proxyHost, proxyPort);
+               HttpQuery torQuery = new HttpQuery("http://thepiratebay.org/search/" + URLEncoder.encode(searchTerms, UTF_8) + "/0/3/0", proxyHost, proxyPort);
+               HttpQuery plainInternetQuery = new HttpQuery("http://thepiratebay.org/search/" + URLEncoder.encode(searchTerms, UTF_8) + "/0/3/0");
+               return new FallbackQuery(hiddenServiceQuery, torQuery, plainInternetQuery);
        }
 
        /**
index 1b9d70f..95d8d24 100644 (file)
@@ -17,9 +17,9 @@
 
 package net.pterodactylus.rhynodge.watchers;
 
+import static java.nio.charset.StandardCharsets.UTF_8;
 import static net.pterodactylus.rhynodge.filters.BlacklistFilter.createDefaultBlacklistFilter;
 
-import java.io.UnsupportedEncodingException;
 import java.net.URLEncoder;
 import java.util.List;
 
@@ -62,12 +62,7 @@ public class TorrentHoundEpisodeWatcher extends DefaultWatcher {
         * @return The query of the watcher
         */
        private static Query createHttpQuery(String searchTerms) {
-               try {
-                       return new HttpQuery("http://www.torrenthound.com/search/1/" + URLEncoder.encode(searchTerms, "UTF-8") + "/added:desc");
-               } catch (UnsupportedEncodingException uee1) {
-                       /* will not happen. */
-                       return null;
-               }
+               return new HttpQuery("http://www.torrenthound.com/search/1/" + URLEncoder.encode(searchTerms, UTF_8) + "/added:desc");
        }
 
        /**
index d874e1a..47ed514 100644 (file)
@@ -17,9 +17,9 @@
 
 package net.pterodactylus.rhynodge.watchers;
 
+import static java.nio.charset.StandardCharsets.UTF_8;
 import static net.pterodactylus.rhynodge.filters.BlacklistFilter.createDefaultBlacklistFilter;
 
-import java.io.UnsupportedEncodingException;
 import java.net.URLEncoder;
 import java.util.List;
 
@@ -61,12 +61,7 @@ public class TorrentHoundWatcher extends DefaultWatcher {
         * @return The query of the watcher
         */
        private static Query createHttpQuery(String searchTerms) {
-               try {
-                       return new HttpQuery("http://www.torrenthound.com/search/1/" + URLEncoder.encode(searchTerms, "UTF-8") + "/added:desc");
-               } catch (UnsupportedEncodingException uee1) {
-                       /* will not happen. */
-                       return null;
-               }
+               return new HttpQuery("http://www.torrenthound.com/search/1/" + URLEncoder.encode(searchTerms, UTF_8) + "/added:desc");
        }
 
        /**
index 3c02dac..9a56c4e 100644 (file)
@@ -17,9 +17,9 @@
 
 package net.pterodactylus.rhynodge.watchers;
 
+import static java.nio.charset.StandardCharsets.UTF_8;
 import static net.pterodactylus.rhynodge.filters.BlacklistFilter.createDefaultBlacklistFilter;
 
-import java.io.UnsupportedEncodingException;
 import java.net.URLEncoder;
 import java.util.List;
 
@@ -62,12 +62,7 @@ public class TorrentzEuEpisodeWatcher extends DefaultWatcher {
         * @return The query of the watcher
         */
        private static Query createHttpQuery(String searchTerms) {
-               try {
-                       return new HttpQuery("http://torrentz.eu/searchA?q=" + URLEncoder.encode(searchTerms, "UTF-8"));
-               } catch (UnsupportedEncodingException uee1) {
-                       /* will not happen. */
-                       return null;
-               }
+               return new HttpQuery("http://torrentz.eu/searchA?q=" + URLEncoder.encode(searchTerms, UTF_8));
        }
 
        /**
index 0d267ba..967f926 100644 (file)
@@ -17,9 +17,9 @@
 
 package net.pterodactylus.rhynodge.watchers;
 
+import static java.nio.charset.StandardCharsets.UTF_8;
 import static net.pterodactylus.rhynodge.filters.BlacklistFilter.createDefaultBlacklistFilter;
 
-import java.io.UnsupportedEncodingException;
 import java.net.URLEncoder;
 import java.util.List;
 
@@ -61,12 +61,7 @@ public class TorrentzEuWatcher extends DefaultWatcher {
         * @return The query of the watcher
         */
        private static Query createHttpQuery(String searchTerms) {
-               try {
-                       return new HttpQuery("http://torrentz.eu/searchA?q=" + URLEncoder.encode(searchTerms, "UTF-8"));
-               } catch (UnsupportedEncodingException uee1) {
-                       /* will not happen. */
-                       return null;
-               }
+               return new HttpQuery("http://torrentz.eu/searchA?q=" + URLEncoder.encode(searchTerms, UTF_8));
        }
 
        /**
diff --git a/src/main/java/net/pterodactylus/rhynodge/watchers/package-info.java b/src/main/java/net/pterodactylus/rhynodge/watchers/package-info.java
new file mode 100644 (file)
index 0000000..caa4438
--- /dev/null
@@ -0,0 +1,4 @@
+@NullMarked
+package net.pterodactylus.rhynodge.watchers;
+
+import org.jspecify.annotations.NullMarked;
index fc474c1..b8fd9d5 100644 (file)
@@ -20,7 +20,7 @@ class MovieExtractor {
 
        private fun parseMovies(jsonNode: JsonNode) =
                jsonNode.get("movies").map { movieNode ->
-                       MovieJson(movieNode.get("id").asText(), movieNode.get("translations").get(0).get("name").asText().split("|").first().strip(), movieNode.get("slug").asText(), movieNode.get("poster").get("src").asText().fixImageUrl(), movieNode.get("translations").get(0).get("descLong").asText())
+                       MovieJson(movieNode.get("id").asText(), movieNode.get("translations").get(0).get("name").asText().split("|").first().trim(), movieNode.get("slug").asText(), movieNode.get("poster").get("src").asText().fixImageUrl(), movieNode.get("translations").get(0).get("descLong").asText())
                }
 
        private data class MovieJson(val id: String, val name: String, val slug: String, val poster: String, val description: String)
index d807bed..fb288ab 100644 (file)
@@ -7,6 +7,7 @@ import static org.hamcrest.Matchers.contains;
 import static org.hamcrest.Matchers.hasSize;
 import static org.hamcrest.Matchers.is;
 import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.mockito.Mockito.mock;
 
 import java.util.Collections;
 import java.util.List;
@@ -32,21 +33,21 @@ public class ComicSiteFilterTest {
        @Test
        public void comicSiteFilterRequiresHtmlState() {
                State state = new FailedState();
-               ComicSiteFilter comicSiteFilter = new TestComicSiteFilter(null, null, null);
+               ComicSiteFilter comicSiteFilter = new TestComicSiteFilter("", mock(), mock());
                assertThrows(IllegalArgumentException.class, () -> comicSiteFilter.filter(state));
        }
 
        @Test
        public void htmlWithoutImagesIsNotRecognizedAsComic() {
-               State state = new HtmlState("http://foo/", null);
-               ComicSiteFilter comicSiteFilter = new TestComicSiteFilter("Title", Collections.<String>emptyList(), null);
+               State state = new HtmlState("http://foo/", mock());
+               ComicSiteFilter comicSiteFilter = new TestComicSiteFilter("Title", Collections.<String>emptyList(), mock());
                State newState = comicSiteFilter.filter(state);
                assertThat(newState.success(), is(false));
        }
 
        @Test
        public void everyUrlGetAStrip() {
-               State state = new HtmlState("http://foo/", null);
+               State state = new HtmlState("http://foo/", mock());
                ComicSiteFilter comicSiteFilter = new TestComicSiteFilter("Title", asList("url1.gif", "url2.gif", "url3.gif"), asList("Comment 1", "Comment 2"));
                State newState = comicSiteFilter.filter(state);
                assertThat(newState instanceof ComicState, is(true));
@@ -58,7 +59,7 @@ public class ComicSiteFilterTest {
 
        @Test
        public void illegalUrlThrowsException() {
-               State state = new HtmlState("http://foo/^", null);
+               State state = new HtmlState("http://foo/^", mock());
                ComicSiteFilter comicSiteFilter = new TestComicSiteFilter("Title", asList("url1.gif"), Collections.<String>emptyList());
                assertThrows(IllegalStateException.class, () -> comicSiteFilter.filter(state));
        }
index 41ff5a8..f4d066e 100644 (file)
@@ -3,6 +3,7 @@ package net.pterodactylus.rhynodge.filters;
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.hamcrest.Matchers.is;
 import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.mockito.Mockito.mock;
 
 import net.pterodactylus.rhynodge.State;
 import net.pterodactylus.rhynodge.states.EpisodeState;
@@ -32,7 +33,7 @@ public class EpisodeFilterTest {
 
        @Test
        public void episodeFilterRequiresATorrentState() {
-               State state = new HtmlState("http://foo/", null);
+               State state = new HtmlState("http://foo/", mock());
                assertThrows(IllegalStateException.class, () -> episodeFilter.filter(state));
        }
 
diff --git a/src/test/java/net/pterodactylus/rhynodge/filters/package-info.java b/src/test/java/net/pterodactylus/rhynodge/filters/package-info.java
new file mode 100644 (file)
index 0000000..f04b606
--- /dev/null
@@ -0,0 +1,4 @@
+@NullMarked
+package net.pterodactylus.rhynodge.filters;
+
+import org.jspecify.annotations.NullMarked;
index a9d004f..c68278e 100644 (file)
@@ -20,6 +20,7 @@ import java.util.Optional.empty
 import java.util.Optional.of
 import java.util.concurrent.atomic.AtomicBoolean
 import java.util.concurrent.atomic.AtomicReference
+import org.mockito.Mockito.mock
 
 /**
  * Unit test for [ReactionRunner].
@@ -87,11 +88,11 @@ class ReactionRunnerTest {
                }, "Test Reaction")
 
        private fun createEmailAction(action: () -> Unit = {}) = object : EmailAction("test.test", "sender@test.test", "recipient@test.test") {
-               override fun execute(output: Output?) = action()
+               override fun execute(output: Output) = action()
        }
 
        private val reactionState = createReactionState()
-       private val failingReaction = object : Reaction("Test", null, null, null) {
+       private val failingReaction = object : Reaction("Test", mock(), mock(), mock()) {
                override fun query() = Query { FailedState() }
        }
        private val nullEmailAction = createEmailAction { }
index d89eaa1..7f8ff41 100644 (file)
@@ -8,6 +8,7 @@ import org.jsoup.Jsoup
 import org.junit.jupiter.api.Test
 import java.time.Instant
 import java.time.ZoneOffset
+import org.mockito.Mockito.mock
 
 class FreeGamesStateTest {
 
@@ -18,7 +19,7 @@ class FreeGamesStateTest {
 
        @Test
        fun `state lists all games in text output`() {
-               val output = state.output(Reaction("", null, null, null)).text("text/plain")
+               val output = state.output(Reaction("", mock(), mock(), mock())).text("text/plain")
                assertThat(
                        output, equalTo(
                                listOf(
@@ -32,7 +33,7 @@ class FreeGamesStateTest {
 
        @Test
        fun `state lists all games in HTML output`() {
-               val output = state.output(Reaction("", null, null, null)).text("text/html")
+               val output = state.output(Reaction("", mock(), mock(), mock())).text("text/html")
                val parsedOutput = Jsoup.parse(output)
                assertThat(
                        parsedOutput.select(".game").map {
index 6fb52cc..8db773c 100644 (file)
@@ -13,6 +13,7 @@ import org.jsoup.nodes.Element
 import org.jsoup.nodes.TextNode
 import org.junit.jupiter.api.Test
 import java.time.LocalDateTime
+import org.mockito.Mockito.mock
 
 class MovieStateTest {
 
@@ -25,7 +26,7 @@ class MovieStateTest {
                                Movie("3", "", "", listOf(Performance(LocalDateTime.of(2024, 2, 11, 21, 15), "", "")))
                        ), emptySet()
                )
-               assertThat(movieState.output(Reaction("", null, null, null)).summary(), containsString("2024-02-11"))
+               assertThat(movieState.output(Reaction("", mock(), mock(), mock())).summary(), containsString("2024-02-11"))
        }
 
        @Test
@@ -41,7 +42,7 @@ class MovieStateTest {
        @Test
        fun `html output does not contain a section for new movies if there are no new movies`() {
                val movieState = MovieState(emptySet(), emptySet())
-               val output = movieState.output(Reaction("", null, null, null))
+               val output = movieState.output(Reaction("", mock(), mock(), mock()))
                val document = Jsoup.parse(output.text("text/html"))
                assertThat(document.select("section.new-movies"), empty())
        }
@@ -49,7 +50,7 @@ class MovieStateTest {
        @Test
        fun `html output does contain a section for new movie if there are new movies`() {
                val movieState = MovieState(emptySet(), setOf(Movie("1", "", ""), Movie("2", "", "")))
-               val output = movieState.output(Reaction("", null, null, null))
+               val output = movieState.output(Reaction("", mock(), mock(), mock()))
                val document = Jsoup.parse(output.text("text/html"))
                assertThat(document.select("section.new-movies"), not(empty()))
        }
@@ -57,7 +58,7 @@ class MovieStateTest {
        @Test
        fun `new movies section contains description of movies`() {
                val movieState = MovieState(emptySet(), setOf(Movie("1", "", "Movie 1 is cool."), Movie("2", "", "Movie 2 is bad.")))
-               val output = movieState.output(Reaction("", null, null, null))
+               val output = movieState.output(Reaction("", mock(), mock(), mock()))
                val document = Jsoup.parse(output.text("text/html"))
                assertThat(document.select("section.new-movies .description").map(Element::text), contains("Movie 1 is cool.", "Movie 2 is bad."))
        }
@@ -65,7 +66,7 @@ class MovieStateTest {
        @Test
        fun `new movies section contains the titles of all new movies`() {
                val movieState = MovieState(emptySet(), setOf(Movie("New Movie", "", ""), Movie("Even Newer Movie", "", "")))
-               val output = movieState.output(Reaction("", null, null, null))
+               val output = movieState.output(Reaction("", mock(), mock(), mock()))
                val document = Jsoup.parse(output.text("text/html"))
                assertThat(document.select("section.new-movies li.movie .name").textNodes().map(TextNode::text), containsInAnyOrder("New Movie", "Even Newer Movie"))
        }
@@ -73,7 +74,7 @@ class MovieStateTest {
        @Test
        fun `html output contains section for the daily programme`() {
                val movieState = MovieState(emptySet(), emptySet())
-               val output = movieState.output(Reaction("", null, null, null))
+               val output = movieState.output(Reaction("", mock(), mock(), mock()))
                val document = Jsoup.parse(output.text("text/html"))
                assertThat(document.select("section.daily-programmes"), not(empty()))
        }
@@ -88,7 +89,7 @@ class MovieStateTest {
                                movie("Movie 4", "", "", "20240216-1000"),
                        ), emptySet()
                )
-               val output = movieState.output(Reaction("", null, null, null))
+               val output = movieState.output(Reaction("", mock(), mock(), mock()))
                val html = output.text("text/html")
                val document = Jsoup.parse(html)
                assertThat(document.select("section.daily-programmes li.day").map { it.attr("data-date") }, contains("2024-02-12", "2024-02-13", "2024-02-14", "2024-02-16"))
@@ -107,7 +108,7 @@ class MovieStateTest {
                                movie("Movie 2", "https://cdn.premiumkino.de/movie/1066/aba09af737677ff6a15676ae588098b1_w300.jpg", "", "20240212-1845", "20240213-1330", "20240214-1815")
                        )
                )
-               val output = movieState.output(Reaction("", null, null, null))
+               val output = movieState.output(Reaction("", mock(), mock(), mock()))
                val html = output.text("text/html")
                val document = Jsoup.parse(html)
                assertThat(
@@ -124,7 +125,7 @@ class MovieStateTest {
                                movie("Movie 1", "https://cdn.premiumkino.de/movie/3047/81c49774d7828a898ae1d525ffd135af_w300.jpg", "", "20240212-1845" to "2D OV", "20240213-1330" to "2D", "20240214-1815" to "2D OmeU"),
                        ), setOf()
                )
-               val output = movieState.output(Reaction("", null, null, null))
+               val output = movieState.output(Reaction("", mock(), mock(), mock()))
                val html = output.text("text/html")
                val document = Jsoup.parse(html)
                assertThat(
index 772904c..c1ef8c9 100644 (file)
@@ -17,6 +17,7 @@ import org.hamcrest.Matchers.containsString
 import org.hamcrest.Matchers.equalTo
 import org.hamcrest.Matchers.not
 import org.junit.jupiter.api.Test
+import org.mockito.Mockito.mock
 import org.xml.sax.InputSource
 
 class ComicStateTest {
@@ -189,4 +190,4 @@ class ComicStateTest {
 
 }
 
-private val reaction = Reaction("foo", null, null, null)
+private val reaction = Reaction("foo", mock(), mock(), mock())