From c89c27befbc5b3bf5e106d1904592ad7a01d7008 Mon Sep 17 00:00:00 2001 From: Jan Ouwens Date: Fri, 8 Mar 2019 08:50:03 +0100 Subject: [PATCH 001/106] Adds a build script --- .mvn/extensions.xml | 9 +++++++++ pom.yml | 21 +++++++++++++++++++++ 2 files changed, 30 insertions(+) create mode 100644 .mvn/extensions.xml create mode 100644 pom.yml diff --git a/.mvn/extensions.xml b/.mvn/extensions.xml new file mode 100644 index 0000000..808be56 --- /dev/null +++ b/.mvn/extensions.xml @@ -0,0 +1,9 @@ + + + + io.takari.polyglot + polyglot-yaml + 0.3.2 + + + diff --git a/pom.yml b/pom.yml new file mode 100644 index 0000000..371ed83 --- /dev/null +++ b/pom.yml @@ -0,0 +1,21 @@ +modelVersion: 4.0.0 +groupId: nl.jqno.paralleljava +artifactId: parallel-java +version: 1.0 +packaging: jar + +name: parallel-java +description: "Todo-Backend built with Java from a Parallel Universe: a demo project to illustrate the point of my talk 'Java from a Parallel Universe'" + +properties: { + encoding: utf-8, + maven.compiler.source: 11, + maven.compiler.target: 11 +} + +dependencies: + - { groupId: io.vavr, artifactId: vavr, version: 0.9.3 } + - { groupId: com.sparkjava, artifactId: spark-core, version: 2.7.2 } + - { groupId: org.slf4j, artifactId: slf4j-simple, version: 1.7.26 } + - { groupId: org.specsy, artifactId: specsy-java, version: 2.3.3, scope: test } + From ea1889cf1e64e9b673d943f978e7bbdb36bdcea3 Mon Sep 17 00:00:00 2001 From: Jan Ouwens Date: Fri, 8 Mar 2019 12:58:06 +0100 Subject: [PATCH 002/106] Adds Hello World app --- src/main/java/nl/jqno/paralleljava/Main.java | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 src/main/java/nl/jqno/paralleljava/Main.java diff --git a/src/main/java/nl/jqno/paralleljava/Main.java b/src/main/java/nl/jqno/paralleljava/Main.java new file mode 100644 index 0000000..94d9859 --- /dev/null +++ b/src/main/java/nl/jqno/paralleljava/Main.java @@ -0,0 +1,9 @@ +package nl.jqno.paralleljava; + +import static spark.Spark.*; + +public class Main { + public static void main(String... args) { + get("/hello", (req, res) -> "Hello World!"); + } +} From 6879ae40d218084f4c728240de82926115f36675 Mon Sep 17 00:00:00 2001 From: Jan Ouwens Date: Sat, 9 Mar 2019 09:26:34 +0100 Subject: [PATCH 003/106] Enables Heroku deployment --- Procfile | 1 + deploy | 3 +++ pom.yml | 13 +++++++++++++ src/main/java/nl/jqno/paralleljava/Main.java | 9 +++++++++ system.properties | 1 + 5 files changed, 27 insertions(+) create mode 100644 Procfile create mode 100755 deploy create mode 100644 system.properties diff --git a/Procfile b/Procfile new file mode 100644 index 0000000..f726b4f --- /dev/null +++ b/Procfile @@ -0,0 +1 @@ +web: java $JAVA_OPTS -jar ./target/parallel-java-1.0-jar-with-dependencies.jar diff --git a/deploy b/deploy new file mode 100755 index 0000000..4d3e1bb --- /dev/null +++ b/deploy @@ -0,0 +1,3 @@ +#!/usr/bin/env bash + +git push heroku master diff --git a/pom.yml b/pom.yml index 371ed83..395f434 100644 --- a/pom.yml +++ b/pom.yml @@ -19,3 +19,16 @@ dependencies: - { groupId: org.slf4j, artifactId: slf4j-simple, version: 1.7.26 } - { groupId: org.specsy, artifactId: specsy-java, version: 2.3.3, scope: test } +build: + plugins: + - groupId: org.apache.maven.plugins + artifactId: maven-assembly-plugin + configuration: + descriptorRefs: [jar-with-dependencies] + archive: + manifest: + mainClass: nl.jqno.paralleljava.Main + executions: + - id: default + goals: [single] + phase: package diff --git a/src/main/java/nl/jqno/paralleljava/Main.java b/src/main/java/nl/jqno/paralleljava/Main.java index 94d9859..acf8082 100644 --- a/src/main/java/nl/jqno/paralleljava/Main.java +++ b/src/main/java/nl/jqno/paralleljava/Main.java @@ -4,6 +4,15 @@ public class Main { public static void main(String... args) { + port(getHerokuAssignedPort()); get("/hello", (req, res) -> "Hello World!"); } + + public static int getHerokuAssignedPort() { + ProcessBuilder processBuilder = new ProcessBuilder(); + if (processBuilder.environment().get("PORT") != null) { + return Integer.parseInt(processBuilder.environment().get("PORT")); + } + return 4567; //return default port if heroku-port isn't set (i.e. on localhost) + } } diff --git a/system.properties b/system.properties new file mode 100644 index 0000000..9146af5 --- /dev/null +++ b/system.properties @@ -0,0 +1 @@ +java.runtime.version=11 From dbdf7b7e843813ce53779b7dfbb7921c1141b5ad Mon Sep 17 00:00:00 2001 From: Jan Ouwens Date: Sat, 9 Mar 2019 09:57:26 +0100 Subject: [PATCH 004/106] Adds logging --- src/main/java/nl/jqno/paralleljava/Main.java | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/main/java/nl/jqno/paralleljava/Main.java b/src/main/java/nl/jqno/paralleljava/Main.java index acf8082..448a726 100644 --- a/src/main/java/nl/jqno/paralleljava/Main.java +++ b/src/main/java/nl/jqno/paralleljava/Main.java @@ -1,10 +1,18 @@ package nl.jqno.paralleljava; -import static spark.Spark.*; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import static spark.Spark.get; +import static spark.Spark.port; public class Main { + private static final Logger log = LoggerFactory.getLogger(Main.class); + public static void main(String... args) { - port(getHerokuAssignedPort()); + var port = getHerokuAssignedPort(); + log.info("Started on port " + port); + port(port); get("/hello", (req, res) -> "Hello World!"); } From 69776a4edddd903059562cfd6aebfbbbc25ad071 Mon Sep 17 00:00:00 2001 From: Jan Ouwens Date: Sun, 10 Mar 2019 09:53:58 +0100 Subject: [PATCH 005/106] Adds Travis CI script --- .travis.yml | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 .travis.yml diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..0ff3e10 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,3 @@ +language: java +jdk: openjdk11 +script: mvn clean verify \ No newline at end of file From ce96cc2b20cbd7d639be48468beb3ed5bb6191ce Mon Sep 17 00:00:00 2001 From: Jan Ouwens Date: Sun, 10 Mar 2019 12:17:40 +0100 Subject: [PATCH 006/106] Disallows annotations --- checkstyle.xml | 16 ++++++++++++++++ pom.yml | 17 +++++++++++++++++ 2 files changed, 33 insertions(+) create mode 100644 checkstyle.xml diff --git a/checkstyle.xml b/checkstyle.xml new file mode 100644 index 0000000..d9c3a49 --- /dev/null +++ b/checkstyle.xml @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/pom.yml b/pom.yml index 395f434..4a6de81 100644 --- a/pom.yml +++ b/pom.yml @@ -32,3 +32,20 @@ build: - id: default goals: [single] phase: package + + - groupId: org.apache.maven.plugins + artifactId: maven-checkstyle-plugin + version: 3.0.0 + dependencies: + - { groupId: com.puppycrawl.tools, artifactId: checkstyle, version: 8.18 } + configuration: + configLocation: checkstyle.xml + includeTestSourceDirectory: true + encoding: UTF-8 + consoleOutput: true + executions: + - id: default + goals: [check] + phase: verify + configuration: + failsOnError: true From 244b0816b242c227e69a836cf7766f639606c944 Mon Sep 17 00:00:00 2001 From: Jan Ouwens Date: Mon, 11 Mar 2019 08:49:05 +0100 Subject: [PATCH 007/106] Modularise this bad boy --- Procfile | 2 +- pom.yml | 17 +++++++++-------- src/main/java/module-info.java | 6 ++++++ .../nl/jqno/paralleljava/{ => main}/Main.java | 2 +- 4 files changed, 17 insertions(+), 10 deletions(-) create mode 100644 src/main/java/module-info.java rename src/main/java/nl/jqno/paralleljava/{ => main}/Main.java (95%) diff --git a/Procfile b/Procfile index f726b4f..076c38c 100644 --- a/Procfile +++ b/Procfile @@ -1 +1 @@ -web: java $JAVA_OPTS -jar ./target/parallel-java-1.0-jar-with-dependencies.jar +web: java $JAVA_OPTS -p ./target/parallel-java-1.0.jar:./target/dependency -m nl.jqno.paralleljava/nl.jqno.paralleljava.main.Main diff --git a/pom.yml b/pom.yml index 4a6de81..4fa8d0b 100644 --- a/pom.yml +++ b/pom.yml @@ -22,16 +22,16 @@ dependencies: build: plugins: - groupId: org.apache.maven.plugins - artifactId: maven-assembly-plugin - configuration: - descriptorRefs: [jar-with-dependencies] - archive: - manifest: - mainClass: nl.jqno.paralleljava.Main + artifactId: maven-compiler-plugin + version: 3.8.0 + + - groupId: org.apache.maven.plugins + artifactId: maven-dependency-plugin + version: 3.1.1 executions: - id: default - goals: [single] phase: package + goals: [copy-dependencies] - groupId: org.apache.maven.plugins artifactId: maven-checkstyle-plugin @@ -43,9 +43,10 @@ build: includeTestSourceDirectory: true encoding: UTF-8 consoleOutput: true + excludes: "**/module-info.java" executions: - id: default - goals: [check] phase: verify + goals: [check] configuration: failsOnError: true diff --git a/src/main/java/module-info.java b/src/main/java/module-info.java new file mode 100644 index 0000000..94bd4b8 --- /dev/null +++ b/src/main/java/module-info.java @@ -0,0 +1,6 @@ +module nl.jqno.paralleljava { + exports nl.jqno.paralleljava.main; + + requires slf4j.api; + requires spark.core; +} \ No newline at end of file diff --git a/src/main/java/nl/jqno/paralleljava/Main.java b/src/main/java/nl/jqno/paralleljava/main/Main.java similarity index 95% rename from src/main/java/nl/jqno/paralleljava/Main.java rename to src/main/java/nl/jqno/paralleljava/main/Main.java index 448a726..b66c1c9 100644 --- a/src/main/java/nl/jqno/paralleljava/Main.java +++ b/src/main/java/nl/jqno/paralleljava/main/Main.java @@ -1,4 +1,4 @@ -package nl.jqno.paralleljava; +package nl.jqno.paralleljava.main; import org.slf4j.Logger; import org.slf4j.LoggerFactory; From 2a3cfe70e364d2dc1dd627e7c56a702a92e0a9cd Mon Sep 17 00:00:00 2001 From: Jan Ouwens Date: Mon, 11 Mar 2019 16:17:24 +0100 Subject: [PATCH 008/106] Applies architecture --- src/main/java/module-info.java | 1 + .../paralleljava/endpoints/Endpoints.java | 8 +++++ .../nl/jqno/paralleljava/endpoints/Route.java | 7 +++++ .../nl/jqno/paralleljava/internal/Heroku.java | 22 ++++++++++++++ .../paralleljava/internal/SparkServer.java | 28 +++++++++++++++++ .../java/nl/jqno/paralleljava/main/Main.java | 30 +++++++++++-------- 6 files changed, 83 insertions(+), 13 deletions(-) create mode 100644 src/main/java/nl/jqno/paralleljava/endpoints/Endpoints.java create mode 100644 src/main/java/nl/jqno/paralleljava/endpoints/Route.java create mode 100644 src/main/java/nl/jqno/paralleljava/internal/Heroku.java create mode 100644 src/main/java/nl/jqno/paralleljava/internal/SparkServer.java diff --git a/src/main/java/module-info.java b/src/main/java/module-info.java index 94bd4b8..acf4340 100644 --- a/src/main/java/module-info.java +++ b/src/main/java/module-info.java @@ -1,6 +1,7 @@ module nl.jqno.paralleljava { exports nl.jqno.paralleljava.main; + requires io.vavr; requires slf4j.api; requires spark.core; } \ No newline at end of file diff --git a/src/main/java/nl/jqno/paralleljava/endpoints/Endpoints.java b/src/main/java/nl/jqno/paralleljava/endpoints/Endpoints.java new file mode 100644 index 0000000..fda30f1 --- /dev/null +++ b/src/main/java/nl/jqno/paralleljava/endpoints/Endpoints.java @@ -0,0 +1,8 @@ +package nl.jqno.paralleljava.endpoints; + +public class Endpoints { + + public Route helloWorld() { + return ignored -> "Hello world"; + } +} diff --git a/src/main/java/nl/jqno/paralleljava/endpoints/Route.java b/src/main/java/nl/jqno/paralleljava/endpoints/Route.java new file mode 100644 index 0000000..5947ad0 --- /dev/null +++ b/src/main/java/nl/jqno/paralleljava/endpoints/Route.java @@ -0,0 +1,7 @@ +package nl.jqno.paralleljava.endpoints; + +import io.vavr.collection.Map; + +public interface Route { + String handle(Map request); +} diff --git a/src/main/java/nl/jqno/paralleljava/internal/Heroku.java b/src/main/java/nl/jqno/paralleljava/internal/Heroku.java new file mode 100644 index 0000000..a2dbc29 --- /dev/null +++ b/src/main/java/nl/jqno/paralleljava/internal/Heroku.java @@ -0,0 +1,22 @@ +package nl.jqno.paralleljava.internal; + +import io.vavr.control.Option; +import io.vavr.control.Try; + +public class Heroku { + + private ProcessBuilder processBuilder; + + public Heroku(ProcessBuilder processBuilder) { + this.processBuilder = processBuilder; + } + + public Option getAssignedPort() { + var port = Option.of(processBuilder.environment().get("PORT")); + return port.flatMap(this::parse); + } + + private Option parse(String port) { + return Try.of(() -> Integer.parseInt(port)).toOption(); + } +} diff --git a/src/main/java/nl/jqno/paralleljava/internal/SparkServer.java b/src/main/java/nl/jqno/paralleljava/internal/SparkServer.java new file mode 100644 index 0000000..a12c574 --- /dev/null +++ b/src/main/java/nl/jqno/paralleljava/internal/SparkServer.java @@ -0,0 +1,28 @@ +package nl.jqno.paralleljava.internal; + +import io.vavr.collection.HashMap; +import nl.jqno.paralleljava.endpoints.Endpoints; +import nl.jqno.paralleljava.endpoints.Route; + +import static spark.Spark.get; +import static spark.Spark.port; + +public class SparkServer { + + private Endpoints endpoints; + private int port; + + public SparkServer(Endpoints endpoints, int port) { + this.endpoints = endpoints; + this.port = port; + } + + public void run() { + port(port); + get("/hello", convert(endpoints.helloWorld())); + } + + private spark.Route convert(Route route) { + return (request, response) -> route.handle(HashMap.ofAll(request.params())); + } +} diff --git a/src/main/java/nl/jqno/paralleljava/main/Main.java b/src/main/java/nl/jqno/paralleljava/main/Main.java index b66c1c9..61f8c8c 100644 --- a/src/main/java/nl/jqno/paralleljava/main/Main.java +++ b/src/main/java/nl/jqno/paralleljava/main/Main.java @@ -1,26 +1,30 @@ package nl.jqno.paralleljava.main; +import nl.jqno.paralleljava.endpoints.Endpoints; +import nl.jqno.paralleljava.internal.Heroku; +import nl.jqno.paralleljava.internal.SparkServer; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import static spark.Spark.get; -import static spark.Spark.port; - public class Main { private static final Logger log = LoggerFactory.getLogger(Main.class); + private static final int DEFAULT_PORT = 4567; + public static void main(String... args) { - var port = getHerokuAssignedPort(); - log.info("Started on port " + port); - port(port); - get("/hello", (req, res) -> "Hello World!"); + Integer port = getPort(); + + var endpoints = new Endpoints(); + var server = new SparkServer(endpoints, port); + + server.run(); } - public static int getHerokuAssignedPort() { - ProcessBuilder processBuilder = new ProcessBuilder(); - if (processBuilder.environment().get("PORT") != null) { - return Integer.parseInt(processBuilder.environment().get("PORT")); - } - return 4567; //return default port if heroku-port isn't set (i.e. on localhost) + private static Integer getPort() { + var processBuilder = new ProcessBuilder(); + var heroku = new Heroku(processBuilder); + var port = heroku.getAssignedPort().getOrElse(DEFAULT_PORT); + log.info("Starting on port " + port); + return port; } } From d04d16c39aa4a33c35501219d69b5e30c4433be3 Mon Sep 17 00:00:00 2001 From: Jan Ouwens Date: Tue, 12 Mar 2019 07:54:45 +0100 Subject: [PATCH 009/106] Applies more architecture --- Procfile | 2 +- src/main/java/module-info.java | 2 +- src/main/java/nl/jqno/paralleljava/Main.java | 9 +++++ .../{ => app}/endpoints/Endpoints.java | 2 +- .../{ => app}/endpoints/Route.java | 2 +- .../{internal => app/server}/Heroku.java | 2 +- .../jqno/paralleljava/app/server/Server.java | 5 +++ .../{internal => app/server}/SparkServer.java | 8 ++-- .../dependencyinjection/WiredApplication.java | 39 +++++++++++++++++++ .../java/nl/jqno/paralleljava/main/Main.java | 30 -------------- 10 files changed, 62 insertions(+), 39 deletions(-) create mode 100644 src/main/java/nl/jqno/paralleljava/Main.java rename src/main/java/nl/jqno/paralleljava/{ => app}/endpoints/Endpoints.java (71%) rename src/main/java/nl/jqno/paralleljava/{ => app}/endpoints/Route.java (71%) rename src/main/java/nl/jqno/paralleljava/{internal => app/server}/Heroku.java (92%) create mode 100644 src/main/java/nl/jqno/paralleljava/app/server/Server.java rename src/main/java/nl/jqno/paralleljava/{internal => app/server}/SparkServer.java (73%) create mode 100644 src/main/java/nl/jqno/paralleljava/dependencyinjection/WiredApplication.java delete mode 100644 src/main/java/nl/jqno/paralleljava/main/Main.java diff --git a/Procfile b/Procfile index 076c38c..c30ee04 100644 --- a/Procfile +++ b/Procfile @@ -1 +1 @@ -web: java $JAVA_OPTS -p ./target/parallel-java-1.0.jar:./target/dependency -m nl.jqno.paralleljava/nl.jqno.paralleljava.main.Main +web: java $JAVA_OPTS -p ./target/parallel-java-1.0.jar:./target/dependency -m nl.jqno.paralleljava/nl.jqno.paralleljava.Main diff --git a/src/main/java/module-info.java b/src/main/java/module-info.java index acf4340..1de53e6 100644 --- a/src/main/java/module-info.java +++ b/src/main/java/module-info.java @@ -1,5 +1,5 @@ module nl.jqno.paralleljava { - exports nl.jqno.paralleljava.main; + exports nl.jqno.paralleljava; requires io.vavr; requires slf4j.api; diff --git a/src/main/java/nl/jqno/paralleljava/Main.java b/src/main/java/nl/jqno/paralleljava/Main.java new file mode 100644 index 0000000..f4ae3a7 --- /dev/null +++ b/src/main/java/nl/jqno/paralleljava/Main.java @@ -0,0 +1,9 @@ +package nl.jqno.paralleljava; + +import nl.jqno.paralleljava.dependencyinjection.WiredApplication; + +public class Main { + public static void main(String... args) { + new WiredApplication().run(); + } +} diff --git a/src/main/java/nl/jqno/paralleljava/endpoints/Endpoints.java b/src/main/java/nl/jqno/paralleljava/app/endpoints/Endpoints.java similarity index 71% rename from src/main/java/nl/jqno/paralleljava/endpoints/Endpoints.java rename to src/main/java/nl/jqno/paralleljava/app/endpoints/Endpoints.java index fda30f1..47d7727 100644 --- a/src/main/java/nl/jqno/paralleljava/endpoints/Endpoints.java +++ b/src/main/java/nl/jqno/paralleljava/app/endpoints/Endpoints.java @@ -1,4 +1,4 @@ -package nl.jqno.paralleljava.endpoints; +package nl.jqno.paralleljava.app.endpoints; public class Endpoints { diff --git a/src/main/java/nl/jqno/paralleljava/endpoints/Route.java b/src/main/java/nl/jqno/paralleljava/app/endpoints/Route.java similarity index 71% rename from src/main/java/nl/jqno/paralleljava/endpoints/Route.java rename to src/main/java/nl/jqno/paralleljava/app/endpoints/Route.java index 5947ad0..5149572 100644 --- a/src/main/java/nl/jqno/paralleljava/endpoints/Route.java +++ b/src/main/java/nl/jqno/paralleljava/app/endpoints/Route.java @@ -1,4 +1,4 @@ -package nl.jqno.paralleljava.endpoints; +package nl.jqno.paralleljava.app.endpoints; import io.vavr.collection.Map; diff --git a/src/main/java/nl/jqno/paralleljava/internal/Heroku.java b/src/main/java/nl/jqno/paralleljava/app/server/Heroku.java similarity index 92% rename from src/main/java/nl/jqno/paralleljava/internal/Heroku.java rename to src/main/java/nl/jqno/paralleljava/app/server/Heroku.java index a2dbc29..b1de73a 100644 --- a/src/main/java/nl/jqno/paralleljava/internal/Heroku.java +++ b/src/main/java/nl/jqno/paralleljava/app/server/Heroku.java @@ -1,4 +1,4 @@ -package nl.jqno.paralleljava.internal; +package nl.jqno.paralleljava.app.server; import io.vavr.control.Option; import io.vavr.control.Try; diff --git a/src/main/java/nl/jqno/paralleljava/app/server/Server.java b/src/main/java/nl/jqno/paralleljava/app/server/Server.java new file mode 100644 index 0000000..681577f --- /dev/null +++ b/src/main/java/nl/jqno/paralleljava/app/server/Server.java @@ -0,0 +1,5 @@ +package nl.jqno.paralleljava.app.server; + +public interface Server { + void run(); +} diff --git a/src/main/java/nl/jqno/paralleljava/internal/SparkServer.java b/src/main/java/nl/jqno/paralleljava/app/server/SparkServer.java similarity index 73% rename from src/main/java/nl/jqno/paralleljava/internal/SparkServer.java rename to src/main/java/nl/jqno/paralleljava/app/server/SparkServer.java index a12c574..1c545de 100644 --- a/src/main/java/nl/jqno/paralleljava/internal/SparkServer.java +++ b/src/main/java/nl/jqno/paralleljava/app/server/SparkServer.java @@ -1,13 +1,13 @@ -package nl.jqno.paralleljava.internal; +package nl.jqno.paralleljava.app.server; import io.vavr.collection.HashMap; -import nl.jqno.paralleljava.endpoints.Endpoints; -import nl.jqno.paralleljava.endpoints.Route; +import nl.jqno.paralleljava.app.endpoints.Endpoints; +import nl.jqno.paralleljava.app.endpoints.Route; import static spark.Spark.get; import static spark.Spark.port; -public class SparkServer { +public class SparkServer implements Server { private Endpoints endpoints; private int port; diff --git a/src/main/java/nl/jqno/paralleljava/dependencyinjection/WiredApplication.java b/src/main/java/nl/jqno/paralleljava/dependencyinjection/WiredApplication.java new file mode 100644 index 0000000..3ee2882 --- /dev/null +++ b/src/main/java/nl/jqno/paralleljava/dependencyinjection/WiredApplication.java @@ -0,0 +1,39 @@ +package nl.jqno.paralleljava.dependencyinjection; + +import nl.jqno.paralleljava.app.endpoints.Endpoints; +import nl.jqno.paralleljava.app.server.Heroku; +import nl.jqno.paralleljava.Main; +import nl.jqno.paralleljava.app.server.Server; +import nl.jqno.paralleljava.app.server.SparkServer; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class WiredApplication { + + private static final Logger log = LoggerFactory.getLogger(Main.class); + private static final int DEFAULT_PORT = 4567; + + private final Server server; + + public WiredApplication() { + server = createServer(); + } + + public void run() { + server.run(); + } + + private static Server createServer() { + int port = getPort(); + var endpoints = new Endpoints(); + return new SparkServer(endpoints, port); + } + + private static int getPort() { + var processBuilder = new ProcessBuilder(); + var heroku = new Heroku(processBuilder); + var port = heroku.getAssignedPort().getOrElse(DEFAULT_PORT); + log.info("Starting on port " + port); + return port; + } +} diff --git a/src/main/java/nl/jqno/paralleljava/main/Main.java b/src/main/java/nl/jqno/paralleljava/main/Main.java deleted file mode 100644 index 61f8c8c..0000000 --- a/src/main/java/nl/jqno/paralleljava/main/Main.java +++ /dev/null @@ -1,30 +0,0 @@ -package nl.jqno.paralleljava.main; - -import nl.jqno.paralleljava.endpoints.Endpoints; -import nl.jqno.paralleljava.internal.Heroku; -import nl.jqno.paralleljava.internal.SparkServer; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -public class Main { - private static final Logger log = LoggerFactory.getLogger(Main.class); - - private static final int DEFAULT_PORT = 4567; - - public static void main(String... args) { - Integer port = getPort(); - - var endpoints = new Endpoints(); - var server = new SparkServer(endpoints, port); - - server.run(); - } - - private static Integer getPort() { - var processBuilder = new ProcessBuilder(); - var heroku = new Heroku(processBuilder); - var port = heroku.getAssignedPort().getOrElse(DEFAULT_PORT); - log.info("Starting on port " + port); - return port; - } -} From d28d955bf54543eb4e240df08aa025cd911a1d30 Mon Sep 17 00:00:00 2001 From: Jan Ouwens Date: Tue, 12 Mar 2019 08:18:01 +0100 Subject: [PATCH 010/106] Applies architecture to logger --- .../jqno/paralleljava/app/logging/Logger.java | 10 ++++++ .../paralleljava/app/logging/Slf4jLogger.java | 35 +++++++++++++++++++ .../paralleljava/app/server/SparkServer.java | 10 ++++-- .../dependencyinjection/WiredApplication.java | 16 ++++----- 4 files changed, 60 insertions(+), 11 deletions(-) create mode 100644 src/main/java/nl/jqno/paralleljava/app/logging/Logger.java create mode 100644 src/main/java/nl/jqno/paralleljava/app/logging/Slf4jLogger.java diff --git a/src/main/java/nl/jqno/paralleljava/app/logging/Logger.java b/src/main/java/nl/jqno/paralleljava/app/logging/Logger.java new file mode 100644 index 0000000..5fb859c --- /dev/null +++ b/src/main/java/nl/jqno/paralleljava/app/logging/Logger.java @@ -0,0 +1,10 @@ +package nl.jqno.paralleljava.app.logging; + +public interface Logger { + void forDevelopment(String message); + void forProduction(String message); + void firstThingNextMorning(String message); + void firstThingNextMorning(String message, Throwable t); + void wakeMeUp(String message); + void wakeMeUp(String message, Throwable t); +} diff --git a/src/main/java/nl/jqno/paralleljava/app/logging/Slf4jLogger.java b/src/main/java/nl/jqno/paralleljava/app/logging/Slf4jLogger.java new file mode 100644 index 0000000..3f52a96 --- /dev/null +++ b/src/main/java/nl/jqno/paralleljava/app/logging/Slf4jLogger.java @@ -0,0 +1,35 @@ +package nl.jqno.paralleljava.app.logging; + +import org.slf4j.LoggerFactory; + +public class Slf4jLogger implements Logger { + private final org.slf4j.Logger logger; + + public Slf4jLogger(Class c) { + this.logger = LoggerFactory.getLogger(c); + } + + public void forDevelopment(String message) { + logger.debug(message); + } + + public void forProduction(String message) { + logger.info(message); + } + + public void firstThingNextMorning(String message) { + logger.warn(message); + } + + public void firstThingNextMorning(String message, Throwable t) { + logger.warn(message, t); + } + + public void wakeMeUp(String message) { + logger.error(message); + } + + public void wakeMeUp(String message, Throwable t) { + logger.error(message, t); + } +} diff --git a/src/main/java/nl/jqno/paralleljava/app/server/SparkServer.java b/src/main/java/nl/jqno/paralleljava/app/server/SparkServer.java index 1c545de..c4f4f91 100644 --- a/src/main/java/nl/jqno/paralleljava/app/server/SparkServer.java +++ b/src/main/java/nl/jqno/paralleljava/app/server/SparkServer.java @@ -3,21 +3,25 @@ import io.vavr.collection.HashMap; import nl.jqno.paralleljava.app.endpoints.Endpoints; import nl.jqno.paralleljava.app.endpoints.Route; +import nl.jqno.paralleljava.app.logging.Logger; import static spark.Spark.get; import static spark.Spark.port; public class SparkServer implements Server { - private Endpoints endpoints; - private int port; + private final Endpoints endpoints; + private final int port; + private final Logger logger; - public SparkServer(Endpoints endpoints, int port) { + public SparkServer(Endpoints endpoints, int port, Logger logger) { this.endpoints = endpoints; this.port = port; + this.logger = logger; } public void run() { + logger.forProduction("Starting on port " + port); port(port); get("/hello", convert(endpoints.helloWorld())); } diff --git a/src/main/java/nl/jqno/paralleljava/dependencyinjection/WiredApplication.java b/src/main/java/nl/jqno/paralleljava/dependencyinjection/WiredApplication.java index 3ee2882..d1dc09e 100644 --- a/src/main/java/nl/jqno/paralleljava/dependencyinjection/WiredApplication.java +++ b/src/main/java/nl/jqno/paralleljava/dependencyinjection/WiredApplication.java @@ -1,39 +1,39 @@ package nl.jqno.paralleljava.dependencyinjection; +import io.vavr.Function1; import nl.jqno.paralleljava.app.endpoints.Endpoints; +import nl.jqno.paralleljava.app.logging.Logger; +import nl.jqno.paralleljava.app.logging.Slf4jLogger; import nl.jqno.paralleljava.app.server.Heroku; -import nl.jqno.paralleljava.Main; import nl.jqno.paralleljava.app.server.Server; import nl.jqno.paralleljava.app.server.SparkServer; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; public class WiredApplication { - private static final Logger log = LoggerFactory.getLogger(Main.class); private static final int DEFAULT_PORT = 4567; + private final Function1, Logger> loggerFactory; private final Server server; public WiredApplication() { - server = createServer(); + loggerFactory = Slf4jLogger::new; + server = createServer(loggerFactory); } public void run() { server.run(); } - private static Server createServer() { + private static Server createServer(Function1, Logger> loggerFactory) { int port = getPort(); var endpoints = new Endpoints(); - return new SparkServer(endpoints, port); + return new SparkServer(endpoints, port, loggerFactory.apply(SparkServer.class)); } private static int getPort() { var processBuilder = new ProcessBuilder(); var heroku = new Heroku(processBuilder); var port = heroku.getAssignedPort().getOrElse(DEFAULT_PORT); - log.info("Starting on port " + port); return port; } } From 25d62923f64d431605750540f0ef361c590ef868 Mon Sep 17 00:00:00 2001 From: Jan Ouwens Date: Thu, 14 Mar 2019 06:30:25 +0100 Subject: [PATCH 011/106] Adds Architecture tests (in JUnit 3 for now) --- pom.yml | 3 +- .../jqno/paralleljava/ArchitectureTest.java | 29 +++++++++++++++++++ 2 files changed, 31 insertions(+), 1 deletion(-) create mode 100644 src/test/java/nl/jqno/paralleljava/ArchitectureTest.java diff --git a/pom.yml b/pom.yml index 4fa8d0b..05a6825 100644 --- a/pom.yml +++ b/pom.yml @@ -17,7 +17,8 @@ dependencies: - { groupId: io.vavr, artifactId: vavr, version: 0.9.3 } - { groupId: com.sparkjava, artifactId: spark-core, version: 2.7.2 } - { groupId: org.slf4j, artifactId: slf4j-simple, version: 1.7.26 } - - { groupId: org.specsy, artifactId: specsy-java, version: 2.3.3, scope: test } + - { groupId: junit, artifactId: junit, version: 3.8.2, scope: test } + - { groupId: com.tngtech.archunit, artifactId: archunit, version: 0.9.3, scope: test } build: plugins: diff --git a/src/test/java/nl/jqno/paralleljava/ArchitectureTest.java b/src/test/java/nl/jqno/paralleljava/ArchitectureTest.java new file mode 100644 index 0000000..8c4cb3a --- /dev/null +++ b/src/test/java/nl/jqno/paralleljava/ArchitectureTest.java @@ -0,0 +1,29 @@ +package nl.jqno.paralleljava; + +import com.tngtech.archunit.core.domain.JavaClasses; +import com.tngtech.archunit.core.importer.ClassFileImporter; +import junit.framework.TestCase; +import nl.jqno.paralleljava.app.logging.Slf4jLogger; +import nl.jqno.paralleljava.app.server.SparkServer; + +import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.noClasses; + +public class ArchitectureTest extends TestCase { + + private static final JavaClasses importedClasses = new ClassFileImporter() + .importPackages("nl.jqno.paralleljava"); + + public void testThatOnlySparkServerAccessesSparkClasses() { + var rule = noClasses() + .that().dontHaveFullyQualifiedName(SparkServer.class.getCanonicalName()) + .should().accessClassesThat().resideInAPackage("spark.."); + rule.check(importedClasses); + } + + public void testThatOnlySlf4jLoggerAccessesSlf4jClasses() { + var rule = noClasses() + .that().dontHaveFullyQualifiedName(Slf4jLogger.class.getCanonicalName()) + .should().accessClassesThat().resideInAPackage("org.slf4j.."); + rule.check(importedClasses); + } +} From 53ab5ed265bad8435639a6341d522c5d1fa623a1 Mon Sep 17 00:00:00 2001 From: Jan Ouwens Date: Thu, 14 Mar 2019 07:30:06 +0100 Subject: [PATCH 012/106] Adds tests for Heroku and Endpoints --- .../jqno/paralleljava/app/server/Heroku.java | 10 ++--- .../dependencyinjection/WiredApplication.java | 5 ++- .../paralleljava/endpoints/EndpointsTest.java | 15 ++++++++ .../jqno/paralleljava/server/HerokuTest.java | 37 +++++++++++++++++++ 4 files changed, 61 insertions(+), 6 deletions(-) create mode 100644 src/test/java/nl/jqno/paralleljava/endpoints/EndpointsTest.java create mode 100644 src/test/java/nl/jqno/paralleljava/server/HerokuTest.java diff --git a/src/main/java/nl/jqno/paralleljava/app/server/Heroku.java b/src/main/java/nl/jqno/paralleljava/app/server/Heroku.java index b1de73a..ccfc784 100644 --- a/src/main/java/nl/jqno/paralleljava/app/server/Heroku.java +++ b/src/main/java/nl/jqno/paralleljava/app/server/Heroku.java @@ -1,19 +1,19 @@ package nl.jqno.paralleljava.app.server; +import io.vavr.collection.Map; import io.vavr.control.Option; import io.vavr.control.Try; public class Heroku { - private ProcessBuilder processBuilder; + private final Map env; - public Heroku(ProcessBuilder processBuilder) { - this.processBuilder = processBuilder; + public Heroku(Map env) { + this.env = env; } public Option getAssignedPort() { - var port = Option.of(processBuilder.environment().get("PORT")); - return port.flatMap(this::parse); + return env.get("PORT").flatMap(this::parse); } private Option parse(String port) { diff --git a/src/main/java/nl/jqno/paralleljava/dependencyinjection/WiredApplication.java b/src/main/java/nl/jqno/paralleljava/dependencyinjection/WiredApplication.java index d1dc09e..5c1b86a 100644 --- a/src/main/java/nl/jqno/paralleljava/dependencyinjection/WiredApplication.java +++ b/src/main/java/nl/jqno/paralleljava/dependencyinjection/WiredApplication.java @@ -1,6 +1,8 @@ package nl.jqno.paralleljava.dependencyinjection; import io.vavr.Function1; +import io.vavr.collection.HashMap; +import io.vavr.collection.Map; import nl.jqno.paralleljava.app.endpoints.Endpoints; import nl.jqno.paralleljava.app.logging.Logger; import nl.jqno.paralleljava.app.logging.Slf4jLogger; @@ -32,7 +34,8 @@ private static Server createServer(Function1, Logger> loggerFactory) { private static int getPort() { var processBuilder = new ProcessBuilder(); - var heroku = new Heroku(processBuilder); + var environment = HashMap.ofAll(processBuilder.environment()); + var heroku = new Heroku(environment); var port = heroku.getAssignedPort().getOrElse(DEFAULT_PORT); return port; } diff --git a/src/test/java/nl/jqno/paralleljava/endpoints/EndpointsTest.java b/src/test/java/nl/jqno/paralleljava/endpoints/EndpointsTest.java new file mode 100644 index 0000000..98b7df9 --- /dev/null +++ b/src/test/java/nl/jqno/paralleljava/endpoints/EndpointsTest.java @@ -0,0 +1,15 @@ +package nl.jqno.paralleljava.endpoints; + +import io.vavr.collection.HashMap; +import junit.framework.TestCase; +import nl.jqno.paralleljava.app.endpoints.Endpoints; + +public class EndpointsTest extends TestCase { + + private final Endpoints endpoints = new Endpoints(); + + public void testHelloWorld() { + var sut = endpoints.helloWorld(); + assertEquals("Hello world", sut.handle(HashMap.empty())); + } +} diff --git a/src/test/java/nl/jqno/paralleljava/server/HerokuTest.java b/src/test/java/nl/jqno/paralleljava/server/HerokuTest.java new file mode 100644 index 0000000..058bead --- /dev/null +++ b/src/test/java/nl/jqno/paralleljava/server/HerokuTest.java @@ -0,0 +1,37 @@ +package nl.jqno.paralleljava.server; + +import io.vavr.collection.HashMap; +import io.vavr.control.Option; +import junit.framework.TestCase; +import nl.jqno.paralleljava.app.server.Heroku; + +public class HerokuTest extends TestCase { + + private Heroku heroku; + + public void setUp() { + heroku = new Heroku(HashMap.empty()); + } + + public void testValidPortInHeroku() { + setEnvironmentVariable("PORT", "42"); + var actual = heroku.getAssignedPort(); + assertEquals(Option.of(42), actual); + } + + public void testInvalidPortInHeroku() { + setEnvironmentVariable("PORT", "this is not the port you're looking for"); + var actual = heroku.getAssignedPort(); + assertEquals(Option.none(), actual); + } + + public void testGetPortOutsideOfHeroku() { + var actual = heroku.getAssignedPort(); + assertEquals(Option.none(), actual); + } + + private void setEnvironmentVariable(String key, String value) { + var env = HashMap.of(key, value); + heroku = new Heroku(env); + } +} From 5cd05af710ebf8b8da421d13028225cb51766bf3 Mon Sep 17 00:00:00 2001 From: Jan Ouwens Date: Fri, 15 Mar 2019 10:06:27 +0100 Subject: [PATCH 013/106] Adds test for SparkServer --- pom.yml | 1 + .../jqno/paralleljava/ArchitectureTest.java | 2 ++ .../paralleljava/server/SparkServerTest.java | 35 +++++++++++++++++++ 3 files changed, 38 insertions(+) create mode 100644 src/test/java/nl/jqno/paralleljava/server/SparkServerTest.java diff --git a/pom.yml b/pom.yml index 05a6825..222e107 100644 --- a/pom.yml +++ b/pom.yml @@ -18,6 +18,7 @@ dependencies: - { groupId: com.sparkjava, artifactId: spark-core, version: 2.7.2 } - { groupId: org.slf4j, artifactId: slf4j-simple, version: 1.7.26 } - { groupId: junit, artifactId: junit, version: 3.8.2, scope: test } + - { groupId: io.rest-assured, artifactId: rest-assured, version: 3.3.0, scope: test } - { groupId: com.tngtech.archunit, artifactId: archunit, version: 0.9.3, scope: test } build: diff --git a/src/test/java/nl/jqno/paralleljava/ArchitectureTest.java b/src/test/java/nl/jqno/paralleljava/ArchitectureTest.java index 8c4cb3a..708a7f4 100644 --- a/src/test/java/nl/jqno/paralleljava/ArchitectureTest.java +++ b/src/test/java/nl/jqno/paralleljava/ArchitectureTest.java @@ -5,6 +5,7 @@ import junit.framework.TestCase; import nl.jqno.paralleljava.app.logging.Slf4jLogger; import nl.jqno.paralleljava.app.server.SparkServer; +import nl.jqno.paralleljava.server.SparkServerTest; import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.noClasses; @@ -16,6 +17,7 @@ public class ArchitectureTest extends TestCase { public void testThatOnlySparkServerAccessesSparkClasses() { var rule = noClasses() .that().dontHaveFullyQualifiedName(SparkServer.class.getCanonicalName()) + .and().dontHaveFullyQualifiedName(SparkServerTest.class.getCanonicalName()) .should().accessClassesThat().resideInAPackage("spark.."); rule.check(importedClasses); } diff --git a/src/test/java/nl/jqno/paralleljava/server/SparkServerTest.java b/src/test/java/nl/jqno/paralleljava/server/SparkServerTest.java new file mode 100644 index 0000000..d9ceb79 --- /dev/null +++ b/src/test/java/nl/jqno/paralleljava/server/SparkServerTest.java @@ -0,0 +1,35 @@ +package nl.jqno.paralleljava.server; + +import junit.framework.TestCase; +import nl.jqno.paralleljava.app.endpoints.Endpoints; +import nl.jqno.paralleljava.app.logging.Slf4jLogger; +import nl.jqno.paralleljava.app.server.SparkServer; +import spark.Spark; + +import static io.restassured.RestAssured.get; +import static io.restassured.RestAssured.given; +import static org.hamcrest.CoreMatchers.equalTo; + +public class SparkServerTest extends TestCase { + + private static final int PORT = 1337; + + public void testServer() { + var endpoints = new Endpoints(); + var server = new SparkServer(endpoints, PORT, new Slf4jLogger(getClass())); + + server.run(); + Spark.awaitInitialization(); + + given() + .port(PORT) + .when() + .get("/hello") + .then() + .statusCode(200) + .body(equalTo("Hello world")); + + + Spark.stop(); + } +} From 1d9baf074bf173f62b4dfd0b71b01e82d8323e77 Mon Sep 17 00:00:00 2001 From: Jan Ouwens Date: Fri, 15 Mar 2019 10:53:10 +0100 Subject: [PATCH 014/106] Don't deploy test dependencies --- pom.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pom.yml b/pom.yml index 222e107..846d9b6 100644 --- a/pom.yml +++ b/pom.yml @@ -30,6 +30,8 @@ build: - groupId: org.apache.maven.plugins artifactId: maven-dependency-plugin version: 3.1.1 + configuration: + includeScope: runtime executions: - id: default phase: package From 6346823369aaf7fe83c5d864d130a23f0594af6c Mon Sep 17 00:00:00 2001 From: Jan Ouwens Date: Fri, 15 Mar 2019 11:27:44 +0100 Subject: [PATCH 015/106] Switches to PicoTest with AssertJ --- pom.yml | 11 +++++- .../jqno/paralleljava/ArchitectureTest.java | 35 ++++++++--------- .../paralleljava/endpoints/EndpointsTest.java | 16 +++++--- .../jqno/paralleljava/server/HerokuTest.java | 38 +++++++++---------- .../paralleljava/server/SparkServerTest.java | 30 +++++++-------- 5 files changed, 72 insertions(+), 58 deletions(-) diff --git a/pom.yml b/pom.yml index 846d9b6..2b65a37 100644 --- a/pom.yml +++ b/pom.yml @@ -13,12 +13,17 @@ properties: { maven.compiler.target: 11 } +repositories: + - { id: bintray-jqno-picotest-repo, url: "https://dl.bintray.com/jqno/picotest-repo" } + dependencies: - { groupId: io.vavr, artifactId: vavr, version: 0.9.3 } - { groupId: com.sparkjava, artifactId: spark-core, version: 2.7.2 } - { groupId: org.slf4j, artifactId: slf4j-simple, version: 1.7.26 } - - { groupId: junit, artifactId: junit, version: 3.8.2, scope: test } + - { groupId: nl.jqno.picotest, artifactId: picotest, version: 0.2, scope: test } + - { groupId: org.assertj, artifactId: assertj-core, version: 3.11.1, scope: test } - { groupId: io.rest-assured, artifactId: rest-assured, version: 3.3.0, scope: test } + - { groupId: org.codehaus.groovy, artifactId: groovy, version: 2.5.6, scope: test } # Overriding the version used by rest-assured, because that breaks - { groupId: com.tngtech.archunit, artifactId: archunit, version: 0.9.3, scope: test } build: @@ -27,6 +32,10 @@ build: artifactId: maven-compiler-plugin version: 3.8.0 + - groupId: org.apache.maven.plugins + artifactId: maven-surefire-plugin + version: 2.22.1 + - groupId: org.apache.maven.plugins artifactId: maven-dependency-plugin version: 3.1.1 diff --git a/src/test/java/nl/jqno/paralleljava/ArchitectureTest.java b/src/test/java/nl/jqno/paralleljava/ArchitectureTest.java index 708a7f4..0fefcdd 100644 --- a/src/test/java/nl/jqno/paralleljava/ArchitectureTest.java +++ b/src/test/java/nl/jqno/paralleljava/ArchitectureTest.java @@ -1,31 +1,32 @@ package nl.jqno.paralleljava; -import com.tngtech.archunit.core.domain.JavaClasses; import com.tngtech.archunit.core.importer.ClassFileImporter; -import junit.framework.TestCase; import nl.jqno.paralleljava.app.logging.Slf4jLogger; import nl.jqno.paralleljava.app.server.SparkServer; import nl.jqno.paralleljava.server.SparkServerTest; +import nl.jqno.picotest.Test; import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.noClasses; -public class ArchitectureTest extends TestCase { +public class ArchitectureTest extends Test { - private static final JavaClasses importedClasses = new ClassFileImporter() - .importPackages("nl.jqno.paralleljava"); + public void architecture() { + var importedClasses = new ClassFileImporter() + .importPackages("nl.jqno.paralleljava"); - public void testThatOnlySparkServerAccessesSparkClasses() { - var rule = noClasses() - .that().dontHaveFullyQualifiedName(SparkServer.class.getCanonicalName()) - .and().dontHaveFullyQualifiedName(SparkServerTest.class.getCanonicalName()) - .should().accessClassesThat().resideInAPackage("spark.."); - rule.check(importedClasses); - } + test("only SparkServer and SparkServerTest access Spark classes", () -> { + var rule = noClasses() + .that().dontHaveFullyQualifiedName(SparkServer.class.getCanonicalName()) + .and().dontHaveFullyQualifiedName(SparkServerTest.class.getCanonicalName()) + .should().accessClassesThat().resideInAPackage("spark.."); + rule.check(importedClasses); + }); - public void testThatOnlySlf4jLoggerAccessesSlf4jClasses() { - var rule = noClasses() - .that().dontHaveFullyQualifiedName(Slf4jLogger.class.getCanonicalName()) - .should().accessClassesThat().resideInAPackage("org.slf4j.."); - rule.check(importedClasses); + test("only Slf4jLogger accesses Slf4j classes", () -> { + var rule = noClasses() + .that().dontHaveFullyQualifiedName(Slf4jLogger.class.getCanonicalName()) + .should().accessClassesThat().resideInAPackage("org.slf4j.."); + rule.check(importedClasses); + }); } } diff --git a/src/test/java/nl/jqno/paralleljava/endpoints/EndpointsTest.java b/src/test/java/nl/jqno/paralleljava/endpoints/EndpointsTest.java index 98b7df9..cfd8b3f 100644 --- a/src/test/java/nl/jqno/paralleljava/endpoints/EndpointsTest.java +++ b/src/test/java/nl/jqno/paralleljava/endpoints/EndpointsTest.java @@ -1,15 +1,19 @@ package nl.jqno.paralleljava.endpoints; import io.vavr.collection.HashMap; -import junit.framework.TestCase; import nl.jqno.paralleljava.app.endpoints.Endpoints; +import nl.jqno.picotest.Test; -public class EndpointsTest extends TestCase { +import static org.assertj.core.api.Assertions.assertThat; - private final Endpoints endpoints = new Endpoints(); +public class EndpointsTest extends Test { - public void testHelloWorld() { - var sut = endpoints.helloWorld(); - assertEquals("Hello world", sut.handle(HashMap.empty())); + public void endoints() { + var endpoints = new Endpoints(); + + test("hello world works", () -> { + var sut = endpoints.helloWorld(); + assertThat(sut.handle(HashMap.empty())).isEqualTo("Hello world"); + }); } } diff --git a/src/test/java/nl/jqno/paralleljava/server/HerokuTest.java b/src/test/java/nl/jqno/paralleljava/server/HerokuTest.java index 058bead..6618e47 100644 --- a/src/test/java/nl/jqno/paralleljava/server/HerokuTest.java +++ b/src/test/java/nl/jqno/paralleljava/server/HerokuTest.java @@ -2,32 +2,32 @@ import io.vavr.collection.HashMap; import io.vavr.control.Option; -import junit.framework.TestCase; import nl.jqno.paralleljava.app.server.Heroku; +import nl.jqno.picotest.Test; -public class HerokuTest extends TestCase { +import static org.assertj.core.api.Assertions.assertThat; - private Heroku heroku; +public class HerokuTest extends Test { - public void setUp() { - heroku = new Heroku(HashMap.empty()); - } + private Heroku heroku = new Heroku(HashMap.empty()); - public void testValidPortInHeroku() { - setEnvironmentVariable("PORT", "42"); - var actual = heroku.getAssignedPort(); - assertEquals(Option.of(42), actual); - } + public void port() { + test("a valid port is provided", () -> { + setEnvironmentVariable("PORT", "42"); + var actual = heroku.getAssignedPort(); + assertThat(actual).isEqualTo(Option.of(42)); + }); - public void testInvalidPortInHeroku() { - setEnvironmentVariable("PORT", "this is not the port you're looking for"); - var actual = heroku.getAssignedPort(); - assertEquals(Option.none(), actual); - } + test("an invalid port is provided", () -> { + setEnvironmentVariable("PORT", "this is not the port you're looking for"); + var actual = heroku.getAssignedPort(); + assertThat(actual).isEqualTo(Option.none()); + }); - public void testGetPortOutsideOfHeroku() { - var actual = heroku.getAssignedPort(); - assertEquals(Option.none(), actual); + test("no port is provided", () -> { + var actual = heroku.getAssignedPort(); + assertThat(actual).isEqualTo(Option.none()); + }); } private void setEnvironmentVariable(String key, String value) { diff --git a/src/test/java/nl/jqno/paralleljava/server/SparkServerTest.java b/src/test/java/nl/jqno/paralleljava/server/SparkServerTest.java index d9ceb79..143463c 100644 --- a/src/test/java/nl/jqno/paralleljava/server/SparkServerTest.java +++ b/src/test/java/nl/jqno/paralleljava/server/SparkServerTest.java @@ -1,35 +1,35 @@ package nl.jqno.paralleljava.server; -import junit.framework.TestCase; import nl.jqno.paralleljava.app.endpoints.Endpoints; import nl.jqno.paralleljava.app.logging.Slf4jLogger; import nl.jqno.paralleljava.app.server.SparkServer; +import nl.jqno.picotest.Test; import spark.Spark; -import static io.restassured.RestAssured.get; import static io.restassured.RestAssured.given; import static org.hamcrest.CoreMatchers.equalTo; -public class SparkServerTest extends TestCase { +public class SparkServerTest extends Test { private static final int PORT = 1337; - public void testServer() { + public void server() { var endpoints = new Endpoints(); var server = new SparkServer(endpoints, PORT, new Slf4jLogger(getClass())); - server.run(); - Spark.awaitInitialization(); + test("hello world works", () -> { + server.run(); + Spark.awaitInitialization(); - given() - .port(PORT) - .when() - .get("/hello") - .then() - .statusCode(200) - .body(equalTo("Hello world")); + given() + .port(PORT) + .when() + .get("/hello") + .then() + .statusCode(200) + .body(equalTo("Hello world")); - - Spark.stop(); + Spark.stop(); + }); } } From d6a21dcdd946309ea40f7c69e61fcbe3a157610d Mon Sep 17 00:00:00 2001 From: Jan Ouwens Date: Fri, 15 Mar 2019 13:03:02 +0100 Subject: [PATCH 016/106] Enables CORS --- .../paralleljava/app/server/SparkServer.java | 20 +++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/src/main/java/nl/jqno/paralleljava/app/server/SparkServer.java b/src/main/java/nl/jqno/paralleljava/app/server/SparkServer.java index c4f4f91..560ae31 100644 --- a/src/main/java/nl/jqno/paralleljava/app/server/SparkServer.java +++ b/src/main/java/nl/jqno/paralleljava/app/server/SparkServer.java @@ -1,12 +1,12 @@ package nl.jqno.paralleljava.app.server; import io.vavr.collection.HashMap; +import io.vavr.control.Option; import nl.jqno.paralleljava.app.endpoints.Endpoints; import nl.jqno.paralleljava.app.endpoints.Route; import nl.jqno.paralleljava.app.logging.Logger; -import static spark.Spark.get; -import static spark.Spark.port; +import static spark.Spark.*; public class SparkServer implements Server { @@ -22,10 +22,26 @@ public SparkServer(Endpoints endpoints, int port, Logger logger) { public void run() { logger.forProduction("Starting on port " + port); + port(port); + enableCors(); get("/hello", convert(endpoints.helloWorld())); } + private void enableCors() { + options("/*", (request, response) -> { + Option.of(request.headers("Access-Control-Request-Headers")) + .forEach(h -> response.header("Access-Control-Allow-Headers", h)); + Option.of(request.headers("Access-Control-Request-Method")) + .forEach(h -> response.header("Access-Control-Allow-Methods", h)); + return "OK"; + }); + + before((request, response) -> { + response.header("Access-Control-Allow-Origin", "*"); + }); + } + private spark.Route convert(Route route) { return (request, response) -> route.handle(HashMap.ofAll(request.params())); } From b40b5bfdc2e36ed8a5f913f5a67ce44612e7b5a4 Mon Sep 17 00:00:00 2001 From: Jan Ouwens Date: Fri, 15 Mar 2019 14:55:39 +0100 Subject: [PATCH 017/106] Sets up before/afterAll for SparkServerTest --- pom.yml | 2 +- .../java/nl/jqno/paralleljava/server/SparkServerTest.java | 8 +++++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/pom.yml b/pom.yml index 2b65a37..482da35 100644 --- a/pom.yml +++ b/pom.yml @@ -20,7 +20,7 @@ dependencies: - { groupId: io.vavr, artifactId: vavr, version: 0.9.3 } - { groupId: com.sparkjava, artifactId: spark-core, version: 2.7.2 } - { groupId: org.slf4j, artifactId: slf4j-simple, version: 1.7.26 } - - { groupId: nl.jqno.picotest, artifactId: picotest, version: 0.2, scope: test } + - { groupId: nl.jqno.picotest, artifactId: picotest, version: 0.3, scope: test } - { groupId: org.assertj, artifactId: assertj-core, version: 3.11.1, scope: test } - { groupId: io.rest-assured, artifactId: rest-assured, version: 3.3.0, scope: test } - { groupId: org.codehaus.groovy, artifactId: groovy, version: 2.5.6, scope: test } # Overriding the version used by rest-assured, because that breaks diff --git a/src/test/java/nl/jqno/paralleljava/server/SparkServerTest.java b/src/test/java/nl/jqno/paralleljava/server/SparkServerTest.java index 143463c..0022dcb 100644 --- a/src/test/java/nl/jqno/paralleljava/server/SparkServerTest.java +++ b/src/test/java/nl/jqno/paralleljava/server/SparkServerTest.java @@ -17,10 +17,14 @@ public void server() { var endpoints = new Endpoints(); var server = new SparkServer(endpoints, PORT, new Slf4jLogger(getClass())); - test("hello world works", () -> { + beforeAll(() -> { server.run(); Spark.awaitInitialization(); + }); + + afterAll(Spark::stop); + test("hello world works", () -> { given() .port(PORT) .when() @@ -28,8 +32,6 @@ public void server() { .then() .statusCode(200) .body(equalTo("Hello world")); - - Spark.stop(); }); } } From 46813ca6f6fe8602ef78ef3ab65519a204de790b Mon Sep 17 00:00:00 2001 From: Jan Ouwens Date: Fri, 15 Mar 2019 15:29:17 +0100 Subject: [PATCH 018/106] Adds JaCoCo to build --- pom.yml | 18 ++++++++++++++++++ deploy => scripts/deploy | 0 scripts/jacoco | 6 ++++++ 3 files changed, 24 insertions(+) rename deploy => scripts/deploy (100%) create mode 100755 scripts/jacoco diff --git a/pom.yml b/pom.yml index 482da35..98470b3 100644 --- a/pom.yml +++ b/pom.yml @@ -63,3 +63,21 @@ build: goals: [check] configuration: failsOnError: true + + - groupId: org.jacoco + artifactId: jacoco-maven-plugin + version: 0.8.3 + executions: + - id: default-prepare-angent + goals: [prepare-agent] + - id: default-report + goals: [report] + - id: default-check + goals: [check] + configuration: + rules: + - element: BUNDLE + limits: + - counter: INSTRUCTION + value: COVEREDRATIO + minimum: 0.45 diff --git a/deploy b/scripts/deploy similarity index 100% rename from deploy rename to scripts/deploy diff --git a/scripts/jacoco b/scripts/jacoco new file mode 100755 index 0000000..ab43460 --- /dev/null +++ b/scripts/jacoco @@ -0,0 +1,6 @@ +#!/usr/bin/env bash + +mvn clean verify +mvn jacoco:report +open target/site/jacoco/index.html + From 534444574e579fc764e235dc16a83bdba17865c6 Mon Sep 17 00:00:00 2001 From: Jan Ouwens Date: Sun, 17 Mar 2019 13:18:33 +0100 Subject: [PATCH 019/106] Adds unit test for CORS --- .../paralleljava/server/SparkServerTest.java | 41 +++++++++++++++---- 1 file changed, 32 insertions(+), 9 deletions(-) diff --git a/src/test/java/nl/jqno/paralleljava/server/SparkServerTest.java b/src/test/java/nl/jqno/paralleljava/server/SparkServerTest.java index 0022dcb..e41c0cf 100644 --- a/src/test/java/nl/jqno/paralleljava/server/SparkServerTest.java +++ b/src/test/java/nl/jqno/paralleljava/server/SparkServerTest.java @@ -1,5 +1,6 @@ package nl.jqno.paralleljava.server; +import io.restassured.specification.RequestSpecification; import nl.jqno.paralleljava.app.endpoints.Endpoints; import nl.jqno.paralleljava.app.logging.Slf4jLogger; import nl.jqno.paralleljava.app.server.SparkServer; @@ -12,6 +13,7 @@ public class SparkServerTest extends Test { private static final int PORT = 1337; + private final RequestSpecification when = given().port(PORT).when(); public void server() { var endpoints = new Endpoints(); @@ -24,14 +26,35 @@ public void server() { afterAll(Spark::stop); - test("hello world works", () -> { - given() - .port(PORT) - .when() - .get("/hello") - .then() - .statusCode(200) - .body(equalTo("Hello world")); - }); + test("hello world works", this::helloWorldWorks); + test("CORS Access-Control-AllowOrigin header is included", this::corsRequestsHeader); + test("OPTION request", this::corsOptionsRequest); + } + + private void helloWorldWorks() { + when + .get("/hello") + .then() + .statusCode(200) + .body(equalTo("Hello world")); + } + + private void corsRequestsHeader() { + when + .get("/hello") + .then() + .header("Access-Control-Allow-Origin", "*"); + } + + private void corsOptionsRequest() { + var headers = "XXX"; + var methods = "YYY"; + when + .header("Access-Control-Request-Headers", headers) + .header("Access-Control-Request-Method", methods) + .options() + .then() + .header("Access-Control-Allow-Headers", headers) + .header("Access-Control-Allow-Methods", methods); } } From 15226ca1d8ee4a9a3763986d1368436759efad13 Mon Sep 17 00:00:00 2001 From: Jan Ouwens Date: Mon, 18 Mar 2019 12:46:13 +0100 Subject: [PATCH 020/106] Moves tests to correct package --- src/test/java/nl/jqno/paralleljava/ArchitectureTest.java | 2 +- .../jqno/paralleljava/{ => app}/endpoints/EndpointsTest.java | 3 +-- .../java/nl/jqno/paralleljava/{ => app}/server/HerokuTest.java | 2 +- .../nl/jqno/paralleljava/{ => app}/server/SparkServerTest.java | 2 +- 4 files changed, 4 insertions(+), 5 deletions(-) rename src/test/java/nl/jqno/paralleljava/{ => app}/endpoints/EndpointsTest.java (82%) rename src/test/java/nl/jqno/paralleljava/{ => app}/server/HerokuTest.java (96%) rename src/test/java/nl/jqno/paralleljava/{ => app}/server/SparkServerTest.java (97%) diff --git a/src/test/java/nl/jqno/paralleljava/ArchitectureTest.java b/src/test/java/nl/jqno/paralleljava/ArchitectureTest.java index 0fefcdd..b43efc9 100644 --- a/src/test/java/nl/jqno/paralleljava/ArchitectureTest.java +++ b/src/test/java/nl/jqno/paralleljava/ArchitectureTest.java @@ -3,7 +3,7 @@ import com.tngtech.archunit.core.importer.ClassFileImporter; import nl.jqno.paralleljava.app.logging.Slf4jLogger; import nl.jqno.paralleljava.app.server.SparkServer; -import nl.jqno.paralleljava.server.SparkServerTest; +import nl.jqno.paralleljava.app.server.SparkServerTest; import nl.jqno.picotest.Test; import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.noClasses; diff --git a/src/test/java/nl/jqno/paralleljava/endpoints/EndpointsTest.java b/src/test/java/nl/jqno/paralleljava/app/endpoints/EndpointsTest.java similarity index 82% rename from src/test/java/nl/jqno/paralleljava/endpoints/EndpointsTest.java rename to src/test/java/nl/jqno/paralleljava/app/endpoints/EndpointsTest.java index cfd8b3f..26c43b8 100644 --- a/src/test/java/nl/jqno/paralleljava/endpoints/EndpointsTest.java +++ b/src/test/java/nl/jqno/paralleljava/app/endpoints/EndpointsTest.java @@ -1,7 +1,6 @@ -package nl.jqno.paralleljava.endpoints; +package nl.jqno.paralleljava.app.endpoints; import io.vavr.collection.HashMap; -import nl.jqno.paralleljava.app.endpoints.Endpoints; import nl.jqno.picotest.Test; import static org.assertj.core.api.Assertions.assertThat; diff --git a/src/test/java/nl/jqno/paralleljava/server/HerokuTest.java b/src/test/java/nl/jqno/paralleljava/app/server/HerokuTest.java similarity index 96% rename from src/test/java/nl/jqno/paralleljava/server/HerokuTest.java rename to src/test/java/nl/jqno/paralleljava/app/server/HerokuTest.java index 6618e47..66e155d 100644 --- a/src/test/java/nl/jqno/paralleljava/server/HerokuTest.java +++ b/src/test/java/nl/jqno/paralleljava/app/server/HerokuTest.java @@ -1,4 +1,4 @@ -package nl.jqno.paralleljava.server; +package nl.jqno.paralleljava.app.server; import io.vavr.collection.HashMap; import io.vavr.control.Option; diff --git a/src/test/java/nl/jqno/paralleljava/server/SparkServerTest.java b/src/test/java/nl/jqno/paralleljava/app/server/SparkServerTest.java similarity index 97% rename from src/test/java/nl/jqno/paralleljava/server/SparkServerTest.java rename to src/test/java/nl/jqno/paralleljava/app/server/SparkServerTest.java index e41c0cf..8b139e7 100644 --- a/src/test/java/nl/jqno/paralleljava/server/SparkServerTest.java +++ b/src/test/java/nl/jqno/paralleljava/app/server/SparkServerTest.java @@ -1,4 +1,4 @@ -package nl.jqno.paralleljava.server; +package nl.jqno.paralleljava.app.server; import io.restassured.specification.RequestSpecification; import nl.jqno.paralleljava.app.endpoints.Endpoints; From f1d86d9a707e612a671e34b1d01442a47f201b04 Mon Sep 17 00:00:00 2001 From: Jan Ouwens Date: Mon, 18 Mar 2019 12:51:26 +0100 Subject: [PATCH 021/106] Adds test for Slf4jLogger --- pom.yml | 5 +- .../paralleljava/app/logging/Slf4jLogger.java | 6 +- .../dependencyinjection/WiredApplication.java | 3 +- .../jqno/paralleljava/ArchitectureTest.java | 4 +- .../paralleljava/app/logging/NopLogger.java | 13 +++ .../app/logging/Slf4jLoggerTest.java | 107 ++++++++++++++++++ .../app/server/SparkServerTest.java | 5 +- 7 files changed, 132 insertions(+), 11 deletions(-) create mode 100644 src/test/java/nl/jqno/paralleljava/app/logging/NopLogger.java create mode 100644 src/test/java/nl/jqno/paralleljava/app/logging/Slf4jLoggerTest.java diff --git a/pom.yml b/pom.yml index 98470b3..7460f1d 100644 --- a/pom.yml +++ b/pom.yml @@ -10,7 +10,8 @@ description: "Todo-Backend built with Java from a Parallel Universe: a demo proj properties: { encoding: utf-8, maven.compiler.source: 11, - maven.compiler.target: 11 + maven.compiler.target: 11, + coverage.threshold: 0.68 } repositories: @@ -80,4 +81,4 @@ build: limits: - counter: INSTRUCTION value: COVEREDRATIO - minimum: 0.45 + minimum: ${coverage.threshold} diff --git a/src/main/java/nl/jqno/paralleljava/app/logging/Slf4jLogger.java b/src/main/java/nl/jqno/paralleljava/app/logging/Slf4jLogger.java index 3f52a96..8c176bd 100644 --- a/src/main/java/nl/jqno/paralleljava/app/logging/Slf4jLogger.java +++ b/src/main/java/nl/jqno/paralleljava/app/logging/Slf4jLogger.java @@ -1,12 +1,10 @@ package nl.jqno.paralleljava.app.logging; -import org.slf4j.LoggerFactory; - public class Slf4jLogger implements Logger { private final org.slf4j.Logger logger; - public Slf4jLogger(Class c) { - this.logger = LoggerFactory.getLogger(c); + public Slf4jLogger(org.slf4j.Logger logger) { + this.logger = logger; } public void forDevelopment(String message) { diff --git a/src/main/java/nl/jqno/paralleljava/dependencyinjection/WiredApplication.java b/src/main/java/nl/jqno/paralleljava/dependencyinjection/WiredApplication.java index 5c1b86a..2fa0dd1 100644 --- a/src/main/java/nl/jqno/paralleljava/dependencyinjection/WiredApplication.java +++ b/src/main/java/nl/jqno/paralleljava/dependencyinjection/WiredApplication.java @@ -9,6 +9,7 @@ import nl.jqno.paralleljava.app.server.Heroku; import nl.jqno.paralleljava.app.server.Server; import nl.jqno.paralleljava.app.server.SparkServer; +import org.slf4j.LoggerFactory; public class WiredApplication { @@ -18,7 +19,7 @@ public class WiredApplication { private final Server server; public WiredApplication() { - loggerFactory = Slf4jLogger::new; + loggerFactory = c -> new Slf4jLogger(LoggerFactory.getLogger(c)); server = createServer(loggerFactory); } diff --git a/src/test/java/nl/jqno/paralleljava/ArchitectureTest.java b/src/test/java/nl/jqno/paralleljava/ArchitectureTest.java index b43efc9..bfa466a 100644 --- a/src/test/java/nl/jqno/paralleljava/ArchitectureTest.java +++ b/src/test/java/nl/jqno/paralleljava/ArchitectureTest.java @@ -4,6 +4,7 @@ import nl.jqno.paralleljava.app.logging.Slf4jLogger; import nl.jqno.paralleljava.app.server.SparkServer; import nl.jqno.paralleljava.app.server.SparkServerTest; +import nl.jqno.paralleljava.dependencyinjection.WiredApplication; import nl.jqno.picotest.Test; import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.noClasses; @@ -24,7 +25,8 @@ public void architecture() { test("only Slf4jLogger accesses Slf4j classes", () -> { var rule = noClasses() - .that().dontHaveFullyQualifiedName(Slf4jLogger.class.getCanonicalName()) + .that().resideOutsideOfPackage(Slf4jLogger.class.getPackageName()) + .and().dontHaveFullyQualifiedName(WiredApplication.class.getCanonicalName()) .should().accessClassesThat().resideInAPackage("org.slf4j.."); rule.check(importedClasses); }); diff --git a/src/test/java/nl/jqno/paralleljava/app/logging/NopLogger.java b/src/test/java/nl/jqno/paralleljava/app/logging/NopLogger.java new file mode 100644 index 0000000..dfd7230 --- /dev/null +++ b/src/test/java/nl/jqno/paralleljava/app/logging/NopLogger.java @@ -0,0 +1,13 @@ +package nl.jqno.paralleljava.app.logging; + +public class NopLogger implements Logger { + + public static final Logger INSTANCE = new NopLogger(); + + public void forDevelopment(String message) {} + public void forProduction(String message) {} + public void firstThingNextMorning(String message) {} + public void firstThingNextMorning(String message, Throwable t) {} + public void wakeMeUp(String message) {} + public void wakeMeUp(String message, Throwable t) {} +} diff --git a/src/test/java/nl/jqno/paralleljava/app/logging/Slf4jLoggerTest.java b/src/test/java/nl/jqno/paralleljava/app/logging/Slf4jLoggerTest.java new file mode 100644 index 0000000..be858eb --- /dev/null +++ b/src/test/java/nl/jqno/paralleljava/app/logging/Slf4jLoggerTest.java @@ -0,0 +1,107 @@ +package nl.jqno.paralleljava.app.logging; + +import nl.jqno.picotest.Test; +import org.slf4j.helpers.SubstituteLogger; + +import static org.assertj.core.api.Assertions.assertThat; + +public class Slf4jLoggerTest extends Test { + + private static final String SOME_MESSAGE = ""; + private static final Throwable SOME_EXCEPTION = new Throwable(); + + private StubLogger underlying; + private Slf4jLogger logger; + + public void logger() { + beforeEach(() -> { + underlying = new StubLogger(); + logger = new Slf4jLogger(underlying); + }); + + test("forDevelopment calls DEBUG", () -> { + logger.forDevelopment(SOME_MESSAGE); + assertThat(underlying.calledDebug).isEqualTo(1); + assertThat(underlying.calledTotal()).isEqualTo(1); + assertThat(underlying.lastThrowable).isNull(); + }); + + test("forProduction calls INFO", () -> { + logger.forProduction(SOME_MESSAGE); + assertThat(underlying.calledInfo).isEqualTo(1); + assertThat(underlying.calledTotal()).isEqualTo(1); + assertThat(underlying.lastThrowable).isNull(); + }); + + test("firstThingNextMorning calls WARN", () -> { + logger.firstThingNextMorning(SOME_MESSAGE); + assertThat(underlying.calledWarn).isEqualTo(1); + assertThat(underlying.calledTotal()).isEqualTo(1); + assertThat(underlying.lastThrowable).isNull(); + }); + + test("firstThingNextMorning with exception calls WARN with exception", () -> { + logger.firstThingNextMorning(SOME_MESSAGE, SOME_EXCEPTION); + assertThat(underlying.calledWarn).isEqualTo(1); + assertThat(underlying.calledTotal()).isEqualTo(1); + assertThat(underlying.lastThrowable).isEqualTo(SOME_EXCEPTION); + }); + + test("wakeMeUp calls ERROR", () -> { + logger.wakeMeUp(SOME_MESSAGE); + assertThat(underlying.calledError).isEqualTo(1); + assertThat(underlying.calledTotal()).isEqualTo(1); + assertThat(underlying.lastThrowable).isNull(); + }); + + test("wakeMeUp with exception calls ERROR with exception", () -> { + logger.wakeMeUp(SOME_MESSAGE, SOME_EXCEPTION); + assertThat(underlying.calledError).isEqualTo(1); + assertThat(underlying.calledTotal()).isEqualTo(1); + assertThat(underlying.lastThrowable).isEqualTo(SOME_EXCEPTION); + }); + } + + private static class StubLogger extends SubstituteLogger { + public int calledDebug = 0; + public int calledInfo = 0; + public int calledWarn = 0; + public int calledError = 0; + public Throwable lastThrowable = null; + + public StubLogger() { + super(StubLogger.class.getName()); + } + + public int calledTotal() { + return calledDebug + calledInfo + calledWarn + calledError; + } + + public void debug(String msg) { + calledDebug += 1; + } + + public void info(String msg) { + calledInfo += 1; + } + + public void warn(String msg) { + calledWarn += 1; + } + + public void warn(String msg, Throwable e) { + calledWarn += 1; + lastThrowable = e; + } + + public void error(String msg) { + calledError += 1; + } + + public void error(String msg, Throwable e) { + calledError += 1; + lastThrowable = e; + } + } +} + diff --git a/src/test/java/nl/jqno/paralleljava/app/server/SparkServerTest.java b/src/test/java/nl/jqno/paralleljava/app/server/SparkServerTest.java index 8b139e7..b319839 100644 --- a/src/test/java/nl/jqno/paralleljava/app/server/SparkServerTest.java +++ b/src/test/java/nl/jqno/paralleljava/app/server/SparkServerTest.java @@ -2,8 +2,7 @@ import io.restassured.specification.RequestSpecification; import nl.jqno.paralleljava.app.endpoints.Endpoints; -import nl.jqno.paralleljava.app.logging.Slf4jLogger; -import nl.jqno.paralleljava.app.server.SparkServer; +import nl.jqno.paralleljava.app.logging.NopLogger; import nl.jqno.picotest.Test; import spark.Spark; @@ -17,7 +16,7 @@ public class SparkServerTest extends Test { public void server() { var endpoints = new Endpoints(); - var server = new SparkServer(endpoints, PORT, new Slf4jLogger(getClass())); + var server = new SparkServer(endpoints, PORT, NopLogger.INSTANCE); beforeAll(() -> { server.run(); From 9c0af8bb9339a812a4eb4c2f7c3245e7c344db27 Mon Sep 17 00:00:00 2001 From: Jan Ouwens Date: Mon, 18 Mar 2019 16:20:17 +0100 Subject: [PATCH 022/106] Makes the build less chatty --- pom.yml | 13 +++++++++++-- src/test/resources/simplelogger.properties | 1 + 2 files changed, 12 insertions(+), 2 deletions(-) create mode 100644 src/test/resources/simplelogger.properties diff --git a/pom.yml b/pom.yml index 7460f1d..6ef6015 100644 --- a/pom.yml +++ b/pom.yml @@ -23,10 +23,19 @@ dependencies: - { groupId: org.slf4j, artifactId: slf4j-simple, version: 1.7.26 } - { groupId: nl.jqno.picotest, artifactId: picotest, version: 0.3, scope: test } - { groupId: org.assertj, artifactId: assertj-core, version: 3.11.1, scope: test } - - { groupId: io.rest-assured, artifactId: rest-assured, version: 3.3.0, scope: test } - - { groupId: org.codehaus.groovy, artifactId: groovy, version: 2.5.6, scope: test } # Overriding the version used by rest-assured, because that breaks - { groupId: com.tngtech.archunit, artifactId: archunit, version: 0.9.3, scope: test } + # REST-Assured is useful but problematic on Java 11. We need to overrule Groovy and exclude JAXB-OSGI. + - { groupId: org.codehaus.groovy, artifactId: groovy, version: 2.5.6, scope: test } + - { groupId: org.codehaus.groovy, artifactId: groovy-xml, version: 2.5.6, scope: test } + - groupId: io.rest-assured + artifactId: rest-assured + version: 3.3.0 + scope: test + exclusions: + - groupId: com.sun.xml.bind + artifactId: jaxb-osgi + build: plugins: - groupId: org.apache.maven.plugins diff --git a/src/test/resources/simplelogger.properties b/src/test/resources/simplelogger.properties new file mode 100644 index 0000000..6640220 --- /dev/null +++ b/src/test/resources/simplelogger.properties @@ -0,0 +1 @@ +org.slf4j.simpleLogger.defaultLogLevel=WARN \ No newline at end of file From e8dd87af69d7a677dccece9b9366672fda74c13a Mon Sep 17 00:00:00 2001 From: Jan Ouwens Date: Mon, 18 Mar 2019 16:49:58 +0100 Subject: [PATCH 023/106] Extracts Endpoints interface --- .../app/endpoints/DefaultEndpoints.java | 8 ++++ .../paralleljava/app/endpoints/Endpoints.java | 7 +--- .../dependencyinjection/WiredApplication.java | 5 +-- .../app/endpoints/EndpointsTest.java | 2 +- .../app/server/SparkServerTest.java | 42 ++++++++++++++++--- 5 files changed, 49 insertions(+), 15 deletions(-) create mode 100644 src/main/java/nl/jqno/paralleljava/app/endpoints/DefaultEndpoints.java diff --git a/src/main/java/nl/jqno/paralleljava/app/endpoints/DefaultEndpoints.java b/src/main/java/nl/jqno/paralleljava/app/endpoints/DefaultEndpoints.java new file mode 100644 index 0000000..83d9764 --- /dev/null +++ b/src/main/java/nl/jqno/paralleljava/app/endpoints/DefaultEndpoints.java @@ -0,0 +1,8 @@ +package nl.jqno.paralleljava.app.endpoints; + +public class DefaultEndpoints implements Endpoints { + + public Route helloWorld() { + return ignored -> "Hello world"; + } +} diff --git a/src/main/java/nl/jqno/paralleljava/app/endpoints/Endpoints.java b/src/main/java/nl/jqno/paralleljava/app/endpoints/Endpoints.java index 47d7727..7fb1ca6 100644 --- a/src/main/java/nl/jqno/paralleljava/app/endpoints/Endpoints.java +++ b/src/main/java/nl/jqno/paralleljava/app/endpoints/Endpoints.java @@ -1,8 +1,5 @@ package nl.jqno.paralleljava.app.endpoints; -public class Endpoints { - - public Route helloWorld() { - return ignored -> "Hello world"; - } +public interface Endpoints { + Route helloWorld(); } diff --git a/src/main/java/nl/jqno/paralleljava/dependencyinjection/WiredApplication.java b/src/main/java/nl/jqno/paralleljava/dependencyinjection/WiredApplication.java index 2fa0dd1..2b82f12 100644 --- a/src/main/java/nl/jqno/paralleljava/dependencyinjection/WiredApplication.java +++ b/src/main/java/nl/jqno/paralleljava/dependencyinjection/WiredApplication.java @@ -2,8 +2,7 @@ import io.vavr.Function1; import io.vavr.collection.HashMap; -import io.vavr.collection.Map; -import nl.jqno.paralleljava.app.endpoints.Endpoints; +import nl.jqno.paralleljava.app.endpoints.DefaultEndpoints; import nl.jqno.paralleljava.app.logging.Logger; import nl.jqno.paralleljava.app.logging.Slf4jLogger; import nl.jqno.paralleljava.app.server.Heroku; @@ -29,7 +28,7 @@ public void run() { private static Server createServer(Function1, Logger> loggerFactory) { int port = getPort(); - var endpoints = new Endpoints(); + var endpoints = new DefaultEndpoints(); return new SparkServer(endpoints, port, loggerFactory.apply(SparkServer.class)); } diff --git a/src/test/java/nl/jqno/paralleljava/app/endpoints/EndpointsTest.java b/src/test/java/nl/jqno/paralleljava/app/endpoints/EndpointsTest.java index 26c43b8..d61504f 100644 --- a/src/test/java/nl/jqno/paralleljava/app/endpoints/EndpointsTest.java +++ b/src/test/java/nl/jqno/paralleljava/app/endpoints/EndpointsTest.java @@ -8,7 +8,7 @@ public class EndpointsTest extends Test { public void endoints() { - var endpoints = new Endpoints(); + var endpoints = new DefaultEndpoints(); test("hello world works", () -> { var sut = endpoints.helloWorld(); diff --git a/src/test/java/nl/jqno/paralleljava/app/server/SparkServerTest.java b/src/test/java/nl/jqno/paralleljava/app/server/SparkServerTest.java index b319839..ae0b0fc 100644 --- a/src/test/java/nl/jqno/paralleljava/app/server/SparkServerTest.java +++ b/src/test/java/nl/jqno/paralleljava/app/server/SparkServerTest.java @@ -2,27 +2,32 @@ import io.restassured.specification.RequestSpecification; import nl.jqno.paralleljava.app.endpoints.Endpoints; +import nl.jqno.paralleljava.app.endpoints.Route; import nl.jqno.paralleljava.app.logging.NopLogger; import nl.jqno.picotest.Test; import spark.Spark; import static io.restassured.RestAssured.given; -import static org.hamcrest.CoreMatchers.equalTo; +import static org.assertj.core.api.Assertions.assertThat; public class SparkServerTest extends Test { private static final int PORT = 1337; private final RequestSpecification when = given().port(PORT).when(); + private StubEndpoints underlying; public void server() { - var endpoints = new Endpoints(); - var server = new SparkServer(endpoints, PORT, NopLogger.INSTANCE); + underlying = new StubEndpoints(); beforeAll(() -> { - server.run(); + new SparkServer(underlying, PORT, NopLogger.INSTANCE).run(); Spark.awaitInitialization(); }); + beforeEach(() -> { + underlying.clear(); + }); + afterAll(Spark::stop); test("hello world works", this::helloWorldWorks); @@ -34,8 +39,9 @@ private void helloWorldWorks() { when .get("/hello") .then() - .statusCode(200) - .body(equalTo("Hello world")); + .statusCode(200); + assertThat(underlying.calledHelloWorld).isEqualTo(1); + assertThat(underlying.calledTotal()).isEqualTo(1); } private void corsRequestsHeader() { @@ -56,4 +62,28 @@ private void corsOptionsRequest() { .header("Access-Control-Allow-Headers", headers) .header("Access-Control-Allow-Methods", methods); } + + private static class StubEndpoints implements Endpoints { + + public int calledHelloWorld = 0; + + public void clear() { + calledHelloWorld = 0; + } + + public int calledTotal() { + return calledHelloWorld; + } + + public Route helloWorld() { + return stubbed(() -> calledHelloWorld += 1); + } + + private Route stubbed(Runnable block) { + return ignored -> { + block.run(); + return ""; + }; + } + } } From a202469c9866f651427b19f9f7b7b89a0a9c754a Mon Sep 17 00:00:00 2001 From: Jan Ouwens Date: Tue, 19 Mar 2019 07:58:48 +0100 Subject: [PATCH 024/106] Adds Todo domain class --- pom.yml | 5 +- .../nl/jqno/paralleljava/app/domain/Todo.java | 55 +++++++++++++++++++ .../paralleljava/app/domain/TodoTest.java | 26 +++++++++ 3 files changed, 85 insertions(+), 1 deletion(-) create mode 100644 src/main/java/nl/jqno/paralleljava/app/domain/Todo.java create mode 100644 src/test/java/nl/jqno/paralleljava/app/domain/TodoTest.java diff --git a/pom.yml b/pom.yml index 6ef6015..18a16d3 100644 --- a/pom.yml +++ b/pom.yml @@ -11,7 +11,7 @@ properties: { encoding: utf-8, maven.compiler.source: 11, maven.compiler.target: 11, - coverage.threshold: 0.68 + coverage.threshold: 0.78 } repositories: @@ -22,6 +22,7 @@ dependencies: - { groupId: com.sparkjava, artifactId: spark-core, version: 2.7.2 } - { groupId: org.slf4j, artifactId: slf4j-simple, version: 1.7.26 } - { groupId: nl.jqno.picotest, artifactId: picotest, version: 0.3, scope: test } + - { groupId: nl.jqno.equalsverifier, artifactId: equalsverifier, version: 3.1.7, scope: test } - { groupId: org.assertj, artifactId: assertj-core, version: 3.11.1, scope: test } - { groupId: com.tngtech.archunit, artifactId: archunit, version: 0.9.3, scope: test } @@ -45,6 +46,8 @@ build: - groupId: org.apache.maven.plugins artifactId: maven-surefire-plugin version: 2.22.1 + configuration: + argLine: "@{argLine} --add-opens nl.jqno.paralleljava/nl.jqno.paralleljava.app.domain=ALL-UNNAMED" - groupId: org.apache.maven.plugins artifactId: maven-dependency-plugin diff --git a/src/main/java/nl/jqno/paralleljava/app/domain/Todo.java b/src/main/java/nl/jqno/paralleljava/app/domain/Todo.java new file mode 100644 index 0000000..63b47ef --- /dev/null +++ b/src/main/java/nl/jqno/paralleljava/app/domain/Todo.java @@ -0,0 +1,55 @@ +package nl.jqno.paralleljava.app.domain; + +import java.util.Objects; + +public final class Todo { + private final int id; + private final String title; + private final String url; + private final boolean completed; + private final int order; + + public Todo(int id, String title, String url, boolean completed, int order) { + this.id = id; + this.title = title; + this.url = url; + this.completed = completed; + this.order = order; + } + + public int id() { + return id; + } + + public String title() { + return title; + } + + public String url() { + return url; + } + + public boolean completed() { + return completed; + } + + public int order() { + return order; + } + + public boolean equals(Object obj) { + if (!(obj instanceof Todo)) { + return false; + } + Todo other = (Todo)obj; + return id == other.id && + Objects.equals(title, other.title) && + Objects.equals(url, other.url) && + completed == other.completed && + order == other.order; + } + + public int hashCode() { + return Objects.hash(id, title, url, completed, order); + } +} diff --git a/src/test/java/nl/jqno/paralleljava/app/domain/TodoTest.java b/src/test/java/nl/jqno/paralleljava/app/domain/TodoTest.java new file mode 100644 index 0000000..496bd26 --- /dev/null +++ b/src/test/java/nl/jqno/paralleljava/app/domain/TodoTest.java @@ -0,0 +1,26 @@ +package nl.jqno.paralleljava.app.domain; + +import nl.jqno.equalsverifier.EqualsVerifier; +import nl.jqno.picotest.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +public class TodoTest extends Test { + + public void todo() { + + test("equals and hashCode", () -> { + EqualsVerifier.forClass(Todo.class) + .verify(); + }); + + test("getters", () -> { + var todo = new Todo(42, "title", "http://www.example.com", true, 1337); + assertThat(todo.id()).isEqualTo(42); + assertThat(todo.title()).isEqualTo("title"); + assertThat(todo.url()).isEqualTo("http://www.example.com"); + assertThat(todo.completed()).isEqualTo(true); + assertThat(todo.order()).isEqualTo(1337); + }); + } +} From 0e0eefcff8a8d284b506076065f183004c614c72 Mon Sep 17 00:00:00 2001 From: Jan Ouwens Date: Tue, 19 Mar 2019 15:49:42 +0100 Subject: [PATCH 025/106] Adds Serializer --- pom.yml | 1 + src/main/java/module-info.java | 1 + .../app/serialization/GsonSerializer.java | 22 ++++++++++ .../app/serialization/Serializer.java | 9 ++++ .../jqno/paralleljava/ArchitectureTest.java | 9 ++++ .../paralleljava/app/domain/TestData.java | 6 +++ .../paralleljava/app/domain/TodoTest.java | 2 +- .../app/serialization/GsonSerializerTest.java | 44 +++++++++++++++++++ 8 files changed, 93 insertions(+), 1 deletion(-) create mode 100644 src/main/java/nl/jqno/paralleljava/app/serialization/GsonSerializer.java create mode 100644 src/main/java/nl/jqno/paralleljava/app/serialization/Serializer.java create mode 100644 src/test/java/nl/jqno/paralleljava/app/domain/TestData.java create mode 100644 src/test/java/nl/jqno/paralleljava/app/serialization/GsonSerializerTest.java diff --git a/pom.yml b/pom.yml index 18a16d3..faf4b24 100644 --- a/pom.yml +++ b/pom.yml @@ -21,6 +21,7 @@ dependencies: - { groupId: io.vavr, artifactId: vavr, version: 0.9.3 } - { groupId: com.sparkjava, artifactId: spark-core, version: 2.7.2 } - { groupId: org.slf4j, artifactId: slf4j-simple, version: 1.7.26 } + - { groupId: com.google.code.gson, artifactId: gson, version: 2.8.5 } - { groupId: nl.jqno.picotest, artifactId: picotest, version: 0.3, scope: test } - { groupId: nl.jqno.equalsverifier, artifactId: equalsverifier, version: 3.1.7, scope: test } - { groupId: org.assertj, artifactId: assertj-core, version: 3.11.1, scope: test } diff --git a/src/main/java/module-info.java b/src/main/java/module-info.java index 1de53e6..e0ef082 100644 --- a/src/main/java/module-info.java +++ b/src/main/java/module-info.java @@ -2,6 +2,7 @@ exports nl.jqno.paralleljava; requires io.vavr; + requires gson; requires slf4j.api; requires spark.core; } \ No newline at end of file diff --git a/src/main/java/nl/jqno/paralleljava/app/serialization/GsonSerializer.java b/src/main/java/nl/jqno/paralleljava/app/serialization/GsonSerializer.java new file mode 100644 index 0000000..23f1c68 --- /dev/null +++ b/src/main/java/nl/jqno/paralleljava/app/serialization/GsonSerializer.java @@ -0,0 +1,22 @@ +package nl.jqno.paralleljava.app.serialization; + +import com.google.gson.Gson; +import io.vavr.control.Option; +import io.vavr.control.Try; +import nl.jqno.paralleljava.app.domain.Todo; + +public class GsonSerializer implements Serializer { + private final Gson gson; + + public GsonSerializer(Gson gson) { + this.gson = gson; + } + + public String serializeTodo(Todo todo) { + return gson.toJson(todo); + } + + public Option deserializeTodo(String json) { + return Try.of(() -> gson.fromJson(json, Todo.class)).toOption(); + } +} diff --git a/src/main/java/nl/jqno/paralleljava/app/serialization/Serializer.java b/src/main/java/nl/jqno/paralleljava/app/serialization/Serializer.java new file mode 100644 index 0000000..d6b6c30 --- /dev/null +++ b/src/main/java/nl/jqno/paralleljava/app/serialization/Serializer.java @@ -0,0 +1,9 @@ +package nl.jqno.paralleljava.app.serialization; + +import io.vavr.control.Option; +import nl.jqno.paralleljava.app.domain.Todo; + +public interface Serializer { + String serializeTodo(Todo todo); + Option deserializeTodo(String json); +} diff --git a/src/test/java/nl/jqno/paralleljava/ArchitectureTest.java b/src/test/java/nl/jqno/paralleljava/ArchitectureTest.java index bfa466a..146b0ee 100644 --- a/src/test/java/nl/jqno/paralleljava/ArchitectureTest.java +++ b/src/test/java/nl/jqno/paralleljava/ArchitectureTest.java @@ -2,6 +2,7 @@ import com.tngtech.archunit.core.importer.ClassFileImporter; import nl.jqno.paralleljava.app.logging.Slf4jLogger; +import nl.jqno.paralleljava.app.serialization.GsonSerializer; import nl.jqno.paralleljava.app.server.SparkServer; import nl.jqno.paralleljava.app.server.SparkServerTest; import nl.jqno.paralleljava.dependencyinjection.WiredApplication; @@ -30,5 +31,13 @@ public void architecture() { .should().accessClassesThat().resideInAPackage("org.slf4j.."); rule.check(importedClasses); }); + + test("only GsonSerializer accesses Gson classes", () -> { + var rule = noClasses() + .that().resideOutsideOfPackage(GsonSerializer.class.getPackageName()) + .and().dontHaveFullyQualifiedName(WiredApplication.class.getCanonicalName()) + .should().accessClassesThat().resideInAPackage("com.google.gson.."); + rule.check(importedClasses); + }); } } diff --git a/src/test/java/nl/jqno/paralleljava/app/domain/TestData.java b/src/test/java/nl/jqno/paralleljava/app/domain/TestData.java new file mode 100644 index 0000000..f70f11f --- /dev/null +++ b/src/test/java/nl/jqno/paralleljava/app/domain/TestData.java @@ -0,0 +1,6 @@ +package nl.jqno.paralleljava.app.domain; + +public class TestData { + + public static final Todo SOME_TODO = new Todo(42, "title", "http://www.example.com", true, 1337); +} diff --git a/src/test/java/nl/jqno/paralleljava/app/domain/TodoTest.java b/src/test/java/nl/jqno/paralleljava/app/domain/TodoTest.java index 496bd26..e9d2714 100644 --- a/src/test/java/nl/jqno/paralleljava/app/domain/TodoTest.java +++ b/src/test/java/nl/jqno/paralleljava/app/domain/TodoTest.java @@ -15,7 +15,7 @@ public void todo() { }); test("getters", () -> { - var todo = new Todo(42, "title", "http://www.example.com", true, 1337); + var todo = TestData.SOME_TODO; assertThat(todo.id()).isEqualTo(42); assertThat(todo.title()).isEqualTo("title"); assertThat(todo.url()).isEqualTo("http://www.example.com"); diff --git a/src/test/java/nl/jqno/paralleljava/app/serialization/GsonSerializerTest.java b/src/test/java/nl/jqno/paralleljava/app/serialization/GsonSerializerTest.java new file mode 100644 index 0000000..c2cc21e --- /dev/null +++ b/src/test/java/nl/jqno/paralleljava/app/serialization/GsonSerializerTest.java @@ -0,0 +1,44 @@ +package nl.jqno.paralleljava.app.serialization; + +import com.google.gson.Gson; +import io.vavr.control.Option; +import nl.jqno.picotest.Test; + +import static nl.jqno.paralleljava.app.domain.TestData.SOME_TODO; +import static org.assertj.core.api.Assertions.assertThat; + +public class GsonSerializerTest extends Test { + + public void serialization() { + + var serializer = new GsonSerializer(new Gson()); + var someJson = "{\"id\":42,\"title\":\"title\",\"url\":\"http://www.example.com\",\"completed\":true,\"order\":1337}"; + + test("Serializes a Todo to json", () -> { + var actual = serializer.serializeTodo(SOME_TODO); + assertThat(actual) + .contains("\"id\":42") + .contains("\"title\":\"title\"") + .contains("\"url\":\"http://www.example.com\"") + .contains("\"completed\":true") + .contains("\"order\":1337"); + }); + + test("Deserializes a Todo from json", () -> { + var actual = serializer.deserializeTodo(someJson); + assertThat(actual).isEqualTo(Option.of(SOME_TODO)); + }); + + test("Deserialization returns none when json is invalid", () -> { + var invalidJson = "this is an invalid json document"; + var actual = serializer.deserializeTodo(invalidJson); + assertThat(actual).isEqualTo(Option.none()); + }); + + test("Does a complete round-trip", () -> { + var json = serializer.serializeTodo(SOME_TODO); + var actual = serializer.deserializeTodo(json); + assertThat(actual).isEqualTo(Option.of(SOME_TODO)); + }); + } +} From 2b3e2c411b6acd484cd52fc9c8cdfc272c3f576b Mon Sep 17 00:00:00 2001 From: Jan Ouwens Date: Tue, 19 Mar 2019 15:58:15 +0100 Subject: [PATCH 026/106] Refactors ArchitectureTest --- .../jqno/paralleljava/ArchitectureTest.java | 34 ++++++++----------- 1 file changed, 15 insertions(+), 19 deletions(-) diff --git a/src/test/java/nl/jqno/paralleljava/ArchitectureTest.java b/src/test/java/nl/jqno/paralleljava/ArchitectureTest.java index 146b0ee..2e2116b 100644 --- a/src/test/java/nl/jqno/paralleljava/ArchitectureTest.java +++ b/src/test/java/nl/jqno/paralleljava/ArchitectureTest.java @@ -1,10 +1,10 @@ package nl.jqno.paralleljava; +import com.tngtech.archunit.core.domain.JavaClasses; import com.tngtech.archunit.core.importer.ClassFileImporter; import nl.jqno.paralleljava.app.logging.Slf4jLogger; import nl.jqno.paralleljava.app.serialization.GsonSerializer; import nl.jqno.paralleljava.app.server.SparkServer; -import nl.jqno.paralleljava.app.server.SparkServerTest; import nl.jqno.paralleljava.dependencyinjection.WiredApplication; import nl.jqno.picotest.Test; @@ -12,32 +12,28 @@ public class ArchitectureTest extends Test { - public void architecture() { - var importedClasses = new ClassFileImporter() - .importPackages("nl.jqno.paralleljava"); + private static final JavaClasses IMPORTED_CLASSES = + new ClassFileImporter().importPackages("nl.jqno.paralleljava"); + public void architecture() { test("only SparkServer and SparkServerTest access Spark classes", () -> { - var rule = noClasses() - .that().dontHaveFullyQualifiedName(SparkServer.class.getCanonicalName()) - .and().dontHaveFullyQualifiedName(SparkServerTest.class.getCanonicalName()) - .should().accessClassesThat().resideInAPackage("spark.."); - rule.check(importedClasses); + assertBoundary("spark..", SparkServer.class.getPackage()); }); test("only Slf4jLogger accesses Slf4j classes", () -> { - var rule = noClasses() - .that().resideOutsideOfPackage(Slf4jLogger.class.getPackageName()) - .and().dontHaveFullyQualifiedName(WiredApplication.class.getCanonicalName()) - .should().accessClassesThat().resideInAPackage("org.slf4j.."); - rule.check(importedClasses); + assertBoundary("org.slf4j..", Slf4jLogger.class.getPackage()); }); test("only GsonSerializer accesses Gson classes", () -> { - var rule = noClasses() - .that().resideOutsideOfPackage(GsonSerializer.class.getPackageName()) - .and().dontHaveFullyQualifiedName(WiredApplication.class.getCanonicalName()) - .should().accessClassesThat().resideInAPackage("com.google.gson.."); - rule.check(importedClasses); + assertBoundary("com.google.gson..", GsonSerializer.class.getPackage()); }); } + + private void assertBoundary(String restrictedPackageIdentifier, Package whiteListedPackage) { + var rule = noClasses() + .that().resideOutsideOfPackage(whiteListedPackage.getName()) + .and().dontHaveFullyQualifiedName(WiredApplication.class.getCanonicalName()) + .should().accessClassesThat().resideInAPackage(restrictedPackageIdentifier); + rule.check(IMPORTED_CLASSES); + } } From 493515a0772635f8fd7207ba9b43621741a7fdf7 Mon Sep 17 00:00:00 2001 From: Jan Ouwens Date: Tue, 19 Mar 2019 16:12:44 +0100 Subject: [PATCH 027/106] Renames hello endpoint to todo --- .../java/nl/jqno/paralleljava/app/server/SparkServer.java | 2 +- .../java/nl/jqno/paralleljava/app/server/SparkServerTest.java | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/java/nl/jqno/paralleljava/app/server/SparkServer.java b/src/main/java/nl/jqno/paralleljava/app/server/SparkServer.java index 560ae31..b657aa1 100644 --- a/src/main/java/nl/jqno/paralleljava/app/server/SparkServer.java +++ b/src/main/java/nl/jqno/paralleljava/app/server/SparkServer.java @@ -25,7 +25,7 @@ public void run() { port(port); enableCors(); - get("/hello", convert(endpoints.helloWorld())); + get("/todo", convert(endpoints.helloWorld())); } private void enableCors() { diff --git a/src/test/java/nl/jqno/paralleljava/app/server/SparkServerTest.java b/src/test/java/nl/jqno/paralleljava/app/server/SparkServerTest.java index ae0b0fc..ab0e283 100644 --- a/src/test/java/nl/jqno/paralleljava/app/server/SparkServerTest.java +++ b/src/test/java/nl/jqno/paralleljava/app/server/SparkServerTest.java @@ -37,7 +37,7 @@ public void server() { private void helloWorldWorks() { when - .get("/hello") + .get("/todo") .then() .statusCode(200); assertThat(underlying.calledHelloWorld).isEqualTo(1); @@ -46,7 +46,7 @@ private void helloWorldWorks() { private void corsRequestsHeader() { when - .get("/hello") + .get("/todo") .then() .header("Access-Control-Allow-Origin", "*"); } From f73dcb0041a1b10cbe0be5c4c287f8fa75f68286 Mon Sep 17 00:00:00 2001 From: Jan Ouwens Date: Tue, 19 Mar 2019 16:22:29 +0100 Subject: [PATCH 028/106] Extracts Request to its own class --- .../jqno/paralleljava/app/endpoints/Request.java | 13 +++++++++++++ .../jqno/paralleljava/app/endpoints/Route.java | 4 +--- .../paralleljava/app/server/SparkServer.java | 6 +++++- .../app/endpoints/EndpointsTest.java | 4 ++-- .../paralleljava/app/endpoints/RequestTest.java | 16 ++++++++++++++++ 5 files changed, 37 insertions(+), 6 deletions(-) create mode 100644 src/main/java/nl/jqno/paralleljava/app/endpoints/Request.java create mode 100644 src/test/java/nl/jqno/paralleljava/app/endpoints/RequestTest.java diff --git a/src/main/java/nl/jqno/paralleljava/app/endpoints/Request.java b/src/main/java/nl/jqno/paralleljava/app/endpoints/Request.java new file mode 100644 index 0000000..5e3412e --- /dev/null +++ b/src/main/java/nl/jqno/paralleljava/app/endpoints/Request.java @@ -0,0 +1,13 @@ +package nl.jqno.paralleljava.app.endpoints; + +public class Request { + private final String body; + + public Request(String body) { + this.body = body; + } + + public String body() { + return body; + } +} diff --git a/src/main/java/nl/jqno/paralleljava/app/endpoints/Route.java b/src/main/java/nl/jqno/paralleljava/app/endpoints/Route.java index 5149572..794d8fb 100644 --- a/src/main/java/nl/jqno/paralleljava/app/endpoints/Route.java +++ b/src/main/java/nl/jqno/paralleljava/app/endpoints/Route.java @@ -1,7 +1,5 @@ package nl.jqno.paralleljava.app.endpoints; -import io.vavr.collection.Map; - public interface Route { - String handle(Map request); + String handle(Request request); } diff --git a/src/main/java/nl/jqno/paralleljava/app/server/SparkServer.java b/src/main/java/nl/jqno/paralleljava/app/server/SparkServer.java index b657aa1..e528439 100644 --- a/src/main/java/nl/jqno/paralleljava/app/server/SparkServer.java +++ b/src/main/java/nl/jqno/paralleljava/app/server/SparkServer.java @@ -3,6 +3,7 @@ import io.vavr.collection.HashMap; import io.vavr.control.Option; import nl.jqno.paralleljava.app.endpoints.Endpoints; +import nl.jqno.paralleljava.app.endpoints.Request; import nl.jqno.paralleljava.app.endpoints.Route; import nl.jqno.paralleljava.app.logging.Logger; @@ -43,6 +44,9 @@ private void enableCors() { } private spark.Route convert(Route route) { - return (request, response) -> route.handle(HashMap.ofAll(request.params())); + return (request, response) -> { + var req = new Request(request.body()); + return route.handle(req); + }; } } diff --git a/src/test/java/nl/jqno/paralleljava/app/endpoints/EndpointsTest.java b/src/test/java/nl/jqno/paralleljava/app/endpoints/EndpointsTest.java index d61504f..76bf771 100644 --- a/src/test/java/nl/jqno/paralleljava/app/endpoints/EndpointsTest.java +++ b/src/test/java/nl/jqno/paralleljava/app/endpoints/EndpointsTest.java @@ -1,6 +1,5 @@ package nl.jqno.paralleljava.app.endpoints; -import io.vavr.collection.HashMap; import nl.jqno.picotest.Test; import static org.assertj.core.api.Assertions.assertThat; @@ -8,11 +7,12 @@ public class EndpointsTest extends Test { public void endoints() { + var someRequest = new Request("body"); var endpoints = new DefaultEndpoints(); test("hello world works", () -> { var sut = endpoints.helloWorld(); - assertThat(sut.handle(HashMap.empty())).isEqualTo("Hello world"); + assertThat(sut.handle(someRequest)).isEqualTo("Hello world"); }); } } diff --git a/src/test/java/nl/jqno/paralleljava/app/endpoints/RequestTest.java b/src/test/java/nl/jqno/paralleljava/app/endpoints/RequestTest.java new file mode 100644 index 0000000..c2b3404 --- /dev/null +++ b/src/test/java/nl/jqno/paralleljava/app/endpoints/RequestTest.java @@ -0,0 +1,16 @@ +package nl.jqno.paralleljava.app.endpoints; + +import nl.jqno.picotest.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +public class RequestTest extends Test { + + public void request() { + var sut = new Request("body"); + + test("getters", () -> { + assertThat(sut.body()).isEqualTo("body"); + }); + } +} From b278cc0b28c343b7c9e95373ec12a5ad74f9baae Mon Sep 17 00:00:00 2001 From: Jan Ouwens Date: Tue, 19 Mar 2019 19:58:21 +0100 Subject: [PATCH 029/106] Implements POST endpoint --- .../app/endpoints/DefaultEndpoints.java | 4 ++++ .../paralleljava/app/endpoints/Endpoints.java | 2 ++ .../paralleljava/app/server/SparkServer.java | 2 ++ .../nl/jqno/paralleljava/app/TestData.java | 12 ++++++++++ .../paralleljava/app/domain/TestData.java | 6 ----- .../paralleljava/app/domain/TodoTest.java | 1 + .../app/endpoints/EndpointsTest.java | 10 +++++++- .../app/serialization/GsonSerializerTest.java | 6 ++--- .../app/server/SparkServerTest.java | 24 ++++++++++++++++++- 9 files changed, 56 insertions(+), 11 deletions(-) create mode 100644 src/test/java/nl/jqno/paralleljava/app/TestData.java delete mode 100644 src/test/java/nl/jqno/paralleljava/app/domain/TestData.java diff --git a/src/main/java/nl/jqno/paralleljava/app/endpoints/DefaultEndpoints.java b/src/main/java/nl/jqno/paralleljava/app/endpoints/DefaultEndpoints.java index 83d9764..e3931f0 100644 --- a/src/main/java/nl/jqno/paralleljava/app/endpoints/DefaultEndpoints.java +++ b/src/main/java/nl/jqno/paralleljava/app/endpoints/DefaultEndpoints.java @@ -5,4 +5,8 @@ public class DefaultEndpoints implements Endpoints { public Route helloWorld() { return ignored -> "Hello world"; } + + public Route post() { + return Request::body; + } } diff --git a/src/main/java/nl/jqno/paralleljava/app/endpoints/Endpoints.java b/src/main/java/nl/jqno/paralleljava/app/endpoints/Endpoints.java index 7fb1ca6..ee60af5 100644 --- a/src/main/java/nl/jqno/paralleljava/app/endpoints/Endpoints.java +++ b/src/main/java/nl/jqno/paralleljava/app/endpoints/Endpoints.java @@ -2,4 +2,6 @@ public interface Endpoints { Route helloWorld(); + + Route post(); } diff --git a/src/main/java/nl/jqno/paralleljava/app/server/SparkServer.java b/src/main/java/nl/jqno/paralleljava/app/server/SparkServer.java index e528439..316c013 100644 --- a/src/main/java/nl/jqno/paralleljava/app/server/SparkServer.java +++ b/src/main/java/nl/jqno/paralleljava/app/server/SparkServer.java @@ -26,7 +26,9 @@ public void run() { port(port); enableCors(); + get("/todo", convert(endpoints.helloWorld())); + post("/todo", convert(endpoints.post())); } private void enableCors() { diff --git a/src/test/java/nl/jqno/paralleljava/app/TestData.java b/src/test/java/nl/jqno/paralleljava/app/TestData.java new file mode 100644 index 0000000..fdc9c39 --- /dev/null +++ b/src/test/java/nl/jqno/paralleljava/app/TestData.java @@ -0,0 +1,12 @@ +package nl.jqno.paralleljava.app; + +import nl.jqno.paralleljava.app.domain.Todo; + +public class TestData { + + public static final Todo SOME_TODO = + new Todo(42, "title", "http://www.example.com", true, 1337); + + public static final String SOME_SERIALIZED_TODO = + "{\"id\":42,\"title\":\"title\",\"url\":\"http://www.example.com\",\"completed\":true,\"order\":1337}"; +} diff --git a/src/test/java/nl/jqno/paralleljava/app/domain/TestData.java b/src/test/java/nl/jqno/paralleljava/app/domain/TestData.java deleted file mode 100644 index f70f11f..0000000 --- a/src/test/java/nl/jqno/paralleljava/app/domain/TestData.java +++ /dev/null @@ -1,6 +0,0 @@ -package nl.jqno.paralleljava.app.domain; - -public class TestData { - - public static final Todo SOME_TODO = new Todo(42, "title", "http://www.example.com", true, 1337); -} diff --git a/src/test/java/nl/jqno/paralleljava/app/domain/TodoTest.java b/src/test/java/nl/jqno/paralleljava/app/domain/TodoTest.java index e9d2714..19f25e2 100644 --- a/src/test/java/nl/jqno/paralleljava/app/domain/TodoTest.java +++ b/src/test/java/nl/jqno/paralleljava/app/domain/TodoTest.java @@ -1,6 +1,7 @@ package nl.jqno.paralleljava.app.domain; import nl.jqno.equalsverifier.EqualsVerifier; +import nl.jqno.paralleljava.app.TestData; import nl.jqno.picotest.Test; import static org.assertj.core.api.Assertions.assertThat; diff --git a/src/test/java/nl/jqno/paralleljava/app/endpoints/EndpointsTest.java b/src/test/java/nl/jqno/paralleljava/app/endpoints/EndpointsTest.java index 76bf771..2231e4b 100644 --- a/src/test/java/nl/jqno/paralleljava/app/endpoints/EndpointsTest.java +++ b/src/test/java/nl/jqno/paralleljava/app/endpoints/EndpointsTest.java @@ -2,6 +2,7 @@ import nl.jqno.picotest.Test; +import static nl.jqno.paralleljava.app.TestData.SOME_SERIALIZED_TODO; import static org.assertj.core.api.Assertions.assertThat; public class EndpointsTest extends Test { @@ -12,7 +13,14 @@ public void endoints() { test("hello world works", () -> { var sut = endpoints.helloWorld(); - assertThat(sut.handle(someRequest)).isEqualTo("Hello world"); + var actual = sut.handle(someRequest); + assertThat(actual).isEqualTo("Hello world"); + }); + + test("post responds with the todo that was posted to it", () -> { + var sut = endpoints.post(); + var actual = sut.handle(new Request(SOME_SERIALIZED_TODO)); + assertThat(actual).isEqualTo(SOME_SERIALIZED_TODO); }); } } diff --git a/src/test/java/nl/jqno/paralleljava/app/serialization/GsonSerializerTest.java b/src/test/java/nl/jqno/paralleljava/app/serialization/GsonSerializerTest.java index c2cc21e..00a4868 100644 --- a/src/test/java/nl/jqno/paralleljava/app/serialization/GsonSerializerTest.java +++ b/src/test/java/nl/jqno/paralleljava/app/serialization/GsonSerializerTest.java @@ -4,7 +4,8 @@ import io.vavr.control.Option; import nl.jqno.picotest.Test; -import static nl.jqno.paralleljava.app.domain.TestData.SOME_TODO; +import static nl.jqno.paralleljava.app.TestData.SOME_SERIALIZED_TODO; +import static nl.jqno.paralleljava.app.TestData.SOME_TODO; import static org.assertj.core.api.Assertions.assertThat; public class GsonSerializerTest extends Test { @@ -12,7 +13,6 @@ public class GsonSerializerTest extends Test { public void serialization() { var serializer = new GsonSerializer(new Gson()); - var someJson = "{\"id\":42,\"title\":\"title\",\"url\":\"http://www.example.com\",\"completed\":true,\"order\":1337}"; test("Serializes a Todo to json", () -> { var actual = serializer.serializeTodo(SOME_TODO); @@ -25,7 +25,7 @@ public void serialization() { }); test("Deserializes a Todo from json", () -> { - var actual = serializer.deserializeTodo(someJson); + var actual = serializer.deserializeTodo(SOME_SERIALIZED_TODO); assertThat(actual).isEqualTo(Option.of(SOME_TODO)); }); diff --git a/src/test/java/nl/jqno/paralleljava/app/server/SparkServerTest.java b/src/test/java/nl/jqno/paralleljava/app/server/SparkServerTest.java index ab0e283..488cbac 100644 --- a/src/test/java/nl/jqno/paralleljava/app/server/SparkServerTest.java +++ b/src/test/java/nl/jqno/paralleljava/app/server/SparkServerTest.java @@ -33,6 +33,8 @@ public void server() { test("hello world works", this::helloWorldWorks); test("CORS Access-Control-AllowOrigin header is included", this::corsRequestsHeader); test("OPTION request", this::corsOptionsRequest); + + test("POST works", this::postRequest); } private void helloWorldWorks() { @@ -49,6 +51,11 @@ private void corsRequestsHeader() { .get("/todo") .then() .header("Access-Control-Allow-Origin", "*"); + + when + .post("/todo") + .then() + .header("Access-Control-Allow-Origin", "*"); } private void corsOptionsRequest() { @@ -63,22 +70,37 @@ private void corsOptionsRequest() { .header("Access-Control-Allow-Methods", methods); } + private void postRequest() { + when + .post("/todo") + .then() + .statusCode(200); + assertThat(underlying.calledPost).isEqualTo(1); + assertThat(underlying.calledTotal()).isEqualTo(1); + } + private static class StubEndpoints implements Endpoints { public int calledHelloWorld = 0; + public int calledPost = 0; public void clear() { calledHelloWorld = 0; + calledPost = 0; } public int calledTotal() { - return calledHelloWorld; + return calledHelloWorld + calledPost; } public Route helloWorld() { return stubbed(() -> calledHelloWorld += 1); } + public Route post() { + return stubbed(() -> calledPost += 1); + } + private Route stubbed(Runnable block) { return ignored -> { block.run(); From 96edf5a5ef0063c2beae769e1cbd4b178403e442 Mon Sep 17 00:00:00 2001 From: Jan Ouwens Date: Tue, 19 Mar 2019 20:02:17 +0100 Subject: [PATCH 030/106] Small refactoring --- .../paralleljava/dependencyinjection/WiredApplication.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/main/java/nl/jqno/paralleljava/dependencyinjection/WiredApplication.java b/src/main/java/nl/jqno/paralleljava/dependencyinjection/WiredApplication.java index 2b82f12..81b8344 100644 --- a/src/main/java/nl/jqno/paralleljava/dependencyinjection/WiredApplication.java +++ b/src/main/java/nl/jqno/paralleljava/dependencyinjection/WiredApplication.java @@ -36,7 +36,6 @@ private static int getPort() { var processBuilder = new ProcessBuilder(); var environment = HashMap.ofAll(processBuilder.environment()); var heroku = new Heroku(environment); - var port = heroku.getAssignedPort().getOrElse(DEFAULT_PORT); - return port; + return heroku.getAssignedPort().getOrElse(DEFAULT_PORT); } } From aedd1bbc26174a63c4f8a38b223ca90dd9ed9102 Mon Sep 17 00:00:00 2001 From: Jan Ouwens Date: Tue, 19 Mar 2019 20:18:01 +0100 Subject: [PATCH 031/106] Adds logs script --- scripts/deploy | 2 +- scripts/logs | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) create mode 100755 scripts/logs diff --git a/scripts/deploy b/scripts/deploy index 4d3e1bb..a6cb42b 100755 --- a/scripts/deploy +++ b/scripts/deploy @@ -1,3 +1,3 @@ #!/usr/bin/env bash -git push heroku master +git push -f heroku master diff --git a/scripts/logs b/scripts/logs new file mode 100755 index 0000000..b00a67a --- /dev/null +++ b/scripts/logs @@ -0,0 +1,4 @@ +#!/usr/bin/env bash + +heroku logs --tail --source app + From e3a14b775130475ee8fcf146babaa1dea8808dec Mon Sep 17 00:00:00 2001 From: Jan Ouwens Date: Tue, 19 Mar 2019 20:33:37 +0100 Subject: [PATCH 032/106] Renames EndpointsTest to DefaultEndpointsTest --- .../endpoints/{EndpointsTest.java => DefaultEndpointsTest.java} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename src/test/java/nl/jqno/paralleljava/app/endpoints/{EndpointsTest.java => DefaultEndpointsTest.java} (94%) diff --git a/src/test/java/nl/jqno/paralleljava/app/endpoints/EndpointsTest.java b/src/test/java/nl/jqno/paralleljava/app/endpoints/DefaultEndpointsTest.java similarity index 94% rename from src/test/java/nl/jqno/paralleljava/app/endpoints/EndpointsTest.java rename to src/test/java/nl/jqno/paralleljava/app/endpoints/DefaultEndpointsTest.java index 2231e4b..aec5564 100644 --- a/src/test/java/nl/jqno/paralleljava/app/endpoints/EndpointsTest.java +++ b/src/test/java/nl/jqno/paralleljava/app/endpoints/DefaultEndpointsTest.java @@ -5,7 +5,7 @@ import static nl.jqno.paralleljava.app.TestData.SOME_SERIALIZED_TODO; import static org.assertj.core.api.Assertions.assertThat; -public class EndpointsTest extends Test { +public class DefaultEndpointsTest extends Test { public void endoints() { var someRequest = new Request("body"); From 6ba04d26c2bb0462ba5095f5c71e6bfd15407ba5 Mon Sep 17 00:00:00 2001 From: Jan Ouwens Date: Tue, 19 Mar 2019 20:38:44 +0100 Subject: [PATCH 033/106] Implements DELETE endpoint --- .../app/endpoints/DefaultEndpoints.java | 4 ++++ .../paralleljava/app/endpoints/Endpoints.java | 1 + .../paralleljava/app/server/SparkServer.java | 1 + .../app/endpoints/DefaultEndpointsTest.java | 6 ++++++ .../app/server/SparkServerTest.java | 18 +++++++++++++++++- 5 files changed, 29 insertions(+), 1 deletion(-) diff --git a/src/main/java/nl/jqno/paralleljava/app/endpoints/DefaultEndpoints.java b/src/main/java/nl/jqno/paralleljava/app/endpoints/DefaultEndpoints.java index e3931f0..b63fc46 100644 --- a/src/main/java/nl/jqno/paralleljava/app/endpoints/DefaultEndpoints.java +++ b/src/main/java/nl/jqno/paralleljava/app/endpoints/DefaultEndpoints.java @@ -9,4 +9,8 @@ public Route helloWorld() { public Route post() { return Request::body; } + + public Route delete() { + return ignored -> ""; + } } diff --git a/src/main/java/nl/jqno/paralleljava/app/endpoints/Endpoints.java b/src/main/java/nl/jqno/paralleljava/app/endpoints/Endpoints.java index ee60af5..69af221 100644 --- a/src/main/java/nl/jqno/paralleljava/app/endpoints/Endpoints.java +++ b/src/main/java/nl/jqno/paralleljava/app/endpoints/Endpoints.java @@ -4,4 +4,5 @@ public interface Endpoints { Route helloWorld(); Route post(); + Route delete(); } diff --git a/src/main/java/nl/jqno/paralleljava/app/server/SparkServer.java b/src/main/java/nl/jqno/paralleljava/app/server/SparkServer.java index 316c013..505dcbd 100644 --- a/src/main/java/nl/jqno/paralleljava/app/server/SparkServer.java +++ b/src/main/java/nl/jqno/paralleljava/app/server/SparkServer.java @@ -29,6 +29,7 @@ public void run() { get("/todo", convert(endpoints.helloWorld())); post("/todo", convert(endpoints.post())); + delete("/todo", convert(endpoints.delete())); } private void enableCors() { diff --git a/src/test/java/nl/jqno/paralleljava/app/endpoints/DefaultEndpointsTest.java b/src/test/java/nl/jqno/paralleljava/app/endpoints/DefaultEndpointsTest.java index aec5564..a3e93c6 100644 --- a/src/test/java/nl/jqno/paralleljava/app/endpoints/DefaultEndpointsTest.java +++ b/src/test/java/nl/jqno/paralleljava/app/endpoints/DefaultEndpointsTest.java @@ -22,5 +22,11 @@ public void endoints() { var actual = sut.handle(new Request(SOME_SERIALIZED_TODO)); assertThat(actual).isEqualTo(SOME_SERIALIZED_TODO); }); + + test("delete basically does nothing", () -> { + var sut = endpoints.delete(); + var actual = sut.handle(someRequest); + assertThat(actual).isEqualTo(""); + }); } } diff --git a/src/test/java/nl/jqno/paralleljava/app/server/SparkServerTest.java b/src/test/java/nl/jqno/paralleljava/app/server/SparkServerTest.java index 488cbac..115e4f6 100644 --- a/src/test/java/nl/jqno/paralleljava/app/server/SparkServerTest.java +++ b/src/test/java/nl/jqno/paralleljava/app/server/SparkServerTest.java @@ -35,6 +35,7 @@ public void server() { test("OPTION request", this::corsOptionsRequest); test("POST works", this::postRequest); + test("DELETE works", this::deleteRequest); } private void helloWorldWorks() { @@ -79,18 +80,29 @@ private void postRequest() { assertThat(underlying.calledTotal()).isEqualTo(1); } + private void deleteRequest() { + when + .delete("/todo") + .then() + .statusCode(200); + assertThat(underlying.calledDelete).isEqualTo(1); + assertThat(underlying.calledTotal()).isEqualTo(1); + } + private static class StubEndpoints implements Endpoints { public int calledHelloWorld = 0; public int calledPost = 0; + public int calledDelete = 0; public void clear() { calledHelloWorld = 0; calledPost = 0; + calledDelete = 0; } public int calledTotal() { - return calledHelloWorld + calledPost; + return calledHelloWorld + calledPost + calledDelete; } public Route helloWorld() { @@ -101,6 +113,10 @@ public Route post() { return stubbed(() -> calledPost += 1); } + public Route delete() { + return stubbed(() -> calledDelete += 1); + } + private Route stubbed(Runnable block) { return ignored -> { block.run(); From f79bed0190b9530d7c21c97dd0c2df308eb97b52 Mon Sep 17 00:00:00 2001 From: Jan Ouwens Date: Tue, 19 Mar 2019 20:41:25 +0100 Subject: [PATCH 034/106] Extracts assertSingleCall method --- .../paralleljava/app/server/SparkServerTest.java | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/test/java/nl/jqno/paralleljava/app/server/SparkServerTest.java b/src/test/java/nl/jqno/paralleljava/app/server/SparkServerTest.java index 115e4f6..f0e1d67 100644 --- a/src/test/java/nl/jqno/paralleljava/app/server/SparkServerTest.java +++ b/src/test/java/nl/jqno/paralleljava/app/server/SparkServerTest.java @@ -43,8 +43,7 @@ private void helloWorldWorks() { .get("/todo") .then() .statusCode(200); - assertThat(underlying.calledHelloWorld).isEqualTo(1); - assertThat(underlying.calledTotal()).isEqualTo(1); + assertSingleCall(underlying.calledHelloWorld); } private void corsRequestsHeader() { @@ -76,8 +75,7 @@ private void postRequest() { .post("/todo") .then() .statusCode(200); - assertThat(underlying.calledPost).isEqualTo(1); - assertThat(underlying.calledTotal()).isEqualTo(1); + assertSingleCall(underlying.calledPost); } private void deleteRequest() { @@ -85,7 +83,11 @@ private void deleteRequest() { .delete("/todo") .then() .statusCode(200); - assertThat(underlying.calledDelete).isEqualTo(1); + assertSingleCall(underlying.calledDelete); + } + + private void assertSingleCall(int calledEndpoint) { + assertThat(calledEndpoint).isEqualTo(1); assertThat(underlying.calledTotal()).isEqualTo(1); } From 23bbddaeb0784fb629cd88a265b1e4a4747e2d65 Mon Sep 17 00:00:00 2001 From: Jan Ouwens Date: Tue, 19 Mar 2019 21:01:20 +0100 Subject: [PATCH 035/106] Adds toString to Todo --- .../nl/jqno/paralleljava/app/domain/Todo.java | 4 ++++ .../jqno/paralleljava/app/domain/TodoTest.java | 17 ++++++++++------- 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/src/main/java/nl/jqno/paralleljava/app/domain/Todo.java b/src/main/java/nl/jqno/paralleljava/app/domain/Todo.java index 63b47ef..e85e753 100644 --- a/src/main/java/nl/jqno/paralleljava/app/domain/Todo.java +++ b/src/main/java/nl/jqno/paralleljava/app/domain/Todo.java @@ -52,4 +52,8 @@ public boolean equals(Object obj) { public int hashCode() { return Objects.hash(id, title, url, completed, order); } + + public String toString() { + return "Todo: [id=" + id + ", title=" + title + ", url=" + url + ", completed=" + completed + ", order=" + order + "]"; + } } diff --git a/src/test/java/nl/jqno/paralleljava/app/domain/TodoTest.java b/src/test/java/nl/jqno/paralleljava/app/domain/TodoTest.java index 19f25e2..89b792f 100644 --- a/src/test/java/nl/jqno/paralleljava/app/domain/TodoTest.java +++ b/src/test/java/nl/jqno/paralleljava/app/domain/TodoTest.java @@ -1,9 +1,9 @@ package nl.jqno.paralleljava.app.domain; import nl.jqno.equalsverifier.EqualsVerifier; -import nl.jqno.paralleljava.app.TestData; import nl.jqno.picotest.Test; +import static nl.jqno.paralleljava.app.TestData.SOME_TODO; import static org.assertj.core.api.Assertions.assertThat; public class TodoTest extends Test { @@ -16,12 +16,15 @@ public void todo() { }); test("getters", () -> { - var todo = TestData.SOME_TODO; - assertThat(todo.id()).isEqualTo(42); - assertThat(todo.title()).isEqualTo("title"); - assertThat(todo.url()).isEqualTo("http://www.example.com"); - assertThat(todo.completed()).isEqualTo(true); - assertThat(todo.order()).isEqualTo(1337); + assertThat(SOME_TODO.id()).isEqualTo(42); + assertThat(SOME_TODO.title()).isEqualTo("title"); + assertThat(SOME_TODO.url()).isEqualTo("http://www.example.com"); + assertThat(SOME_TODO.completed()).isEqualTo(true); + assertThat(SOME_TODO.order()).isEqualTo(1337); + }); + + test("toString", () -> { + assertThat(SOME_TODO.toString()).isEqualTo("Todo: [id=42, title=title, url=http://www.example.com, completed=true, order=1337]"); }); } } From fb9754254702d7c159d17062b9a81d60beab3e46 Mon Sep 17 00:00:00 2001 From: Jan Ouwens Date: Tue, 19 Mar 2019 21:18:28 +0100 Subject: [PATCH 036/106] Implements serializer for lists of Todos --- pom.yml | 3 +- src/main/java/module-info.java | 1 + .../app/serialization/GsonSerializer.java | 14 +++++++ .../app/serialization/Serializer.java | 4 ++ .../dependencyinjection/WiredApplication.java | 11 +++++ .../nl/jqno/paralleljava/app/TestData.java | 12 ++++++ .../app/serialization/GsonSerializerTest.java | 41 +++++++++++++++---- 7 files changed, 78 insertions(+), 8 deletions(-) diff --git a/pom.yml b/pom.yml index faf4b24..f5641c7 100644 --- a/pom.yml +++ b/pom.yml @@ -18,7 +18,8 @@ repositories: - { id: bintray-jqno-picotest-repo, url: "https://dl.bintray.com/jqno/picotest-repo" } dependencies: - - { groupId: io.vavr, artifactId: vavr, version: 0.9.3 } + - { groupId: io.vavr, artifactId: vavr, version: 0.10.0 } + - { groupId: io.vavr, artifactId: vavr-gson, version: 0.10.0 } - { groupId: com.sparkjava, artifactId: spark-core, version: 2.7.2 } - { groupId: org.slf4j, artifactId: slf4j-simple, version: 1.7.26 } - { groupId: com.google.code.gson, artifactId: gson, version: 2.8.5 } diff --git a/src/main/java/module-info.java b/src/main/java/module-info.java index e0ef082..fe0a93b 100644 --- a/src/main/java/module-info.java +++ b/src/main/java/module-info.java @@ -2,6 +2,7 @@ exports nl.jqno.paralleljava; requires io.vavr; + requires io.vavr.gson; requires gson; requires slf4j.api; requires spark.core; diff --git a/src/main/java/nl/jqno/paralleljava/app/serialization/GsonSerializer.java b/src/main/java/nl/jqno/paralleljava/app/serialization/GsonSerializer.java index 23f1c68..fb26d19 100644 --- a/src/main/java/nl/jqno/paralleljava/app/serialization/GsonSerializer.java +++ b/src/main/java/nl/jqno/paralleljava/app/serialization/GsonSerializer.java @@ -1,11 +1,17 @@ package nl.jqno.paralleljava.app.serialization; import com.google.gson.Gson; +import com.google.gson.reflect.TypeToken; +import io.vavr.collection.List; import io.vavr.control.Option; import io.vavr.control.Try; import nl.jqno.paralleljava.app.domain.Todo; +import java.lang.reflect.Type; + public class GsonSerializer implements Serializer { + private static final Type LIST_OF_TODO_TYPE = new TypeToken>(){}.getType(); + private final Gson gson; public GsonSerializer(Gson gson) { @@ -19,4 +25,12 @@ public String serializeTodo(Todo todo) { public Option deserializeTodo(String json) { return Try.of(() -> gson.fromJson(json, Todo.class)).toOption(); } + + public String serializeTodos(List todos) { + return gson.toJson(todos); + } + + public List deserializeTodos(String json) { + return Try.of(() -> (List)gson.fromJson(json, LIST_OF_TODO_TYPE)).getOrElse(List.empty()); + } } diff --git a/src/main/java/nl/jqno/paralleljava/app/serialization/Serializer.java b/src/main/java/nl/jqno/paralleljava/app/serialization/Serializer.java index d6b6c30..0ea79a9 100644 --- a/src/main/java/nl/jqno/paralleljava/app/serialization/Serializer.java +++ b/src/main/java/nl/jqno/paralleljava/app/serialization/Serializer.java @@ -1,9 +1,13 @@ package nl.jqno.paralleljava.app.serialization; +import io.vavr.collection.List; import io.vavr.control.Option; import nl.jqno.paralleljava.app.domain.Todo; public interface Serializer { String serializeTodo(Todo todo); Option deserializeTodo(String json); + + String serializeTodos(List todos); + List deserializeTodos(String json); } diff --git a/src/main/java/nl/jqno/paralleljava/dependencyinjection/WiredApplication.java b/src/main/java/nl/jqno/paralleljava/dependencyinjection/WiredApplication.java index 81b8344..172fa09 100644 --- a/src/main/java/nl/jqno/paralleljava/dependencyinjection/WiredApplication.java +++ b/src/main/java/nl/jqno/paralleljava/dependencyinjection/WiredApplication.java @@ -1,10 +1,15 @@ package nl.jqno.paralleljava.dependencyinjection; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; import io.vavr.Function1; import io.vavr.collection.HashMap; +import io.vavr.gson.VavrGson; import nl.jqno.paralleljava.app.endpoints.DefaultEndpoints; import nl.jqno.paralleljava.app.logging.Logger; import nl.jqno.paralleljava.app.logging.Slf4jLogger; +import nl.jqno.paralleljava.app.serialization.GsonSerializer; +import nl.jqno.paralleljava.app.serialization.Serializer; import nl.jqno.paralleljava.app.server.Heroku; import nl.jqno.paralleljava.app.server.Server; import nl.jqno.paralleljava.app.server.SparkServer; @@ -26,6 +31,12 @@ public void run() { server.run(); } + public static Serializer defaultSerializer() { + var gsonBuilder = new GsonBuilder(); + VavrGson.registerAll(gsonBuilder); + return new GsonSerializer(gsonBuilder.create()); + } + private static Server createServer(Function1, Logger> loggerFactory) { int port = getPort(); var endpoints = new DefaultEndpoints(); diff --git a/src/test/java/nl/jqno/paralleljava/app/TestData.java b/src/test/java/nl/jqno/paralleljava/app/TestData.java index fdc9c39..0568506 100644 --- a/src/test/java/nl/jqno/paralleljava/app/TestData.java +++ b/src/test/java/nl/jqno/paralleljava/app/TestData.java @@ -1,5 +1,6 @@ package nl.jqno.paralleljava.app; +import io.vavr.collection.List; import nl.jqno.paralleljava.app.domain.Todo; public class TestData { @@ -7,6 +8,17 @@ public class TestData { public static final Todo SOME_TODO = new Todo(42, "title", "http://www.example.com", true, 1337); + public static final Todo ANOTHER_TODO = + new Todo(47, "something", "http://www.todobackend.com", false, 1); + + public static final List SOME_LIST_OF_TODOS = List.of(SOME_TODO, ANOTHER_TODO); + public static final String SOME_SERIALIZED_TODO = "{\"id\":42,\"title\":\"title\",\"url\":\"http://www.example.com\",\"completed\":true,\"order\":1337}"; + + public static final String ANOTHER_SERIALIZED_TODO = + "{\"id\":47,\"title\":\"something\",\"url\":\"http://www.todobackend.com\",\"completed\":false,\"order\":1}"; + + public static final String SOME_SERIALIZED_LIST_OF_TODOS = + "[" + SOME_SERIALIZED_TODO + "," + ANOTHER_SERIALIZED_TODO + "]"; } diff --git a/src/test/java/nl/jqno/paralleljava/app/serialization/GsonSerializerTest.java b/src/test/java/nl/jqno/paralleljava/app/serialization/GsonSerializerTest.java index 00a4868..af292ac 100644 --- a/src/test/java/nl/jqno/paralleljava/app/serialization/GsonSerializerTest.java +++ b/src/test/java/nl/jqno/paralleljava/app/serialization/GsonSerializerTest.java @@ -1,18 +1,18 @@ package nl.jqno.paralleljava.app.serialization; -import com.google.gson.Gson; +import io.vavr.collection.List; import io.vavr.control.Option; +import nl.jqno.paralleljava.dependencyinjection.WiredApplication; import nl.jqno.picotest.Test; -import static nl.jqno.paralleljava.app.TestData.SOME_SERIALIZED_TODO; -import static nl.jqno.paralleljava.app.TestData.SOME_TODO; +import static nl.jqno.paralleljava.app.TestData.*; import static org.assertj.core.api.Assertions.assertThat; public class GsonSerializerTest extends Test { - public void serialization() { + private Serializer serializer = WiredApplication.defaultSerializer(); - var serializer = new GsonSerializer(new Gson()); + public void serializationOfASingleTodo() { test("Serializes a Todo to json", () -> { var actual = serializer.serializeTodo(SOME_TODO); @@ -29,16 +29,43 @@ public void serialization() { assertThat(actual).isEqualTo(Option.of(SOME_TODO)); }); - test("Deserialization returns none when json is invalid", () -> { + test("Deserialization of a Todo returns none when json is invalid", () -> { var invalidJson = "this is an invalid json document"; var actual = serializer.deserializeTodo(invalidJson); assertThat(actual).isEqualTo(Option.none()); }); - test("Does a complete round-trip", () -> { + test("Does a complete round-trip on Todo", () -> { var json = serializer.serializeTodo(SOME_TODO); var actual = serializer.deserializeTodo(json); assertThat(actual).isEqualTo(Option.of(SOME_TODO)); }); } + + public void serializationOfAListOfTodos() { + + test("Serializes a list of Todos to json", () -> { + var actual = serializer.serializeTodos(SOME_LIST_OF_TODOS); + assertThat(actual) + .contains(SOME_SERIALIZED_TODO) + .contains(ANOTHER_SERIALIZED_TODO); + }); + + test("Deserializes a list of Todos from json", () -> { + var actual = serializer.deserializeTodos(SOME_SERIALIZED_LIST_OF_TODOS); + assertThat(actual).containsExactlyElementsOf(SOME_LIST_OF_TODOS); + }); + + test("Deserialization of a list of Todos returns an empty list when json is invalid", () -> { + var invalidJson = SOME_SERIALIZED_TODO; // but not a list + var actual = serializer.deserializeTodos(invalidJson); + assertThat(actual).isEqualTo(List.empty()); + }); + + test("Does a complete round-trip on lists of Todos", () -> { + var json = serializer.serializeTodos(SOME_LIST_OF_TODOS); + var actual = serializer.deserializeTodos(json); + assertThat(actual).isEqualTo(SOME_LIST_OF_TODOS); + }); + } } From 91083fc3949a9b31bca912e76be70b60d774c714 Mon Sep 17 00:00:00 2001 From: Jan Ouwens Date: Wed, 20 Mar 2019 17:04:50 +0100 Subject: [PATCH 037/106] Implements GET endpoint to replace helloworld --- .../app/endpoints/DefaultEndpoints.java | 12 ++++++-- .../paralleljava/app/endpoints/Endpoints.java | 3 +- .../paralleljava/app/server/SparkServer.java | 2 +- .../dependencyinjection/WiredApplication.java | 2 +- .../app/endpoints/DefaultEndpointsTest.java | 11 +++++--- .../app/server/SparkServerTest.java | 28 +++++++++---------- 6 files changed, 34 insertions(+), 24 deletions(-) diff --git a/src/main/java/nl/jqno/paralleljava/app/endpoints/DefaultEndpoints.java b/src/main/java/nl/jqno/paralleljava/app/endpoints/DefaultEndpoints.java index b63fc46..6796d95 100644 --- a/src/main/java/nl/jqno/paralleljava/app/endpoints/DefaultEndpoints.java +++ b/src/main/java/nl/jqno/paralleljava/app/endpoints/DefaultEndpoints.java @@ -1,9 +1,17 @@ package nl.jqno.paralleljava.app.endpoints; +import io.vavr.collection.List; +import nl.jqno.paralleljava.app.serialization.Serializer; + public class DefaultEndpoints implements Endpoints { + private final Serializer serializer; + + public DefaultEndpoints(Serializer serializer) { + this.serializer = serializer; + } - public Route helloWorld() { - return ignored -> "Hello world"; + public Route get() { + return ignored -> serializer.serializeTodos(List.empty()); } public Route post() { diff --git a/src/main/java/nl/jqno/paralleljava/app/endpoints/Endpoints.java b/src/main/java/nl/jqno/paralleljava/app/endpoints/Endpoints.java index 69af221..d67aa08 100644 --- a/src/main/java/nl/jqno/paralleljava/app/endpoints/Endpoints.java +++ b/src/main/java/nl/jqno/paralleljava/app/endpoints/Endpoints.java @@ -1,8 +1,7 @@ package nl.jqno.paralleljava.app.endpoints; public interface Endpoints { - Route helloWorld(); - + Route get(); Route post(); Route delete(); } diff --git a/src/main/java/nl/jqno/paralleljava/app/server/SparkServer.java b/src/main/java/nl/jqno/paralleljava/app/server/SparkServer.java index 505dcbd..a85aa90 100644 --- a/src/main/java/nl/jqno/paralleljava/app/server/SparkServer.java +++ b/src/main/java/nl/jqno/paralleljava/app/server/SparkServer.java @@ -27,7 +27,7 @@ public void run() { port(port); enableCors(); - get("/todo", convert(endpoints.helloWorld())); + get("/todo", convert(endpoints.get())); post("/todo", convert(endpoints.post())); delete("/todo", convert(endpoints.delete())); } diff --git a/src/main/java/nl/jqno/paralleljava/dependencyinjection/WiredApplication.java b/src/main/java/nl/jqno/paralleljava/dependencyinjection/WiredApplication.java index 172fa09..68df4d5 100644 --- a/src/main/java/nl/jqno/paralleljava/dependencyinjection/WiredApplication.java +++ b/src/main/java/nl/jqno/paralleljava/dependencyinjection/WiredApplication.java @@ -39,7 +39,7 @@ public static Serializer defaultSerializer() { private static Server createServer(Function1, Logger> loggerFactory) { int port = getPort(); - var endpoints = new DefaultEndpoints(); + var endpoints = new DefaultEndpoints(defaultSerializer()); return new SparkServer(endpoints, port, loggerFactory.apply(SparkServer.class)); } diff --git a/src/test/java/nl/jqno/paralleljava/app/endpoints/DefaultEndpointsTest.java b/src/test/java/nl/jqno/paralleljava/app/endpoints/DefaultEndpointsTest.java index a3e93c6..95eef90 100644 --- a/src/test/java/nl/jqno/paralleljava/app/endpoints/DefaultEndpointsTest.java +++ b/src/test/java/nl/jqno/paralleljava/app/endpoints/DefaultEndpointsTest.java @@ -1,5 +1,7 @@ package nl.jqno.paralleljava.app.endpoints; +import io.vavr.collection.List; +import nl.jqno.paralleljava.dependencyinjection.WiredApplication; import nl.jqno.picotest.Test; import static nl.jqno.paralleljava.app.TestData.SOME_SERIALIZED_TODO; @@ -8,13 +10,14 @@ public class DefaultEndpointsTest extends Test { public void endoints() { + var serializer = WiredApplication.defaultSerializer(); var someRequest = new Request("body"); - var endpoints = new DefaultEndpoints(); + var endpoints = new DefaultEndpoints(serializer); - test("hello world works", () -> { - var sut = endpoints.helloWorld(); + test("get returns an empty array", () -> { + var sut = endpoints.get(); var actual = sut.handle(someRequest); - assertThat(actual).isEqualTo("Hello world"); + assertThat(actual).isEqualTo(serializer.serializeTodos(List.empty())); }); test("post responds with the todo that was posted to it", () -> { diff --git a/src/test/java/nl/jqno/paralleljava/app/server/SparkServerTest.java b/src/test/java/nl/jqno/paralleljava/app/server/SparkServerTest.java index f0e1d67..827d799 100644 --- a/src/test/java/nl/jqno/paralleljava/app/server/SparkServerTest.java +++ b/src/test/java/nl/jqno/paralleljava/app/server/SparkServerTest.java @@ -30,22 +30,14 @@ public void server() { afterAll(Spark::stop); - test("hello world works", this::helloWorldWorks); test("CORS Access-Control-AllowOrigin header is included", this::corsRequestsHeader); test("OPTION request", this::corsOptionsRequest); + test("GET works", this::getRequest); test("POST works", this::postRequest); test("DELETE works", this::deleteRequest); } - private void helloWorldWorks() { - when - .get("/todo") - .then() - .statusCode(200); - assertSingleCall(underlying.calledHelloWorld); - } - private void corsRequestsHeader() { when .get("/todo") @@ -70,6 +62,14 @@ private void corsOptionsRequest() { .header("Access-Control-Allow-Methods", methods); } + private void getRequest() { + when + .get("/todo") + .then() + .statusCode(200); + assertSingleCall(underlying.calledGet); + } + private void postRequest() { when .post("/todo") @@ -93,22 +93,22 @@ private void assertSingleCall(int calledEndpoint) { private static class StubEndpoints implements Endpoints { - public int calledHelloWorld = 0; + public int calledGet = 0; public int calledPost = 0; public int calledDelete = 0; public void clear() { - calledHelloWorld = 0; + calledGet = 0; calledPost = 0; calledDelete = 0; } public int calledTotal() { - return calledHelloWorld + calledPost + calledDelete; + return calledGet + calledPost + calledDelete; } - public Route helloWorld() { - return stubbed(() -> calledHelloWorld += 1); + public Route get() { + return stubbed(() -> calledGet += 1); } public Route post() { From 5e9928f351cf04d6689e4502aa881129b652cdb0 Mon Sep 17 00:00:00 2001 From: Jan Ouwens Date: Wed, 20 Mar 2019 17:39:41 +0100 Subject: [PATCH 038/106] Fixes NoClassDefError in gson --- src/main/java/module-info.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/module-info.java b/src/main/java/module-info.java index fe0a93b..5867575 100644 --- a/src/main/java/module-info.java +++ b/src/main/java/module-info.java @@ -3,6 +3,7 @@ requires io.vavr; requires io.vavr.gson; + requires java.sql; // required for gson requires gson; requires slf4j.api; requires spark.core; From e49b55798d8bdd65a05320645cd5d54f3d98feb2 Mon Sep 17 00:00:00 2001 From: Jan Ouwens Date: Wed, 20 Mar 2019 20:51:57 +0100 Subject: [PATCH 039/106] Adds InMemoryRepository --- .../app/persistence/InMemoryRepository.java | 23 +++++++++++++++ .../app/persistence/Repository.java | 10 +++++++ .../persistence/InMemoryRepositoryTest.java | 28 +++++++++++++++++++ 3 files changed, 61 insertions(+) create mode 100644 src/main/java/nl/jqno/paralleljava/app/persistence/InMemoryRepository.java create mode 100644 src/main/java/nl/jqno/paralleljava/app/persistence/Repository.java create mode 100644 src/test/java/nl/jqno/paralleljava/app/persistence/InMemoryRepositoryTest.java diff --git a/src/main/java/nl/jqno/paralleljava/app/persistence/InMemoryRepository.java b/src/main/java/nl/jqno/paralleljava/app/persistence/InMemoryRepository.java new file mode 100644 index 0000000..f0f8f1b --- /dev/null +++ b/src/main/java/nl/jqno/paralleljava/app/persistence/InMemoryRepository.java @@ -0,0 +1,23 @@ +package nl.jqno.paralleljava.app.persistence; + +import io.vavr.collection.List; +import nl.jqno.paralleljava.app.domain.Todo; + +import java.util.ArrayList; + +public class InMemoryRepository implements Repository { + + private static final java.util.List todos = new ArrayList<>(); + + public void createTodo(Todo todo) { + todos.add(todo); + } + + public List getAllTodos() { + return List.ofAll(todos); + } + + public void clearAllTodos() { + todos.clear(); + } +} diff --git a/src/main/java/nl/jqno/paralleljava/app/persistence/Repository.java b/src/main/java/nl/jqno/paralleljava/app/persistence/Repository.java new file mode 100644 index 0000000..5ec3b26 --- /dev/null +++ b/src/main/java/nl/jqno/paralleljava/app/persistence/Repository.java @@ -0,0 +1,10 @@ +package nl.jqno.paralleljava.app.persistence; + +import io.vavr.collection.List; +import nl.jqno.paralleljava.app.domain.Todo; + +public interface Repository { + void createTodo(Todo todo); + List getAllTodos(); + void clearAllTodos(); +} diff --git a/src/test/java/nl/jqno/paralleljava/app/persistence/InMemoryRepositoryTest.java b/src/test/java/nl/jqno/paralleljava/app/persistence/InMemoryRepositoryTest.java new file mode 100644 index 0000000..ad270d4 --- /dev/null +++ b/src/test/java/nl/jqno/paralleljava/app/persistence/InMemoryRepositoryTest.java @@ -0,0 +1,28 @@ +package nl.jqno.paralleljava.app.persistence; + +import nl.jqno.picotest.Test; + +import static nl.jqno.paralleljava.app.TestData.SOME_TODO; +import static org.assertj.core.api.Assertions.assertThat; + +public class InMemoryRepositoryTest extends Test { + + public void repository() { + var repo = new InMemoryRepository(); + + beforeEach(() -> { + repo.clearAllTodos(); + }); + + test("create a todo", () -> { + repo.createTodo(SOME_TODO); + assertThat(repo.getAllTodos()).contains(SOME_TODO); + }); + + test("clearing all todos", () -> { + repo.createTodo(SOME_TODO); + repo.clearAllTodos(); + assertThat(repo.getAllTodos()).isEmpty(); + }); + } +} From 242db69de92d6d417dfec87f1d65a94dfebb97d2 Mon Sep 17 00:00:00 2001 From: Jan Ouwens Date: Wed, 20 Mar 2019 21:04:36 +0100 Subject: [PATCH 040/106] Uses Repository to store Todos in Endpoints --- .../app/endpoints/DefaultEndpoints.java | 20 ++++++++--- .../dependencyinjection/WiredApplication.java | 10 ++++-- .../app/endpoints/DefaultEndpointsTest.java | 33 +++++++++++++++---- 3 files changed, 49 insertions(+), 14 deletions(-) diff --git a/src/main/java/nl/jqno/paralleljava/app/endpoints/DefaultEndpoints.java b/src/main/java/nl/jqno/paralleljava/app/endpoints/DefaultEndpoints.java index 6796d95..568bd0f 100644 --- a/src/main/java/nl/jqno/paralleljava/app/endpoints/DefaultEndpoints.java +++ b/src/main/java/nl/jqno/paralleljava/app/endpoints/DefaultEndpoints.java @@ -1,24 +1,34 @@ package nl.jqno.paralleljava.app.endpoints; -import io.vavr.collection.List; +import nl.jqno.paralleljava.app.persistence.Repository; import nl.jqno.paralleljava.app.serialization.Serializer; public class DefaultEndpoints implements Endpoints { + private final Repository repository; private final Serializer serializer; - public DefaultEndpoints(Serializer serializer) { + public DefaultEndpoints(Repository repository, Serializer serializer) { + this.repository = repository; this.serializer = serializer; } public Route get() { - return ignored -> serializer.serializeTodos(List.empty()); + return ignored -> serializer.serializeTodos(repository.getAllTodos()); } public Route post() { - return Request::body; + return request -> { + var json = request.body(); + var todo = serializer.deserializeTodo(json); + todo.forEach(repository::createTodo); + return json; + }; } public Route delete() { - return ignored -> ""; + return ignored -> { + repository.clearAllTodos(); + return ""; + }; } } diff --git a/src/main/java/nl/jqno/paralleljava/dependencyinjection/WiredApplication.java b/src/main/java/nl/jqno/paralleljava/dependencyinjection/WiredApplication.java index 68df4d5..0a0b2eb 100644 --- a/src/main/java/nl/jqno/paralleljava/dependencyinjection/WiredApplication.java +++ b/src/main/java/nl/jqno/paralleljava/dependencyinjection/WiredApplication.java @@ -8,6 +8,8 @@ import nl.jqno.paralleljava.app.endpoints.DefaultEndpoints; import nl.jqno.paralleljava.app.logging.Logger; import nl.jqno.paralleljava.app.logging.Slf4jLogger; +import nl.jqno.paralleljava.app.persistence.InMemoryRepository; +import nl.jqno.paralleljava.app.persistence.Repository; import nl.jqno.paralleljava.app.serialization.GsonSerializer; import nl.jqno.paralleljava.app.serialization.Serializer; import nl.jqno.paralleljava.app.server.Heroku; @@ -20,11 +22,13 @@ public class WiredApplication { private static final int DEFAULT_PORT = 4567; private final Function1, Logger> loggerFactory; + private final Repository repository; private final Server server; public WiredApplication() { loggerFactory = c -> new Slf4jLogger(LoggerFactory.getLogger(c)); - server = createServer(loggerFactory); + repository = new InMemoryRepository(); + server = createServer(repository, loggerFactory); } public void run() { @@ -37,9 +41,9 @@ public static Serializer defaultSerializer() { return new GsonSerializer(gsonBuilder.create()); } - private static Server createServer(Function1, Logger> loggerFactory) { + private static Server createServer(Repository repository, Function1, Logger> loggerFactory) { int port = getPort(); - var endpoints = new DefaultEndpoints(defaultSerializer()); + var endpoints = new DefaultEndpoints(repository, defaultSerializer()); return new SparkServer(endpoints, port, loggerFactory.apply(SparkServer.class)); } diff --git a/src/test/java/nl/jqno/paralleljava/app/endpoints/DefaultEndpointsTest.java b/src/test/java/nl/jqno/paralleljava/app/endpoints/DefaultEndpointsTest.java index 95eef90..f2e4f01 100644 --- a/src/test/java/nl/jqno/paralleljava/app/endpoints/DefaultEndpointsTest.java +++ b/src/test/java/nl/jqno/paralleljava/app/endpoints/DefaultEndpointsTest.java @@ -1,35 +1,56 @@ package nl.jqno.paralleljava.app.endpoints; import io.vavr.collection.List; +import nl.jqno.paralleljava.app.persistence.InMemoryRepository; import nl.jqno.paralleljava.dependencyinjection.WiredApplication; import nl.jqno.picotest.Test; -import static nl.jqno.paralleljava.app.TestData.SOME_SERIALIZED_TODO; +import static nl.jqno.paralleljava.app.TestData.*; import static org.assertj.core.api.Assertions.assertThat; public class DefaultEndpointsTest extends Test { public void endoints() { var serializer = WiredApplication.defaultSerializer(); - var someRequest = new Request("body"); - var endpoints = new DefaultEndpoints(serializer); + var repository = new InMemoryRepository(); + var someRequest = new Request(""); + var endpoints = new DefaultEndpoints(repository, serializer); - test("get returns an empty array", () -> { + beforeEach(() -> { + repository.clearAllTodos(); + }); + + test("get returns an empty list when no todos are present", () -> { var sut = endpoints.get(); var actual = sut.handle(someRequest); assertThat(actual).isEqualTo(serializer.serializeTodos(List.empty())); }); - test("post responds with the todo that was posted to it", () -> { + test("get returns all todos", () -> { + repository.createTodo(SOME_TODO); + repository.createTodo(ANOTHER_TODO); + var sut = endpoints.get(); + + var actual = sut.handle(someRequest); + assertThat(actual).isEqualTo(SOME_SERIALIZED_LIST_OF_TODOS); + }); + + test("post adds a todo", () -> { var sut = endpoints.post(); + var actual = sut.handle(new Request(SOME_SERIALIZED_TODO)); assertThat(actual).isEqualTo(SOME_SERIALIZED_TODO); + assertThat(repository.getAllTodos()).contains(SOME_TODO); }); - test("delete basically does nothing", () -> { + test("delete clears all todos", () -> { + repository.createTodo(SOME_TODO); var sut = endpoints.delete(); + var actual = sut.handle(someRequest); + assertThat(actual).isEqualTo(""); + assertThat(repository.getAllTodos()).isEmpty(); }); } } From bfe5b7cb2cb3523ca255b608977bb9fd84d26934 Mon Sep 17 00:00:00 2001 From: Jan Ouwens Date: Thu, 21 Mar 2019 07:39:55 +0100 Subject: [PATCH 041/106] Introduces PartialTodo --- .../paralleljava/app/domain/PartialTodo.java | 43 +++++++++++++++++++ .../nl/jqno/paralleljava/app/TestData.java | 8 ++++ .../app/domain/PartialTodoTest.java | 37 ++++++++++++++++ 3 files changed, 88 insertions(+) create mode 100644 src/main/java/nl/jqno/paralleljava/app/domain/PartialTodo.java create mode 100644 src/test/java/nl/jqno/paralleljava/app/domain/PartialTodoTest.java diff --git a/src/main/java/nl/jqno/paralleljava/app/domain/PartialTodo.java b/src/main/java/nl/jqno/paralleljava/app/domain/PartialTodo.java new file mode 100644 index 0000000..b46ad90 --- /dev/null +++ b/src/main/java/nl/jqno/paralleljava/app/domain/PartialTodo.java @@ -0,0 +1,43 @@ +package nl.jqno.paralleljava.app.domain; + +import io.vavr.control.Option; + +public class PartialTodo { + private final Option id; + private final String title; + private final Option url; + private final Option completed; + private final int order; + + public PartialTodo(Option id, String title, Option url, Option completed, int order) { + this.id = id; + this.title = title; + this.url = url; + this.completed = completed; + this.order = order; + } + + public Option id() { + return id; + } + + public String title() { + return title; + } + + public Option url() { + return url; + } + + public Option completed() { + return completed; + } + + public int order() { + return order; + } + + public String toString() { + return "PartialTodo: [id=" + id + ", title=" + title + ", url=" + url + ", completed=" + completed + ", order=" + order + "]"; + } +} diff --git a/src/test/java/nl/jqno/paralleljava/app/TestData.java b/src/test/java/nl/jqno/paralleljava/app/TestData.java index 0568506..96adf1d 100644 --- a/src/test/java/nl/jqno/paralleljava/app/TestData.java +++ b/src/test/java/nl/jqno/paralleljava/app/TestData.java @@ -1,6 +1,8 @@ package nl.jqno.paralleljava.app; import io.vavr.collection.List; +import io.vavr.control.Option; +import nl.jqno.paralleljava.app.domain.PartialTodo; import nl.jqno.paralleljava.app.domain.Todo; public class TestData { @@ -13,6 +15,12 @@ public class TestData { public static final List SOME_LIST_OF_TODOS = List.of(SOME_TODO, ANOTHER_TODO); + public static final PartialTodo SOME_PARTIAL_TODO = + new PartialTodo(Option.of(42), "title", Option.of("http://www.example.com"), Option.of(true), 1337); + + public static final PartialTodo SOME_POSTED_PARTIAL_TODO = + new PartialTodo(Option.none(), "title", Option.none(), Option.none(), 1337); + public static final String SOME_SERIALIZED_TODO = "{\"id\":42,\"title\":\"title\",\"url\":\"http://www.example.com\",\"completed\":true,\"order\":1337}"; diff --git a/src/test/java/nl/jqno/paralleljava/app/domain/PartialTodoTest.java b/src/test/java/nl/jqno/paralleljava/app/domain/PartialTodoTest.java new file mode 100644 index 0000000..61716e7 --- /dev/null +++ b/src/test/java/nl/jqno/paralleljava/app/domain/PartialTodoTest.java @@ -0,0 +1,37 @@ +package nl.jqno.paralleljava.app.domain; + +import io.vavr.control.Option; +import nl.jqno.picotest.Test; + +import static nl.jqno.paralleljava.app.TestData.SOME_PARTIAL_TODO; +import static nl.jqno.paralleljava.app.TestData.SOME_POSTED_PARTIAL_TODO; +import static org.assertj.core.api.Assertions.assertThat; + +public class PartialTodoTest extends Test { + + public void partialTodo() { + + test("getters (Some)", () -> { + assertThat(SOME_PARTIAL_TODO.id()).isEqualTo(Option.of(42)); + assertThat(SOME_PARTIAL_TODO.title()).isEqualTo("title"); + assertThat(SOME_PARTIAL_TODO.url()).isEqualTo(Option.of("http://www.example.com")); + assertThat(SOME_PARTIAL_TODO.completed()).isEqualTo(Option.of(true)); + assertThat(SOME_PARTIAL_TODO.order()).isEqualTo(1337); + }); + + test("getters (None)", () -> { + assertThat(SOME_POSTED_PARTIAL_TODO.id()).isEqualTo(Option.none()); + assertThat(SOME_POSTED_PARTIAL_TODO.title()).isEqualTo("title"); + assertThat(SOME_POSTED_PARTIAL_TODO.url()).isEqualTo(Option.none()); + assertThat(SOME_POSTED_PARTIAL_TODO.completed()).isEqualTo(Option.none()); + assertThat(SOME_POSTED_PARTIAL_TODO.order()).isEqualTo(1337); + }); + + test("toString", () -> { + assertThat(SOME_PARTIAL_TODO.toString()) + .isEqualTo("PartialTodo: [id=Some(42), title=title, url=Some(http://www.example.com), completed=Some(true), order=1337]"); + assertThat(SOME_POSTED_PARTIAL_TODO.toString()) + .isEqualTo("PartialTodo: [id=None, title=title, url=None, completed=None, order=1337]"); + }); + } +} From 8f23dd34a77555b1f4d68dc308e902bb74c6a1ab Mon Sep 17 00:00:00 2001 From: Jan Ouwens Date: Thu, 21 Mar 2019 07:48:04 +0100 Subject: [PATCH 042/106] Reorganises TestData --- .../nl/jqno/paralleljava/app/TestData.java | 37 +++++++++++-------- .../app/domain/PartialTodoTest.java | 27 +++++++------- .../paralleljava/app/domain/TodoTest.java | 14 +++---- .../app/endpoints/DefaultEndpointsTest.java | 14 +++---- .../persistence/InMemoryRepositoryTest.java | 8 ++-- .../app/serialization/GsonSerializerTest.java | 26 ++++++------- 6 files changed, 66 insertions(+), 60 deletions(-) diff --git a/src/test/java/nl/jqno/paralleljava/app/TestData.java b/src/test/java/nl/jqno/paralleljava/app/TestData.java index 96adf1d..c04bfe5 100644 --- a/src/test/java/nl/jqno/paralleljava/app/TestData.java +++ b/src/test/java/nl/jqno/paralleljava/app/TestData.java @@ -7,26 +7,33 @@ public class TestData { - public static final Todo SOME_TODO = - new Todo(42, "title", "http://www.example.com", true, 1337); + public static class SomeTodo { + public static final Todo TODO = + new Todo(42, "title", "http://www.example.com", true, 1337); - public static final Todo ANOTHER_TODO = - new Todo(47, "something", "http://www.todobackend.com", false, 1); + public static final PartialTodo PARTIAL_COMPLETE = + new PartialTodo(Option.of(42), "title", Option.of("http://www.example.com"), Option.of(true), 1337); - public static final List SOME_LIST_OF_TODOS = List.of(SOME_TODO, ANOTHER_TODO); + public static final PartialTodo PARTIAL_POST = + new PartialTodo(Option.none(), "title", Option.none(), Option.none(), 1337); - public static final PartialTodo SOME_PARTIAL_TODO = - new PartialTodo(Option.of(42), "title", Option.of("http://www.example.com"), Option.of(true), 1337); + public static final String SERIALIZED = + "{\"id\":42,\"title\":\"title\",\"url\":\"http://www.example.com\",\"completed\":true,\"order\":1337}"; + } - public static final PartialTodo SOME_POSTED_PARTIAL_TODO = - new PartialTodo(Option.none(), "title", Option.none(), Option.none(), 1337); + public static class AnotherTodo { + public static final Todo TODO = + new Todo(47, "something", "http://www.todobackend.com", false, 1); - public static final String SOME_SERIALIZED_TODO = - "{\"id\":42,\"title\":\"title\",\"url\":\"http://www.example.com\",\"completed\":true,\"order\":1337}"; + public static final String SERIALIZED = + "{\"id\":47,\"title\":\"something\",\"url\":\"http://www.todobackend.com\",\"completed\":false,\"order\":1}"; + } - public static final String ANOTHER_SERIALIZED_TODO = - "{\"id\":47,\"title\":\"something\",\"url\":\"http://www.todobackend.com\",\"completed\":false,\"order\":1}"; + public static class ListOfTodos { + public static final List LIST = + List.of(SomeTodo.TODO, AnotherTodo.TODO); - public static final String SOME_SERIALIZED_LIST_OF_TODOS = - "[" + SOME_SERIALIZED_TODO + "," + ANOTHER_SERIALIZED_TODO + "]"; + public static final String SERIALIZED = + "[" + SomeTodo.SERIALIZED + "," + AnotherTodo.SERIALIZED + "]"; + } } diff --git a/src/test/java/nl/jqno/paralleljava/app/domain/PartialTodoTest.java b/src/test/java/nl/jqno/paralleljava/app/domain/PartialTodoTest.java index 61716e7..b4b41c2 100644 --- a/src/test/java/nl/jqno/paralleljava/app/domain/PartialTodoTest.java +++ b/src/test/java/nl/jqno/paralleljava/app/domain/PartialTodoTest.java @@ -1,10 +1,9 @@ package nl.jqno.paralleljava.app.domain; import io.vavr.control.Option; +import nl.jqno.paralleljava.app.TestData.SomeTodo; import nl.jqno.picotest.Test; -import static nl.jqno.paralleljava.app.TestData.SOME_PARTIAL_TODO; -import static nl.jqno.paralleljava.app.TestData.SOME_POSTED_PARTIAL_TODO; import static org.assertj.core.api.Assertions.assertThat; public class PartialTodoTest extends Test { @@ -12,25 +11,25 @@ public class PartialTodoTest extends Test { public void partialTodo() { test("getters (Some)", () -> { - assertThat(SOME_PARTIAL_TODO.id()).isEqualTo(Option.of(42)); - assertThat(SOME_PARTIAL_TODO.title()).isEqualTo("title"); - assertThat(SOME_PARTIAL_TODO.url()).isEqualTo(Option.of("http://www.example.com")); - assertThat(SOME_PARTIAL_TODO.completed()).isEqualTo(Option.of(true)); - assertThat(SOME_PARTIAL_TODO.order()).isEqualTo(1337); + assertThat(SomeTodo.PARTIAL_COMPLETE.id()).isEqualTo(Option.of(42)); + assertThat(SomeTodo.PARTIAL_COMPLETE.title()).isEqualTo("title"); + assertThat(SomeTodo.PARTIAL_COMPLETE.url()).isEqualTo(Option.of("http://www.example.com")); + assertThat(SomeTodo.PARTIAL_COMPLETE.completed()).isEqualTo(Option.of(true)); + assertThat(SomeTodo.PARTIAL_COMPLETE.order()).isEqualTo(1337); }); test("getters (None)", () -> { - assertThat(SOME_POSTED_PARTIAL_TODO.id()).isEqualTo(Option.none()); - assertThat(SOME_POSTED_PARTIAL_TODO.title()).isEqualTo("title"); - assertThat(SOME_POSTED_PARTIAL_TODO.url()).isEqualTo(Option.none()); - assertThat(SOME_POSTED_PARTIAL_TODO.completed()).isEqualTo(Option.none()); - assertThat(SOME_POSTED_PARTIAL_TODO.order()).isEqualTo(1337); + assertThat(SomeTodo.PARTIAL_POST.id()).isEqualTo(Option.none()); + assertThat(SomeTodo.PARTIAL_POST.title()).isEqualTo("title"); + assertThat(SomeTodo.PARTIAL_POST.url()).isEqualTo(Option.none()); + assertThat(SomeTodo.PARTIAL_POST.completed()).isEqualTo(Option.none()); + assertThat(SomeTodo.PARTIAL_POST.order()).isEqualTo(1337); }); test("toString", () -> { - assertThat(SOME_PARTIAL_TODO.toString()) + assertThat(SomeTodo.PARTIAL_COMPLETE.toString()) .isEqualTo("PartialTodo: [id=Some(42), title=title, url=Some(http://www.example.com), completed=Some(true), order=1337]"); - assertThat(SOME_POSTED_PARTIAL_TODO.toString()) + assertThat(SomeTodo.PARTIAL_POST.toString()) .isEqualTo("PartialTodo: [id=None, title=title, url=None, completed=None, order=1337]"); }); } diff --git a/src/test/java/nl/jqno/paralleljava/app/domain/TodoTest.java b/src/test/java/nl/jqno/paralleljava/app/domain/TodoTest.java index 89b792f..82b8599 100644 --- a/src/test/java/nl/jqno/paralleljava/app/domain/TodoTest.java +++ b/src/test/java/nl/jqno/paralleljava/app/domain/TodoTest.java @@ -1,9 +1,9 @@ package nl.jqno.paralleljava.app.domain; import nl.jqno.equalsverifier.EqualsVerifier; +import nl.jqno.paralleljava.app.TestData.SomeTodo; import nl.jqno.picotest.Test; -import static nl.jqno.paralleljava.app.TestData.SOME_TODO; import static org.assertj.core.api.Assertions.assertThat; public class TodoTest extends Test { @@ -16,15 +16,15 @@ public void todo() { }); test("getters", () -> { - assertThat(SOME_TODO.id()).isEqualTo(42); - assertThat(SOME_TODO.title()).isEqualTo("title"); - assertThat(SOME_TODO.url()).isEqualTo("http://www.example.com"); - assertThat(SOME_TODO.completed()).isEqualTo(true); - assertThat(SOME_TODO.order()).isEqualTo(1337); + assertThat(SomeTodo.TODO.id()).isEqualTo(42); + assertThat(SomeTodo.TODO.title()).isEqualTo("title"); + assertThat(SomeTodo.TODO.url()).isEqualTo("http://www.example.com"); + assertThat(SomeTodo.TODO.completed()).isEqualTo(true); + assertThat(SomeTodo.TODO.order()).isEqualTo(1337); }); test("toString", () -> { - assertThat(SOME_TODO.toString()).isEqualTo("Todo: [id=42, title=title, url=http://www.example.com, completed=true, order=1337]"); + assertThat(SomeTodo.TODO.toString()).isEqualTo("Todo: [id=42, title=title, url=http://www.example.com, completed=true, order=1337]"); }); } } diff --git a/src/test/java/nl/jqno/paralleljava/app/endpoints/DefaultEndpointsTest.java b/src/test/java/nl/jqno/paralleljava/app/endpoints/DefaultEndpointsTest.java index f2e4f01..91fa18b 100644 --- a/src/test/java/nl/jqno/paralleljava/app/endpoints/DefaultEndpointsTest.java +++ b/src/test/java/nl/jqno/paralleljava/app/endpoints/DefaultEndpointsTest.java @@ -27,24 +27,24 @@ public void endoints() { }); test("get returns all todos", () -> { - repository.createTodo(SOME_TODO); - repository.createTodo(ANOTHER_TODO); + repository.createTodo(SomeTodo.TODO); + repository.createTodo(AnotherTodo.TODO); var sut = endpoints.get(); var actual = sut.handle(someRequest); - assertThat(actual).isEqualTo(SOME_SERIALIZED_LIST_OF_TODOS); + assertThat(actual).isEqualTo(ListOfTodos.SERIALIZED); }); test("post adds a todo", () -> { var sut = endpoints.post(); - var actual = sut.handle(new Request(SOME_SERIALIZED_TODO)); - assertThat(actual).isEqualTo(SOME_SERIALIZED_TODO); - assertThat(repository.getAllTodos()).contains(SOME_TODO); + var actual = sut.handle(new Request(SomeTodo.SERIALIZED)); + assertThat(actual).isEqualTo(SomeTodo.SERIALIZED); + assertThat(repository.getAllTodos()).contains(SomeTodo.TODO); }); test("delete clears all todos", () -> { - repository.createTodo(SOME_TODO); + repository.createTodo(SomeTodo.TODO); var sut = endpoints.delete(); var actual = sut.handle(someRequest); diff --git a/src/test/java/nl/jqno/paralleljava/app/persistence/InMemoryRepositoryTest.java b/src/test/java/nl/jqno/paralleljava/app/persistence/InMemoryRepositoryTest.java index ad270d4..fded019 100644 --- a/src/test/java/nl/jqno/paralleljava/app/persistence/InMemoryRepositoryTest.java +++ b/src/test/java/nl/jqno/paralleljava/app/persistence/InMemoryRepositoryTest.java @@ -1,8 +1,8 @@ package nl.jqno.paralleljava.app.persistence; +import nl.jqno.paralleljava.app.TestData.SomeTodo; import nl.jqno.picotest.Test; -import static nl.jqno.paralleljava.app.TestData.SOME_TODO; import static org.assertj.core.api.Assertions.assertThat; public class InMemoryRepositoryTest extends Test { @@ -15,12 +15,12 @@ public void repository() { }); test("create a todo", () -> { - repo.createTodo(SOME_TODO); - assertThat(repo.getAllTodos()).contains(SOME_TODO); + repo.createTodo(SomeTodo.TODO); + assertThat(repo.getAllTodos()).contains(SomeTodo.TODO); }); test("clearing all todos", () -> { - repo.createTodo(SOME_TODO); + repo.createTodo(SomeTodo.TODO); repo.clearAllTodos(); assertThat(repo.getAllTodos()).isEmpty(); }); diff --git a/src/test/java/nl/jqno/paralleljava/app/serialization/GsonSerializerTest.java b/src/test/java/nl/jqno/paralleljava/app/serialization/GsonSerializerTest.java index af292ac..5e1a640 100644 --- a/src/test/java/nl/jqno/paralleljava/app/serialization/GsonSerializerTest.java +++ b/src/test/java/nl/jqno/paralleljava/app/serialization/GsonSerializerTest.java @@ -15,7 +15,7 @@ public class GsonSerializerTest extends Test { public void serializationOfASingleTodo() { test("Serializes a Todo to json", () -> { - var actual = serializer.serializeTodo(SOME_TODO); + var actual = serializer.serializeTodo(SomeTodo.TODO); assertThat(actual) .contains("\"id\":42") .contains("\"title\":\"title\"") @@ -25,8 +25,8 @@ public void serializationOfASingleTodo() { }); test("Deserializes a Todo from json", () -> { - var actual = serializer.deserializeTodo(SOME_SERIALIZED_TODO); - assertThat(actual).isEqualTo(Option.of(SOME_TODO)); + var actual = serializer.deserializeTodo(SomeTodo.SERIALIZED); + assertThat(actual).isEqualTo(Option.of(SomeTodo.TODO)); }); test("Deserialization of a Todo returns none when json is invalid", () -> { @@ -36,36 +36,36 @@ public void serializationOfASingleTodo() { }); test("Does a complete round-trip on Todo", () -> { - var json = serializer.serializeTodo(SOME_TODO); + var json = serializer.serializeTodo(SomeTodo.TODO); var actual = serializer.deserializeTodo(json); - assertThat(actual).isEqualTo(Option.of(SOME_TODO)); + assertThat(actual).isEqualTo(Option.of(SomeTodo.TODO)); }); } public void serializationOfAListOfTodos() { test("Serializes a list of Todos to json", () -> { - var actual = serializer.serializeTodos(SOME_LIST_OF_TODOS); + var actual = serializer.serializeTodos(ListOfTodos.LIST); assertThat(actual) - .contains(SOME_SERIALIZED_TODO) - .contains(ANOTHER_SERIALIZED_TODO); + .contains(SomeTodo.SERIALIZED) + .contains(AnotherTodo.SERIALIZED); }); test("Deserializes a list of Todos from json", () -> { - var actual = serializer.deserializeTodos(SOME_SERIALIZED_LIST_OF_TODOS); - assertThat(actual).containsExactlyElementsOf(SOME_LIST_OF_TODOS); + var actual = serializer.deserializeTodos(ListOfTodos.SERIALIZED); + assertThat(actual).containsExactlyElementsOf(ListOfTodos.LIST); }); test("Deserialization of a list of Todos returns an empty list when json is invalid", () -> { - var invalidJson = SOME_SERIALIZED_TODO; // but not a list + var invalidJson = SomeTodo.SERIALIZED; // but not a list var actual = serializer.deserializeTodos(invalidJson); assertThat(actual).isEqualTo(List.empty()); }); test("Does a complete round-trip on lists of Todos", () -> { - var json = serializer.serializeTodos(SOME_LIST_OF_TODOS); + var json = serializer.serializeTodos(ListOfTodos.LIST); var actual = serializer.deserializeTodos(json); - assertThat(actual).isEqualTo(SOME_LIST_OF_TODOS); + assertThat(actual).isEqualTo(ListOfTodos.LIST); }); } } From 2abadc8678f0c96f5e084037555b6df48dfa43c5 Mon Sep 17 00:00:00 2001 From: Jan Ouwens Date: Thu, 21 Mar 2019 16:20:05 +0100 Subject: [PATCH 043/106] Fixes serialization for PartialTodos --- .../paralleljava/app/domain/PartialTodo.java | 40 ++++++++++---- .../app/serialization/GsonSerializer.java | 9 ++++ .../app/serialization/Serializer.java | 4 ++ .../dependencyinjection/WiredApplication.java | 1 - .../nl/jqno/paralleljava/app/TestData.java | 3 ++ .../app/domain/PartialTodoTest.java | 6 +++ .../app/serialization/GsonSerializerTest.java | 52 +++++++++++++++++++ 7 files changed, 103 insertions(+), 12 deletions(-) diff --git a/src/main/java/nl/jqno/paralleljava/app/domain/PartialTodo.java b/src/main/java/nl/jqno/paralleljava/app/domain/PartialTodo.java index b46ad90..9f6f762 100644 --- a/src/main/java/nl/jqno/paralleljava/app/domain/PartialTodo.java +++ b/src/main/java/nl/jqno/paralleljava/app/domain/PartialTodo.java @@ -2,23 +2,25 @@ import io.vavr.control.Option; -public class PartialTodo { - private final Option id; +import java.util.Objects; + +public final class PartialTodo { + private final Integer id; private final String title; - private final Option url; - private final Option completed; + private final String url; + private final Boolean completed; private final int order; public PartialTodo(Option id, String title, Option url, Option completed, int order) { - this.id = id; + this.id = id.getOrNull(); this.title = title; - this.url = url; - this.completed = completed; + this.url = url.getOrNull(); + this.completed = completed.getOrNull(); this.order = order; } public Option id() { - return id; + return Option.of(id); } public String title() { @@ -26,18 +28,34 @@ public String title() { } public Option url() { - return url; + return Option.of(url); } public Option completed() { - return completed; + return Option.of(completed); } public int order() { return order; } + public boolean equals(Object obj) { + if (!(obj instanceof PartialTodo)) { + return false; + } + PartialTodo other = (PartialTodo)obj; + return Objects.equals(id, other.id) && + Objects.equals(title, other.title) && + Objects.equals(url, other.url) && + Objects.equals(completed, other.completed) && + order == other.order; + } + + public int hashCode() { + return Objects.hash(id, title, url, completed, order); + } + public String toString() { - return "PartialTodo: [id=" + id + ", title=" + title + ", url=" + url + ", completed=" + completed + ", order=" + order + "]"; + return "PartialTodo: [id=" + id() + ", title=" + title() + ", url=" + url() + ", completed=" + completed() + ", order=" + order() + "]"; } } diff --git a/src/main/java/nl/jqno/paralleljava/app/serialization/GsonSerializer.java b/src/main/java/nl/jqno/paralleljava/app/serialization/GsonSerializer.java index fb26d19..e85ed73 100644 --- a/src/main/java/nl/jqno/paralleljava/app/serialization/GsonSerializer.java +++ b/src/main/java/nl/jqno/paralleljava/app/serialization/GsonSerializer.java @@ -5,6 +5,7 @@ import io.vavr.collection.List; import io.vavr.control.Option; import io.vavr.control.Try; +import nl.jqno.paralleljava.app.domain.PartialTodo; import nl.jqno.paralleljava.app.domain.Todo; import java.lang.reflect.Type; @@ -26,6 +27,14 @@ public Option deserializeTodo(String json) { return Try.of(() -> gson.fromJson(json, Todo.class)).toOption(); } + public String serializePartialTodo(PartialTodo todo) { + return gson.toJson(todo); + } + + public Option deserializePartialTodo(String json) { + return Try.of(() -> gson.fromJson(json, PartialTodo.class)).toOption(); + } + public String serializeTodos(List todos) { return gson.toJson(todos); } diff --git a/src/main/java/nl/jqno/paralleljava/app/serialization/Serializer.java b/src/main/java/nl/jqno/paralleljava/app/serialization/Serializer.java index 0ea79a9..6872b0f 100644 --- a/src/main/java/nl/jqno/paralleljava/app/serialization/Serializer.java +++ b/src/main/java/nl/jqno/paralleljava/app/serialization/Serializer.java @@ -2,12 +2,16 @@ import io.vavr.collection.List; import io.vavr.control.Option; +import nl.jqno.paralleljava.app.domain.PartialTodo; import nl.jqno.paralleljava.app.domain.Todo; public interface Serializer { String serializeTodo(Todo todo); Option deserializeTodo(String json); + String serializePartialTodo(PartialTodo todo); + Option deserializePartialTodo(String json); + String serializeTodos(List todos); List deserializeTodos(String json); } diff --git a/src/main/java/nl/jqno/paralleljava/dependencyinjection/WiredApplication.java b/src/main/java/nl/jqno/paralleljava/dependencyinjection/WiredApplication.java index 0a0b2eb..d68600b 100644 --- a/src/main/java/nl/jqno/paralleljava/dependencyinjection/WiredApplication.java +++ b/src/main/java/nl/jqno/paralleljava/dependencyinjection/WiredApplication.java @@ -1,6 +1,5 @@ package nl.jqno.paralleljava.dependencyinjection; -import com.google.gson.Gson; import com.google.gson.GsonBuilder; import io.vavr.Function1; import io.vavr.collection.HashMap; diff --git a/src/test/java/nl/jqno/paralleljava/app/TestData.java b/src/test/java/nl/jqno/paralleljava/app/TestData.java index c04bfe5..eab4d16 100644 --- a/src/test/java/nl/jqno/paralleljava/app/TestData.java +++ b/src/test/java/nl/jqno/paralleljava/app/TestData.java @@ -19,6 +19,9 @@ public static class SomeTodo { public static final String SERIALIZED = "{\"id\":42,\"title\":\"title\",\"url\":\"http://www.example.com\",\"completed\":true,\"order\":1337}"; + + public static final String SERIALIZED_PARTIAL_POST = + "{\"title\":\"title\",\"order\":1337}"; } public static class AnotherTodo { diff --git a/src/test/java/nl/jqno/paralleljava/app/domain/PartialTodoTest.java b/src/test/java/nl/jqno/paralleljava/app/domain/PartialTodoTest.java index b4b41c2..03ac711 100644 --- a/src/test/java/nl/jqno/paralleljava/app/domain/PartialTodoTest.java +++ b/src/test/java/nl/jqno/paralleljava/app/domain/PartialTodoTest.java @@ -1,6 +1,7 @@ package nl.jqno.paralleljava.app.domain; import io.vavr.control.Option; +import nl.jqno.equalsverifier.EqualsVerifier; import nl.jqno.paralleljava.app.TestData.SomeTodo; import nl.jqno.picotest.Test; @@ -10,6 +11,11 @@ public class PartialTodoTest extends Test { public void partialTodo() { + test("equals and hashCode", () -> { + EqualsVerifier.forClass(PartialTodo.class) + .verify(); + }); + test("getters (Some)", () -> { assertThat(SomeTodo.PARTIAL_COMPLETE.id()).isEqualTo(Option.of(42)); assertThat(SomeTodo.PARTIAL_COMPLETE.title()).isEqualTo("title"); diff --git a/src/test/java/nl/jqno/paralleljava/app/serialization/GsonSerializerTest.java b/src/test/java/nl/jqno/paralleljava/app/serialization/GsonSerializerTest.java index 5e1a640..c9843f3 100644 --- a/src/test/java/nl/jqno/paralleljava/app/serialization/GsonSerializerTest.java +++ b/src/test/java/nl/jqno/paralleljava/app/serialization/GsonSerializerTest.java @@ -42,6 +42,58 @@ public void serializationOfASingleTodo() { }); } + public void serializationOfACompletePartialTodo() { + test("Serializes a complete PartialTodo to json", () -> { + var actual = serializer.serializePartialTodo(SomeTodo.PARTIAL_COMPLETE); + assertThat(actual) + .contains("\"id\":42") + .contains("\"title\":\"title\"") + .contains("\"url\":\"http://www.example.com\"") + .contains("\"completed\":true") + .contains("\"order\":1337"); + }); + + test("Deserializes a complete PartialTodo from json", () -> { + var actual = serializer.deserializePartialTodo(SomeTodo.SERIALIZED); + assertThat(actual).isEqualTo(Option.of(SomeTodo.PARTIAL_COMPLETE)); + }); + + test("Deserialization of a complete PartialTodo returns none when json is invalid", () -> { + var invalidJson = "this is an invalid json document"; + var actual = serializer.deserializePartialTodo(invalidJson); + assertThat(actual).isEqualTo(Option.none()); + }); + + test("Does a complete round-trip on PartialTodo", () -> { + var json = serializer.serializePartialTodo(SomeTodo.PARTIAL_COMPLETE); + var actual = serializer.deserializePartialTodo(json); + assertThat(actual).isEqualTo(Option.of(SomeTodo.PARTIAL_COMPLETE)); + }); + } + + public void serializationOfAPOSTedPartialTodo() { + test("Serializes a POSTed PartialTodo to json", () -> { + var actual = serializer.serializePartialTodo(SomeTodo.PARTIAL_POST); + assertThat(actual) + .contains("\"title\":\"title\"") + .contains("\"order\":1337") + .doesNotContain("\"id\":") + .doesNotContain("\"url\":") + .doesNotContain("\"completed\":"); + }); + + test("Deserializes a POSTed PartialTodo from json", () -> { + var actual = serializer.deserializePartialTodo(SomeTodo.SERIALIZED_PARTIAL_POST); + assertThat(actual).isEqualTo(Option.of(SomeTodo.PARTIAL_POST)); + }); + + test("Does a complete round-trip on a POSTed PartialTodo", () -> { + var json = serializer.serializePartialTodo(SomeTodo.PARTIAL_POST); + var actual = serializer.deserializePartialTodo(json); + assertThat(actual).isEqualTo(Option.of(SomeTodo.PARTIAL_POST)); + }); + } + public void serializationOfAListOfTodos() { test("Serializes a list of Todos to json", () -> { From ec0054ac12a2a9cb2e911e040c348eb13d5798bb Mon Sep 17 00:00:00 2001 From: Jan Ouwens Date: Thu, 21 Mar 2019 16:33:22 +0100 Subject: [PATCH 044/106] Adds support for partial Todo to POST --- .../app/endpoints/DefaultEndpoints.java | 14 +++++++++++--- .../app/endpoints/DefaultEndpointsTest.java | 8 +++++--- 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/src/main/java/nl/jqno/paralleljava/app/endpoints/DefaultEndpoints.java b/src/main/java/nl/jqno/paralleljava/app/endpoints/DefaultEndpoints.java index 568bd0f..b6b03d7 100644 --- a/src/main/java/nl/jqno/paralleljava/app/endpoints/DefaultEndpoints.java +++ b/src/main/java/nl/jqno/paralleljava/app/endpoints/DefaultEndpoints.java @@ -1,5 +1,6 @@ package nl.jqno.paralleljava.app.endpoints; +import nl.jqno.paralleljava.app.domain.Todo; import nl.jqno.paralleljava.app.persistence.Repository; import nl.jqno.paralleljava.app.serialization.Serializer; @@ -19,9 +20,16 @@ public Route get() { public Route post() { return request -> { var json = request.body(); - var todo = serializer.deserializeTodo(json); - todo.forEach(repository::createTodo); - return json; + var partialTodo = serializer.deserializePartialTodo(json); + if (partialTodo.isDefined()) { + var pt = partialTodo.get(); + var todo = new Todo(-1, pt.title(), "", false, pt.order()); + repository.createTodo(todo); + return json; + } + else { + return ""; + } }; } diff --git a/src/test/java/nl/jqno/paralleljava/app/endpoints/DefaultEndpointsTest.java b/src/test/java/nl/jqno/paralleljava/app/endpoints/DefaultEndpointsTest.java index 91fa18b..71efc3c 100644 --- a/src/test/java/nl/jqno/paralleljava/app/endpoints/DefaultEndpointsTest.java +++ b/src/test/java/nl/jqno/paralleljava/app/endpoints/DefaultEndpointsTest.java @@ -1,6 +1,7 @@ package nl.jqno.paralleljava.app.endpoints; import io.vavr.collection.List; +import nl.jqno.paralleljava.app.domain.Todo; import nl.jqno.paralleljava.app.persistence.InMemoryRepository; import nl.jqno.paralleljava.dependencyinjection.WiredApplication; import nl.jqno.picotest.Test; @@ -36,11 +37,12 @@ public void endoints() { }); test("post adds a todo", () -> { + var expected = new Todo(-1, "title", "", false, 1337); var sut = endpoints.post(); - var actual = sut.handle(new Request(SomeTodo.SERIALIZED)); - assertThat(actual).isEqualTo(SomeTodo.SERIALIZED); - assertThat(repository.getAllTodos()).contains(SomeTodo.TODO); + var actual = sut.handle(new Request(SomeTodo.SERIALIZED_PARTIAL_POST)); + assertThat(actual).isEqualTo(SomeTodo.SERIALIZED_PARTIAL_POST); + assertThat(repository.getAllTodos()).contains(expected); }); test("delete clears all todos", () -> { From 3e97c50ab694e266624befc8b18bb6796f774472 Mon Sep 17 00:00:00 2001 From: Jan Ouwens Date: Thu, 21 Mar 2019 16:42:18 +0100 Subject: [PATCH 045/106] Adds logging to Endpoints --- .../jqno/paralleljava/app/endpoints/DefaultEndpoints.java | 7 ++++++- .../paralleljava/dependencyinjection/WiredApplication.java | 2 +- .../paralleljava/app/endpoints/DefaultEndpointsTest.java | 3 ++- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/src/main/java/nl/jqno/paralleljava/app/endpoints/DefaultEndpoints.java b/src/main/java/nl/jqno/paralleljava/app/endpoints/DefaultEndpoints.java index b6b03d7..f136c21 100644 --- a/src/main/java/nl/jqno/paralleljava/app/endpoints/DefaultEndpoints.java +++ b/src/main/java/nl/jqno/paralleljava/app/endpoints/DefaultEndpoints.java @@ -1,16 +1,19 @@ package nl.jqno.paralleljava.app.endpoints; import nl.jqno.paralleljava.app.domain.Todo; +import nl.jqno.paralleljava.app.logging.Logger; import nl.jqno.paralleljava.app.persistence.Repository; import nl.jqno.paralleljava.app.serialization.Serializer; public class DefaultEndpoints implements Endpoints { private final Repository repository; private final Serializer serializer; + private final Logger logger; - public DefaultEndpoints(Repository repository, Serializer serializer) { + public DefaultEndpoints(Repository repository, Serializer serializer, Logger logger) { this.repository = repository; this.serializer = serializer; + this.logger = logger; } public Route get() { @@ -20,11 +23,13 @@ public Route get() { public Route post() { return request -> { var json = request.body(); + logger.forProduction("POSTed: " + json); var partialTodo = serializer.deserializePartialTodo(json); if (partialTodo.isDefined()) { var pt = partialTodo.get(); var todo = new Todo(-1, pt.title(), "", false, pt.order()); repository.createTodo(todo); + logger.forProduction("Returning from POST: " + json); return json; } else { diff --git a/src/main/java/nl/jqno/paralleljava/dependencyinjection/WiredApplication.java b/src/main/java/nl/jqno/paralleljava/dependencyinjection/WiredApplication.java index d68600b..c258a9f 100644 --- a/src/main/java/nl/jqno/paralleljava/dependencyinjection/WiredApplication.java +++ b/src/main/java/nl/jqno/paralleljava/dependencyinjection/WiredApplication.java @@ -42,7 +42,7 @@ public static Serializer defaultSerializer() { private static Server createServer(Repository repository, Function1, Logger> loggerFactory) { int port = getPort(); - var endpoints = new DefaultEndpoints(repository, defaultSerializer()); + var endpoints = new DefaultEndpoints(repository, defaultSerializer(), loggerFactory.apply(DefaultEndpoints.class)); return new SparkServer(endpoints, port, loggerFactory.apply(SparkServer.class)); } diff --git a/src/test/java/nl/jqno/paralleljava/app/endpoints/DefaultEndpointsTest.java b/src/test/java/nl/jqno/paralleljava/app/endpoints/DefaultEndpointsTest.java index 71efc3c..1a809a6 100644 --- a/src/test/java/nl/jqno/paralleljava/app/endpoints/DefaultEndpointsTest.java +++ b/src/test/java/nl/jqno/paralleljava/app/endpoints/DefaultEndpointsTest.java @@ -2,6 +2,7 @@ import io.vavr.collection.List; import nl.jqno.paralleljava.app.domain.Todo; +import nl.jqno.paralleljava.app.logging.NopLogger; import nl.jqno.paralleljava.app.persistence.InMemoryRepository; import nl.jqno.paralleljava.dependencyinjection.WiredApplication; import nl.jqno.picotest.Test; @@ -15,7 +16,7 @@ public void endoints() { var serializer = WiredApplication.defaultSerializer(); var repository = new InMemoryRepository(); var someRequest = new Request(""); - var endpoints = new DefaultEndpoints(repository, serializer); + var endpoints = new DefaultEndpoints(repository, serializer, new NopLogger()); beforeEach(() -> { repository.clearAllTodos(); From cf8167dfbfaa3af4425e3eb01e2b0f5e191c72c1 Mon Sep 17 00:00:00 2001 From: Jan Ouwens Date: Thu, 21 Mar 2019 16:51:26 +0100 Subject: [PATCH 046/106] Makes all PartialTodo fields optional --- .../paralleljava/app/domain/PartialTodo.java | 18 +++++++++--------- .../app/endpoints/DefaultEndpoints.java | 4 ++-- .../nl/jqno/paralleljava/app/TestData.java | 6 +++--- .../app/domain/PartialTodoTest.java | 12 ++++++------ .../app/endpoints/DefaultEndpointsTest.java | 2 +- .../app/serialization/GsonSerializerTest.java | 4 ++-- 6 files changed, 23 insertions(+), 23 deletions(-) diff --git a/src/main/java/nl/jqno/paralleljava/app/domain/PartialTodo.java b/src/main/java/nl/jqno/paralleljava/app/domain/PartialTodo.java index 9f6f762..cb4bfd7 100644 --- a/src/main/java/nl/jqno/paralleljava/app/domain/PartialTodo.java +++ b/src/main/java/nl/jqno/paralleljava/app/domain/PartialTodo.java @@ -9,22 +9,22 @@ public final class PartialTodo { private final String title; private final String url; private final Boolean completed; - private final int order; + private final Integer order; - public PartialTodo(Option id, String title, Option url, Option completed, int order) { + public PartialTodo(Option id, Option title, Option url, Option completed, Option order) { this.id = id.getOrNull(); - this.title = title; + this.title = title.getOrNull(); this.url = url.getOrNull(); this.completed = completed.getOrNull(); - this.order = order; + this.order = order.getOrNull(); } public Option id() { return Option.of(id); } - public String title() { - return title; + public Option title() { + return Option.of(title); } public Option url() { @@ -35,8 +35,8 @@ public Option completed() { return Option.of(completed); } - public int order() { - return order; + public Option order() { + return Option.of(order); } public boolean equals(Object obj) { @@ -48,7 +48,7 @@ public boolean equals(Object obj) { Objects.equals(title, other.title) && Objects.equals(url, other.url) && Objects.equals(completed, other.completed) && - order == other.order; + Objects.equals(order, other.order); } public int hashCode() { diff --git a/src/main/java/nl/jqno/paralleljava/app/endpoints/DefaultEndpoints.java b/src/main/java/nl/jqno/paralleljava/app/endpoints/DefaultEndpoints.java index f136c21..45ef849 100644 --- a/src/main/java/nl/jqno/paralleljava/app/endpoints/DefaultEndpoints.java +++ b/src/main/java/nl/jqno/paralleljava/app/endpoints/DefaultEndpoints.java @@ -25,9 +25,9 @@ public Route post() { var json = request.body(); logger.forProduction("POSTed: " + json); var partialTodo = serializer.deserializePartialTodo(json); - if (partialTodo.isDefined()) { + if (partialTodo.isDefined() && partialTodo.get().title().isDefined()) { var pt = partialTodo.get(); - var todo = new Todo(-1, pt.title(), "", false, pt.order()); + var todo = new Todo(-1, pt.title().get(), "", false, 0); repository.createTodo(todo); logger.forProduction("Returning from POST: " + json); return json; diff --git a/src/test/java/nl/jqno/paralleljava/app/TestData.java b/src/test/java/nl/jqno/paralleljava/app/TestData.java index eab4d16..19a3ee5 100644 --- a/src/test/java/nl/jqno/paralleljava/app/TestData.java +++ b/src/test/java/nl/jqno/paralleljava/app/TestData.java @@ -12,16 +12,16 @@ public static class SomeTodo { new Todo(42, "title", "http://www.example.com", true, 1337); public static final PartialTodo PARTIAL_COMPLETE = - new PartialTodo(Option.of(42), "title", Option.of("http://www.example.com"), Option.of(true), 1337); + new PartialTodo(Option.of(42), Option.of("title"), Option.of("http://www.example.com"), Option.of(true), Option.of(1337)); public static final PartialTodo PARTIAL_POST = - new PartialTodo(Option.none(), "title", Option.none(), Option.none(), 1337); + new PartialTodo(Option.none(), Option.of("title"), Option.none(), Option.none(), Option.none()); public static final String SERIALIZED = "{\"id\":42,\"title\":\"title\",\"url\":\"http://www.example.com\",\"completed\":true,\"order\":1337}"; public static final String SERIALIZED_PARTIAL_POST = - "{\"title\":\"title\",\"order\":1337}"; + "{\"title\":\"title\"}"; } public static class AnotherTodo { diff --git a/src/test/java/nl/jqno/paralleljava/app/domain/PartialTodoTest.java b/src/test/java/nl/jqno/paralleljava/app/domain/PartialTodoTest.java index 03ac711..aa8cd8d 100644 --- a/src/test/java/nl/jqno/paralleljava/app/domain/PartialTodoTest.java +++ b/src/test/java/nl/jqno/paralleljava/app/domain/PartialTodoTest.java @@ -18,25 +18,25 @@ public void partialTodo() { test("getters (Some)", () -> { assertThat(SomeTodo.PARTIAL_COMPLETE.id()).isEqualTo(Option.of(42)); - assertThat(SomeTodo.PARTIAL_COMPLETE.title()).isEqualTo("title"); + assertThat(SomeTodo.PARTIAL_COMPLETE.title()).isEqualTo(Option.of("title")); assertThat(SomeTodo.PARTIAL_COMPLETE.url()).isEqualTo(Option.of("http://www.example.com")); assertThat(SomeTodo.PARTIAL_COMPLETE.completed()).isEqualTo(Option.of(true)); - assertThat(SomeTodo.PARTIAL_COMPLETE.order()).isEqualTo(1337); + assertThat(SomeTodo.PARTIAL_COMPLETE.order()).isEqualTo(Option.of(1337)); }); test("getters (None)", () -> { assertThat(SomeTodo.PARTIAL_POST.id()).isEqualTo(Option.none()); - assertThat(SomeTodo.PARTIAL_POST.title()).isEqualTo("title"); + assertThat(SomeTodo.PARTIAL_POST.title()).isEqualTo(Option.of("title")); assertThat(SomeTodo.PARTIAL_POST.url()).isEqualTo(Option.none()); assertThat(SomeTodo.PARTIAL_POST.completed()).isEqualTo(Option.none()); - assertThat(SomeTodo.PARTIAL_POST.order()).isEqualTo(1337); + assertThat(SomeTodo.PARTIAL_POST.order()).isEqualTo(Option.none()); }); test("toString", () -> { assertThat(SomeTodo.PARTIAL_COMPLETE.toString()) - .isEqualTo("PartialTodo: [id=Some(42), title=title, url=Some(http://www.example.com), completed=Some(true), order=1337]"); + .isEqualTo("PartialTodo: [id=Some(42), title=Some(title), url=Some(http://www.example.com), completed=Some(true), order=Some(1337)]"); assertThat(SomeTodo.PARTIAL_POST.toString()) - .isEqualTo("PartialTodo: [id=None, title=title, url=None, completed=None, order=1337]"); + .isEqualTo("PartialTodo: [id=None, title=Some(title), url=None, completed=None, order=None]"); }); } } diff --git a/src/test/java/nl/jqno/paralleljava/app/endpoints/DefaultEndpointsTest.java b/src/test/java/nl/jqno/paralleljava/app/endpoints/DefaultEndpointsTest.java index 1a809a6..d2d1bf6 100644 --- a/src/test/java/nl/jqno/paralleljava/app/endpoints/DefaultEndpointsTest.java +++ b/src/test/java/nl/jqno/paralleljava/app/endpoints/DefaultEndpointsTest.java @@ -38,7 +38,7 @@ public void endoints() { }); test("post adds a todo", () -> { - var expected = new Todo(-1, "title", "", false, 1337); + var expected = new Todo(-1, "title", "", false, 0); var sut = endpoints.post(); var actual = sut.handle(new Request(SomeTodo.SERIALIZED_PARTIAL_POST)); diff --git a/src/test/java/nl/jqno/paralleljava/app/serialization/GsonSerializerTest.java b/src/test/java/nl/jqno/paralleljava/app/serialization/GsonSerializerTest.java index c9843f3..9e5e53b 100644 --- a/src/test/java/nl/jqno/paralleljava/app/serialization/GsonSerializerTest.java +++ b/src/test/java/nl/jqno/paralleljava/app/serialization/GsonSerializerTest.java @@ -76,10 +76,10 @@ public void serializationOfAPOSTedPartialTodo() { var actual = serializer.serializePartialTodo(SomeTodo.PARTIAL_POST); assertThat(actual) .contains("\"title\":\"title\"") - .contains("\"order\":1337") .doesNotContain("\"id\":") .doesNotContain("\"url\":") - .doesNotContain("\"completed\":"); + .doesNotContain("\"completed\":") + .doesNotContain("\"order\":"); }); test("Deserializes a POSTed PartialTodo from json", () -> { From 0913cc941ccd0d447f3bb9fb4e6a1e054ad660ad Mon Sep 17 00:00:00 2001 From: Jan Ouwens Date: Thu, 21 Mar 2019 16:57:18 +0100 Subject: [PATCH 047/106] Adds logging to InMemoryRepository --- .../app/persistence/InMemoryRepository.java | 10 +++++++++- .../dependencyinjection/WiredApplication.java | 2 +- .../app/endpoints/DefaultEndpointsTest.java | 5 +++-- .../app/persistence/InMemoryRepositoryTest.java | 3 ++- 4 files changed, 15 insertions(+), 5 deletions(-) diff --git a/src/main/java/nl/jqno/paralleljava/app/persistence/InMemoryRepository.java b/src/main/java/nl/jqno/paralleljava/app/persistence/InMemoryRepository.java index f0f8f1b..c4951f1 100644 --- a/src/main/java/nl/jqno/paralleljava/app/persistence/InMemoryRepository.java +++ b/src/main/java/nl/jqno/paralleljava/app/persistence/InMemoryRepository.java @@ -2,14 +2,21 @@ import io.vavr.collection.List; import nl.jqno.paralleljava.app.domain.Todo; +import nl.jqno.paralleljava.app.logging.Logger; import java.util.ArrayList; public class InMemoryRepository implements Repository { - private static final java.util.List todos = new ArrayList<>(); + private final Logger logger; + + public InMemoryRepository(Logger logger) { + this.logger = logger; + } + public void createTodo(Todo todo) { + logger.forProduction("Creating Todo " + todo); todos.add(todo); } @@ -18,6 +25,7 @@ public List getAllTodos() { } public void clearAllTodos() { + logger.forProduction("Clearing all Todos"); todos.clear(); } } diff --git a/src/main/java/nl/jqno/paralleljava/dependencyinjection/WiredApplication.java b/src/main/java/nl/jqno/paralleljava/dependencyinjection/WiredApplication.java index c258a9f..b931618 100644 --- a/src/main/java/nl/jqno/paralleljava/dependencyinjection/WiredApplication.java +++ b/src/main/java/nl/jqno/paralleljava/dependencyinjection/WiredApplication.java @@ -26,7 +26,7 @@ public class WiredApplication { public WiredApplication() { loggerFactory = c -> new Slf4jLogger(LoggerFactory.getLogger(c)); - repository = new InMemoryRepository(); + repository = new InMemoryRepository(loggerFactory.apply(InMemoryRepository.class)); server = createServer(repository, loggerFactory); } diff --git a/src/test/java/nl/jqno/paralleljava/app/endpoints/DefaultEndpointsTest.java b/src/test/java/nl/jqno/paralleljava/app/endpoints/DefaultEndpointsTest.java index d2d1bf6..9ae4e75 100644 --- a/src/test/java/nl/jqno/paralleljava/app/endpoints/DefaultEndpointsTest.java +++ b/src/test/java/nl/jqno/paralleljava/app/endpoints/DefaultEndpointsTest.java @@ -14,9 +14,10 @@ public class DefaultEndpointsTest extends Test { public void endoints() { var serializer = WiredApplication.defaultSerializer(); - var repository = new InMemoryRepository(); + var logger = new NopLogger(); + var repository = new InMemoryRepository(logger); var someRequest = new Request(""); - var endpoints = new DefaultEndpoints(repository, serializer, new NopLogger()); + var endpoints = new DefaultEndpoints(repository, serializer, logger); beforeEach(() -> { repository.clearAllTodos(); diff --git a/src/test/java/nl/jqno/paralleljava/app/persistence/InMemoryRepositoryTest.java b/src/test/java/nl/jqno/paralleljava/app/persistence/InMemoryRepositoryTest.java index fded019..ecb6291 100644 --- a/src/test/java/nl/jqno/paralleljava/app/persistence/InMemoryRepositoryTest.java +++ b/src/test/java/nl/jqno/paralleljava/app/persistence/InMemoryRepositoryTest.java @@ -1,6 +1,7 @@ package nl.jqno.paralleljava.app.persistence; import nl.jqno.paralleljava.app.TestData.SomeTodo; +import nl.jqno.paralleljava.app.logging.NopLogger; import nl.jqno.picotest.Test; import static org.assertj.core.api.Assertions.assertThat; @@ -8,7 +9,7 @@ public class InMemoryRepositoryTest extends Test { public void repository() { - var repo = new InMemoryRepository(); + var repo = new InMemoryRepository(new NopLogger()); beforeEach(() -> { repo.clearAllTodos(); From 36878f7440e9b40dfe872592e73df03083316192 Mon Sep 17 00:00:00 2001 From: Jan Ouwens Date: Thu, 21 Mar 2019 17:34:20 +0100 Subject: [PATCH 048/106] Adds logging to GsonSerializer --- .../app/serialization/GsonSerializer.java | 18 ++++++++++++++---- .../dependencyinjection/WiredApplication.java | 6 +++--- .../app/endpoints/DefaultEndpointsTest.java | 2 +- .../app/serialization/GsonSerializerTest.java | 3 ++- 4 files changed, 20 insertions(+), 9 deletions(-) diff --git a/src/main/java/nl/jqno/paralleljava/app/serialization/GsonSerializer.java b/src/main/java/nl/jqno/paralleljava/app/serialization/GsonSerializer.java index e85ed73..873756c 100644 --- a/src/main/java/nl/jqno/paralleljava/app/serialization/GsonSerializer.java +++ b/src/main/java/nl/jqno/paralleljava/app/serialization/GsonSerializer.java @@ -2,11 +2,13 @@ import com.google.gson.Gson; import com.google.gson.reflect.TypeToken; +import io.vavr.CheckedFunction0; import io.vavr.collection.List; import io.vavr.control.Option; import io.vavr.control.Try; import nl.jqno.paralleljava.app.domain.PartialTodo; import nl.jqno.paralleljava.app.domain.Todo; +import nl.jqno.paralleljava.app.logging.Logger; import java.lang.reflect.Type; @@ -14,9 +16,11 @@ public class GsonSerializer implements Serializer { private static final Type LIST_OF_TODO_TYPE = new TypeToken>(){}.getType(); private final Gson gson; + private final Logger logger; - public GsonSerializer(Gson gson) { + public GsonSerializer(Gson gson, Logger logger) { this.gson = gson; + this.logger = logger; } public String serializeTodo(Todo todo) { @@ -24,7 +28,7 @@ public String serializeTodo(Todo todo) { } public Option deserializeTodo(String json) { - return Try.of(() -> gson.fromJson(json, Todo.class)).toOption(); + return attemptDeserialization(json, () -> gson.fromJson(json, Todo.class)); } public String serializePartialTodo(PartialTodo todo) { @@ -32,7 +36,7 @@ public String serializePartialTodo(PartialTodo todo) { } public Option deserializePartialTodo(String json) { - return Try.of(() -> gson.fromJson(json, PartialTodo.class)).toOption(); + return attemptDeserialization(json, () -> gson.fromJson(json, PartialTodo.class)); } public String serializeTodos(List todos) { @@ -40,6 +44,12 @@ public String serializeTodos(List todos) { } public List deserializeTodos(String json) { - return Try.of(() -> (List)gson.fromJson(json, LIST_OF_TODO_TYPE)).getOrElse(List.empty()); + return attemptDeserialization(json, () -> (List)gson.fromJson(json, LIST_OF_TODO_TYPE)).getOrElse(List.empty()); + } + + private Option attemptDeserialization(String json, CheckedFunction0 f) { + return Try.of(f) + .onFailure(t -> logger.firstThingNextMorning("Failed to deserialize " + json, t)) + .toOption(); } } diff --git a/src/main/java/nl/jqno/paralleljava/dependencyinjection/WiredApplication.java b/src/main/java/nl/jqno/paralleljava/dependencyinjection/WiredApplication.java index b931618..ce723e1 100644 --- a/src/main/java/nl/jqno/paralleljava/dependencyinjection/WiredApplication.java +++ b/src/main/java/nl/jqno/paralleljava/dependencyinjection/WiredApplication.java @@ -34,15 +34,15 @@ public void run() { server.run(); } - public static Serializer defaultSerializer() { + public static Serializer defaultSerializer(Logger logger) { var gsonBuilder = new GsonBuilder(); VavrGson.registerAll(gsonBuilder); - return new GsonSerializer(gsonBuilder.create()); + return new GsonSerializer(gsonBuilder.create(), logger); } private static Server createServer(Repository repository, Function1, Logger> loggerFactory) { int port = getPort(); - var endpoints = new DefaultEndpoints(repository, defaultSerializer(), loggerFactory.apply(DefaultEndpoints.class)); + var endpoints = new DefaultEndpoints(repository, defaultSerializer(loggerFactory.apply(GsonSerializer.class)), loggerFactory.apply(DefaultEndpoints.class)); return new SparkServer(endpoints, port, loggerFactory.apply(SparkServer.class)); } diff --git a/src/test/java/nl/jqno/paralleljava/app/endpoints/DefaultEndpointsTest.java b/src/test/java/nl/jqno/paralleljava/app/endpoints/DefaultEndpointsTest.java index 9ae4e75..d2345d2 100644 --- a/src/test/java/nl/jqno/paralleljava/app/endpoints/DefaultEndpointsTest.java +++ b/src/test/java/nl/jqno/paralleljava/app/endpoints/DefaultEndpointsTest.java @@ -13,8 +13,8 @@ public class DefaultEndpointsTest extends Test { public void endoints() { - var serializer = WiredApplication.defaultSerializer(); var logger = new NopLogger(); + var serializer = WiredApplication.defaultSerializer(logger); var repository = new InMemoryRepository(logger); var someRequest = new Request(""); var endpoints = new DefaultEndpoints(repository, serializer, logger); diff --git a/src/test/java/nl/jqno/paralleljava/app/serialization/GsonSerializerTest.java b/src/test/java/nl/jqno/paralleljava/app/serialization/GsonSerializerTest.java index 9e5e53b..1cc503e 100644 --- a/src/test/java/nl/jqno/paralleljava/app/serialization/GsonSerializerTest.java +++ b/src/test/java/nl/jqno/paralleljava/app/serialization/GsonSerializerTest.java @@ -2,6 +2,7 @@ import io.vavr.collection.List; import io.vavr.control.Option; +import nl.jqno.paralleljava.app.logging.NopLogger; import nl.jqno.paralleljava.dependencyinjection.WiredApplication; import nl.jqno.picotest.Test; @@ -10,7 +11,7 @@ public class GsonSerializerTest extends Test { - private Serializer serializer = WiredApplication.defaultSerializer(); + private Serializer serializer = WiredApplication.defaultSerializer(new NopLogger()); public void serializationOfASingleTodo() { From bf31eda17fdb9611f0d422cc2ce3f4feaf52bc2e Mon Sep 17 00:00:00 2001 From: Jan Ouwens Date: Thu, 21 Mar 2019 17:37:33 +0100 Subject: [PATCH 049/106] =?UTF-8?q?=E0=B2=A0=5F=E0=B2=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/module-info.java | 2 ++ .../java/nl/jqno/paralleljava/app/domain/PartialTodo.java | 8 ++++++++ 2 files changed, 10 insertions(+) diff --git a/src/main/java/module-info.java b/src/main/java/module-info.java index 5867575..0db6291 100644 --- a/src/main/java/module-info.java +++ b/src/main/java/module-info.java @@ -1,6 +1,8 @@ module nl.jqno.paralleljava { exports nl.jqno.paralleljava; + opens nl.jqno.paralleljava.app.domain to gson; + requires io.vavr; requires io.vavr.gson; requires java.sql; // required for gson diff --git a/src/main/java/nl/jqno/paralleljava/app/domain/PartialTodo.java b/src/main/java/nl/jqno/paralleljava/app/domain/PartialTodo.java index cb4bfd7..da3904d 100644 --- a/src/main/java/nl/jqno/paralleljava/app/domain/PartialTodo.java +++ b/src/main/java/nl/jqno/paralleljava/app/domain/PartialTodo.java @@ -11,6 +11,14 @@ public final class PartialTodo { private final Boolean completed; private final Integer order; + public PartialTodo() { + this.id = null; + this.title = null; + this.url = null; + this.completed = null; + this.order = null; + } + public PartialTodo(Option id, Option title, Option url, Option completed, Option order) { this.id = id.getOrNull(); this.title = title.getOrNull(); From 4007193bbd7dfeb79ad0c656d3c47a62a844ae20 Mon Sep 17 00:00:00 2001 From: Jan Ouwens Date: Thu, 21 Mar 2019 20:40:31 +0100 Subject: [PATCH 050/106] Responds with the actual Todo after a POST --- .../nl/jqno/paralleljava/app/endpoints/DefaultEndpoints.java | 2 +- .../jqno/paralleljava/app/endpoints/DefaultEndpointsTest.java | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/main/java/nl/jqno/paralleljava/app/endpoints/DefaultEndpoints.java b/src/main/java/nl/jqno/paralleljava/app/endpoints/DefaultEndpoints.java index 45ef849..4cb02c7 100644 --- a/src/main/java/nl/jqno/paralleljava/app/endpoints/DefaultEndpoints.java +++ b/src/main/java/nl/jqno/paralleljava/app/endpoints/DefaultEndpoints.java @@ -30,7 +30,7 @@ public Route post() { var todo = new Todo(-1, pt.title().get(), "", false, 0); repository.createTodo(todo); logger.forProduction("Returning from POST: " + json); - return json; + return serializer.serializeTodo(todo); } else { return ""; diff --git a/src/test/java/nl/jqno/paralleljava/app/endpoints/DefaultEndpointsTest.java b/src/test/java/nl/jqno/paralleljava/app/endpoints/DefaultEndpointsTest.java index d2345d2..affdf6d 100644 --- a/src/test/java/nl/jqno/paralleljava/app/endpoints/DefaultEndpointsTest.java +++ b/src/test/java/nl/jqno/paralleljava/app/endpoints/DefaultEndpointsTest.java @@ -40,10 +40,11 @@ public void endoints() { test("post adds a todo", () -> { var expected = new Todo(-1, "title", "", false, 0); + var expectedSerialized = serializer.serializeTodo(expected); var sut = endpoints.post(); var actual = sut.handle(new Request(SomeTodo.SERIALIZED_PARTIAL_POST)); - assertThat(actual).isEqualTo(SomeTodo.SERIALIZED_PARTIAL_POST); + assertThat(actual).isEqualTo(expectedSerialized); assertThat(repository.getAllTodos()).contains(expected); }); From 53dfc6f6ede4a2193e34e05213b21cc0ea42274e Mon Sep 17 00:00:00 2001 From: Jan Ouwens Date: Fri, 22 Mar 2019 07:42:47 +0100 Subject: [PATCH 051/106] Returns correct URL from POST --- .../app/endpoints/DefaultEndpoints.java | 11 +++++++++-- .../nl/jqno/paralleljava/app/server/Heroku.java | 5 +++++ .../jqno/paralleljava/app/server/SparkServer.java | 11 ++++++----- .../dependencyinjection/WiredApplication.java | 15 +++++++++------ .../app/endpoints/DefaultEndpointsTest.java | 5 +++-- .../jqno/paralleljava/app/server/HerokuTest.java | 5 +++++ .../paralleljava/app/server/SparkServerTest.java | 15 ++++++++------- 7 files changed, 45 insertions(+), 22 deletions(-) diff --git a/src/main/java/nl/jqno/paralleljava/app/endpoints/DefaultEndpoints.java b/src/main/java/nl/jqno/paralleljava/app/endpoints/DefaultEndpoints.java index 4cb02c7..db4078a 100644 --- a/src/main/java/nl/jqno/paralleljava/app/endpoints/DefaultEndpoints.java +++ b/src/main/java/nl/jqno/paralleljava/app/endpoints/DefaultEndpoints.java @@ -6,11 +6,13 @@ import nl.jqno.paralleljava.app.serialization.Serializer; public class DefaultEndpoints implements Endpoints { + private final String url; private final Repository repository; private final Serializer serializer; private final Logger logger; - public DefaultEndpoints(Repository repository, Serializer serializer, Logger logger) { + public DefaultEndpoints(String url, Repository repository, Serializer serializer, Logger logger) { + this.url = url; this.repository = repository; this.serializer = serializer; this.logger = logger; @@ -27,7 +29,8 @@ public Route post() { var partialTodo = serializer.deserializePartialTodo(json); if (partialTodo.isDefined() && partialTodo.get().title().isDefined()) { var pt = partialTodo.get(); - var todo = new Todo(-1, pt.title().get(), "", false, 0); + var id = 1; + var todo = new Todo(id, pt.title().get(), buildUrlFor(id), false, 0); repository.createTodo(todo); logger.forProduction("Returning from POST: " + json); return serializer.serializeTodo(todo); @@ -44,4 +47,8 @@ public Route delete() { return ""; }; } + + private String buildUrlFor(int id) { + return url + "/" + id; + } } diff --git a/src/main/java/nl/jqno/paralleljava/app/server/Heroku.java b/src/main/java/nl/jqno/paralleljava/app/server/Heroku.java index ccfc784..3efcca6 100644 --- a/src/main/java/nl/jqno/paralleljava/app/server/Heroku.java +++ b/src/main/java/nl/jqno/paralleljava/app/server/Heroku.java @@ -16,6 +16,11 @@ public Option getAssignedPort() { return env.get("PORT").flatMap(this::parse); } + public Option getHostUrl() { + // hard-coded for now + return Option.some("https://parallel-java.herokuapp.com"); + } + private Option parse(String port) { return Try.of(() -> Integer.parseInt(port)).toOption(); } diff --git a/src/main/java/nl/jqno/paralleljava/app/server/SparkServer.java b/src/main/java/nl/jqno/paralleljava/app/server/SparkServer.java index a85aa90..692a9dd 100644 --- a/src/main/java/nl/jqno/paralleljava/app/server/SparkServer.java +++ b/src/main/java/nl/jqno/paralleljava/app/server/SparkServer.java @@ -1,6 +1,5 @@ package nl.jqno.paralleljava.app.server; -import io.vavr.collection.HashMap; import io.vavr.control.Option; import nl.jqno.paralleljava.app.endpoints.Endpoints; import nl.jqno.paralleljava.app.endpoints.Request; @@ -11,11 +10,13 @@ public class SparkServer implements Server { + private final String endpoint; private final Endpoints endpoints; private final int port; private final Logger logger; - public SparkServer(Endpoints endpoints, int port, Logger logger) { + public SparkServer(String endpoint, Endpoints endpoints, int port, Logger logger) { + this.endpoint = endpoint; this.endpoints = endpoints; this.port = port; this.logger = logger; @@ -27,9 +28,9 @@ public void run() { port(port); enableCors(); - get("/todo", convert(endpoints.get())); - post("/todo", convert(endpoints.post())); - delete("/todo", convert(endpoints.delete())); + get(endpoint, convert(endpoints.get())); + post(endpoint, convert(endpoints.post())); + delete(endpoint, convert(endpoints.delete())); } private void enableCors() { diff --git a/src/main/java/nl/jqno/paralleljava/dependencyinjection/WiredApplication.java b/src/main/java/nl/jqno/paralleljava/dependencyinjection/WiredApplication.java index ce723e1..6a39e4d 100644 --- a/src/main/java/nl/jqno/paralleljava/dependencyinjection/WiredApplication.java +++ b/src/main/java/nl/jqno/paralleljava/dependencyinjection/WiredApplication.java @@ -19,6 +19,8 @@ public class WiredApplication { private static final int DEFAULT_PORT = 4567; + private static final String DEFAULT_URL = "http://localhost"; + private static final String ENDPOINT = "/todo"; private final Function1, Logger> loggerFactory; private final Repository repository; @@ -41,15 +43,16 @@ public static Serializer defaultSerializer(Logger logger) { } private static Server createServer(Repository repository, Function1, Logger> loggerFactory) { - int port = getPort(); - var endpoints = new DefaultEndpoints(repository, defaultSerializer(loggerFactory.apply(GsonSerializer.class)), loggerFactory.apply(DefaultEndpoints.class)); - return new SparkServer(endpoints, port, loggerFactory.apply(SparkServer.class)); + var heroku = createHeroku(); + int port = heroku.getAssignedPort().getOrElse(DEFAULT_PORT); + var fullUrl = heroku.getHostUrl().getOrElse(DEFAULT_URL) + ENDPOINT; + var endpoints = new DefaultEndpoints(fullUrl, repository, defaultSerializer(loggerFactory.apply(GsonSerializer.class)), loggerFactory.apply(DefaultEndpoints.class)); + return new SparkServer(ENDPOINT, endpoints, port, loggerFactory.apply(SparkServer.class)); } - private static int getPort() { + private static Heroku createHeroku() { var processBuilder = new ProcessBuilder(); var environment = HashMap.ofAll(processBuilder.environment()); - var heroku = new Heroku(environment); - return heroku.getAssignedPort().getOrElse(DEFAULT_PORT); + return new Heroku(environment); } } diff --git a/src/test/java/nl/jqno/paralleljava/app/endpoints/DefaultEndpointsTest.java b/src/test/java/nl/jqno/paralleljava/app/endpoints/DefaultEndpointsTest.java index affdf6d..b0e7116 100644 --- a/src/test/java/nl/jqno/paralleljava/app/endpoints/DefaultEndpointsTest.java +++ b/src/test/java/nl/jqno/paralleljava/app/endpoints/DefaultEndpointsTest.java @@ -17,7 +17,8 @@ public void endoints() { var serializer = WiredApplication.defaultSerializer(logger); var repository = new InMemoryRepository(logger); var someRequest = new Request(""); - var endpoints = new DefaultEndpoints(repository, serializer, logger); + var urlBase = "/blabla/todo"; + var endpoints = new DefaultEndpoints(urlBase, repository, serializer, logger); beforeEach(() -> { repository.clearAllTodos(); @@ -39,7 +40,7 @@ public void endoints() { }); test("post adds a todo", () -> { - var expected = new Todo(-1, "title", "", false, 0); + var expected = new Todo(1, "title", urlBase + "/1", false, 0); var expectedSerialized = serializer.serializeTodo(expected); var sut = endpoints.post(); diff --git a/src/test/java/nl/jqno/paralleljava/app/server/HerokuTest.java b/src/test/java/nl/jqno/paralleljava/app/server/HerokuTest.java index 66e155d..de54f4e 100644 --- a/src/test/java/nl/jqno/paralleljava/app/server/HerokuTest.java +++ b/src/test/java/nl/jqno/paralleljava/app/server/HerokuTest.java @@ -28,6 +28,11 @@ public void port() { var actual = heroku.getAssignedPort(); assertThat(actual).isEqualTo(Option.none()); }); + + test("host url", () -> { + var actual = heroku.getHostUrl(); + assertThat(actual).isEqualTo(Option.some("https://parallel-java.herokuapp.com")); + }); } private void setEnvironmentVariable(String key, String value) { diff --git a/src/test/java/nl/jqno/paralleljava/app/server/SparkServerTest.java b/src/test/java/nl/jqno/paralleljava/app/server/SparkServerTest.java index 827d799..47ad424 100644 --- a/src/test/java/nl/jqno/paralleljava/app/server/SparkServerTest.java +++ b/src/test/java/nl/jqno/paralleljava/app/server/SparkServerTest.java @@ -13,6 +13,7 @@ public class SparkServerTest extends Test { private static final int PORT = 1337; + private static final String ENDPOINT = "/todo"; private final RequestSpecification when = given().port(PORT).when(); private StubEndpoints underlying; @@ -20,7 +21,7 @@ public void server() { underlying = new StubEndpoints(); beforeAll(() -> { - new SparkServer(underlying, PORT, NopLogger.INSTANCE).run(); + new SparkServer(ENDPOINT, underlying, PORT, NopLogger.INSTANCE).run(); Spark.awaitInitialization(); }); @@ -40,12 +41,12 @@ public void server() { private void corsRequestsHeader() { when - .get("/todo") + .get(ENDPOINT) .then() .header("Access-Control-Allow-Origin", "*"); when - .post("/todo") + .post(ENDPOINT) .then() .header("Access-Control-Allow-Origin", "*"); } @@ -56,7 +57,7 @@ private void corsOptionsRequest() { when .header("Access-Control-Request-Headers", headers) .header("Access-Control-Request-Method", methods) - .options() + .options(ENDPOINT) .then() .header("Access-Control-Allow-Headers", headers) .header("Access-Control-Allow-Methods", methods); @@ -64,7 +65,7 @@ private void corsOptionsRequest() { private void getRequest() { when - .get("/todo") + .get(ENDPOINT) .then() .statusCode(200); assertSingleCall(underlying.calledGet); @@ -72,7 +73,7 @@ private void getRequest() { private void postRequest() { when - .post("/todo") + .post(ENDPOINT) .then() .statusCode(200); assertSingleCall(underlying.calledPost); @@ -80,7 +81,7 @@ private void postRequest() { private void deleteRequest() { when - .delete("/todo") + .delete(ENDPOINT) .then() .statusCode(200); assertSingleCall(underlying.calledDelete); From fb1244f5cdef9c6a87a1ec9ec923a15e36ec7f03 Mon Sep 17 00:00:00 2001 From: Jan Ouwens Date: Fri, 22 Mar 2019 08:12:23 +0100 Subject: [PATCH 052/106] Switches from int to UUID for ids --- .../paralleljava/app/domain/PartialTodo.java | 7 ++++--- .../nl/jqno/paralleljava/app/domain/Todo.java | 9 +++++---- .../app/endpoints/DefaultEndpoints.java | 13 +++++++++---- .../app/persistence/IdGenerator.java | 7 +++++++ .../app/persistence/RandomIdGenerator.java | 9 +++++++++ .../dependencyinjection/WiredApplication.java | 4 +++- .../nl/jqno/paralleljava/app/TestData.java | 16 +++++++++++----- .../app/domain/PartialTodoTest.java | 4 ++-- .../paralleljava/app/domain/TodoTest.java | 5 +++-- .../app/endpoints/DefaultEndpointsTest.java | 12 +++++++++--- .../app/persistence/ConstantIdGenerator.java | 15 +++++++++++++++ .../persistence/RandomIdGeneratorTest.java | 19 +++++++++++++++++++ .../app/serialization/GsonSerializerTest.java | 4 ++-- 13 files changed, 98 insertions(+), 26 deletions(-) create mode 100644 src/main/java/nl/jqno/paralleljava/app/persistence/IdGenerator.java create mode 100644 src/main/java/nl/jqno/paralleljava/app/persistence/RandomIdGenerator.java create mode 100644 src/test/java/nl/jqno/paralleljava/app/persistence/ConstantIdGenerator.java create mode 100644 src/test/java/nl/jqno/paralleljava/app/persistence/RandomIdGeneratorTest.java diff --git a/src/main/java/nl/jqno/paralleljava/app/domain/PartialTodo.java b/src/main/java/nl/jqno/paralleljava/app/domain/PartialTodo.java index da3904d..44079b4 100644 --- a/src/main/java/nl/jqno/paralleljava/app/domain/PartialTodo.java +++ b/src/main/java/nl/jqno/paralleljava/app/domain/PartialTodo.java @@ -3,9 +3,10 @@ import io.vavr.control.Option; import java.util.Objects; +import java.util.UUID; public final class PartialTodo { - private final Integer id; + private final UUID id; private final String title; private final String url; private final Boolean completed; @@ -19,7 +20,7 @@ public PartialTodo() { this.order = null; } - public PartialTodo(Option id, Option title, Option url, Option completed, Option order) { + public PartialTodo(Option id, Option title, Option url, Option completed, Option order) { this.id = id.getOrNull(); this.title = title.getOrNull(); this.url = url.getOrNull(); @@ -27,7 +28,7 @@ public PartialTodo(Option id, Option title, Option url, this.order = order.getOrNull(); } - public Option id() { + public Option id() { return Option.of(id); } diff --git a/src/main/java/nl/jqno/paralleljava/app/domain/Todo.java b/src/main/java/nl/jqno/paralleljava/app/domain/Todo.java index e85e753..34aad94 100644 --- a/src/main/java/nl/jqno/paralleljava/app/domain/Todo.java +++ b/src/main/java/nl/jqno/paralleljava/app/domain/Todo.java @@ -1,15 +1,16 @@ package nl.jqno.paralleljava.app.domain; import java.util.Objects; +import java.util.UUID; public final class Todo { - private final int id; + private final UUID id; private final String title; private final String url; private final boolean completed; private final int order; - public Todo(int id, String title, String url, boolean completed, int order) { + public Todo(UUID id, String title, String url, boolean completed, int order) { this.id = id; this.title = title; this.url = url; @@ -17,7 +18,7 @@ public Todo(int id, String title, String url, boolean completed, int order) { this.order = order; } - public int id() { + public UUID id() { return id; } @@ -42,7 +43,7 @@ public boolean equals(Object obj) { return false; } Todo other = (Todo)obj; - return id == other.id && + return Objects.equals(id, other.id) && Objects.equals(title, other.title) && Objects.equals(url, other.url) && completed == other.completed && diff --git a/src/main/java/nl/jqno/paralleljava/app/endpoints/DefaultEndpoints.java b/src/main/java/nl/jqno/paralleljava/app/endpoints/DefaultEndpoints.java index db4078a..a9f0c0a 100644 --- a/src/main/java/nl/jqno/paralleljava/app/endpoints/DefaultEndpoints.java +++ b/src/main/java/nl/jqno/paralleljava/app/endpoints/DefaultEndpoints.java @@ -2,18 +2,23 @@ import nl.jqno.paralleljava.app.domain.Todo; import nl.jqno.paralleljava.app.logging.Logger; +import nl.jqno.paralleljava.app.persistence.IdGenerator; import nl.jqno.paralleljava.app.persistence.Repository; import nl.jqno.paralleljava.app.serialization.Serializer; +import java.util.UUID; + public class DefaultEndpoints implements Endpoints { private final String url; private final Repository repository; + private final IdGenerator generator; private final Serializer serializer; private final Logger logger; - public DefaultEndpoints(String url, Repository repository, Serializer serializer, Logger logger) { + public DefaultEndpoints(String url, Repository repository, IdGenerator generator, Serializer serializer, Logger logger) { this.url = url; this.repository = repository; + this.generator = generator; this.serializer = serializer; this.logger = logger; } @@ -29,7 +34,7 @@ public Route post() { var partialTodo = serializer.deserializePartialTodo(json); if (partialTodo.isDefined() && partialTodo.get().title().isDefined()) { var pt = partialTodo.get(); - var id = 1; + var id = generator.generateId(); var todo = new Todo(id, pt.title().get(), buildUrlFor(id), false, 0); repository.createTodo(todo); logger.forProduction("Returning from POST: " + json); @@ -48,7 +53,7 @@ public Route delete() { }; } - private String buildUrlFor(int id) { - return url + "/" + id; + private String buildUrlFor(UUID id) { + return url + "/" + id.toString(); } } diff --git a/src/main/java/nl/jqno/paralleljava/app/persistence/IdGenerator.java b/src/main/java/nl/jqno/paralleljava/app/persistence/IdGenerator.java new file mode 100644 index 0000000..db32641 --- /dev/null +++ b/src/main/java/nl/jqno/paralleljava/app/persistence/IdGenerator.java @@ -0,0 +1,7 @@ +package nl.jqno.paralleljava.app.persistence; + +import java.util.UUID; + +public interface IdGenerator { + UUID generateId(); +} diff --git a/src/main/java/nl/jqno/paralleljava/app/persistence/RandomIdGenerator.java b/src/main/java/nl/jqno/paralleljava/app/persistence/RandomIdGenerator.java new file mode 100644 index 0000000..36be1c2 --- /dev/null +++ b/src/main/java/nl/jqno/paralleljava/app/persistence/RandomIdGenerator.java @@ -0,0 +1,9 @@ +package nl.jqno.paralleljava.app.persistence; + +import java.util.UUID; + +public class RandomIdGenerator implements IdGenerator { + public UUID generateId() { + return UUID.randomUUID(); + } +} diff --git a/src/main/java/nl/jqno/paralleljava/dependencyinjection/WiredApplication.java b/src/main/java/nl/jqno/paralleljava/dependencyinjection/WiredApplication.java index 6a39e4d..7f0bae2 100644 --- a/src/main/java/nl/jqno/paralleljava/dependencyinjection/WiredApplication.java +++ b/src/main/java/nl/jqno/paralleljava/dependencyinjection/WiredApplication.java @@ -9,6 +9,7 @@ import nl.jqno.paralleljava.app.logging.Slf4jLogger; import nl.jqno.paralleljava.app.persistence.InMemoryRepository; import nl.jqno.paralleljava.app.persistence.Repository; +import nl.jqno.paralleljava.app.persistence.RandomIdGenerator; import nl.jqno.paralleljava.app.serialization.GsonSerializer; import nl.jqno.paralleljava.app.serialization.Serializer; import nl.jqno.paralleljava.app.server.Heroku; @@ -46,7 +47,8 @@ private static Server createServer(Repository repository, Function1, Lo var heroku = createHeroku(); int port = heroku.getAssignedPort().getOrElse(DEFAULT_PORT); var fullUrl = heroku.getHostUrl().getOrElse(DEFAULT_URL) + ENDPOINT; - var endpoints = new DefaultEndpoints(fullUrl, repository, defaultSerializer(loggerFactory.apply(GsonSerializer.class)), loggerFactory.apply(DefaultEndpoints.class)); + var idGenerator = new RandomIdGenerator(); + var endpoints = new DefaultEndpoints(fullUrl, repository, idGenerator, defaultSerializer(loggerFactory.apply(GsonSerializer.class)), loggerFactory.apply(DefaultEndpoints.class)); return new SparkServer(ENDPOINT, endpoints, port, loggerFactory.apply(SparkServer.class)); } diff --git a/src/test/java/nl/jqno/paralleljava/app/TestData.java b/src/test/java/nl/jqno/paralleljava/app/TestData.java index 19a3ee5..0ce679a 100644 --- a/src/test/java/nl/jqno/paralleljava/app/TestData.java +++ b/src/test/java/nl/jqno/paralleljava/app/TestData.java @@ -5,31 +5,37 @@ import nl.jqno.paralleljava.app.domain.PartialTodo; import nl.jqno.paralleljava.app.domain.Todo; +import java.util.UUID; + public class TestData { public static class SomeTodo { + public static final UUID ID = UUID.randomUUID(); + public static final Todo TODO = - new Todo(42, "title", "http://www.example.com", true, 1337); + new Todo(ID, "title", "http://www.example.com", true, 1337); public static final PartialTodo PARTIAL_COMPLETE = - new PartialTodo(Option.of(42), Option.of("title"), Option.of("http://www.example.com"), Option.of(true), Option.of(1337)); + new PartialTodo(Option.of(ID), Option.of("title"), Option.of("http://www.example.com"), Option.of(true), Option.of(1337)); public static final PartialTodo PARTIAL_POST = new PartialTodo(Option.none(), Option.of("title"), Option.none(), Option.none(), Option.none()); public static final String SERIALIZED = - "{\"id\":42,\"title\":\"title\",\"url\":\"http://www.example.com\",\"completed\":true,\"order\":1337}"; + "{\"id\":\"" + ID + "\",\"title\":\"title\",\"url\":\"http://www.example.com\",\"completed\":true,\"order\":1337}"; public static final String SERIALIZED_PARTIAL_POST = "{\"title\":\"title\"}"; } public static class AnotherTodo { + public static final UUID ID = UUID.randomUUID(); + public static final Todo TODO = - new Todo(47, "something", "http://www.todobackend.com", false, 1); + new Todo(ID, "something", "http://www.todobackend.com", false, 1); public static final String SERIALIZED = - "{\"id\":47,\"title\":\"something\",\"url\":\"http://www.todobackend.com\",\"completed\":false,\"order\":1}"; + "{\"id\":\"" + ID + "\",\"title\":\"something\",\"url\":\"http://www.todobackend.com\",\"completed\":false,\"order\":1}"; } public static class ListOfTodos { diff --git a/src/test/java/nl/jqno/paralleljava/app/domain/PartialTodoTest.java b/src/test/java/nl/jqno/paralleljava/app/domain/PartialTodoTest.java index aa8cd8d..87cbfe8 100644 --- a/src/test/java/nl/jqno/paralleljava/app/domain/PartialTodoTest.java +++ b/src/test/java/nl/jqno/paralleljava/app/domain/PartialTodoTest.java @@ -17,7 +17,7 @@ public void partialTodo() { }); test("getters (Some)", () -> { - assertThat(SomeTodo.PARTIAL_COMPLETE.id()).isEqualTo(Option.of(42)); + assertThat(SomeTodo.PARTIAL_COMPLETE.id()).isEqualTo(Option.of(SomeTodo.ID)); assertThat(SomeTodo.PARTIAL_COMPLETE.title()).isEqualTo(Option.of("title")); assertThat(SomeTodo.PARTIAL_COMPLETE.url()).isEqualTo(Option.of("http://www.example.com")); assertThat(SomeTodo.PARTIAL_COMPLETE.completed()).isEqualTo(Option.of(true)); @@ -34,7 +34,7 @@ public void partialTodo() { test("toString", () -> { assertThat(SomeTodo.PARTIAL_COMPLETE.toString()) - .isEqualTo("PartialTodo: [id=Some(42), title=Some(title), url=Some(http://www.example.com), completed=Some(true), order=Some(1337)]"); + .isEqualTo("PartialTodo: [id=Some(" + SomeTodo.ID + "), title=Some(title), url=Some(http://www.example.com), completed=Some(true), order=Some(1337)]"); assertThat(SomeTodo.PARTIAL_POST.toString()) .isEqualTo("PartialTodo: [id=None, title=Some(title), url=None, completed=None, order=None]"); }); diff --git a/src/test/java/nl/jqno/paralleljava/app/domain/TodoTest.java b/src/test/java/nl/jqno/paralleljava/app/domain/TodoTest.java index 82b8599..5bceec8 100644 --- a/src/test/java/nl/jqno/paralleljava/app/domain/TodoTest.java +++ b/src/test/java/nl/jqno/paralleljava/app/domain/TodoTest.java @@ -16,7 +16,7 @@ public void todo() { }); test("getters", () -> { - assertThat(SomeTodo.TODO.id()).isEqualTo(42); + assertThat(SomeTodo.TODO.id()).isEqualTo(SomeTodo.ID); assertThat(SomeTodo.TODO.title()).isEqualTo("title"); assertThat(SomeTodo.TODO.url()).isEqualTo("http://www.example.com"); assertThat(SomeTodo.TODO.completed()).isEqualTo(true); @@ -24,7 +24,8 @@ public void todo() { }); test("toString", () -> { - assertThat(SomeTodo.TODO.toString()).isEqualTo("Todo: [id=42, title=title, url=http://www.example.com, completed=true, order=1337]"); + assertThat(SomeTodo.TODO.toString()) + .isEqualTo("Todo: [id=" + SomeTodo.ID + ", title=title, url=http://www.example.com, completed=true, order=1337]"); }); } } diff --git a/src/test/java/nl/jqno/paralleljava/app/endpoints/DefaultEndpointsTest.java b/src/test/java/nl/jqno/paralleljava/app/endpoints/DefaultEndpointsTest.java index b0e7116..b7a2db9 100644 --- a/src/test/java/nl/jqno/paralleljava/app/endpoints/DefaultEndpointsTest.java +++ b/src/test/java/nl/jqno/paralleljava/app/endpoints/DefaultEndpointsTest.java @@ -3,10 +3,14 @@ import io.vavr.collection.List; import nl.jqno.paralleljava.app.domain.Todo; import nl.jqno.paralleljava.app.logging.NopLogger; +import nl.jqno.paralleljava.app.persistence.ConstantIdGenerator; +import nl.jqno.paralleljava.app.persistence.RandomIdGenerator; import nl.jqno.paralleljava.app.persistence.InMemoryRepository; import nl.jqno.paralleljava.dependencyinjection.WiredApplication; import nl.jqno.picotest.Test; +import java.util.UUID; + import static nl.jqno.paralleljava.app.TestData.*; import static org.assertj.core.api.Assertions.assertThat; @@ -14,11 +18,13 @@ public class DefaultEndpointsTest extends Test { public void endoints() { var logger = new NopLogger(); - var serializer = WiredApplication.defaultSerializer(logger); var repository = new InMemoryRepository(logger); + var constantId = UUID.randomUUID(); + var idGenerator = new ConstantIdGenerator(constantId); + var serializer = WiredApplication.defaultSerializer(logger); var someRequest = new Request(""); var urlBase = "/blabla/todo"; - var endpoints = new DefaultEndpoints(urlBase, repository, serializer, logger); + var endpoints = new DefaultEndpoints(urlBase, repository, idGenerator, serializer, logger); beforeEach(() -> { repository.clearAllTodos(); @@ -40,7 +46,7 @@ public void endoints() { }); test("post adds a todo", () -> { - var expected = new Todo(1, "title", urlBase + "/1", false, 0); + var expected = new Todo(constantId, "title", urlBase + "/" + constantId, false, 0); var expectedSerialized = serializer.serializeTodo(expected); var sut = endpoints.post(); diff --git a/src/test/java/nl/jqno/paralleljava/app/persistence/ConstantIdGenerator.java b/src/test/java/nl/jqno/paralleljava/app/persistence/ConstantIdGenerator.java new file mode 100644 index 0000000..ef3e894 --- /dev/null +++ b/src/test/java/nl/jqno/paralleljava/app/persistence/ConstantIdGenerator.java @@ -0,0 +1,15 @@ +package nl.jqno.paralleljava.app.persistence; + +import java.util.UUID; + +public class ConstantIdGenerator implements IdGenerator { + private final UUID id; + + public ConstantIdGenerator(UUID id) { + this.id = id; + } + + public UUID generateId() { + return id; + } +} diff --git a/src/test/java/nl/jqno/paralleljava/app/persistence/RandomIdGeneratorTest.java b/src/test/java/nl/jqno/paralleljava/app/persistence/RandomIdGeneratorTest.java new file mode 100644 index 0000000..bea8ff9 --- /dev/null +++ b/src/test/java/nl/jqno/paralleljava/app/persistence/RandomIdGeneratorTest.java @@ -0,0 +1,19 @@ +package nl.jqno.paralleljava.app.persistence; + +import nl.jqno.picotest.Test; +import org.assertj.core.api.Assertions; + +import java.util.UUID; + +public class RandomIdGeneratorTest extends Test { + + public void uuidGenerator() { + var generator = new RandomIdGenerator(); + + test("generates a valid uuid", () -> { + UUID actual = generator.generateId(); + var roundTrip = UUID.fromString(actual.toString()); + Assertions.assertThat(actual).isEqualTo(roundTrip); + }); + } +} diff --git a/src/test/java/nl/jqno/paralleljava/app/serialization/GsonSerializerTest.java b/src/test/java/nl/jqno/paralleljava/app/serialization/GsonSerializerTest.java index 1cc503e..4847ecd 100644 --- a/src/test/java/nl/jqno/paralleljava/app/serialization/GsonSerializerTest.java +++ b/src/test/java/nl/jqno/paralleljava/app/serialization/GsonSerializerTest.java @@ -18,7 +18,7 @@ public void serializationOfASingleTodo() { test("Serializes a Todo to json", () -> { var actual = serializer.serializeTodo(SomeTodo.TODO); assertThat(actual) - .contains("\"id\":42") + .contains("\"id\":\"" + SomeTodo.ID + "\"") .contains("\"title\":\"title\"") .contains("\"url\":\"http://www.example.com\"") .contains("\"completed\":true") @@ -47,7 +47,7 @@ public void serializationOfACompletePartialTodo() { test("Serializes a complete PartialTodo to json", () -> { var actual = serializer.serializePartialTodo(SomeTodo.PARTIAL_COMPLETE); assertThat(actual) - .contains("\"id\":42") + .contains("\"id\":\"" + SomeTodo.ID + "\"") .contains("\"title\":\"title\"") .contains("\"url\":\"http://www.example.com\"") .contains("\"completed\":true") From 296ab19aabc178c5736a8a91a0aa74ab136a1d25 Mon Sep 17 00:00:00 2001 From: Jan Ouwens Date: Fri, 22 Mar 2019 08:18:31 +0100 Subject: [PATCH 053/106] Renames Endpoints to Controller --- .../app/controller/Controller.java | 7 +++++++ .../DefaultController.java} | 6 +++--- .../app/{endpoints => controller}/Request.java | 2 +- .../app/{endpoints => controller}/Route.java | 2 +- .../paralleljava/app/endpoints/Endpoints.java | 7 ------- .../paralleljava/app/server/SparkServer.java | 18 +++++++++--------- .../dependencyinjection/WiredApplication.java | 6 +++--- .../DefaultControllerTest.java} | 15 +++++++-------- .../{endpoints => controller}/RequestTest.java | 2 +- .../app/server/SparkServerTest.java | 10 +++++----- 10 files changed, 37 insertions(+), 38 deletions(-) create mode 100644 src/main/java/nl/jqno/paralleljava/app/controller/Controller.java rename src/main/java/nl/jqno/paralleljava/app/{endpoints/DefaultEndpoints.java => controller/DefaultController.java} (88%) rename src/main/java/nl/jqno/paralleljava/app/{endpoints => controller}/Request.java (79%) rename src/main/java/nl/jqno/paralleljava/app/{endpoints => controller}/Route.java (58%) delete mode 100644 src/main/java/nl/jqno/paralleljava/app/endpoints/Endpoints.java rename src/test/java/nl/jqno/paralleljava/app/{endpoints/DefaultEndpointsTest.java => controller/DefaultControllerTest.java} (83%) rename src/test/java/nl/jqno/paralleljava/app/{endpoints => controller}/RequestTest.java (87%) diff --git a/src/main/java/nl/jqno/paralleljava/app/controller/Controller.java b/src/main/java/nl/jqno/paralleljava/app/controller/Controller.java new file mode 100644 index 0000000..c1c3ccf --- /dev/null +++ b/src/main/java/nl/jqno/paralleljava/app/controller/Controller.java @@ -0,0 +1,7 @@ +package nl.jqno.paralleljava.app.controller; + +public interface Controller { + Route get(); + Route post(); + Route delete(); +} diff --git a/src/main/java/nl/jqno/paralleljava/app/endpoints/DefaultEndpoints.java b/src/main/java/nl/jqno/paralleljava/app/controller/DefaultController.java similarity index 88% rename from src/main/java/nl/jqno/paralleljava/app/endpoints/DefaultEndpoints.java rename to src/main/java/nl/jqno/paralleljava/app/controller/DefaultController.java index a9f0c0a..5c46251 100644 --- a/src/main/java/nl/jqno/paralleljava/app/endpoints/DefaultEndpoints.java +++ b/src/main/java/nl/jqno/paralleljava/app/controller/DefaultController.java @@ -1,4 +1,4 @@ -package nl.jqno.paralleljava.app.endpoints; +package nl.jqno.paralleljava.app.controller; import nl.jqno.paralleljava.app.domain.Todo; import nl.jqno.paralleljava.app.logging.Logger; @@ -8,14 +8,14 @@ import java.util.UUID; -public class DefaultEndpoints implements Endpoints { +public class DefaultController implements Controller { private final String url; private final Repository repository; private final IdGenerator generator; private final Serializer serializer; private final Logger logger; - public DefaultEndpoints(String url, Repository repository, IdGenerator generator, Serializer serializer, Logger logger) { + public DefaultController(String url, Repository repository, IdGenerator generator, Serializer serializer, Logger logger) { this.url = url; this.repository = repository; this.generator = generator; diff --git a/src/main/java/nl/jqno/paralleljava/app/endpoints/Request.java b/src/main/java/nl/jqno/paralleljava/app/controller/Request.java similarity index 79% rename from src/main/java/nl/jqno/paralleljava/app/endpoints/Request.java rename to src/main/java/nl/jqno/paralleljava/app/controller/Request.java index 5e3412e..bd330cc 100644 --- a/src/main/java/nl/jqno/paralleljava/app/endpoints/Request.java +++ b/src/main/java/nl/jqno/paralleljava/app/controller/Request.java @@ -1,4 +1,4 @@ -package nl.jqno.paralleljava.app.endpoints; +package nl.jqno.paralleljava.app.controller; public class Request { private final String body; diff --git a/src/main/java/nl/jqno/paralleljava/app/endpoints/Route.java b/src/main/java/nl/jqno/paralleljava/app/controller/Route.java similarity index 58% rename from src/main/java/nl/jqno/paralleljava/app/endpoints/Route.java rename to src/main/java/nl/jqno/paralleljava/app/controller/Route.java index 794d8fb..5720d87 100644 --- a/src/main/java/nl/jqno/paralleljava/app/endpoints/Route.java +++ b/src/main/java/nl/jqno/paralleljava/app/controller/Route.java @@ -1,4 +1,4 @@ -package nl.jqno.paralleljava.app.endpoints; +package nl.jqno.paralleljava.app.controller; public interface Route { String handle(Request request); diff --git a/src/main/java/nl/jqno/paralleljava/app/endpoints/Endpoints.java b/src/main/java/nl/jqno/paralleljava/app/endpoints/Endpoints.java deleted file mode 100644 index d67aa08..0000000 --- a/src/main/java/nl/jqno/paralleljava/app/endpoints/Endpoints.java +++ /dev/null @@ -1,7 +0,0 @@ -package nl.jqno.paralleljava.app.endpoints; - -public interface Endpoints { - Route get(); - Route post(); - Route delete(); -} diff --git a/src/main/java/nl/jqno/paralleljava/app/server/SparkServer.java b/src/main/java/nl/jqno/paralleljava/app/server/SparkServer.java index 692a9dd..0fd3a4c 100644 --- a/src/main/java/nl/jqno/paralleljava/app/server/SparkServer.java +++ b/src/main/java/nl/jqno/paralleljava/app/server/SparkServer.java @@ -1,9 +1,9 @@ package nl.jqno.paralleljava.app.server; import io.vavr.control.Option; -import nl.jqno.paralleljava.app.endpoints.Endpoints; -import nl.jqno.paralleljava.app.endpoints.Request; -import nl.jqno.paralleljava.app.endpoints.Route; +import nl.jqno.paralleljava.app.controller.Controller; +import nl.jqno.paralleljava.app.controller.Request; +import nl.jqno.paralleljava.app.controller.Route; import nl.jqno.paralleljava.app.logging.Logger; import static spark.Spark.*; @@ -11,13 +11,13 @@ public class SparkServer implements Server { private final String endpoint; - private final Endpoints endpoints; + private final Controller controller; private final int port; private final Logger logger; - public SparkServer(String endpoint, Endpoints endpoints, int port, Logger logger) { + public SparkServer(String endpoint, Controller controller, int port, Logger logger) { this.endpoint = endpoint; - this.endpoints = endpoints; + this.controller = controller; this.port = port; this.logger = logger; } @@ -28,9 +28,9 @@ public void run() { port(port); enableCors(); - get(endpoint, convert(endpoints.get())); - post(endpoint, convert(endpoints.post())); - delete(endpoint, convert(endpoints.delete())); + get(endpoint, convert(controller.get())); + post(endpoint, convert(controller.post())); + delete(endpoint, convert(controller.delete())); } private void enableCors() { diff --git a/src/main/java/nl/jqno/paralleljava/dependencyinjection/WiredApplication.java b/src/main/java/nl/jqno/paralleljava/dependencyinjection/WiredApplication.java index 7f0bae2..0f220d6 100644 --- a/src/main/java/nl/jqno/paralleljava/dependencyinjection/WiredApplication.java +++ b/src/main/java/nl/jqno/paralleljava/dependencyinjection/WiredApplication.java @@ -4,7 +4,7 @@ import io.vavr.Function1; import io.vavr.collection.HashMap; import io.vavr.gson.VavrGson; -import nl.jqno.paralleljava.app.endpoints.DefaultEndpoints; +import nl.jqno.paralleljava.app.controller.DefaultController; import nl.jqno.paralleljava.app.logging.Logger; import nl.jqno.paralleljava.app.logging.Slf4jLogger; import nl.jqno.paralleljava.app.persistence.InMemoryRepository; @@ -48,8 +48,8 @@ private static Server createServer(Repository repository, Function1, Lo int port = heroku.getAssignedPort().getOrElse(DEFAULT_PORT); var fullUrl = heroku.getHostUrl().getOrElse(DEFAULT_URL) + ENDPOINT; var idGenerator = new RandomIdGenerator(); - var endpoints = new DefaultEndpoints(fullUrl, repository, idGenerator, defaultSerializer(loggerFactory.apply(GsonSerializer.class)), loggerFactory.apply(DefaultEndpoints.class)); - return new SparkServer(ENDPOINT, endpoints, port, loggerFactory.apply(SparkServer.class)); + var controller = new DefaultController(fullUrl, repository, idGenerator, defaultSerializer(loggerFactory.apply(GsonSerializer.class)), loggerFactory.apply(DefaultController.class)); + return new SparkServer(ENDPOINT, controller, port, loggerFactory.apply(SparkServer.class)); } private static Heroku createHeroku() { diff --git a/src/test/java/nl/jqno/paralleljava/app/endpoints/DefaultEndpointsTest.java b/src/test/java/nl/jqno/paralleljava/app/controller/DefaultControllerTest.java similarity index 83% rename from src/test/java/nl/jqno/paralleljava/app/endpoints/DefaultEndpointsTest.java rename to src/test/java/nl/jqno/paralleljava/app/controller/DefaultControllerTest.java index b7a2db9..db5a9d8 100644 --- a/src/test/java/nl/jqno/paralleljava/app/endpoints/DefaultEndpointsTest.java +++ b/src/test/java/nl/jqno/paralleljava/app/controller/DefaultControllerTest.java @@ -1,10 +1,9 @@ -package nl.jqno.paralleljava.app.endpoints; +package nl.jqno.paralleljava.app.controller; import io.vavr.collection.List; import nl.jqno.paralleljava.app.domain.Todo; import nl.jqno.paralleljava.app.logging.NopLogger; import nl.jqno.paralleljava.app.persistence.ConstantIdGenerator; -import nl.jqno.paralleljava.app.persistence.RandomIdGenerator; import nl.jqno.paralleljava.app.persistence.InMemoryRepository; import nl.jqno.paralleljava.dependencyinjection.WiredApplication; import nl.jqno.picotest.Test; @@ -14,7 +13,7 @@ import static nl.jqno.paralleljava.app.TestData.*; import static org.assertj.core.api.Assertions.assertThat; -public class DefaultEndpointsTest extends Test { +public class DefaultControllerTest extends Test { public void endoints() { var logger = new NopLogger(); @@ -24,14 +23,14 @@ public void endoints() { var serializer = WiredApplication.defaultSerializer(logger); var someRequest = new Request(""); var urlBase = "/blabla/todo"; - var endpoints = new DefaultEndpoints(urlBase, repository, idGenerator, serializer, logger); + var controller = new DefaultController(urlBase, repository, idGenerator, serializer, logger); beforeEach(() -> { repository.clearAllTodos(); }); test("get returns an empty list when no todos are present", () -> { - var sut = endpoints.get(); + var sut = controller.get(); var actual = sut.handle(someRequest); assertThat(actual).isEqualTo(serializer.serializeTodos(List.empty())); }); @@ -39,7 +38,7 @@ public void endoints() { test("get returns all todos", () -> { repository.createTodo(SomeTodo.TODO); repository.createTodo(AnotherTodo.TODO); - var sut = endpoints.get(); + var sut = controller.get(); var actual = sut.handle(someRequest); assertThat(actual).isEqualTo(ListOfTodos.SERIALIZED); @@ -48,7 +47,7 @@ public void endoints() { test("post adds a todo", () -> { var expected = new Todo(constantId, "title", urlBase + "/" + constantId, false, 0); var expectedSerialized = serializer.serializeTodo(expected); - var sut = endpoints.post(); + var sut = controller.post(); var actual = sut.handle(new Request(SomeTodo.SERIALIZED_PARTIAL_POST)); assertThat(actual).isEqualTo(expectedSerialized); @@ -57,7 +56,7 @@ public void endoints() { test("delete clears all todos", () -> { repository.createTodo(SomeTodo.TODO); - var sut = endpoints.delete(); + var sut = controller.delete(); var actual = sut.handle(someRequest); diff --git a/src/test/java/nl/jqno/paralleljava/app/endpoints/RequestTest.java b/src/test/java/nl/jqno/paralleljava/app/controller/RequestTest.java similarity index 87% rename from src/test/java/nl/jqno/paralleljava/app/endpoints/RequestTest.java rename to src/test/java/nl/jqno/paralleljava/app/controller/RequestTest.java index c2b3404..97e62f5 100644 --- a/src/test/java/nl/jqno/paralleljava/app/endpoints/RequestTest.java +++ b/src/test/java/nl/jqno/paralleljava/app/controller/RequestTest.java @@ -1,4 +1,4 @@ -package nl.jqno.paralleljava.app.endpoints; +package nl.jqno.paralleljava.app.controller; import nl.jqno.picotest.Test; diff --git a/src/test/java/nl/jqno/paralleljava/app/server/SparkServerTest.java b/src/test/java/nl/jqno/paralleljava/app/server/SparkServerTest.java index 47ad424..69f265d 100644 --- a/src/test/java/nl/jqno/paralleljava/app/server/SparkServerTest.java +++ b/src/test/java/nl/jqno/paralleljava/app/server/SparkServerTest.java @@ -1,8 +1,8 @@ package nl.jqno.paralleljava.app.server; import io.restassured.specification.RequestSpecification; -import nl.jqno.paralleljava.app.endpoints.Endpoints; -import nl.jqno.paralleljava.app.endpoints.Route; +import nl.jqno.paralleljava.app.controller.Controller; +import nl.jqno.paralleljava.app.controller.Route; import nl.jqno.paralleljava.app.logging.NopLogger; import nl.jqno.picotest.Test; import spark.Spark; @@ -15,10 +15,10 @@ public class SparkServerTest extends Test { private static final int PORT = 1337; private static final String ENDPOINT = "/todo"; private final RequestSpecification when = given().port(PORT).when(); - private StubEndpoints underlying; + private StubController underlying; public void server() { - underlying = new StubEndpoints(); + underlying = new StubController(); beforeAll(() -> { new SparkServer(ENDPOINT, underlying, PORT, NopLogger.INSTANCE).run(); @@ -92,7 +92,7 @@ private void assertSingleCall(int calledEndpoint) { assertThat(underlying.calledTotal()).isEqualTo(1); } - private static class StubEndpoints implements Endpoints { + private static class StubController implements Controller { public int calledGet = 0; public int calledPost = 0; From e16970ef4554e18db4b865a8784bb83d23454de6 Mon Sep 17 00:00:00 2001 From: Jan Ouwens Date: Fri, 22 Mar 2019 08:25:25 +0100 Subject: [PATCH 054/106] Simplifies Controller by removing Route and Request --- .../app/controller/Controller.java | 6 +-- .../app/controller/DefaultController.java | 43 ++++++++----------- .../paralleljava/app/controller/Request.java | 13 ------ .../paralleljava/app/controller/Route.java | 5 --- .../paralleljava/app/server/SparkServer.java | 15 ++----- .../app/controller/DefaultControllerTest.java | 13 ++---- .../app/controller/RequestTest.java | 16 ------- .../app/server/SparkServerTest.java | 23 ++++------ 8 files changed, 38 insertions(+), 96 deletions(-) delete mode 100644 src/main/java/nl/jqno/paralleljava/app/controller/Request.java delete mode 100644 src/main/java/nl/jqno/paralleljava/app/controller/Route.java delete mode 100644 src/test/java/nl/jqno/paralleljava/app/controller/RequestTest.java diff --git a/src/main/java/nl/jqno/paralleljava/app/controller/Controller.java b/src/main/java/nl/jqno/paralleljava/app/controller/Controller.java index c1c3ccf..564dc49 100644 --- a/src/main/java/nl/jqno/paralleljava/app/controller/Controller.java +++ b/src/main/java/nl/jqno/paralleljava/app/controller/Controller.java @@ -1,7 +1,7 @@ package nl.jqno.paralleljava.app.controller; public interface Controller { - Route get(); - Route post(); - Route delete(); + String get(); + String post(String json); + String delete(); } diff --git a/src/main/java/nl/jqno/paralleljava/app/controller/DefaultController.java b/src/main/java/nl/jqno/paralleljava/app/controller/DefaultController.java index 5c46251..4036dcd 100644 --- a/src/main/java/nl/jqno/paralleljava/app/controller/DefaultController.java +++ b/src/main/java/nl/jqno/paralleljava/app/controller/DefaultController.java @@ -23,34 +23,29 @@ public DefaultController(String url, Repository repository, IdGenerator generato this.logger = logger; } - public Route get() { - return ignored -> serializer.serializeTodos(repository.getAllTodos()); + public String get() { + return serializer.serializeTodos(repository.getAllTodos()); } - public Route post() { - return request -> { - var json = request.body(); - logger.forProduction("POSTed: " + json); - var partialTodo = serializer.deserializePartialTodo(json); - if (partialTodo.isDefined() && partialTodo.get().title().isDefined()) { - var pt = partialTodo.get(); - var id = generator.generateId(); - var todo = new Todo(id, pt.title().get(), buildUrlFor(id), false, 0); - repository.createTodo(todo); - logger.forProduction("Returning from POST: " + json); - return serializer.serializeTodo(todo); - } - else { - return ""; - } - }; + public String post(String json) { + logger.forProduction("POSTed: " + json); + var partialTodo = serializer.deserializePartialTodo(json); + if (partialTodo.isDefined() && partialTodo.get().title().isDefined()) { + var pt = partialTodo.get(); + var id = generator.generateId(); + var todo = new Todo(id, pt.title().get(), buildUrlFor(id), false, 0); + repository.createTodo(todo); + logger.forProduction("Returning from POST: " + json); + return serializer.serializeTodo(todo); + } + else { + return ""; + } } - public Route delete() { - return ignored -> { - repository.clearAllTodos(); - return ""; - }; + public String delete() { + repository.clearAllTodos(); + return ""; } private String buildUrlFor(UUID id) { diff --git a/src/main/java/nl/jqno/paralleljava/app/controller/Request.java b/src/main/java/nl/jqno/paralleljava/app/controller/Request.java deleted file mode 100644 index bd330cc..0000000 --- a/src/main/java/nl/jqno/paralleljava/app/controller/Request.java +++ /dev/null @@ -1,13 +0,0 @@ -package nl.jqno.paralleljava.app.controller; - -public class Request { - private final String body; - - public Request(String body) { - this.body = body; - } - - public String body() { - return body; - } -} diff --git a/src/main/java/nl/jqno/paralleljava/app/controller/Route.java b/src/main/java/nl/jqno/paralleljava/app/controller/Route.java deleted file mode 100644 index 5720d87..0000000 --- a/src/main/java/nl/jqno/paralleljava/app/controller/Route.java +++ /dev/null @@ -1,5 +0,0 @@ -package nl.jqno.paralleljava.app.controller; - -public interface Route { - String handle(Request request); -} diff --git a/src/main/java/nl/jqno/paralleljava/app/server/SparkServer.java b/src/main/java/nl/jqno/paralleljava/app/server/SparkServer.java index 0fd3a4c..2bef3cb 100644 --- a/src/main/java/nl/jqno/paralleljava/app/server/SparkServer.java +++ b/src/main/java/nl/jqno/paralleljava/app/server/SparkServer.java @@ -2,8 +2,6 @@ import io.vavr.control.Option; import nl.jqno.paralleljava.app.controller.Controller; -import nl.jqno.paralleljava.app.controller.Request; -import nl.jqno.paralleljava.app.controller.Route; import nl.jqno.paralleljava.app.logging.Logger; import static spark.Spark.*; @@ -28,9 +26,9 @@ public void run() { port(port); enableCors(); - get(endpoint, convert(controller.get())); - post(endpoint, convert(controller.post())); - delete(endpoint, convert(controller.delete())); + get(endpoint, (request, response) -> controller.get()); + post(endpoint, (request, response) -> controller.post(request.body())); + delete(endpoint, (request, response) -> controller.delete()); } private void enableCors() { @@ -46,11 +44,4 @@ private void enableCors() { response.header("Access-Control-Allow-Origin", "*"); }); } - - private spark.Route convert(Route route) { - return (request, response) -> { - var req = new Request(request.body()); - return route.handle(req); - }; - } } diff --git a/src/test/java/nl/jqno/paralleljava/app/controller/DefaultControllerTest.java b/src/test/java/nl/jqno/paralleljava/app/controller/DefaultControllerTest.java index db5a9d8..eb8ab33 100644 --- a/src/test/java/nl/jqno/paralleljava/app/controller/DefaultControllerTest.java +++ b/src/test/java/nl/jqno/paralleljava/app/controller/DefaultControllerTest.java @@ -21,7 +21,6 @@ public void endoints() { var constantId = UUID.randomUUID(); var idGenerator = new ConstantIdGenerator(constantId); var serializer = WiredApplication.defaultSerializer(logger); - var someRequest = new Request(""); var urlBase = "/blabla/todo"; var controller = new DefaultController(urlBase, repository, idGenerator, serializer, logger); @@ -30,35 +29,31 @@ public void endoints() { }); test("get returns an empty list when no todos are present", () -> { - var sut = controller.get(); - var actual = sut.handle(someRequest); + var actual = controller.get(); assertThat(actual).isEqualTo(serializer.serializeTodos(List.empty())); }); test("get returns all todos", () -> { repository.createTodo(SomeTodo.TODO); repository.createTodo(AnotherTodo.TODO); - var sut = controller.get(); - var actual = sut.handle(someRequest); + var actual = controller.get(); assertThat(actual).isEqualTo(ListOfTodos.SERIALIZED); }); test("post adds a todo", () -> { var expected = new Todo(constantId, "title", urlBase + "/" + constantId, false, 0); var expectedSerialized = serializer.serializeTodo(expected); - var sut = controller.post(); - var actual = sut.handle(new Request(SomeTodo.SERIALIZED_PARTIAL_POST)); + var actual = controller.post(SomeTodo.SERIALIZED_PARTIAL_POST); assertThat(actual).isEqualTo(expectedSerialized); assertThat(repository.getAllTodos()).contains(expected); }); test("delete clears all todos", () -> { repository.createTodo(SomeTodo.TODO); - var sut = controller.delete(); - var actual = sut.handle(someRequest); + var actual = controller.delete(); assertThat(actual).isEqualTo(""); assertThat(repository.getAllTodos()).isEmpty(); diff --git a/src/test/java/nl/jqno/paralleljava/app/controller/RequestTest.java b/src/test/java/nl/jqno/paralleljava/app/controller/RequestTest.java deleted file mode 100644 index 97e62f5..0000000 --- a/src/test/java/nl/jqno/paralleljava/app/controller/RequestTest.java +++ /dev/null @@ -1,16 +0,0 @@ -package nl.jqno.paralleljava.app.controller; - -import nl.jqno.picotest.Test; - -import static org.assertj.core.api.Assertions.assertThat; - -public class RequestTest extends Test { - - public void request() { - var sut = new Request("body"); - - test("getters", () -> { - assertThat(sut.body()).isEqualTo("body"); - }); - } -} diff --git a/src/test/java/nl/jqno/paralleljava/app/server/SparkServerTest.java b/src/test/java/nl/jqno/paralleljava/app/server/SparkServerTest.java index 69f265d..ea65e31 100644 --- a/src/test/java/nl/jqno/paralleljava/app/server/SparkServerTest.java +++ b/src/test/java/nl/jqno/paralleljava/app/server/SparkServerTest.java @@ -2,7 +2,6 @@ import io.restassured.specification.RequestSpecification; import nl.jqno.paralleljava.app.controller.Controller; -import nl.jqno.paralleljava.app.controller.Route; import nl.jqno.paralleljava.app.logging.NopLogger; import nl.jqno.picotest.Test; import spark.Spark; @@ -108,23 +107,19 @@ public int calledTotal() { return calledGet + calledPost + calledDelete; } - public Route get() { - return stubbed(() -> calledGet += 1); + public String get() { + calledGet += 1; + return ""; } - public Route post() { - return stubbed(() -> calledPost += 1); + public String post(String json) { + calledPost += 1; + return ""; } - public Route delete() { - return stubbed(() -> calledDelete += 1); - } - - private Route stubbed(Runnable block) { - return ignored -> { - block.run(); - return ""; - }; + public String delete() { + calledDelete += 1; + return ""; } } } From 4a59e3794f1c5cbfd74309808d88e5e725ee894b Mon Sep 17 00:00:00 2001 From: Jan Ouwens Date: Fri, 22 Mar 2019 09:28:24 +0100 Subject: [PATCH 055/106] Adds GET endpoint with id --- .../app/controller/Controller.java | 1 + .../app/controller/DefaultController.java | 7 ++++++ .../app/persistence/InMemoryRepository.java | 7 ++++++ .../app/persistence/Repository.java | 4 +++ .../app/serialization/GsonSerializer.java | 9 +++++++ .../app/serialization/Serializer.java | 5 ++++ .../paralleljava/app/server/SparkServer.java | 1 + .../app/controller/DefaultControllerTest.java | 12 +++++++++ .../persistence/InMemoryRepositoryTest.java | 10 ++++++++ .../app/serialization/GsonSerializerTest.java | 25 +++++++++++++++++++ .../app/server/SparkServerTest.java | 19 +++++++++++++- 11 files changed, 99 insertions(+), 1 deletion(-) diff --git a/src/main/java/nl/jqno/paralleljava/app/controller/Controller.java b/src/main/java/nl/jqno/paralleljava/app/controller/Controller.java index 564dc49..c8f04f6 100644 --- a/src/main/java/nl/jqno/paralleljava/app/controller/Controller.java +++ b/src/main/java/nl/jqno/paralleljava/app/controller/Controller.java @@ -2,6 +2,7 @@ public interface Controller { String get(); + String get(String json); String post(String json); String delete(); } diff --git a/src/main/java/nl/jqno/paralleljava/app/controller/DefaultController.java b/src/main/java/nl/jqno/paralleljava/app/controller/DefaultController.java index 4036dcd..e5c146f 100644 --- a/src/main/java/nl/jqno/paralleljava/app/controller/DefaultController.java +++ b/src/main/java/nl/jqno/paralleljava/app/controller/DefaultController.java @@ -27,6 +27,13 @@ public String get() { return serializer.serializeTodos(repository.getAllTodos()); } + public String get(String json) { + return serializer.deserializeUuid(json) + .flatMap(repository::get) + .map(serializer::serializeTodo) + .getOrElse(""); + } + public String post(String json) { logger.forProduction("POSTed: " + json); var partialTodo = serializer.deserializePartialTodo(json); diff --git a/src/main/java/nl/jqno/paralleljava/app/persistence/InMemoryRepository.java b/src/main/java/nl/jqno/paralleljava/app/persistence/InMemoryRepository.java index c4951f1..1b46d12 100644 --- a/src/main/java/nl/jqno/paralleljava/app/persistence/InMemoryRepository.java +++ b/src/main/java/nl/jqno/paralleljava/app/persistence/InMemoryRepository.java @@ -1,10 +1,12 @@ package nl.jqno.paralleljava.app.persistence; import io.vavr.collection.List; +import io.vavr.control.Option; import nl.jqno.paralleljava.app.domain.Todo; import nl.jqno.paralleljava.app.logging.Logger; import java.util.ArrayList; +import java.util.UUID; public class InMemoryRepository implements Repository { private static final java.util.List todos = new ArrayList<>(); @@ -20,6 +22,11 @@ public void createTodo(Todo todo) { todos.add(todo); } + public Option get(UUID id) { + return List.ofAll(todos) + .find(t -> t.id().equals(id)); + } + public List getAllTodos() { return List.ofAll(todos); } diff --git a/src/main/java/nl/jqno/paralleljava/app/persistence/Repository.java b/src/main/java/nl/jqno/paralleljava/app/persistence/Repository.java index 5ec3b26..b2d70d8 100644 --- a/src/main/java/nl/jqno/paralleljava/app/persistence/Repository.java +++ b/src/main/java/nl/jqno/paralleljava/app/persistence/Repository.java @@ -1,10 +1,14 @@ package nl.jqno.paralleljava.app.persistence; import io.vavr.collection.List; +import io.vavr.control.Option; import nl.jqno.paralleljava.app.domain.Todo; +import java.util.UUID; + public interface Repository { void createTodo(Todo todo); + Option get(UUID id); List getAllTodos(); void clearAllTodos(); } diff --git a/src/main/java/nl/jqno/paralleljava/app/serialization/GsonSerializer.java b/src/main/java/nl/jqno/paralleljava/app/serialization/GsonSerializer.java index 873756c..3f6c5cf 100644 --- a/src/main/java/nl/jqno/paralleljava/app/serialization/GsonSerializer.java +++ b/src/main/java/nl/jqno/paralleljava/app/serialization/GsonSerializer.java @@ -11,6 +11,7 @@ import nl.jqno.paralleljava.app.logging.Logger; import java.lang.reflect.Type; +import java.util.UUID; public class GsonSerializer implements Serializer { private static final Type LIST_OF_TODO_TYPE = new TypeToken>(){}.getType(); @@ -47,6 +48,14 @@ public List deserializeTodos(String json) { return attemptDeserialization(json, () -> (List)gson.fromJson(json, LIST_OF_TODO_TYPE)).getOrElse(List.empty()); } + public String serializeUuid(UUID id) { + return gson.toJson(id); + } + + public Option deserializeUuid(String json) { + return attemptDeserialization(json, () -> gson.fromJson(json, UUID.class)); + } + private Option attemptDeserialization(String json, CheckedFunction0 f) { return Try.of(f) .onFailure(t -> logger.firstThingNextMorning("Failed to deserialize " + json, t)) diff --git a/src/main/java/nl/jqno/paralleljava/app/serialization/Serializer.java b/src/main/java/nl/jqno/paralleljava/app/serialization/Serializer.java index 6872b0f..f5b9e05 100644 --- a/src/main/java/nl/jqno/paralleljava/app/serialization/Serializer.java +++ b/src/main/java/nl/jqno/paralleljava/app/serialization/Serializer.java @@ -5,6 +5,8 @@ import nl.jqno.paralleljava.app.domain.PartialTodo; import nl.jqno.paralleljava.app.domain.Todo; +import java.util.UUID; + public interface Serializer { String serializeTodo(Todo todo); Option deserializeTodo(String json); @@ -14,4 +16,7 @@ public interface Serializer { String serializeTodos(List todos); List deserializeTodos(String json); + + String serializeUuid(UUID uuid); + Option deserializeUuid(String json); } diff --git a/src/main/java/nl/jqno/paralleljava/app/server/SparkServer.java b/src/main/java/nl/jqno/paralleljava/app/server/SparkServer.java index 2bef3cb..1b57bce 100644 --- a/src/main/java/nl/jqno/paralleljava/app/server/SparkServer.java +++ b/src/main/java/nl/jqno/paralleljava/app/server/SparkServer.java @@ -27,6 +27,7 @@ public void run() { enableCors(); get(endpoint, (request, response) -> controller.get()); + get(endpoint + "/:id", (request, response) -> controller.get(request.params("id"))); post(endpoint, (request, response) -> controller.post(request.body())); delete(endpoint, (request, response) -> controller.delete()); } diff --git a/src/test/java/nl/jqno/paralleljava/app/controller/DefaultControllerTest.java b/src/test/java/nl/jqno/paralleljava/app/controller/DefaultControllerTest.java index eb8ab33..7f91b9d 100644 --- a/src/test/java/nl/jqno/paralleljava/app/controller/DefaultControllerTest.java +++ b/src/test/java/nl/jqno/paralleljava/app/controller/DefaultControllerTest.java @@ -41,6 +41,18 @@ public void endoints() { assertThat(actual).isEqualTo(ListOfTodos.SERIALIZED); }); + test("get with id returns a specific serialized todo if it exists", () -> { + repository.createTodo(SomeTodo.TODO); + + var actual = controller.get(SomeTodo.ID.toString()); + assertThat(actual).isEqualTo(SomeTodo.SERIALIZED); + }); + + test("get with id returns nothing if it doesn't exist", () -> { + var actual = controller.get(SomeTodo.ID.toString()); + assertThat(actual).isEqualTo(""); + }); + test("post adds a todo", () -> { var expected = new Todo(constantId, "title", urlBase + "/" + constantId, false, 0); var expectedSerialized = serializer.serializeTodo(expected); diff --git a/src/test/java/nl/jqno/paralleljava/app/persistence/InMemoryRepositoryTest.java b/src/test/java/nl/jqno/paralleljava/app/persistence/InMemoryRepositoryTest.java index ecb6291..cbe298b 100644 --- a/src/test/java/nl/jqno/paralleljava/app/persistence/InMemoryRepositoryTest.java +++ b/src/test/java/nl/jqno/paralleljava/app/persistence/InMemoryRepositoryTest.java @@ -1,9 +1,12 @@ package nl.jqno.paralleljava.app.persistence; +import io.vavr.control.Option; import nl.jqno.paralleljava.app.TestData.SomeTodo; import nl.jqno.paralleljava.app.logging.NopLogger; import nl.jqno.picotest.Test; +import java.util.UUID; + import static org.assertj.core.api.Assertions.assertThat; public class InMemoryRepositoryTest extends Test { @@ -20,6 +23,13 @@ public void repository() { assertThat(repo.getAllTodos()).contains(SomeTodo.TODO); }); + test("get a specific todo", () -> { + repo.createTodo(SomeTodo.TODO); + + assertThat(repo.get(SomeTodo.ID)).isEqualTo(Option.some(SomeTodo.TODO)); + assertThat(repo.get(UUID.randomUUID())).isEqualTo(Option.none()); + }); + test("clearing all todos", () -> { repo.createTodo(SomeTodo.TODO); repo.clearAllTodos(); diff --git a/src/test/java/nl/jqno/paralleljava/app/serialization/GsonSerializerTest.java b/src/test/java/nl/jqno/paralleljava/app/serialization/GsonSerializerTest.java index 4847ecd..9c5f5c1 100644 --- a/src/test/java/nl/jqno/paralleljava/app/serialization/GsonSerializerTest.java +++ b/src/test/java/nl/jqno/paralleljava/app/serialization/GsonSerializerTest.java @@ -121,4 +121,29 @@ public void serializationOfAListOfTodos() { assertThat(actual).isEqualTo(ListOfTodos.LIST); }); } + + public void serializationOfAUuid() { + + test("Serializes a UUID to json", () -> { + var actual = serializer.serializeUuid(SomeTodo.ID); + assertThat(actual).isEqualTo("\"" + SomeTodo.ID.toString() + "\""); + }); + + test("Deserializes a UUID from json", () -> { + var actual = serializer.deserializeUuid(SomeTodo.ID.toString()); + assertThat(actual).isEqualTo(Option.of(SomeTodo.ID)); + }); + + test("Deserialization of a UUID returns none when json is invalid", () -> { + var invalidJson = "this is an invalid json document"; + var actual = serializer.deserializeUuid(invalidJson); + assertThat(actual).isEqualTo(Option.none()); + }); + + test("Does a complete round-trip on UUID", () -> { + var json = serializer.serializeUuid(SomeTodo.ID); + var actual = serializer.deserializeUuid(json); + assertThat(actual).isEqualTo(Option.of(SomeTodo.ID)); + }); + } } diff --git a/src/test/java/nl/jqno/paralleljava/app/server/SparkServerTest.java b/src/test/java/nl/jqno/paralleljava/app/server/SparkServerTest.java index ea65e31..eda767c 100644 --- a/src/test/java/nl/jqno/paralleljava/app/server/SparkServerTest.java +++ b/src/test/java/nl/jqno/paralleljava/app/server/SparkServerTest.java @@ -1,6 +1,7 @@ package nl.jqno.paralleljava.app.server; import io.restassured.specification.RequestSpecification; +import io.vavr.collection.HashMap; import nl.jqno.paralleljava.app.controller.Controller; import nl.jqno.paralleljava.app.logging.NopLogger; import nl.jqno.picotest.Test; @@ -34,6 +35,7 @@ public void server() { test("OPTION request", this::corsOptionsRequest); test("GET works", this::getRequest); + test("GET with id works", this::getWithIdRequest); test("POST works", this::postRequest); test("DELETE works", this::deleteRequest); } @@ -70,6 +72,14 @@ private void getRequest() { assertSingleCall(underlying.calledGet); } + private void getWithIdRequest() { + when + .get(ENDPOINT + "/some-id") + .then() + .statusCode(200); + assertSingleCall(underlying.calledGetWithId); + } + private void postRequest() { when .post(ENDPOINT) @@ -94,17 +104,19 @@ private void assertSingleCall(int calledEndpoint) { private static class StubController implements Controller { public int calledGet = 0; + public int calledGetWithId = 0; public int calledPost = 0; public int calledDelete = 0; public void clear() { calledGet = 0; + calledGetWithId = 0; calledPost = 0; calledDelete = 0; } public int calledTotal() { - return calledGet + calledPost + calledDelete; + return calledGet + calledGetWithId + calledPost + calledDelete; } public String get() { @@ -112,6 +124,11 @@ public String get() { return ""; } + public String get(String json) { + calledGetWithId += 1; + return ""; + } + public String post(String json) { calledPost += 1; return ""; From f7b4af5b1b66096ce9f94549b67e18a0fa396ab7 Mon Sep 17 00:00:00 2001 From: Jan Ouwens Date: Fri, 22 Mar 2019 10:50:30 +0100 Subject: [PATCH 056/106] Cleans up wiring of loggers --- .../app/controller/DefaultController.java | 5 ++-- .../app/logging/LoggerFactory.java | 9 ++++++ .../app/persistence/InMemoryRepository.java | 5 ++-- .../app/serialization/GsonSerializer.java | 5 ++-- .../paralleljava/app/server/SparkServer.java | 5 ++-- .../dependencyinjection/WiredApplication.java | 30 +++++++++---------- .../app/controller/DefaultControllerTest.java | 8 ++--- .../paralleljava/app/logging/NopLogger.java | 2 +- .../persistence/InMemoryRepositoryTest.java | 2 +- .../app/serialization/GsonSerializerTest.java | 2 +- .../app/server/SparkServerTest.java | 3 +- 11 files changed, 43 insertions(+), 33 deletions(-) create mode 100644 src/main/java/nl/jqno/paralleljava/app/logging/LoggerFactory.java diff --git a/src/main/java/nl/jqno/paralleljava/app/controller/DefaultController.java b/src/main/java/nl/jqno/paralleljava/app/controller/DefaultController.java index e5c146f..8d436cb 100644 --- a/src/main/java/nl/jqno/paralleljava/app/controller/DefaultController.java +++ b/src/main/java/nl/jqno/paralleljava/app/controller/DefaultController.java @@ -2,6 +2,7 @@ import nl.jqno.paralleljava.app.domain.Todo; import nl.jqno.paralleljava.app.logging.Logger; +import nl.jqno.paralleljava.app.logging.LoggerFactory; import nl.jqno.paralleljava.app.persistence.IdGenerator; import nl.jqno.paralleljava.app.persistence.Repository; import nl.jqno.paralleljava.app.serialization.Serializer; @@ -15,12 +16,12 @@ public class DefaultController implements Controller { private final Serializer serializer; private final Logger logger; - public DefaultController(String url, Repository repository, IdGenerator generator, Serializer serializer, Logger logger) { + public DefaultController(String url, Repository repository, IdGenerator generator, Serializer serializer, LoggerFactory loggerFactory) { this.url = url; this.repository = repository; this.generator = generator; this.serializer = serializer; - this.logger = logger; + this.logger = loggerFactory.create(getClass()); } public String get() { diff --git a/src/main/java/nl/jqno/paralleljava/app/logging/LoggerFactory.java b/src/main/java/nl/jqno/paralleljava/app/logging/LoggerFactory.java new file mode 100644 index 0000000..4a6a624 --- /dev/null +++ b/src/main/java/nl/jqno/paralleljava/app/logging/LoggerFactory.java @@ -0,0 +1,9 @@ +package nl.jqno.paralleljava.app.logging; + +import io.vavr.Function1; + +public interface LoggerFactory extends Function1, Logger> { + default Logger create(Class c) { + return apply(c); + } +} diff --git a/src/main/java/nl/jqno/paralleljava/app/persistence/InMemoryRepository.java b/src/main/java/nl/jqno/paralleljava/app/persistence/InMemoryRepository.java index 1b46d12..c76b088 100644 --- a/src/main/java/nl/jqno/paralleljava/app/persistence/InMemoryRepository.java +++ b/src/main/java/nl/jqno/paralleljava/app/persistence/InMemoryRepository.java @@ -4,6 +4,7 @@ import io.vavr.control.Option; import nl.jqno.paralleljava.app.domain.Todo; import nl.jqno.paralleljava.app.logging.Logger; +import nl.jqno.paralleljava.app.logging.LoggerFactory; import java.util.ArrayList; import java.util.UUID; @@ -13,8 +14,8 @@ public class InMemoryRepository implements Repository { private final Logger logger; - public InMemoryRepository(Logger logger) { - this.logger = logger; + public InMemoryRepository(LoggerFactory loggerFactory) { + this.logger = loggerFactory.create(getClass()); } public void createTodo(Todo todo) { diff --git a/src/main/java/nl/jqno/paralleljava/app/serialization/GsonSerializer.java b/src/main/java/nl/jqno/paralleljava/app/serialization/GsonSerializer.java index 3f6c5cf..8a0ddf5 100644 --- a/src/main/java/nl/jqno/paralleljava/app/serialization/GsonSerializer.java +++ b/src/main/java/nl/jqno/paralleljava/app/serialization/GsonSerializer.java @@ -9,6 +9,7 @@ import nl.jqno.paralleljava.app.domain.PartialTodo; import nl.jqno.paralleljava.app.domain.Todo; import nl.jqno.paralleljava.app.logging.Logger; +import nl.jqno.paralleljava.app.logging.LoggerFactory; import java.lang.reflect.Type; import java.util.UUID; @@ -19,9 +20,9 @@ public class GsonSerializer implements Serializer { private final Gson gson; private final Logger logger; - public GsonSerializer(Gson gson, Logger logger) { + public GsonSerializer(Gson gson, LoggerFactory loggerFactory) { this.gson = gson; - this.logger = logger; + this.logger = loggerFactory.create(getClass()); } public String serializeTodo(Todo todo) { diff --git a/src/main/java/nl/jqno/paralleljava/app/server/SparkServer.java b/src/main/java/nl/jqno/paralleljava/app/server/SparkServer.java index 1b57bce..df1d1da 100644 --- a/src/main/java/nl/jqno/paralleljava/app/server/SparkServer.java +++ b/src/main/java/nl/jqno/paralleljava/app/server/SparkServer.java @@ -3,6 +3,7 @@ import io.vavr.control.Option; import nl.jqno.paralleljava.app.controller.Controller; import nl.jqno.paralleljava.app.logging.Logger; +import nl.jqno.paralleljava.app.logging.LoggerFactory; import static spark.Spark.*; @@ -13,11 +14,11 @@ public class SparkServer implements Server { private final int port; private final Logger logger; - public SparkServer(String endpoint, Controller controller, int port, Logger logger) { + public SparkServer(String endpoint, Controller controller, int port, LoggerFactory loggerFactory) { this.endpoint = endpoint; this.controller = controller; this.port = port; - this.logger = logger; + this.logger = loggerFactory.create(getClass()); } public void run() { diff --git a/src/main/java/nl/jqno/paralleljava/dependencyinjection/WiredApplication.java b/src/main/java/nl/jqno/paralleljava/dependencyinjection/WiredApplication.java index 0f220d6..88067ca 100644 --- a/src/main/java/nl/jqno/paralleljava/dependencyinjection/WiredApplication.java +++ b/src/main/java/nl/jqno/paralleljava/dependencyinjection/WiredApplication.java @@ -1,21 +1,19 @@ package nl.jqno.paralleljava.dependencyinjection; import com.google.gson.GsonBuilder; -import io.vavr.Function1; import io.vavr.collection.HashMap; import io.vavr.gson.VavrGson; import nl.jqno.paralleljava.app.controller.DefaultController; -import nl.jqno.paralleljava.app.logging.Logger; +import nl.jqno.paralleljava.app.logging.LoggerFactory; import nl.jqno.paralleljava.app.logging.Slf4jLogger; import nl.jqno.paralleljava.app.persistence.InMemoryRepository; -import nl.jqno.paralleljava.app.persistence.Repository; import nl.jqno.paralleljava.app.persistence.RandomIdGenerator; +import nl.jqno.paralleljava.app.persistence.Repository; import nl.jqno.paralleljava.app.serialization.GsonSerializer; import nl.jqno.paralleljava.app.serialization.Serializer; import nl.jqno.paralleljava.app.server.Heroku; import nl.jqno.paralleljava.app.server.Server; import nl.jqno.paralleljava.app.server.SparkServer; -import org.slf4j.LoggerFactory; public class WiredApplication { @@ -23,13 +21,13 @@ public class WiredApplication { private static final String DEFAULT_URL = "http://localhost"; private static final String ENDPOINT = "/todo"; - private final Function1, Logger> loggerFactory; + private final LoggerFactory loggerFactory; private final Repository repository; private final Server server; public WiredApplication() { - loggerFactory = c -> new Slf4jLogger(LoggerFactory.getLogger(c)); - repository = new InMemoryRepository(loggerFactory.apply(InMemoryRepository.class)); + loggerFactory = c -> new Slf4jLogger(org.slf4j.LoggerFactory.getLogger(c)); + repository = new InMemoryRepository(loggerFactory); server = createServer(repository, loggerFactory); } @@ -37,19 +35,13 @@ public void run() { server.run(); } - public static Serializer defaultSerializer(Logger logger) { - var gsonBuilder = new GsonBuilder(); - VavrGson.registerAll(gsonBuilder); - return new GsonSerializer(gsonBuilder.create(), logger); - } - - private static Server createServer(Repository repository, Function1, Logger> loggerFactory) { + private static Server createServer(Repository repository, LoggerFactory loggerFactory) { var heroku = createHeroku(); int port = heroku.getAssignedPort().getOrElse(DEFAULT_PORT); var fullUrl = heroku.getHostUrl().getOrElse(DEFAULT_URL) + ENDPOINT; var idGenerator = new RandomIdGenerator(); - var controller = new DefaultController(fullUrl, repository, idGenerator, defaultSerializer(loggerFactory.apply(GsonSerializer.class)), loggerFactory.apply(DefaultController.class)); - return new SparkServer(ENDPOINT, controller, port, loggerFactory.apply(SparkServer.class)); + var controller = new DefaultController(fullUrl, repository, idGenerator, defaultSerializer(loggerFactory), loggerFactory); + return new SparkServer(ENDPOINT, controller, port, loggerFactory); } private static Heroku createHeroku() { @@ -57,4 +49,10 @@ private static Heroku createHeroku() { var environment = HashMap.ofAll(processBuilder.environment()); return new Heroku(environment); } + + public static Serializer defaultSerializer(LoggerFactory loggerFactory) { + var gsonBuilder = new GsonBuilder(); + VavrGson.registerAll(gsonBuilder); + return new GsonSerializer(gsonBuilder.create(), loggerFactory); + } } diff --git a/src/test/java/nl/jqno/paralleljava/app/controller/DefaultControllerTest.java b/src/test/java/nl/jqno/paralleljava/app/controller/DefaultControllerTest.java index 7f91b9d..5643602 100644 --- a/src/test/java/nl/jqno/paralleljava/app/controller/DefaultControllerTest.java +++ b/src/test/java/nl/jqno/paralleljava/app/controller/DefaultControllerTest.java @@ -16,13 +16,13 @@ public class DefaultControllerTest extends Test { public void endoints() { - var logger = new NopLogger(); - var repository = new InMemoryRepository(logger); + var loggerFactory = NopLogger.FACTORY; + var repository = new InMemoryRepository(loggerFactory); var constantId = UUID.randomUUID(); var idGenerator = new ConstantIdGenerator(constantId); - var serializer = WiredApplication.defaultSerializer(logger); + var serializer = WiredApplication.defaultSerializer(loggerFactory); var urlBase = "/blabla/todo"; - var controller = new DefaultController(urlBase, repository, idGenerator, serializer, logger); + var controller = new DefaultController(urlBase, repository, idGenerator, serializer, loggerFactory); beforeEach(() -> { repository.clearAllTodos(); diff --git a/src/test/java/nl/jqno/paralleljava/app/logging/NopLogger.java b/src/test/java/nl/jqno/paralleljava/app/logging/NopLogger.java index dfd7230..0b461f7 100644 --- a/src/test/java/nl/jqno/paralleljava/app/logging/NopLogger.java +++ b/src/test/java/nl/jqno/paralleljava/app/logging/NopLogger.java @@ -2,7 +2,7 @@ public class NopLogger implements Logger { - public static final Logger INSTANCE = new NopLogger(); + public static final LoggerFactory FACTORY = c -> new NopLogger(); public void forDevelopment(String message) {} public void forProduction(String message) {} diff --git a/src/test/java/nl/jqno/paralleljava/app/persistence/InMemoryRepositoryTest.java b/src/test/java/nl/jqno/paralleljava/app/persistence/InMemoryRepositoryTest.java index cbe298b..80b7084 100644 --- a/src/test/java/nl/jqno/paralleljava/app/persistence/InMemoryRepositoryTest.java +++ b/src/test/java/nl/jqno/paralleljava/app/persistence/InMemoryRepositoryTest.java @@ -12,7 +12,7 @@ public class InMemoryRepositoryTest extends Test { public void repository() { - var repo = new InMemoryRepository(new NopLogger()); + var repo = new InMemoryRepository(NopLogger.FACTORY); beforeEach(() -> { repo.clearAllTodos(); diff --git a/src/test/java/nl/jqno/paralleljava/app/serialization/GsonSerializerTest.java b/src/test/java/nl/jqno/paralleljava/app/serialization/GsonSerializerTest.java index 9c5f5c1..a163d18 100644 --- a/src/test/java/nl/jqno/paralleljava/app/serialization/GsonSerializerTest.java +++ b/src/test/java/nl/jqno/paralleljava/app/serialization/GsonSerializerTest.java @@ -11,7 +11,7 @@ public class GsonSerializerTest extends Test { - private Serializer serializer = WiredApplication.defaultSerializer(new NopLogger()); + private Serializer serializer = WiredApplication.defaultSerializer(NopLogger.FACTORY); public void serializationOfASingleTodo() { diff --git a/src/test/java/nl/jqno/paralleljava/app/server/SparkServerTest.java b/src/test/java/nl/jqno/paralleljava/app/server/SparkServerTest.java index eda767c..6b90f77 100644 --- a/src/test/java/nl/jqno/paralleljava/app/server/SparkServerTest.java +++ b/src/test/java/nl/jqno/paralleljava/app/server/SparkServerTest.java @@ -1,7 +1,6 @@ package nl.jqno.paralleljava.app.server; import io.restassured.specification.RequestSpecification; -import io.vavr.collection.HashMap; import nl.jqno.paralleljava.app.controller.Controller; import nl.jqno.paralleljava.app.logging.NopLogger; import nl.jqno.picotest.Test; @@ -21,7 +20,7 @@ public void server() { underlying = new StubController(); beforeAll(() -> { - new SparkServer(ENDPOINT, underlying, PORT, NopLogger.INSTANCE).run(); + new SparkServer(ENDPOINT, underlying, PORT, NopLogger.FACTORY).run(); Spark.awaitInitialization(); }); From 98fb1d5ae63ec8fdffb02cf14e8691ce2e885496 Mon Sep 17 00:00:00 2001 From: Jan Ouwens Date: Fri, 22 Mar 2019 11:09:13 +0100 Subject: [PATCH 057/106] Refactors WiredApplication --- .../dependencyinjection/WiredApplication.java | 22 ++++++++++--------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/src/main/java/nl/jqno/paralleljava/dependencyinjection/WiredApplication.java b/src/main/java/nl/jqno/paralleljava/dependencyinjection/WiredApplication.java index 88067ca..3de6a0a 100644 --- a/src/main/java/nl/jqno/paralleljava/dependencyinjection/WiredApplication.java +++ b/src/main/java/nl/jqno/paralleljava/dependencyinjection/WiredApplication.java @@ -3,6 +3,7 @@ import com.google.gson.GsonBuilder; import io.vavr.collection.HashMap; import io.vavr.gson.VavrGson; +import nl.jqno.paralleljava.app.controller.Controller; import nl.jqno.paralleljava.app.controller.DefaultController; import nl.jqno.paralleljava.app.logging.LoggerFactory; import nl.jqno.paralleljava.app.logging.Slf4jLogger; @@ -21,35 +22,36 @@ public class WiredApplication { private static final String DEFAULT_URL = "http://localhost"; private static final String ENDPOINT = "/todo"; + private final Heroku heroku; private final LoggerFactory loggerFactory; private final Repository repository; + private final Controller controller; private final Server server; public WiredApplication() { + heroku = createHeroku(); loggerFactory = c -> new Slf4jLogger(org.slf4j.LoggerFactory.getLogger(c)); repository = new InMemoryRepository(loggerFactory); - server = createServer(repository, loggerFactory); + controller = createController(repository, heroku, loggerFactory); + server = new SparkServer(ENDPOINT, controller, heroku.getAssignedPort().getOrElse(DEFAULT_PORT), loggerFactory); } public void run() { server.run(); } - private static Server createServer(Repository repository, LoggerFactory loggerFactory) { - var heroku = createHeroku(); - int port = heroku.getAssignedPort().getOrElse(DEFAULT_PORT); - var fullUrl = heroku.getHostUrl().getOrElse(DEFAULT_URL) + ENDPOINT; - var idGenerator = new RandomIdGenerator(); - var controller = new DefaultController(fullUrl, repository, idGenerator, defaultSerializer(loggerFactory), loggerFactory); - return new SparkServer(ENDPOINT, controller, port, loggerFactory); - } - private static Heroku createHeroku() { var processBuilder = new ProcessBuilder(); var environment = HashMap.ofAll(processBuilder.environment()); return new Heroku(environment); } + private static Controller createController(Repository repository, Heroku heroku, LoggerFactory loggerFactory) { + var fullUrl = heroku.getHostUrl().getOrElse(DEFAULT_URL) + ENDPOINT; + var idGenerator = new RandomIdGenerator(); + return new DefaultController(fullUrl, repository, idGenerator, defaultSerializer(loggerFactory), loggerFactory); + } + public static Serializer defaultSerializer(LoggerFactory loggerFactory) { var gsonBuilder = new GsonBuilder(); VavrGson.registerAll(gsonBuilder); From 911686f1f83e79f6613d232c4c1266cbb9775887 Mon Sep 17 00:00:00 2001 From: Jan Ouwens Date: Fri, 22 Mar 2019 11:13:09 +0100 Subject: [PATCH 058/106] Updates coverage threshold --- pom.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.yml b/pom.yml index f5641c7..e4bf497 100644 --- a/pom.yml +++ b/pom.yml @@ -11,7 +11,7 @@ properties: { encoding: utf-8, maven.compiler.source: 11, maven.compiler.target: 11, - coverage.threshold: 0.78 + coverage.threshold: 0.88 } repositories: From e3f0cabf4aa9d90b2f7bad3299621b3a94d6776a Mon Sep 17 00:00:00 2001 From: Jan Ouwens Date: Fri, 22 Mar 2019 12:50:28 +0100 Subject: [PATCH 059/106] Implements PATCH endpoint --- .../app/controller/Controller.java | 3 ++- .../app/controller/DefaultController.java | 25 +++++++++++++++++-- .../nl/jqno/paralleljava/app/domain/Todo.java | 8 ++++++ .../app/persistence/InMemoryRepository.java | 11 ++++++++ .../app/persistence/Repository.java | 1 + .../paralleljava/app/server/SparkServer.java | 1 + .../app/controller/DefaultControllerTest.java | 23 +++++++++++++++++ .../paralleljava/app/domain/TodoTest.java | 18 +++++++++++++ .../persistence/InMemoryRepositoryTest.java | 21 ++++++++++++++++ .../app/server/SparkServerTest.java | 20 +++++++++++++-- 10 files changed, 126 insertions(+), 5 deletions(-) diff --git a/src/main/java/nl/jqno/paralleljava/app/controller/Controller.java b/src/main/java/nl/jqno/paralleljava/app/controller/Controller.java index c8f04f6..75565a3 100644 --- a/src/main/java/nl/jqno/paralleljava/app/controller/Controller.java +++ b/src/main/java/nl/jqno/paralleljava/app/controller/Controller.java @@ -2,7 +2,8 @@ public interface Controller { String get(); - String get(String json); + String get(String id); String post(String json); + String patch(String id, String json); String delete(); } diff --git a/src/main/java/nl/jqno/paralleljava/app/controller/DefaultController.java b/src/main/java/nl/jqno/paralleljava/app/controller/DefaultController.java index 8d436cb..08ece99 100644 --- a/src/main/java/nl/jqno/paralleljava/app/controller/DefaultController.java +++ b/src/main/java/nl/jqno/paralleljava/app/controller/DefaultController.java @@ -28,8 +28,8 @@ public String get() { return serializer.serializeTodos(repository.getAllTodos()); } - public String get(String json) { - return serializer.deserializeUuid(json) + public String get(String id) { + return serializer.deserializeUuid(id) .flatMap(repository::get) .map(serializer::serializeTodo) .getOrElse(""); @@ -51,6 +51,27 @@ public String post(String json) { } } + public String patch(String id, String json) { + logger.forProduction("PATCHed: " + json); + var uuid = serializer.deserializeUuid(id); + var partialTodo = serializer.deserializePartialTodo(json); + if (uuid.isDefined() && partialTodo.isDefined()) { + var pt = partialTodo.get(); + var todo = repository.get(uuid.get()); + if (todo.isDefined()) { + var todo0 = todo.get(); + var todo1 = pt.title().map(todo0::withTitle).getOrElse(todo0); + var todo2 = pt.completed().map(todo1::withCompleted).getOrElse(todo1); + repository.updateTodo(todo2); + return serializer.serializeTodo(todo2); + } + return ""; + } + else { + return ""; + } + } + public String delete() { repository.clearAllTodos(); return ""; diff --git a/src/main/java/nl/jqno/paralleljava/app/domain/Todo.java b/src/main/java/nl/jqno/paralleljava/app/domain/Todo.java index 34aad94..c9f47d2 100644 --- a/src/main/java/nl/jqno/paralleljava/app/domain/Todo.java +++ b/src/main/java/nl/jqno/paralleljava/app/domain/Todo.java @@ -38,6 +38,14 @@ public int order() { return order; } + public Todo withTitle(String title) { + return new Todo(id(), title, url(), completed(), order()); + } + + public Todo withCompleted(boolean completed) { + return new Todo(id(), title(), url(), completed, order()); + } + public boolean equals(Object obj) { if (!(obj instanceof Todo)) { return false; diff --git a/src/main/java/nl/jqno/paralleljava/app/persistence/InMemoryRepository.java b/src/main/java/nl/jqno/paralleljava/app/persistence/InMemoryRepository.java index c76b088..75a2a74 100644 --- a/src/main/java/nl/jqno/paralleljava/app/persistence/InMemoryRepository.java +++ b/src/main/java/nl/jqno/paralleljava/app/persistence/InMemoryRepository.java @@ -9,6 +9,9 @@ import java.util.ArrayList; import java.util.UUID; +/** + * NOTE: this class is totally not thread-safe! + */ public class InMemoryRepository implements Repository { private static final java.util.List todos = new ArrayList<>(); @@ -32,6 +35,14 @@ public List getAllTodos() { return List.ofAll(todos); } + public void updateTodo(Todo todo) { + var index = List.ofAll(todos) + .map(Todo::id) + .indexOf(todo.id()); + todos.remove(index); + todos.add(index, todo); + } + public void clearAllTodos() { logger.forProduction("Clearing all Todos"); todos.clear(); diff --git a/src/main/java/nl/jqno/paralleljava/app/persistence/Repository.java b/src/main/java/nl/jqno/paralleljava/app/persistence/Repository.java index b2d70d8..db6763f 100644 --- a/src/main/java/nl/jqno/paralleljava/app/persistence/Repository.java +++ b/src/main/java/nl/jqno/paralleljava/app/persistence/Repository.java @@ -10,5 +10,6 @@ public interface Repository { void createTodo(Todo todo); Option get(UUID id); List getAllTodos(); + void updateTodo(Todo todo); void clearAllTodos(); } diff --git a/src/main/java/nl/jqno/paralleljava/app/server/SparkServer.java b/src/main/java/nl/jqno/paralleljava/app/server/SparkServer.java index df1d1da..f2995f5 100644 --- a/src/main/java/nl/jqno/paralleljava/app/server/SparkServer.java +++ b/src/main/java/nl/jqno/paralleljava/app/server/SparkServer.java @@ -30,6 +30,7 @@ public void run() { get(endpoint, (request, response) -> controller.get()); get(endpoint + "/:id", (request, response) -> controller.get(request.params("id"))); post(endpoint, (request, response) -> controller.post(request.body())); + patch(endpoint + "/:id", (request, response) -> controller.patch(request.params("id"), request.body())); delete(endpoint, (request, response) -> controller.delete()); } diff --git a/src/test/java/nl/jqno/paralleljava/app/controller/DefaultControllerTest.java b/src/test/java/nl/jqno/paralleljava/app/controller/DefaultControllerTest.java index 5643602..ed3df65 100644 --- a/src/test/java/nl/jqno/paralleljava/app/controller/DefaultControllerTest.java +++ b/src/test/java/nl/jqno/paralleljava/app/controller/DefaultControllerTest.java @@ -1,6 +1,7 @@ package nl.jqno.paralleljava.app.controller; import io.vavr.collection.List; +import io.vavr.control.Option; import nl.jqno.paralleljava.app.domain.Todo; import nl.jqno.paralleljava.app.logging.NopLogger; import nl.jqno.paralleljava.app.persistence.ConstantIdGenerator; @@ -62,6 +63,28 @@ public void endoints() { assertThat(repository.getAllTodos()).contains(expected); }); + test("patch changes title", () -> { + repository.createTodo(SomeTodo.TODO); + var expected = SomeTodo.TODO.withTitle("another title"); + + var result = controller.patch(SomeTodo.ID.toString(), "{\"title\":\"another title\"}"); + var actual = repository.get(SomeTodo.ID); + + assertThat(result).isEqualTo(serializer.serializeTodo(expected)); + assertThat(actual).isEqualTo(Option.some(expected)); + }); + + test("patch changes completed", () -> { + repository.createTodo(SomeTodo.TODO); + var expected = SomeTodo.TODO.withCompleted(false); + + var result = controller.patch(SomeTodo.ID.toString(), "{\"completed\":false}"); + var actual = repository.get(SomeTodo.ID); + + assertThat(result).isEqualTo(serializer.serializeTodo(expected)); + assertThat(actual).isEqualTo(Option.some(expected)); + }); + test("delete clears all todos", () -> { repository.createTodo(SomeTodo.TODO); diff --git a/src/test/java/nl/jqno/paralleljava/app/domain/TodoTest.java b/src/test/java/nl/jqno/paralleljava/app/domain/TodoTest.java index 5bceec8..f18d1f9 100644 --- a/src/test/java/nl/jqno/paralleljava/app/domain/TodoTest.java +++ b/src/test/java/nl/jqno/paralleljava/app/domain/TodoTest.java @@ -27,5 +27,23 @@ public void todo() { assertThat(SomeTodo.TODO.toString()) .isEqualTo("Todo: [id=" + SomeTodo.ID + ", title=title, url=http://www.example.com, completed=true, order=1337]"); }); + + test("withTitle", () -> { + var actual = SomeTodo.TODO.withTitle("another title"); + assertThat(actual.title()).isEqualTo("another title"); + assertThat(actual.id()).isEqualTo(SomeTodo.ID); + assertThat(actual.url()).isEqualTo(SomeTodo.TODO.url()); + assertThat(actual.completed()).isEqualTo(SomeTodo.TODO.completed()); + assertThat(actual.order()).isEqualTo(SomeTodo.TODO.order()); + }); + + test("withCompleted", () -> { + var actual = SomeTodo.TODO.withCompleted(!SomeTodo.TODO.completed()); + assertThat(actual.completed()).isEqualTo(!SomeTodo.TODO.completed()); + assertThat(actual.id()).isEqualTo(SomeTodo.ID); + assertThat(actual.title()).isEqualTo(SomeTodo.TODO.title()); + assertThat(actual.url()).isEqualTo(SomeTodo.TODO.url()); + assertThat(actual.order()).isEqualTo(SomeTodo.TODO.order()); + }); } } diff --git a/src/test/java/nl/jqno/paralleljava/app/persistence/InMemoryRepositoryTest.java b/src/test/java/nl/jqno/paralleljava/app/persistence/InMemoryRepositoryTest.java index 80b7084..3cd1488 100644 --- a/src/test/java/nl/jqno/paralleljava/app/persistence/InMemoryRepositoryTest.java +++ b/src/test/java/nl/jqno/paralleljava/app/persistence/InMemoryRepositoryTest.java @@ -1,6 +1,7 @@ package nl.jqno.paralleljava.app.persistence; import io.vavr.control.Option; +import nl.jqno.paralleljava.app.TestData.AnotherTodo; import nl.jqno.paralleljava.app.TestData.SomeTodo; import nl.jqno.paralleljava.app.logging.NopLogger; import nl.jqno.picotest.Test; @@ -30,6 +31,26 @@ public void repository() { assertThat(repo.get(UUID.randomUUID())).isEqualTo(Option.none()); }); + test("update a specific todo at index 0", () -> { + var expected = SomeTodo.TODO.withTitle("another title"); + repo.createTodo(SomeTodo.TODO); + repo.createTodo(AnotherTodo.TODO); + + repo.updateTodo(expected); + var actual = repo.get(SomeTodo.ID); + assertThat(actual).isEqualTo(Option.some(expected)); + }); + + test("update a specific todo at index 1", () -> { + var expected = SomeTodo.TODO.withTitle("another title"); + repo.createTodo(AnotherTodo.TODO); + repo.createTodo(SomeTodo.TODO); + + repo.updateTodo(expected); + var actual = repo.get(SomeTodo.ID); + assertThat(actual).isEqualTo(Option.some(expected)); + }); + test("clearing all todos", () -> { repo.createTodo(SomeTodo.TODO); repo.clearAllTodos(); diff --git a/src/test/java/nl/jqno/paralleljava/app/server/SparkServerTest.java b/src/test/java/nl/jqno/paralleljava/app/server/SparkServerTest.java index 6b90f77..21e53b8 100644 --- a/src/test/java/nl/jqno/paralleljava/app/server/SparkServerTest.java +++ b/src/test/java/nl/jqno/paralleljava/app/server/SparkServerTest.java @@ -36,6 +36,7 @@ public void server() { test("GET works", this::getRequest); test("GET with id works", this::getWithIdRequest); test("POST works", this::postRequest); + test("PATCH with id works", this::patchWithIdRequest); test("DELETE works", this::deleteRequest); } @@ -87,6 +88,14 @@ private void postRequest() { assertSingleCall(underlying.calledPost); } + private void patchWithIdRequest() { + when + .patch(ENDPOINT + "/some-id") + .then() + .statusCode(200); + assertSingleCall(underlying.calledPatchWithId); + } + private void deleteRequest() { when .delete(ENDPOINT) @@ -105,17 +114,19 @@ private static class StubController implements Controller { public int calledGet = 0; public int calledGetWithId = 0; public int calledPost = 0; + public int calledPatchWithId = 0; public int calledDelete = 0; public void clear() { calledGet = 0; calledGetWithId = 0; calledPost = 0; + calledPatchWithId = 0; calledDelete = 0; } public int calledTotal() { - return calledGet + calledGetWithId + calledPost + calledDelete; + return calledGet + calledGetWithId + calledPost + calledPatchWithId + calledDelete; } public String get() { @@ -123,7 +134,7 @@ public String get() { return ""; } - public String get(String json) { + public String get(String id) { calledGetWithId += 1; return ""; } @@ -133,6 +144,11 @@ public String post(String json) { return ""; } + public String patch(String id, String json) { + calledPatchWithId += 1; + return ""; + } + public String delete() { calledDelete += 1; return ""; From b480478ef0c1ab99166ddf18916267dc07ccd9a5 Mon Sep 17 00:00:00 2001 From: Jan Ouwens Date: Fri, 22 Mar 2019 14:58:21 +0100 Subject: [PATCH 060/106] Implements DELETE with id endpoint --- .../app/controller/Controller.java | 1 + .../app/controller/DefaultController.java | 8 ++++++++ .../app/persistence/InMemoryRepository.java | 7 +++++++ .../app/persistence/Repository.java | 1 + .../paralleljava/app/server/SparkServer.java | 1 + .../app/controller/DefaultControllerTest.java | 12 ++++++++++++ .../persistence/InMemoryRepositoryTest.java | 18 ++++++++++++++++++ .../app/server/SparkServerTest.java | 18 +++++++++++++++++- 8 files changed, 65 insertions(+), 1 deletion(-) diff --git a/src/main/java/nl/jqno/paralleljava/app/controller/Controller.java b/src/main/java/nl/jqno/paralleljava/app/controller/Controller.java index 75565a3..071c714 100644 --- a/src/main/java/nl/jqno/paralleljava/app/controller/Controller.java +++ b/src/main/java/nl/jqno/paralleljava/app/controller/Controller.java @@ -6,4 +6,5 @@ public interface Controller { String post(String json); String patch(String id, String json); String delete(); + String delete(String id); } diff --git a/src/main/java/nl/jqno/paralleljava/app/controller/DefaultController.java b/src/main/java/nl/jqno/paralleljava/app/controller/DefaultController.java index 08ece99..f2e6dfb 100644 --- a/src/main/java/nl/jqno/paralleljava/app/controller/DefaultController.java +++ b/src/main/java/nl/jqno/paralleljava/app/controller/DefaultController.java @@ -77,6 +77,14 @@ public String delete() { return ""; } + public String delete(String id) { + var uuid = serializer.deserializeUuid(id); + if (uuid.isDefined()) { + repository.delete(uuid.get()); + } + return ""; + } + private String buildUrlFor(UUID id) { return url + "/" + id.toString(); } diff --git a/src/main/java/nl/jqno/paralleljava/app/persistence/InMemoryRepository.java b/src/main/java/nl/jqno/paralleljava/app/persistence/InMemoryRepository.java index 75a2a74..311f5fd 100644 --- a/src/main/java/nl/jqno/paralleljava/app/persistence/InMemoryRepository.java +++ b/src/main/java/nl/jqno/paralleljava/app/persistence/InMemoryRepository.java @@ -43,6 +43,13 @@ public void updateTodo(Todo todo) { todos.add(index, todo); } + public void delete(UUID id) { + var index = List.ofAll(todos) + .map(Todo::id) + .indexOf(id); + todos.remove(index); + } + public void clearAllTodos() { logger.forProduction("Clearing all Todos"); todos.clear(); diff --git a/src/main/java/nl/jqno/paralleljava/app/persistence/Repository.java b/src/main/java/nl/jqno/paralleljava/app/persistence/Repository.java index db6763f..e85adf0 100644 --- a/src/main/java/nl/jqno/paralleljava/app/persistence/Repository.java +++ b/src/main/java/nl/jqno/paralleljava/app/persistence/Repository.java @@ -11,5 +11,6 @@ public interface Repository { Option get(UUID id); List getAllTodos(); void updateTodo(Todo todo); + void delete(UUID id); void clearAllTodos(); } diff --git a/src/main/java/nl/jqno/paralleljava/app/server/SparkServer.java b/src/main/java/nl/jqno/paralleljava/app/server/SparkServer.java index f2995f5..ac616d9 100644 --- a/src/main/java/nl/jqno/paralleljava/app/server/SparkServer.java +++ b/src/main/java/nl/jqno/paralleljava/app/server/SparkServer.java @@ -32,6 +32,7 @@ public void run() { post(endpoint, (request, response) -> controller.post(request.body())); patch(endpoint + "/:id", (request, response) -> controller.patch(request.params("id"), request.body())); delete(endpoint, (request, response) -> controller.delete()); + delete(endpoint + "/:id", (request, response) -> controller.delete(request.params("id"))); } private void enableCors() { diff --git a/src/test/java/nl/jqno/paralleljava/app/controller/DefaultControllerTest.java b/src/test/java/nl/jqno/paralleljava/app/controller/DefaultControllerTest.java index ed3df65..9631319 100644 --- a/src/test/java/nl/jqno/paralleljava/app/controller/DefaultControllerTest.java +++ b/src/test/java/nl/jqno/paralleljava/app/controller/DefaultControllerTest.java @@ -93,5 +93,17 @@ public void endoints() { assertThat(actual).isEqualTo(""); assertThat(repository.getAllTodos()).isEmpty(); }); + + test("delete with id removes the corresponding todo", () -> { + repository.createTodo(AnotherTodo.TODO); + repository.createTodo(SomeTodo.TODO); + + var actual = controller.delete(SomeTodo.ID.toString()); + + assertThat(actual).isEqualTo(""); + assertThat(repository.getAllTodos()) + .doesNotContain(SomeTodo.TODO) + .contains(AnotherTodo.TODO); + }); } } diff --git a/src/test/java/nl/jqno/paralleljava/app/persistence/InMemoryRepositoryTest.java b/src/test/java/nl/jqno/paralleljava/app/persistence/InMemoryRepositoryTest.java index 3cd1488..06eecf9 100644 --- a/src/test/java/nl/jqno/paralleljava/app/persistence/InMemoryRepositoryTest.java +++ b/src/test/java/nl/jqno/paralleljava/app/persistence/InMemoryRepositoryTest.java @@ -51,6 +51,24 @@ public void repository() { assertThat(actual).isEqualTo(Option.some(expected)); }); + test("delete a specific todo at index 0", () -> { + repo.createTodo(SomeTodo.TODO); + repo.createTodo(AnotherTodo.TODO); + + repo.delete(SomeTodo.ID); + var actual = repo.get(SomeTodo.ID); + assertThat(actual).isEqualTo(Option.none()); + }); + + test("update a specific todo at index 1", () -> { + repo.createTodo(AnotherTodo.TODO); + repo.createTodo(SomeTodo.TODO); + + repo.delete(SomeTodo.ID); + var actual = repo.get(SomeTodo.ID); + assertThat(actual).isEqualTo(Option.none()); + }); + test("clearing all todos", () -> { repo.createTodo(SomeTodo.TODO); repo.clearAllTodos(); diff --git a/src/test/java/nl/jqno/paralleljava/app/server/SparkServerTest.java b/src/test/java/nl/jqno/paralleljava/app/server/SparkServerTest.java index 21e53b8..2a66243 100644 --- a/src/test/java/nl/jqno/paralleljava/app/server/SparkServerTest.java +++ b/src/test/java/nl/jqno/paralleljava/app/server/SparkServerTest.java @@ -38,6 +38,7 @@ public void server() { test("POST works", this::postRequest); test("PATCH with id works", this::patchWithIdRequest); test("DELETE works", this::deleteRequest); + test("DELETE with id works", this::deleteWithIdRequest); } private void corsRequestsHeader() { @@ -104,6 +105,14 @@ private void deleteRequest() { assertSingleCall(underlying.calledDelete); } + private void deleteWithIdRequest() { + when + .delete(ENDPOINT + "/some-id") + .then() + .statusCode(200); + assertSingleCall(underlying.calledDeleteWithId); + } + private void assertSingleCall(int calledEndpoint) { assertThat(calledEndpoint).isEqualTo(1); assertThat(underlying.calledTotal()).isEqualTo(1); @@ -116,6 +125,7 @@ private static class StubController implements Controller { public int calledPost = 0; public int calledPatchWithId = 0; public int calledDelete = 0; + public int calledDeleteWithId = 0; public void clear() { calledGet = 0; @@ -123,10 +133,11 @@ public void clear() { calledPost = 0; calledPatchWithId = 0; calledDelete = 0; + calledDeleteWithId = 0; } public int calledTotal() { - return calledGet + calledGetWithId + calledPost + calledPatchWithId + calledDelete; + return calledGet + calledGetWithId + calledPost + calledPatchWithId + calledDelete + calledDeleteWithId; } public String get() { @@ -153,5 +164,10 @@ public String delete() { calledDelete += 1; return ""; } + + public String delete(String id) { + calledDeleteWithId += 1; + return ""; + } } } From 84475007bf8b97394b118a6b98e84bac3ef6acbf Mon Sep 17 00:00:00 2001 From: Jan Ouwens Date: Fri, 22 Mar 2019 15:07:04 +0100 Subject: [PATCH 061/106] Implements dealing with order --- .../app/controller/DefaultController.java | 7 +++--- .../nl/jqno/paralleljava/app/domain/Todo.java | 4 ++++ .../nl/jqno/paralleljava/app/TestData.java | 3 +++ .../app/controller/DefaultControllerTest.java | 22 ++++++++++++++++++- .../paralleljava/app/domain/TodoTest.java | 9 ++++++++ .../app/server/SparkServerTest.java | 1 + 6 files changed, 42 insertions(+), 4 deletions(-) diff --git a/src/main/java/nl/jqno/paralleljava/app/controller/DefaultController.java b/src/main/java/nl/jqno/paralleljava/app/controller/DefaultController.java index f2e6dfb..c983f52 100644 --- a/src/main/java/nl/jqno/paralleljava/app/controller/DefaultController.java +++ b/src/main/java/nl/jqno/paralleljava/app/controller/DefaultController.java @@ -41,7 +41,7 @@ public String post(String json) { if (partialTodo.isDefined() && partialTodo.get().title().isDefined()) { var pt = partialTodo.get(); var id = generator.generateId(); - var todo = new Todo(id, pt.title().get(), buildUrlFor(id), false, 0); + var todo = new Todo(id, pt.title().get(), buildUrlFor(id), false, pt.order().getOrElse(0)); repository.createTodo(todo); logger.forProduction("Returning from POST: " + json); return serializer.serializeTodo(todo); @@ -62,8 +62,9 @@ public String patch(String id, String json) { var todo0 = todo.get(); var todo1 = pt.title().map(todo0::withTitle).getOrElse(todo0); var todo2 = pt.completed().map(todo1::withCompleted).getOrElse(todo1); - repository.updateTodo(todo2); - return serializer.serializeTodo(todo2); + var todo3 = pt.order().map(todo2::withOrder).getOrElse(todo2); + repository.updateTodo(todo3); + return serializer.serializeTodo(todo3); } return ""; } diff --git a/src/main/java/nl/jqno/paralleljava/app/domain/Todo.java b/src/main/java/nl/jqno/paralleljava/app/domain/Todo.java index c9f47d2..85917dc 100644 --- a/src/main/java/nl/jqno/paralleljava/app/domain/Todo.java +++ b/src/main/java/nl/jqno/paralleljava/app/domain/Todo.java @@ -46,6 +46,10 @@ public Todo withCompleted(boolean completed) { return new Todo(id(), title(), url(), completed, order()); } + public Todo withOrder(int order) { + return new Todo(id(), title(), url(), completed(), order); + } + public boolean equals(Object obj) { if (!(obj instanceof Todo)) { return false; diff --git a/src/test/java/nl/jqno/paralleljava/app/TestData.java b/src/test/java/nl/jqno/paralleljava/app/TestData.java index 0ce679a..3611caf 100644 --- a/src/test/java/nl/jqno/paralleljava/app/TestData.java +++ b/src/test/java/nl/jqno/paralleljava/app/TestData.java @@ -26,6 +26,9 @@ public static class SomeTodo { public static final String SERIALIZED_PARTIAL_POST = "{\"title\":\"title\"}"; + + public static final String SERIALIZED_PARTIAL_POST_WITH_ORDER = + "{\"title\":\"title\",\"order\":1337}"; } public static class AnotherTodo { diff --git a/src/test/java/nl/jqno/paralleljava/app/controller/DefaultControllerTest.java b/src/test/java/nl/jqno/paralleljava/app/controller/DefaultControllerTest.java index 9631319..9ae3afe 100644 --- a/src/test/java/nl/jqno/paralleljava/app/controller/DefaultControllerTest.java +++ b/src/test/java/nl/jqno/paralleljava/app/controller/DefaultControllerTest.java @@ -54,7 +54,7 @@ public void endoints() { assertThat(actual).isEqualTo(""); }); - test("post adds a todo", () -> { + test("post adds a todo without order", () -> { var expected = new Todo(constantId, "title", urlBase + "/" + constantId, false, 0); var expectedSerialized = serializer.serializeTodo(expected); @@ -63,6 +63,15 @@ public void endoints() { assertThat(repository.getAllTodos()).contains(expected); }); + test("post adds a todo with order", () -> { + var expected = new Todo(constantId, "title", urlBase + "/" + constantId, false, 1337); + var expectedSerialized = serializer.serializeTodo(expected); + + var actual = controller.post(SomeTodo.SERIALIZED_PARTIAL_POST_WITH_ORDER); + assertThat(actual).isEqualTo(expectedSerialized); + assertThat(repository.getAllTodos()).contains(expected); + }); + test("patch changes title", () -> { repository.createTodo(SomeTodo.TODO); var expected = SomeTodo.TODO.withTitle("another title"); @@ -85,6 +94,17 @@ public void endoints() { assertThat(actual).isEqualTo(Option.some(expected)); }); + test("patch changes order", () -> { + repository.createTodo(SomeTodo.TODO); + var expected = SomeTodo.TODO.withOrder(47); + + var result = controller.patch(SomeTodo.ID.toString(), "{\"order\":47}"); + var actual = repository.get(SomeTodo.ID); + + assertThat(result).isEqualTo(serializer.serializeTodo(expected)); + assertThat(actual).isEqualTo(Option.some(expected)); + }); + test("delete clears all todos", () -> { repository.createTodo(SomeTodo.TODO); diff --git a/src/test/java/nl/jqno/paralleljava/app/domain/TodoTest.java b/src/test/java/nl/jqno/paralleljava/app/domain/TodoTest.java index f18d1f9..2e843e7 100644 --- a/src/test/java/nl/jqno/paralleljava/app/domain/TodoTest.java +++ b/src/test/java/nl/jqno/paralleljava/app/domain/TodoTest.java @@ -45,5 +45,14 @@ public void todo() { assertThat(actual.url()).isEqualTo(SomeTodo.TODO.url()); assertThat(actual.order()).isEqualTo(SomeTodo.TODO.order()); }); + + test("withOrder", () -> { + var actual = SomeTodo.TODO.withOrder(86); + assertThat(actual.order()).isEqualTo(86); + assertThat(actual.id()).isEqualTo(SomeTodo.ID); + assertThat(actual.title()).isEqualTo(SomeTodo.TODO.title()); + assertThat(actual.url()).isEqualTo(SomeTodo.TODO.url()); + assertThat(actual.completed()).isEqualTo(SomeTodo.TODO.completed()); + }); } } diff --git a/src/test/java/nl/jqno/paralleljava/app/server/SparkServerTest.java b/src/test/java/nl/jqno/paralleljava/app/server/SparkServerTest.java index 2a66243..fc7571f 100644 --- a/src/test/java/nl/jqno/paralleljava/app/server/SparkServerTest.java +++ b/src/test/java/nl/jqno/paralleljava/app/server/SparkServerTest.java @@ -111,6 +111,7 @@ private void deleteWithIdRequest() { .then() .statusCode(200); assertSingleCall(underlying.calledDeleteWithId); + assertSingleCall(underlying.calledDeleteWithId); } private void assertSingleCall(int calledEndpoint) { From 7feaa1f727dcabccb3453b8d499701adcd9afaad Mon Sep 17 00:00:00 2001 From: Jan Ouwens Date: Fri, 22 Mar 2019 15:13:44 +0100 Subject: [PATCH 062/106] Adds CORS tests for all endpoints --- .../app/server/SparkServerTest.java | 25 ++++++++++--------- 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/src/test/java/nl/jqno/paralleljava/app/server/SparkServerTest.java b/src/test/java/nl/jqno/paralleljava/app/server/SparkServerTest.java index fc7571f..9dcab85 100644 --- a/src/test/java/nl/jqno/paralleljava/app/server/SparkServerTest.java +++ b/src/test/java/nl/jqno/paralleljava/app/server/SparkServerTest.java @@ -1,6 +1,7 @@ package nl.jqno.paralleljava.app.server; import io.restassured.specification.RequestSpecification; +import io.vavr.collection.List; import nl.jqno.paralleljava.app.controller.Controller; import nl.jqno.paralleljava.app.logging.NopLogger; import nl.jqno.picotest.Test; @@ -13,6 +14,7 @@ public class SparkServerTest extends Test { private static final int PORT = 1337; private static final String ENDPOINT = "/todo"; + private static final String ENDPOINT_WITH_ID = ENDPOINT + "/some-id"; private final RequestSpecification when = given().port(PORT).when(); private StubController underlying; @@ -42,15 +44,14 @@ public void server() { } private void corsRequestsHeader() { - when - .get(ENDPOINT) - .then() - .header("Access-Control-Allow-Origin", "*"); - - when - .post(ENDPOINT) - .then() - .header("Access-Control-Allow-Origin", "*"); + var requests = List.of( + when.get(ENDPOINT), + when.get(ENDPOINT_WITH_ID), + when.post(ENDPOINT), + when.patch(ENDPOINT_WITH_ID), + when.delete(ENDPOINT), + when.delete(ENDPOINT_WITH_ID)); + requests.forEach(r -> r.then().header("Access-Control-Allow-Origin", "*")); } private void corsOptionsRequest() { @@ -75,7 +76,7 @@ private void getRequest() { private void getWithIdRequest() { when - .get(ENDPOINT + "/some-id") + .get(ENDPOINT_WITH_ID) .then() .statusCode(200); assertSingleCall(underlying.calledGetWithId); @@ -91,7 +92,7 @@ private void postRequest() { private void patchWithIdRequest() { when - .patch(ENDPOINT + "/some-id") + .patch(ENDPOINT_WITH_ID) .then() .statusCode(200); assertSingleCall(underlying.calledPatchWithId); @@ -107,7 +108,7 @@ private void deleteRequest() { private void deleteWithIdRequest() { when - .delete(ENDPOINT + "/some-id") + .delete(ENDPOINT_WITH_ID) .then() .statusCode(200); assertSingleCall(underlying.calledDeleteWithId); From ec6078118708ebd3f2efc3d6be94d98413608153 Mon Sep 17 00:00:00 2001 From: Jan Ouwens Date: Fri, 22 Mar 2019 15:41:14 +0100 Subject: [PATCH 063/106] Simplifies patch logic --- .../app/controller/DefaultController.java | 28 +++++++++---------- 1 file changed, 13 insertions(+), 15 deletions(-) diff --git a/src/main/java/nl/jqno/paralleljava/app/controller/DefaultController.java b/src/main/java/nl/jqno/paralleljava/app/controller/DefaultController.java index c983f52..ca8789e 100644 --- a/src/main/java/nl/jqno/paralleljava/app/controller/DefaultController.java +++ b/src/main/java/nl/jqno/paralleljava/app/controller/DefaultController.java @@ -46,9 +46,7 @@ public String post(String json) { logger.forProduction("Returning from POST: " + json); return serializer.serializeTodo(todo); } - else { - return ""; - } + return ""; } public String patch(String id, String json) { @@ -57,20 +55,20 @@ public String patch(String id, String json) { var partialTodo = serializer.deserializePartialTodo(json); if (uuid.isDefined() && partialTodo.isDefined()) { var pt = partialTodo.get(); - var todo = repository.get(uuid.get()); - if (todo.isDefined()) { - var todo0 = todo.get(); - var todo1 = pt.title().map(todo0::withTitle).getOrElse(todo0); - var todo2 = pt.completed().map(todo1::withCompleted).getOrElse(todo1); - var todo3 = pt.order().map(todo2::withOrder).getOrElse(todo2); - repository.updateTodo(todo3); - return serializer.serializeTodo(todo3); + var existingTodo = repository.get(uuid.get()); + if (existingTodo.isDefined()) { + var todo = existingTodo.get(); + var updatedTodo = new Todo( + todo.id(), + pt.title().getOrElse(todo.title()), + todo.url(), + pt.completed().getOrElse(todo.completed()), + pt.order().getOrElse(todo.order())); + repository.updateTodo(updatedTodo); + return serializer.serializeTodo(updatedTodo); } - return ""; - } - else { - return ""; } + return ""; } public String delete() { From f3a8f08ae8507337fdd6612583096b814a166a36 Mon Sep 17 00:00:00 2001 From: Jan Ouwens Date: Sun, 24 Mar 2019 11:31:57 +0100 Subject: [PATCH 064/106] Start with adding error handling --- .../app/controller/Controller.java | 4 +- .../app/controller/DefaultController.java | 5 +- .../paralleljava/app/server/SparkServer.java | 10 +++- .../app/controller/DefaultControllerTest.java | 6 +- .../app/server/SparkServerTest.java | 60 +++++++++++++------ 5 files changed, 59 insertions(+), 26 deletions(-) diff --git a/src/main/java/nl/jqno/paralleljava/app/controller/Controller.java b/src/main/java/nl/jqno/paralleljava/app/controller/Controller.java index 071c714..87f7f58 100644 --- a/src/main/java/nl/jqno/paralleljava/app/controller/Controller.java +++ b/src/main/java/nl/jqno/paralleljava/app/controller/Controller.java @@ -1,7 +1,9 @@ package nl.jqno.paralleljava.app.controller; +import io.vavr.control.Try; + public interface Controller { - String get(); + Try get(); String get(String id); String post(String json); String patch(String id, String json); diff --git a/src/main/java/nl/jqno/paralleljava/app/controller/DefaultController.java b/src/main/java/nl/jqno/paralleljava/app/controller/DefaultController.java index ca8789e..5cc90b4 100644 --- a/src/main/java/nl/jqno/paralleljava/app/controller/DefaultController.java +++ b/src/main/java/nl/jqno/paralleljava/app/controller/DefaultController.java @@ -1,5 +1,6 @@ package nl.jqno.paralleljava.app.controller; +import io.vavr.control.Try; import nl.jqno.paralleljava.app.domain.Todo; import nl.jqno.paralleljava.app.logging.Logger; import nl.jqno.paralleljava.app.logging.LoggerFactory; @@ -24,8 +25,8 @@ public DefaultController(String url, Repository repository, IdGenerator generato this.logger = loggerFactory.create(getClass()); } - public String get() { - return serializer.serializeTodos(repository.getAllTodos()); + public Try get() { + return Try.of(() -> serializer.serializeTodos(repository.getAllTodos())); } public String get(String id) { diff --git a/src/main/java/nl/jqno/paralleljava/app/server/SparkServer.java b/src/main/java/nl/jqno/paralleljava/app/server/SparkServer.java index ac616d9..a2b562c 100644 --- a/src/main/java/nl/jqno/paralleljava/app/server/SparkServer.java +++ b/src/main/java/nl/jqno/paralleljava/app/server/SparkServer.java @@ -1,9 +1,11 @@ package nl.jqno.paralleljava.app.server; import io.vavr.control.Option; +import io.vavr.control.Try; import nl.jqno.paralleljava.app.controller.Controller; import nl.jqno.paralleljava.app.logging.Logger; import nl.jqno.paralleljava.app.logging.LoggerFactory; +import spark.Response; import static spark.Spark.*; @@ -27,7 +29,7 @@ public void run() { port(port); enableCors(); - get(endpoint, (request, response) -> controller.get()); + get(endpoint, (request, response) -> buildResponse(response, controller.get())); get(endpoint + "/:id", (request, response) -> controller.get(request.params("id"))); post(endpoint, (request, response) -> controller.post(request.body())); patch(endpoint + "/:id", (request, response) -> controller.patch(request.params("id"), request.body())); @@ -48,4 +50,10 @@ private void enableCors() { response.header("Access-Control-Allow-Origin", "*"); }); } + + private String buildResponse(Response response, Try method) { + return method + .onFailure(e -> response.status(500)) + .getOrElse(""); + } } diff --git a/src/test/java/nl/jqno/paralleljava/app/controller/DefaultControllerTest.java b/src/test/java/nl/jqno/paralleljava/app/controller/DefaultControllerTest.java index 9ae3afe..dbbe200 100644 --- a/src/test/java/nl/jqno/paralleljava/app/controller/DefaultControllerTest.java +++ b/src/test/java/nl/jqno/paralleljava/app/controller/DefaultControllerTest.java @@ -1,7 +1,7 @@ package nl.jqno.paralleljava.app.controller; -import io.vavr.collection.List; import io.vavr.control.Option; +import io.vavr.control.Try; import nl.jqno.paralleljava.app.domain.Todo; import nl.jqno.paralleljava.app.logging.NopLogger; import nl.jqno.paralleljava.app.persistence.ConstantIdGenerator; @@ -31,7 +31,7 @@ public void endoints() { test("get returns an empty list when no todos are present", () -> { var actual = controller.get(); - assertThat(actual).isEqualTo(serializer.serializeTodos(List.empty())); + assertThat(actual).isEqualTo(Try.success("[]")); }); test("get returns all todos", () -> { @@ -39,7 +39,7 @@ public void endoints() { repository.createTodo(AnotherTodo.TODO); var actual = controller.get(); - assertThat(actual).isEqualTo(ListOfTodos.SERIALIZED); + assertThat(actual).isEqualTo(Try.success(ListOfTodos.SERIALIZED)); }); test("get with id returns a specific serialized todo if it exists", () -> { diff --git a/src/test/java/nl/jqno/paralleljava/app/server/SparkServerTest.java b/src/test/java/nl/jqno/paralleljava/app/server/SparkServerTest.java index 9dcab85..5fa3707 100644 --- a/src/test/java/nl/jqno/paralleljava/app/server/SparkServerTest.java +++ b/src/test/java/nl/jqno/paralleljava/app/server/SparkServerTest.java @@ -1,12 +1,17 @@ package nl.jqno.paralleljava.app.server; +import io.restassured.response.Response; import io.restassured.specification.RequestSpecification; import io.vavr.collection.List; +import io.vavr.control.Try; import nl.jqno.paralleljava.app.controller.Controller; import nl.jqno.paralleljava.app.logging.NopLogger; import nl.jqno.picotest.Test; import spark.Spark; +import java.util.function.IntSupplier; +import java.util.function.Supplier; + import static io.restassured.RestAssured.given; import static org.assertj.core.api.Assertions.assertThat; @@ -35,7 +40,7 @@ public void server() { test("CORS Access-Control-AllowOrigin header is included", this::corsRequestsHeader); test("OPTION request", this::corsOptionsRequest); - test("GET works", this::getRequest); + test("GET works", () -> checkEndpoint(() -> underlying.calledGet, () -> when.get(ENDPOINT))); test("GET with id works", this::getWithIdRequest); test("POST works", this::postRequest); test("PATCH with id works", this::patchWithIdRequest); @@ -66,20 +71,12 @@ private void corsOptionsRequest() { .header("Access-Control-Allow-Methods", methods); } - private void getRequest() { - when - .get(ENDPOINT) - .then() - .statusCode(200); - assertSingleCall(underlying.calledGet); - } - private void getWithIdRequest() { when .get(ENDPOINT_WITH_ID) .then() .statusCode(200); - assertSingleCall(underlying.calledGetWithId); + assertSingleCall(() -> underlying.calledGetWithId); } private void postRequest() { @@ -87,7 +84,7 @@ private void postRequest() { .post(ENDPOINT) .then() .statusCode(200); - assertSingleCall(underlying.calledPost); + assertSingleCall(() -> underlying.calledPost); } private void patchWithIdRequest() { @@ -95,7 +92,7 @@ private void patchWithIdRequest() { .patch(ENDPOINT_WITH_ID) .then() .statusCode(200); - assertSingleCall(underlying.calledPatchWithId); + assertSingleCall(() -> underlying.calledPatchWithId); } private void deleteRequest() { @@ -103,7 +100,7 @@ private void deleteRequest() { .delete(ENDPOINT) .then() .statusCode(200); - assertSingleCall(underlying.calledDelete); + assertSingleCall(() -> underlying.calledDelete); } private void deleteWithIdRequest() { @@ -111,17 +108,37 @@ private void deleteWithIdRequest() { .delete(ENDPOINT_WITH_ID) .then() .statusCode(200); - assertSingleCall(underlying.calledDeleteWithId); - assertSingleCall(underlying.calledDeleteWithId); + assertSingleCall(() -> underlying.calledDeleteWithId); + } + + private void checkEndpoint(IntSupplier calledEndpoint, Supplier r) { + checkSuccess(calledEndpoint, r); + underlying.clear(); + checkFailure(calledEndpoint, r); } - private void assertSingleCall(int calledEndpoint) { - assertThat(calledEndpoint).isEqualTo(1); + private void checkSuccess(IntSupplier calledEndpoint, Supplier r) { + r.get().then().statusCode(200); + assertSingleCall(calledEndpoint); + } + + private void checkFailure(IntSupplier calledEndpoint, Supplier r) { + underlying.nextRequestFails = true; + r.get().then().statusCode(500); + assertSingleCall(calledEndpoint); + } + + private void assertSingleCall(IntSupplier calledEndpoint) { + assertThat(calledEndpoint.getAsInt()).isEqualTo(1); assertThat(underlying.calledTotal()).isEqualTo(1); } private static class StubController implements Controller { + private static final Try SUCCESS = Try.success(""); + private static final Try FAILURE = Try.failure(new IllegalStateException()); + + public boolean nextRequestFails = false; public int calledGet = 0; public int calledGetWithId = 0; public int calledPost = 0; @@ -130,6 +147,7 @@ private static class StubController implements Controller { public int calledDeleteWithId = 0; public void clear() { + nextRequestFails = false; calledGet = 0; calledGetWithId = 0; calledPost = 0; @@ -142,9 +160,9 @@ public int calledTotal() { return calledGet + calledGetWithId + calledPost + calledPatchWithId + calledDelete + calledDeleteWithId; } - public String get() { + public Try get() { calledGet += 1; - return ""; + return response(); } public String get(String id) { @@ -171,5 +189,9 @@ public String delete(String id) { calledDeleteWithId += 1; return ""; } + + private Try response() { + return nextRequestFails ? FAILURE : SUCCESS; + } } } From ea5b42a6707fbebef6e9488c097a71e15b816367 Mon Sep 17 00:00:00 2001 From: Jan Ouwens Date: Sun, 24 Mar 2019 13:18:19 +0100 Subject: [PATCH 065/106] Adds error handling to controller --- .../app/controller/Controller.java | 10 +-- .../app/controller/DefaultController.java | 25 +++--- .../paralleljava/app/server/SparkServer.java | 18 ++-- .../app/controller/DefaultControllerTest.java | 22 ++--- .../app/server/SparkServerTest.java | 86 ++++++------------- 5 files changed, 69 insertions(+), 92 deletions(-) diff --git a/src/main/java/nl/jqno/paralleljava/app/controller/Controller.java b/src/main/java/nl/jqno/paralleljava/app/controller/Controller.java index 87f7f58..18cb989 100644 --- a/src/main/java/nl/jqno/paralleljava/app/controller/Controller.java +++ b/src/main/java/nl/jqno/paralleljava/app/controller/Controller.java @@ -4,9 +4,9 @@ public interface Controller { Try get(); - String get(String id); - String post(String json); - String patch(String id, String json); - String delete(); - String delete(String id); + Try get(String id); + Try post(String json); + Try patch(String id, String json); + Try delete(); + Try delete(String id); } diff --git a/src/main/java/nl/jqno/paralleljava/app/controller/DefaultController.java b/src/main/java/nl/jqno/paralleljava/app/controller/DefaultController.java index 5cc90b4..f7e9451 100644 --- a/src/main/java/nl/jqno/paralleljava/app/controller/DefaultController.java +++ b/src/main/java/nl/jqno/paralleljava/app/controller/DefaultController.java @@ -29,14 +29,14 @@ public Try get() { return Try.of(() -> serializer.serializeTodos(repository.getAllTodos())); } - public String get(String id) { + public Try get(String id) { return serializer.deserializeUuid(id) .flatMap(repository::get) .map(serializer::serializeTodo) - .getOrElse(""); + .toTry(() -> new IllegalArgumentException("Cannot find " + id)); } - public String post(String json) { + public Try post(String json) { logger.forProduction("POSTed: " + json); var partialTodo = serializer.deserializePartialTodo(json); if (partialTodo.isDefined() && partialTodo.get().title().isDefined()) { @@ -45,12 +45,12 @@ public String post(String json) { var todo = new Todo(id, pt.title().get(), buildUrlFor(id), false, pt.order().getOrElse(0)); repository.createTodo(todo); logger.forProduction("Returning from POST: " + json); - return serializer.serializeTodo(todo); + return Try.of(() -> serializer.serializeTodo(todo)); } - return ""; + return Try.failure(new IllegalArgumentException("Invalid POST request: " + json)); } - public String patch(String id, String json) { + public Try patch(String id, String json) { logger.forProduction("PATCHed: " + json); var uuid = serializer.deserializeUuid(id); var partialTodo = serializer.deserializePartialTodo(json); @@ -66,23 +66,24 @@ public String patch(String id, String json) { pt.completed().getOrElse(todo.completed()), pt.order().getOrElse(todo.order())); repository.updateTodo(updatedTodo); - return serializer.serializeTodo(updatedTodo); + return Try.of(() -> serializer.serializeTodo(updatedTodo)); } } - return ""; + return Try.failure(new IllegalArgumentException("Invalid PATCH request: " + id + ", " + json)); } - public String delete() { + public Try delete() { repository.clearAllTodos(); - return ""; + return Try.success(""); } - public String delete(String id) { + public Try delete(String id) { var uuid = serializer.deserializeUuid(id); if (uuid.isDefined()) { repository.delete(uuid.get()); + return Try.success(""); } - return ""; + return Try.failure(new IllegalArgumentException("Invalid DELETE request: " + id)); } private String buildUrlFor(UUID id) { diff --git a/src/main/java/nl/jqno/paralleljava/app/server/SparkServer.java b/src/main/java/nl/jqno/paralleljava/app/server/SparkServer.java index a2b562c..eed93b8 100644 --- a/src/main/java/nl/jqno/paralleljava/app/server/SparkServer.java +++ b/src/main/java/nl/jqno/paralleljava/app/server/SparkServer.java @@ -29,12 +29,18 @@ public void run() { port(port); enableCors(); - get(endpoint, (request, response) -> buildResponse(response, controller.get())); - get(endpoint + "/:id", (request, response) -> controller.get(request.params("id"))); - post(endpoint, (request, response) -> controller.post(request.body())); - patch(endpoint + "/:id", (request, response) -> controller.patch(request.params("id"), request.body())); - delete(endpoint, (request, response) -> controller.delete()); - delete(endpoint + "/:id", (request, response) -> controller.delete(request.params("id"))); + get(endpoint, + (request, response) -> buildResponse(response, controller.get())); + get(endpoint + "/:id", + (request, response) -> buildResponse(response, controller.get(request.params("id")))); + post(endpoint, + (request, response) -> buildResponse(response, controller.post(request.body()))); + patch(endpoint + "/:id", + (request, response) -> buildResponse(response, controller.patch(request.params("id"), request.body()))); + delete(endpoint, + (request, response) -> buildResponse(response, controller.delete())); + delete(endpoint + "/:id", + (request, response) -> buildResponse(response, controller.delete(request.params("id")))); } private void enableCors() { diff --git a/src/test/java/nl/jqno/paralleljava/app/controller/DefaultControllerTest.java b/src/test/java/nl/jqno/paralleljava/app/controller/DefaultControllerTest.java index dbbe200..9dea63a 100644 --- a/src/test/java/nl/jqno/paralleljava/app/controller/DefaultControllerTest.java +++ b/src/test/java/nl/jqno/paralleljava/app/controller/DefaultControllerTest.java @@ -2,6 +2,7 @@ import io.vavr.control.Option; import io.vavr.control.Try; +import io.vavr.control.Try.Failure; import nl.jqno.paralleljava.app.domain.Todo; import nl.jqno.paralleljava.app.logging.NopLogger; import nl.jqno.paralleljava.app.persistence.ConstantIdGenerator; @@ -46,12 +47,13 @@ public void endoints() { repository.createTodo(SomeTodo.TODO); var actual = controller.get(SomeTodo.ID.toString()); - assertThat(actual).isEqualTo(SomeTodo.SERIALIZED); + assertThat(actual).isEqualTo(Try.success(SomeTodo.SERIALIZED)); }); - test("get with id returns nothing if it doesn't exist", () -> { + test("get with id fails if it doesn't exist", () -> { var actual = controller.get(SomeTodo.ID.toString()); - assertThat(actual).isEqualTo(""); + assertThat(actual.isFailure()).isTrue(); + assertThat(actual.getCause().getClass()).isEqualTo(IllegalArgumentException.class); }); test("post adds a todo without order", () -> { @@ -59,7 +61,7 @@ public void endoints() { var expectedSerialized = serializer.serializeTodo(expected); var actual = controller.post(SomeTodo.SERIALIZED_PARTIAL_POST); - assertThat(actual).isEqualTo(expectedSerialized); + assertThat(actual).isEqualTo(Try.success(expectedSerialized)); assertThat(repository.getAllTodos()).contains(expected); }); @@ -68,7 +70,7 @@ public void endoints() { var expectedSerialized = serializer.serializeTodo(expected); var actual = controller.post(SomeTodo.SERIALIZED_PARTIAL_POST_WITH_ORDER); - assertThat(actual).isEqualTo(expectedSerialized); + assertThat(actual).isEqualTo(Try.success(expectedSerialized)); assertThat(repository.getAllTodos()).contains(expected); }); @@ -79,7 +81,7 @@ public void endoints() { var result = controller.patch(SomeTodo.ID.toString(), "{\"title\":\"another title\"}"); var actual = repository.get(SomeTodo.ID); - assertThat(result).isEqualTo(serializer.serializeTodo(expected)); + assertThat(result).isEqualTo(Try.success(serializer.serializeTodo(expected))); assertThat(actual).isEqualTo(Option.some(expected)); }); @@ -90,7 +92,7 @@ public void endoints() { var result = controller.patch(SomeTodo.ID.toString(), "{\"completed\":false}"); var actual = repository.get(SomeTodo.ID); - assertThat(result).isEqualTo(serializer.serializeTodo(expected)); + assertThat(result).isEqualTo(Try.success(serializer.serializeTodo(expected))); assertThat(actual).isEqualTo(Option.some(expected)); }); @@ -101,7 +103,7 @@ public void endoints() { var result = controller.patch(SomeTodo.ID.toString(), "{\"order\":47}"); var actual = repository.get(SomeTodo.ID); - assertThat(result).isEqualTo(serializer.serializeTodo(expected)); + assertThat(result).isEqualTo(Try.success(serializer.serializeTodo(expected))); assertThat(actual).isEqualTo(Option.some(expected)); }); @@ -110,7 +112,7 @@ public void endoints() { var actual = controller.delete(); - assertThat(actual).isEqualTo(""); + assertThat(actual).isEqualTo(Try.success("")); assertThat(repository.getAllTodos()).isEmpty(); }); @@ -120,7 +122,7 @@ public void endoints() { var actual = controller.delete(SomeTodo.ID.toString()); - assertThat(actual).isEqualTo(""); + assertThat(actual).isEqualTo(Try.success("")); assertThat(repository.getAllTodos()) .doesNotContain(SomeTodo.TODO) .contains(AnotherTodo.TODO); diff --git a/src/test/java/nl/jqno/paralleljava/app/server/SparkServerTest.java b/src/test/java/nl/jqno/paralleljava/app/server/SparkServerTest.java index 5fa3707..31407a6 100644 --- a/src/test/java/nl/jqno/paralleljava/app/server/SparkServerTest.java +++ b/src/test/java/nl/jqno/paralleljava/app/server/SparkServerTest.java @@ -37,15 +37,23 @@ public void server() { afterAll(Spark::stop); - test("CORS Access-Control-AllowOrigin header is included", this::corsRequestsHeader); - test("OPTION request", this::corsOptionsRequest); - - test("GET works", () -> checkEndpoint(() -> underlying.calledGet, () -> when.get(ENDPOINT))); - test("GET with id works", this::getWithIdRequest); - test("POST works", this::postRequest); - test("PATCH with id works", this::patchWithIdRequest); - test("DELETE works", this::deleteRequest); - test("DELETE with id works", this::deleteWithIdRequest); + test("CORS Access-Control-AllowOrigin header is included", + this::corsRequestsHeader); + test("OPTION request", + this::corsOptionsRequest); + + test("GET works", + () -> checkEndpoint(() -> underlying.calledGet, () -> when.get(ENDPOINT))); + test("GET with id works", + () -> checkEndpoint(() -> underlying.calledGetWithId, () -> when.get(ENDPOINT_WITH_ID))); + test("POST works", + () -> checkEndpoint(() -> underlying.calledPost, () -> when.post(ENDPOINT))); + test("PATCH with id works", + () -> checkEndpoint(() -> underlying.calledPatchWithId, () -> when.patch(ENDPOINT_WITH_ID))); + test("DELETE works", + () -> checkEndpoint(() -> underlying.calledDelete, () -> when.delete(ENDPOINT))); + test("DELETE with id works", + () -> checkEndpoint(() -> underlying.calledDeleteWithId, () -> when.delete(ENDPOINT_WITH_ID))); } private void corsRequestsHeader() { @@ -71,46 +79,6 @@ private void corsOptionsRequest() { .header("Access-Control-Allow-Methods", methods); } - private void getWithIdRequest() { - when - .get(ENDPOINT_WITH_ID) - .then() - .statusCode(200); - assertSingleCall(() -> underlying.calledGetWithId); - } - - private void postRequest() { - when - .post(ENDPOINT) - .then() - .statusCode(200); - assertSingleCall(() -> underlying.calledPost); - } - - private void patchWithIdRequest() { - when - .patch(ENDPOINT_WITH_ID) - .then() - .statusCode(200); - assertSingleCall(() -> underlying.calledPatchWithId); - } - - private void deleteRequest() { - when - .delete(ENDPOINT) - .then() - .statusCode(200); - assertSingleCall(() -> underlying.calledDelete); - } - - private void deleteWithIdRequest() { - when - .delete(ENDPOINT_WITH_ID) - .then() - .statusCode(200); - assertSingleCall(() -> underlying.calledDeleteWithId); - } - private void checkEndpoint(IntSupplier calledEndpoint, Supplier r) { checkSuccess(calledEndpoint, r); underlying.clear(); @@ -165,29 +133,29 @@ public Try get() { return response(); } - public String get(String id) { + public Try get(String id) { calledGetWithId += 1; - return ""; + return response(); } - public String post(String json) { + public Try post(String json) { calledPost += 1; - return ""; + return response(); } - public String patch(String id, String json) { + public Try patch(String id, String json) { calledPatchWithId += 1; - return ""; + return response(); } - public String delete() { + public Try delete() { calledDelete += 1; - return ""; + return response(); } - public String delete(String id) { + public Try delete(String id) { calledDeleteWithId += 1; - return ""; + return response(); } private Try response() { From 26d756f54bff80e4f579a787a4b74113dc0f3f6c Mon Sep 17 00:00:00 2001 From: Jan Ouwens Date: Sun, 24 Mar 2019 13:24:56 +0100 Subject: [PATCH 066/106] Differentiates between invalid requests and internal server errors --- .../paralleljava/app/server/SparkServer.java | 5 ++- .../app/server/SparkServerTest.java | 33 +++++++++++-------- 2 files changed, 24 insertions(+), 14 deletions(-) diff --git a/src/main/java/nl/jqno/paralleljava/app/server/SparkServer.java b/src/main/java/nl/jqno/paralleljava/app/server/SparkServer.java index eed93b8..86d5b2c 100644 --- a/src/main/java/nl/jqno/paralleljava/app/server/SparkServer.java +++ b/src/main/java/nl/jqno/paralleljava/app/server/SparkServer.java @@ -59,7 +59,10 @@ private void enableCors() { private String buildResponse(Response response, Try method) { return method - .onFailure(e -> response.status(500)) + .onFailure(e -> { + var isInvalidRequest = IllegalArgumentException.class.equals(e.getClass()); + response.status(isInvalidRequest ? 400 : 500); + }) .getOrElse(""); } } diff --git a/src/test/java/nl/jqno/paralleljava/app/server/SparkServerTest.java b/src/test/java/nl/jqno/paralleljava/app/server/SparkServerTest.java index 31407a6..f3751ec 100644 --- a/src/test/java/nl/jqno/paralleljava/app/server/SparkServerTest.java +++ b/src/test/java/nl/jqno/paralleljava/app/server/SparkServerTest.java @@ -80,18 +80,16 @@ private void corsOptionsRequest() { } private void checkEndpoint(IntSupplier calledEndpoint, Supplier r) { - checkSuccess(calledEndpoint, r); - underlying.clear(); - checkFailure(calledEndpoint, r); - } - - private void checkSuccess(IntSupplier calledEndpoint, Supplier r) { r.get().then().statusCode(200); assertSingleCall(calledEndpoint); - } - private void checkFailure(IntSupplier calledEndpoint, Supplier r) { - underlying.nextRequestFails = true; + underlying.clear(); + underlying.nextRequestFails4xx = true; + r.get().then().statusCode(400); + assertSingleCall(calledEndpoint); + + underlying.clear(); + underlying.nextRequestFails5xx = true; r.get().then().statusCode(500); assertSingleCall(calledEndpoint); } @@ -104,9 +102,11 @@ private void assertSingleCall(IntSupplier calledEndpoint) { private static class StubController implements Controller { private static final Try SUCCESS = Try.success(""); - private static final Try FAILURE = Try.failure(new IllegalStateException()); + private static final Try FAILURE_4xx = Try.failure(new IllegalArgumentException()); + private static final Try FAILURE_5xx = Try.failure(new IllegalStateException()); - public boolean nextRequestFails = false; + public boolean nextRequestFails4xx = false; + public boolean nextRequestFails5xx = false; public int calledGet = 0; public int calledGetWithId = 0; public int calledPost = 0; @@ -115,7 +115,8 @@ private static class StubController implements Controller { public int calledDeleteWithId = 0; public void clear() { - nextRequestFails = false; + nextRequestFails4xx = false; + nextRequestFails5xx = false; calledGet = 0; calledGetWithId = 0; calledPost = 0; @@ -159,7 +160,13 @@ public Try delete(String id) { } private Try response() { - return nextRequestFails ? FAILURE : SUCCESS; + if (nextRequestFails4xx) { + return FAILURE_4xx; + } + if (nextRequestFails5xx) { + return FAILURE_5xx; + } + return SUCCESS; } } } From 1c94c72cbc0d814a29fd35b87a277ec515d20e43 Mon Sep 17 00:00:00 2001 From: Jan Ouwens Date: Mon, 25 Mar 2019 07:54:35 +0100 Subject: [PATCH 067/106] Allows for failures in Repository --- .../app/controller/DefaultController.java | 33 +++++++++------ .../app/persistence/InMemoryRepository.java | 23 +++++++---- .../app/persistence/Repository.java | 13 +++--- .../app/controller/DefaultControllerTest.java | 15 ++++--- .../persistence/InMemoryRepositoryTest.java | 41 ++++++++++++------- 5 files changed, 76 insertions(+), 49 deletions(-) diff --git a/src/main/java/nl/jqno/paralleljava/app/controller/DefaultController.java b/src/main/java/nl/jqno/paralleljava/app/controller/DefaultController.java index f7e9451..3c9903d 100644 --- a/src/main/java/nl/jqno/paralleljava/app/controller/DefaultController.java +++ b/src/main/java/nl/jqno/paralleljava/app/controller/DefaultController.java @@ -26,14 +26,18 @@ public DefaultController(String url, Repository repository, IdGenerator generato } public Try get() { - return Try.of(() -> serializer.serializeTodos(repository.getAllTodos())); + return repository.getAllTodos() + .map(serializer::serializeTodos); } public Try get(String id) { - return serializer.deserializeUuid(id) - .flatMap(repository::get) - .map(serializer::serializeTodo) - .toTry(() -> new IllegalArgumentException("Cannot find " + id)); + var uuid = serializer.deserializeUuid(id); + if (uuid.isDefined()) { + return repository + .get(uuid.get()) + .flatMap(o -> o.map(serializer::serializeTodo).toTry(() -> new IllegalArgumentException("Cannot find " + id))); + } + return Try.failure(new IllegalArgumentException("Invalid GET request: " + id)); } public Try post(String json) { @@ -43,9 +47,9 @@ public Try post(String json) { var pt = partialTodo.get(); var id = generator.generateId(); var todo = new Todo(id, pt.title().get(), buildUrlFor(id), false, pt.order().getOrElse(0)); - repository.createTodo(todo); logger.forProduction("Returning from POST: " + json); - return Try.of(() -> serializer.serializeTodo(todo)); + return repository.createTodo(todo) + .map(ignored -> serializer.serializeTodo(todo)); } return Try.failure(new IllegalArgumentException("Invalid POST request: " + json)); } @@ -57,8 +61,11 @@ public Try patch(String id, String json) { if (uuid.isDefined() && partialTodo.isDefined()) { var pt = partialTodo.get(); var existingTodo = repository.get(uuid.get()); - if (existingTodo.isDefined()) { - var todo = existingTodo.get(); + if (existingTodo.isFailure()) { + return existingTodo.map(ignored -> ""); + } + if (existingTodo.get().isDefined()) { + var todo = existingTodo.get().get(); var updatedTodo = new Todo( todo.id(), pt.title().getOrElse(todo.title()), @@ -73,15 +80,15 @@ public Try patch(String id, String json) { } public Try delete() { - repository.clearAllTodos(); - return Try.success(""); + return repository.clearAllTodos() + .map(ignored -> ""); } public Try delete(String id) { var uuid = serializer.deserializeUuid(id); if (uuid.isDefined()) { - repository.delete(uuid.get()); - return Try.success(""); + return repository.delete(uuid.get()) + .map(ignored -> ""); } return Try.failure(new IllegalArgumentException("Invalid DELETE request: " + id)); } diff --git a/src/main/java/nl/jqno/paralleljava/app/persistence/InMemoryRepository.java b/src/main/java/nl/jqno/paralleljava/app/persistence/InMemoryRepository.java index 311f5fd..3fc01dd 100644 --- a/src/main/java/nl/jqno/paralleljava/app/persistence/InMemoryRepository.java +++ b/src/main/java/nl/jqno/paralleljava/app/persistence/InMemoryRepository.java @@ -2,6 +2,7 @@ import io.vavr.collection.List; import io.vavr.control.Option; +import io.vavr.control.Try; import nl.jqno.paralleljava.app.domain.Todo; import nl.jqno.paralleljava.app.logging.Logger; import nl.jqno.paralleljava.app.logging.LoggerFactory; @@ -14,6 +15,7 @@ */ public class InMemoryRepository implements Repository { private static final java.util.List todos = new ArrayList<>(); + private static final Try SUCCESS = Try.success(null); private final Logger logger; @@ -21,37 +23,42 @@ public InMemoryRepository(LoggerFactory loggerFactory) { this.logger = loggerFactory.create(getClass()); } - public void createTodo(Todo todo) { + public Try createTodo(Todo todo) { logger.forProduction("Creating Todo " + todo); todos.add(todo); + return SUCCESS; } - public Option get(UUID id) { - return List.ofAll(todos) + public Try> get(UUID id) { + var result = List.ofAll(todos) .find(t -> t.id().equals(id)); + return Try.success(result); } - public List getAllTodos() { - return List.ofAll(todos); + public Try> getAllTodos() { + return Try.success(List.ofAll(todos)); } - public void updateTodo(Todo todo) { + public Try updateTodo(Todo todo) { var index = List.ofAll(todos) .map(Todo::id) .indexOf(todo.id()); todos.remove(index); todos.add(index, todo); + return SUCCESS; } - public void delete(UUID id) { + public Try delete(UUID id) { var index = List.ofAll(todos) .map(Todo::id) .indexOf(id); todos.remove(index); + return SUCCESS; } - public void clearAllTodos() { + public Try clearAllTodos() { logger.forProduction("Clearing all Todos"); todos.clear(); + return SUCCESS; } } diff --git a/src/main/java/nl/jqno/paralleljava/app/persistence/Repository.java b/src/main/java/nl/jqno/paralleljava/app/persistence/Repository.java index e85adf0..1f40da8 100644 --- a/src/main/java/nl/jqno/paralleljava/app/persistence/Repository.java +++ b/src/main/java/nl/jqno/paralleljava/app/persistence/Repository.java @@ -2,15 +2,16 @@ import io.vavr.collection.List; import io.vavr.control.Option; +import io.vavr.control.Try; import nl.jqno.paralleljava.app.domain.Todo; import java.util.UUID; public interface Repository { - void createTodo(Todo todo); - Option get(UUID id); - List getAllTodos(); - void updateTodo(Todo todo); - void delete(UUID id); - void clearAllTodos(); + Try createTodo(Todo todo); + Try> get(UUID id); + Try> getAllTodos(); + Try updateTodo(Todo todo); + Try delete(UUID id); + Try clearAllTodos(); } diff --git a/src/test/java/nl/jqno/paralleljava/app/controller/DefaultControllerTest.java b/src/test/java/nl/jqno/paralleljava/app/controller/DefaultControllerTest.java index 9dea63a..45f814d 100644 --- a/src/test/java/nl/jqno/paralleljava/app/controller/DefaultControllerTest.java +++ b/src/test/java/nl/jqno/paralleljava/app/controller/DefaultControllerTest.java @@ -2,7 +2,6 @@ import io.vavr.control.Option; import io.vavr.control.Try; -import io.vavr.control.Try.Failure; import nl.jqno.paralleljava.app.domain.Todo; import nl.jqno.paralleljava.app.logging.NopLogger; import nl.jqno.paralleljava.app.persistence.ConstantIdGenerator; @@ -62,7 +61,7 @@ public void endoints() { var actual = controller.post(SomeTodo.SERIALIZED_PARTIAL_POST); assertThat(actual).isEqualTo(Try.success(expectedSerialized)); - assertThat(repository.getAllTodos()).contains(expected); + assertThat(repository.getAllTodos().get()).contains(expected); }); test("post adds a todo with order", () -> { @@ -71,7 +70,7 @@ public void endoints() { var actual = controller.post(SomeTodo.SERIALIZED_PARTIAL_POST_WITH_ORDER); assertThat(actual).isEqualTo(Try.success(expectedSerialized)); - assertThat(repository.getAllTodos()).contains(expected); + assertThat(repository.getAllTodos().get()).contains(expected); }); test("patch changes title", () -> { @@ -82,7 +81,7 @@ public void endoints() { var actual = repository.get(SomeTodo.ID); assertThat(result).isEqualTo(Try.success(serializer.serializeTodo(expected))); - assertThat(actual).isEqualTo(Option.some(expected)); + assertThat(actual.get()).isEqualTo(Option.some(expected)); }); test("patch changes completed", () -> { @@ -93,7 +92,7 @@ public void endoints() { var actual = repository.get(SomeTodo.ID); assertThat(result).isEqualTo(Try.success(serializer.serializeTodo(expected))); - assertThat(actual).isEqualTo(Option.some(expected)); + assertThat(actual.get()).isEqualTo(Option.some(expected)); }); test("patch changes order", () -> { @@ -104,7 +103,7 @@ public void endoints() { var actual = repository.get(SomeTodo.ID); assertThat(result).isEqualTo(Try.success(serializer.serializeTodo(expected))); - assertThat(actual).isEqualTo(Option.some(expected)); + assertThat(actual.get()).isEqualTo(Option.some(expected)); }); test("delete clears all todos", () -> { @@ -113,7 +112,7 @@ public void endoints() { var actual = controller.delete(); assertThat(actual).isEqualTo(Try.success("")); - assertThat(repository.getAllTodos()).isEmpty(); + assertThat(repository.getAllTodos().get()).isEmpty(); }); test("delete with id removes the corresponding todo", () -> { @@ -123,7 +122,7 @@ public void endoints() { var actual = controller.delete(SomeTodo.ID.toString()); assertThat(actual).isEqualTo(Try.success("")); - assertThat(repository.getAllTodos()) + assertThat(repository.getAllTodos().get()) .doesNotContain(SomeTodo.TODO) .contains(AnotherTodo.TODO); }); diff --git a/src/test/java/nl/jqno/paralleljava/app/persistence/InMemoryRepositoryTest.java b/src/test/java/nl/jqno/paralleljava/app/persistence/InMemoryRepositoryTest.java index 06eecf9..0d50423 100644 --- a/src/test/java/nl/jqno/paralleljava/app/persistence/InMemoryRepositoryTest.java +++ b/src/test/java/nl/jqno/paralleljava/app/persistence/InMemoryRepositoryTest.java @@ -20,15 +20,17 @@ public void repository() { }); test("create a todo", () -> { - repo.createTodo(SomeTodo.TODO); - assertThat(repo.getAllTodos()).contains(SomeTodo.TODO); + var result = repo.createTodo(SomeTodo.TODO); + + assertThat(result.isSuccess()).isTrue(); + assertThat(repo.getAllTodos().get()).contains(SomeTodo.TODO); }); test("get a specific todo", () -> { repo.createTodo(SomeTodo.TODO); - assertThat(repo.get(SomeTodo.ID)).isEqualTo(Option.some(SomeTodo.TODO)); - assertThat(repo.get(UUID.randomUUID())).isEqualTo(Option.none()); + assertThat(repo.get(SomeTodo.ID).get()).isEqualTo(Option.some(SomeTodo.TODO)); + assertThat(repo.get(UUID.randomUUID()).get()).isEqualTo(Option.none()); }); test("update a specific todo at index 0", () -> { @@ -36,8 +38,10 @@ public void repository() { repo.createTodo(SomeTodo.TODO); repo.createTodo(AnotherTodo.TODO); - repo.updateTodo(expected); - var actual = repo.get(SomeTodo.ID); + var result = repo.updateTodo(expected); + var actual = repo.get(SomeTodo.ID).get(); + + assertThat(result.isSuccess()).isTrue(); assertThat(actual).isEqualTo(Option.some(expected)); }); @@ -46,8 +50,10 @@ public void repository() { repo.createTodo(AnotherTodo.TODO); repo.createTodo(SomeTodo.TODO); - repo.updateTodo(expected); - var actual = repo.get(SomeTodo.ID); + var result = repo.updateTodo(expected); + var actual = repo.get(SomeTodo.ID).get(); + + assertThat(result.isSuccess()).isTrue(); assertThat(actual).isEqualTo(Option.some(expected)); }); @@ -55,8 +61,10 @@ public void repository() { repo.createTodo(SomeTodo.TODO); repo.createTodo(AnotherTodo.TODO); - repo.delete(SomeTodo.ID); - var actual = repo.get(SomeTodo.ID); + var result = repo.delete(SomeTodo.ID); + var actual = repo.get(SomeTodo.ID).get(); + + assertThat(result.isSuccess()).isTrue(); assertThat(actual).isEqualTo(Option.none()); }); @@ -64,15 +72,20 @@ public void repository() { repo.createTodo(AnotherTodo.TODO); repo.createTodo(SomeTodo.TODO); - repo.delete(SomeTodo.ID); - var actual = repo.get(SomeTodo.ID); + var result = repo.delete(SomeTodo.ID); + var actual = repo.get(SomeTodo.ID).get(); + + assertThat(result.isSuccess()).isTrue(); assertThat(actual).isEqualTo(Option.none()); }); test("clearing all todos", () -> { repo.createTodo(SomeTodo.TODO); - repo.clearAllTodos(); - assertThat(repo.getAllTodos()).isEmpty(); + + var result = repo.clearAllTodos(); + + assertThat(result.isSuccess()).isTrue(); + assertThat(repo.getAllTodos().get()).isEmpty(); }); } } From 7405d565c856fbf831c2178e90d926fc2518dd65 Mon Sep 17 00:00:00 2001 From: Jan Ouwens Date: Mon, 25 Mar 2019 07:54:56 +0100 Subject: [PATCH 068/106] Improves naming --- .../jqno/paralleljava/app/controller/DefaultControllerTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/java/nl/jqno/paralleljava/app/controller/DefaultControllerTest.java b/src/test/java/nl/jqno/paralleljava/app/controller/DefaultControllerTest.java index 45f814d..976661f 100644 --- a/src/test/java/nl/jqno/paralleljava/app/controller/DefaultControllerTest.java +++ b/src/test/java/nl/jqno/paralleljava/app/controller/DefaultControllerTest.java @@ -16,7 +16,7 @@ public class DefaultControllerTest extends Test { - public void endoints() { + public void controller() { var loggerFactory = NopLogger.FACTORY; var repository = new InMemoryRepository(loggerFactory); var constantId = UUID.randomUUID(); From 2cb7356d040ae3196a4fb2af94cfbee39f243ed1 Mon Sep 17 00:00:00 2001 From: Jan Ouwens Date: Mon, 25 Mar 2019 08:01:24 +0100 Subject: [PATCH 069/106] Flips ifs around for readability --- .../app/controller/DefaultController.java | 75 ++++++++++--------- 1 file changed, 40 insertions(+), 35 deletions(-) diff --git a/src/main/java/nl/jqno/paralleljava/app/controller/DefaultController.java b/src/main/java/nl/jqno/paralleljava/app/controller/DefaultController.java index 3c9903d..4992abf 100644 --- a/src/main/java/nl/jqno/paralleljava/app/controller/DefaultController.java +++ b/src/main/java/nl/jqno/paralleljava/app/controller/DefaultController.java @@ -32,51 +32,55 @@ public Try get() { public Try get(String id) { var uuid = serializer.deserializeUuid(id); - if (uuid.isDefined()) { - return repository - .get(uuid.get()) - .flatMap(o -> o.map(serializer::serializeTodo).toTry(() -> new IllegalArgumentException("Cannot find " + id))); + if (uuid.isEmpty()) { + return Try.failure(new IllegalArgumentException("Invalid GET request: " + id)); } - return Try.failure(new IllegalArgumentException("Invalid GET request: " + id)); + + return repository + .get(uuid.get()) + .flatMap(o -> o.map(serializer::serializeTodo).toTry(() -> new IllegalArgumentException("Cannot find " + id))); } public Try post(String json) { logger.forProduction("POSTed: " + json); var partialTodo = serializer.deserializePartialTodo(json); - if (partialTodo.isDefined() && partialTodo.get().title().isDefined()) { - var pt = partialTodo.get(); - var id = generator.generateId(); - var todo = new Todo(id, pt.title().get(), buildUrlFor(id), false, pt.order().getOrElse(0)); - logger.forProduction("Returning from POST: " + json); - return repository.createTodo(todo) - .map(ignored -> serializer.serializeTodo(todo)); + if (partialTodo.isEmpty() || partialTodo.get().title().isEmpty()) { + return Try.failure(new IllegalArgumentException("Invalid POST request: " + json)); } - return Try.failure(new IllegalArgumentException("Invalid POST request: " + json)); + + var pt = partialTodo.get(); + var id = generator.generateId(); + var todo = new Todo(id, pt.title().get(), buildUrlFor(id), false, pt.order().getOrElse(0)); + return repository.createTodo(todo) + .map(ignored -> serializer.serializeTodo(todo)); } public Try patch(String id, String json) { logger.forProduction("PATCHed: " + json); var uuid = serializer.deserializeUuid(id); var partialTodo = serializer.deserializePartialTodo(json); - if (uuid.isDefined() && partialTodo.isDefined()) { - var pt = partialTodo.get(); - var existingTodo = repository.get(uuid.get()); - if (existingTodo.isFailure()) { - return existingTodo.map(ignored -> ""); - } - if (existingTodo.get().isDefined()) { - var todo = existingTodo.get().get(); - var updatedTodo = new Todo( - todo.id(), - pt.title().getOrElse(todo.title()), - todo.url(), - pt.completed().getOrElse(todo.completed()), - pt.order().getOrElse(todo.order())); - repository.updateTodo(updatedTodo); - return Try.of(() -> serializer.serializeTodo(updatedTodo)); - } + if (uuid.isEmpty() || partialTodo.isEmpty()) { + return Try.failure(new IllegalArgumentException("Invalid PATCH request: " + id + ", " + json)); + } + + var pt = partialTodo.get(); + var existingTodo = repository.get(uuid.get()); + if (existingTodo.isFailure()) { + return existingTodo.map(ignored -> ""); } - return Try.failure(new IllegalArgumentException("Invalid PATCH request: " + id + ", " + json)); + if (existingTodo.get().isEmpty()) { + return Try.failure(new IllegalArgumentException("Can't find Todo with id " + id)); + } + + var todo = existingTodo.get().get(); + var updatedTodo = new Todo( + todo.id(), + pt.title().getOrElse(todo.title()), + todo.url(), + pt.completed().getOrElse(todo.completed()), + pt.order().getOrElse(todo.order())); + repository.updateTodo(updatedTodo); + return Try.of(() -> serializer.serializeTodo(updatedTodo)); } public Try delete() { @@ -86,11 +90,12 @@ public Try delete() { public Try delete(String id) { var uuid = serializer.deserializeUuid(id); - if (uuid.isDefined()) { - return repository.delete(uuid.get()) - .map(ignored -> ""); + if (uuid.isEmpty()) { + return Try.failure(new IllegalArgumentException("Invalid DELETE request: " + id)); } - return Try.failure(new IllegalArgumentException("Invalid DELETE request: " + id)); + + return repository.delete(uuid.get()) + .map(ignored -> ""); } private String buildUrlFor(UUID id) { From 9d2a4f0924cde7d4f14d1ab50f02dfbc0e1b36e4 Mon Sep 17 00:00:00 2001 From: Jan Ouwens Date: Mon, 25 Mar 2019 16:03:06 +0100 Subject: [PATCH 070/106] Restructures wiring --- src/main/java/nl/jqno/paralleljava/Main.java | 19 ++++++- .../paralleljava/app/server/SparkServer.java | 2 +- ...redApplication.java => DefaultWiring.java} | 51 +++++++++---------- .../jqno/paralleljava/ArchitectureTest.java | 4 +- .../app/controller/DefaultControllerTest.java | 4 +- .../app/serialization/GsonSerializerTest.java | 4 +- .../app/server/SparkServerTest.java | 2 +- 7 files changed, 49 insertions(+), 37 deletions(-) rename src/main/java/nl/jqno/paralleljava/dependencyinjection/{WiredApplication.java => DefaultWiring.java} (50%) diff --git a/src/main/java/nl/jqno/paralleljava/Main.java b/src/main/java/nl/jqno/paralleljava/Main.java index f4ae3a7..db19751 100644 --- a/src/main/java/nl/jqno/paralleljava/Main.java +++ b/src/main/java/nl/jqno/paralleljava/Main.java @@ -1,9 +1,24 @@ package nl.jqno.paralleljava; -import nl.jqno.paralleljava.dependencyinjection.WiredApplication; +import nl.jqno.paralleljava.dependencyinjection.DefaultWiring; public class Main { + private static final int DEFAULT_PORT = 4567; + private static final String DEFAULT_URL = "http://localhost"; + private static final String ENDPOINT = "/todo"; + public static void main(String... args) { - new WiredApplication().run(); + var loggerFactory = DefaultWiring.slf4jLoggerFactory(); + + var heroku = DefaultWiring.heroku(); + var fullUrl = heroku.getHostUrl().getOrElse(DEFAULT_URL) + ENDPOINT; + var port = heroku.getAssignedPort().getOrElse(DEFAULT_PORT); + + var repository = DefaultWiring.inMemoryRepository(loggerFactory); + var idGenerator = DefaultWiring.randomIdGenerator(); + var controller = DefaultWiring.defaultController(fullUrl, repository, idGenerator, loggerFactory); + var server = DefaultWiring.sparkServer(ENDPOINT, port, controller, loggerFactory); + + server.run(); } } diff --git a/src/main/java/nl/jqno/paralleljava/app/server/SparkServer.java b/src/main/java/nl/jqno/paralleljava/app/server/SparkServer.java index 86d5b2c..b28e066 100644 --- a/src/main/java/nl/jqno/paralleljava/app/server/SparkServer.java +++ b/src/main/java/nl/jqno/paralleljava/app/server/SparkServer.java @@ -16,7 +16,7 @@ public class SparkServer implements Server { private final int port; private final Logger logger; - public SparkServer(String endpoint, Controller controller, int port, LoggerFactory loggerFactory) { + public SparkServer(String endpoint, int port, Controller controller, LoggerFactory loggerFactory) { this.endpoint = endpoint; this.controller = controller; this.port = port; diff --git a/src/main/java/nl/jqno/paralleljava/dependencyinjection/WiredApplication.java b/src/main/java/nl/jqno/paralleljava/dependencyinjection/DefaultWiring.java similarity index 50% rename from src/main/java/nl/jqno/paralleljava/dependencyinjection/WiredApplication.java rename to src/main/java/nl/jqno/paralleljava/dependencyinjection/DefaultWiring.java index 3de6a0a..f4edb34 100644 --- a/src/main/java/nl/jqno/paralleljava/dependencyinjection/WiredApplication.java +++ b/src/main/java/nl/jqno/paralleljava/dependencyinjection/DefaultWiring.java @@ -7,6 +7,7 @@ import nl.jqno.paralleljava.app.controller.DefaultController; import nl.jqno.paralleljava.app.logging.LoggerFactory; import nl.jqno.paralleljava.app.logging.Slf4jLogger; +import nl.jqno.paralleljava.app.persistence.IdGenerator; import nl.jqno.paralleljava.app.persistence.InMemoryRepository; import nl.jqno.paralleljava.app.persistence.RandomIdGenerator; import nl.jqno.paralleljava.app.persistence.Repository; @@ -16,45 +17,41 @@ import nl.jqno.paralleljava.app.server.Server; import nl.jqno.paralleljava.app.server.SparkServer; -public class WiredApplication { +public class DefaultWiring { - private static final int DEFAULT_PORT = 4567; - private static final String DEFAULT_URL = "http://localhost"; - private static final String ENDPOINT = "/todo"; - - private final Heroku heroku; - private final LoggerFactory loggerFactory; - private final Repository repository; - private final Controller controller; - private final Server server; - - public WiredApplication() { - heroku = createHeroku(); - loggerFactory = c -> new Slf4jLogger(org.slf4j.LoggerFactory.getLogger(c)); - repository = new InMemoryRepository(loggerFactory); - controller = createController(repository, heroku, loggerFactory); - server = new SparkServer(ENDPOINT, controller, heroku.getAssignedPort().getOrElse(DEFAULT_PORT), loggerFactory); - } - - public void run() { - server.run(); + private DefaultWiring() { + // Don't instantiate } - private static Heroku createHeroku() { + public static Heroku heroku() { var processBuilder = new ProcessBuilder(); var environment = HashMap.ofAll(processBuilder.environment()); return new Heroku(environment); } - private static Controller createController(Repository repository, Heroku heroku, LoggerFactory loggerFactory) { - var fullUrl = heroku.getHostUrl().getOrElse(DEFAULT_URL) + ENDPOINT; - var idGenerator = new RandomIdGenerator(); - return new DefaultController(fullUrl, repository, idGenerator, defaultSerializer(loggerFactory), loggerFactory); + public static LoggerFactory slf4jLoggerFactory() { + return c -> new Slf4jLogger(org.slf4j.LoggerFactory.getLogger(c)); } - public static Serializer defaultSerializer(LoggerFactory loggerFactory) { + public static Serializer gsonSerializer(LoggerFactory loggerFactory) { var gsonBuilder = new GsonBuilder(); VavrGson.registerAll(gsonBuilder); return new GsonSerializer(gsonBuilder.create(), loggerFactory); } + + public static Repository inMemoryRepository(LoggerFactory loggerFactory) { + return new InMemoryRepository(loggerFactory); + } + + public static IdGenerator randomIdGenerator() { + return new RandomIdGenerator(); + } + + public static Controller defaultController(String fullUrl, Repository repository, IdGenerator idGenerator, LoggerFactory loggerFactory) { + return new DefaultController(fullUrl, repository, idGenerator, gsonSerializer(loggerFactory), loggerFactory); + } + + public static Server sparkServer(String endpoint, int port, Controller controller, LoggerFactory loggerFactory) { + return new SparkServer(endpoint, port, controller, loggerFactory); + } } diff --git a/src/test/java/nl/jqno/paralleljava/ArchitectureTest.java b/src/test/java/nl/jqno/paralleljava/ArchitectureTest.java index 2e2116b..52bca6d 100644 --- a/src/test/java/nl/jqno/paralleljava/ArchitectureTest.java +++ b/src/test/java/nl/jqno/paralleljava/ArchitectureTest.java @@ -5,7 +5,7 @@ import nl.jqno.paralleljava.app.logging.Slf4jLogger; import nl.jqno.paralleljava.app.serialization.GsonSerializer; import nl.jqno.paralleljava.app.server.SparkServer; -import nl.jqno.paralleljava.dependencyinjection.WiredApplication; +import nl.jqno.paralleljava.dependencyinjection.DefaultWiring; import nl.jqno.picotest.Test; import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.noClasses; @@ -32,7 +32,7 @@ public void architecture() { private void assertBoundary(String restrictedPackageIdentifier, Package whiteListedPackage) { var rule = noClasses() .that().resideOutsideOfPackage(whiteListedPackage.getName()) - .and().dontHaveFullyQualifiedName(WiredApplication.class.getCanonicalName()) + .and().dontHaveFullyQualifiedName(DefaultWiring.class.getCanonicalName()) .should().accessClassesThat().resideInAPackage(restrictedPackageIdentifier); rule.check(IMPORTED_CLASSES); } diff --git a/src/test/java/nl/jqno/paralleljava/app/controller/DefaultControllerTest.java b/src/test/java/nl/jqno/paralleljava/app/controller/DefaultControllerTest.java index 976661f..84b2b25 100644 --- a/src/test/java/nl/jqno/paralleljava/app/controller/DefaultControllerTest.java +++ b/src/test/java/nl/jqno/paralleljava/app/controller/DefaultControllerTest.java @@ -6,7 +6,7 @@ import nl.jqno.paralleljava.app.logging.NopLogger; import nl.jqno.paralleljava.app.persistence.ConstantIdGenerator; import nl.jqno.paralleljava.app.persistence.InMemoryRepository; -import nl.jqno.paralleljava.dependencyinjection.WiredApplication; +import nl.jqno.paralleljava.dependencyinjection.DefaultWiring; import nl.jqno.picotest.Test; import java.util.UUID; @@ -21,7 +21,7 @@ public void controller() { var repository = new InMemoryRepository(loggerFactory); var constantId = UUID.randomUUID(); var idGenerator = new ConstantIdGenerator(constantId); - var serializer = WiredApplication.defaultSerializer(loggerFactory); + var serializer = DefaultWiring.gsonSerializer(loggerFactory); var urlBase = "/blabla/todo"; var controller = new DefaultController(urlBase, repository, idGenerator, serializer, loggerFactory); diff --git a/src/test/java/nl/jqno/paralleljava/app/serialization/GsonSerializerTest.java b/src/test/java/nl/jqno/paralleljava/app/serialization/GsonSerializerTest.java index a163d18..ab5e6c3 100644 --- a/src/test/java/nl/jqno/paralleljava/app/serialization/GsonSerializerTest.java +++ b/src/test/java/nl/jqno/paralleljava/app/serialization/GsonSerializerTest.java @@ -3,7 +3,7 @@ import io.vavr.collection.List; import io.vavr.control.Option; import nl.jqno.paralleljava.app.logging.NopLogger; -import nl.jqno.paralleljava.dependencyinjection.WiredApplication; +import nl.jqno.paralleljava.dependencyinjection.DefaultWiring; import nl.jqno.picotest.Test; import static nl.jqno.paralleljava.app.TestData.*; @@ -11,7 +11,7 @@ public class GsonSerializerTest extends Test { - private Serializer serializer = WiredApplication.defaultSerializer(NopLogger.FACTORY); + private Serializer serializer = DefaultWiring.gsonSerializer(NopLogger.FACTORY); public void serializationOfASingleTodo() { diff --git a/src/test/java/nl/jqno/paralleljava/app/server/SparkServerTest.java b/src/test/java/nl/jqno/paralleljava/app/server/SparkServerTest.java index f3751ec..cec29bc 100644 --- a/src/test/java/nl/jqno/paralleljava/app/server/SparkServerTest.java +++ b/src/test/java/nl/jqno/paralleljava/app/server/SparkServerTest.java @@ -27,7 +27,7 @@ public void server() { underlying = new StubController(); beforeAll(() -> { - new SparkServer(ENDPOINT, underlying, PORT, NopLogger.FACTORY).run(); + new SparkServer(ENDPOINT, PORT, underlying, NopLogger.FACTORY).run(); Spark.awaitInitialization(); }); From 450430ae2f668e1468315628fa0f5bddd3749bcb Mon Sep 17 00:00:00 2001 From: Jan Ouwens Date: Mon, 25 Mar 2019 16:24:51 +0100 Subject: [PATCH 071/106] Restructures test wiring --- .../jqno/paralleljava/ArchitectureTest.java | 1 + .../app/controller/DefaultControllerTest.java | 22 ++--- .../app/domain/PartialTodoTest.java | 2 +- .../paralleljava/app/domain/TodoTest.java | 2 +- .../app/logging/Slf4jLoggerTest.java | 47 +---------- .../persistence/InMemoryRepositoryTest.java | 9 +- .../persistence/RandomIdGeneratorTest.java | 3 +- .../app/serialization/GsonSerializerTest.java | 6 +- .../app/server/SparkServerTest.java | 83 ++----------------- .../TestData.java | 2 +- .../dependencyinjection/TestWiring.java | 32 +++++++ .../stubs}/ConstantIdGenerator.java | 4 +- .../stubs}/NopLogger.java | 6 +- .../stubs/StubController.java | 75 +++++++++++++++++ .../dependencyinjection/stubs/StubLogger.java | 45 ++++++++++ 15 files changed, 193 insertions(+), 146 deletions(-) rename src/test/java/nl/jqno/paralleljava/{app => dependencyinjection}/TestData.java (97%) create mode 100644 src/test/java/nl/jqno/paralleljava/dependencyinjection/TestWiring.java rename src/test/java/nl/jqno/paralleljava/{app/persistence => dependencyinjection/stubs}/ConstantIdGenerator.java (67%) rename src/test/java/nl/jqno/paralleljava/{app/logging => dependencyinjection/stubs}/NopLogger.java (77%) create mode 100644 src/test/java/nl/jqno/paralleljava/dependencyinjection/stubs/StubController.java create mode 100644 src/test/java/nl/jqno/paralleljava/dependencyinjection/stubs/StubLogger.java diff --git a/src/test/java/nl/jqno/paralleljava/ArchitectureTest.java b/src/test/java/nl/jqno/paralleljava/ArchitectureTest.java index 52bca6d..4124ffd 100644 --- a/src/test/java/nl/jqno/paralleljava/ArchitectureTest.java +++ b/src/test/java/nl/jqno/paralleljava/ArchitectureTest.java @@ -33,6 +33,7 @@ private void assertBoundary(String restrictedPackageIdentifier, Package whiteLis var rule = noClasses() .that().resideOutsideOfPackage(whiteListedPackage.getName()) .and().dontHaveFullyQualifiedName(DefaultWiring.class.getCanonicalName()) + .and().resideOutsideOfPackage("nl.jqno.paralleljava.dependencyinjection.stubs") .should().accessClassesThat().resideInAPackage(restrictedPackageIdentifier); rule.check(IMPORTED_CLASSES); } diff --git a/src/test/java/nl/jqno/paralleljava/app/controller/DefaultControllerTest.java b/src/test/java/nl/jqno/paralleljava/app/controller/DefaultControllerTest.java index 84b2b25..372449a 100644 --- a/src/test/java/nl/jqno/paralleljava/app/controller/DefaultControllerTest.java +++ b/src/test/java/nl/jqno/paralleljava/app/controller/DefaultControllerTest.java @@ -3,27 +3,27 @@ import io.vavr.control.Option; import io.vavr.control.Try; import nl.jqno.paralleljava.app.domain.Todo; -import nl.jqno.paralleljava.app.logging.NopLogger; -import nl.jqno.paralleljava.app.persistence.ConstantIdGenerator; -import nl.jqno.paralleljava.app.persistence.InMemoryRepository; import nl.jqno.paralleljava.dependencyinjection.DefaultWiring; +import nl.jqno.paralleljava.dependencyinjection.TestWiring; import nl.jqno.picotest.Test; import java.util.UUID; -import static nl.jqno.paralleljava.app.TestData.*; +import static nl.jqno.paralleljava.dependencyinjection.TestData.*; import static org.assertj.core.api.Assertions.assertThat; public class DefaultControllerTest extends Test { public void controller() { - var loggerFactory = NopLogger.FACTORY; - var repository = new InMemoryRepository(loggerFactory); var constantId = UUID.randomUUID(); - var idGenerator = new ConstantIdGenerator(constantId); + var fullUrl = "/blabla/todo"; + + var loggerFactory = TestWiring.nopLoggerFactory(); + var repository = DefaultWiring.inMemoryRepository(loggerFactory); + var idGenerator = TestWiring.constantIdGenerator(constantId); var serializer = DefaultWiring.gsonSerializer(loggerFactory); - var urlBase = "/blabla/todo"; - var controller = new DefaultController(urlBase, repository, idGenerator, serializer, loggerFactory); + + var controller = new DefaultController(fullUrl, repository, idGenerator, serializer, loggerFactory); beforeEach(() -> { repository.clearAllTodos(); @@ -56,7 +56,7 @@ public void controller() { }); test("post adds a todo without order", () -> { - var expected = new Todo(constantId, "title", urlBase + "/" + constantId, false, 0); + var expected = new Todo(constantId, "title", fullUrl + "/" + constantId, false, 0); var expectedSerialized = serializer.serializeTodo(expected); var actual = controller.post(SomeTodo.SERIALIZED_PARTIAL_POST); @@ -65,7 +65,7 @@ public void controller() { }); test("post adds a todo with order", () -> { - var expected = new Todo(constantId, "title", urlBase + "/" + constantId, false, 1337); + var expected = new Todo(constantId, "title", fullUrl + "/" + constantId, false, 1337); var expectedSerialized = serializer.serializeTodo(expected); var actual = controller.post(SomeTodo.SERIALIZED_PARTIAL_POST_WITH_ORDER); diff --git a/src/test/java/nl/jqno/paralleljava/app/domain/PartialTodoTest.java b/src/test/java/nl/jqno/paralleljava/app/domain/PartialTodoTest.java index 87cbfe8..e03bc9e 100644 --- a/src/test/java/nl/jqno/paralleljava/app/domain/PartialTodoTest.java +++ b/src/test/java/nl/jqno/paralleljava/app/domain/PartialTodoTest.java @@ -2,7 +2,7 @@ import io.vavr.control.Option; import nl.jqno.equalsverifier.EqualsVerifier; -import nl.jqno.paralleljava.app.TestData.SomeTodo; +import nl.jqno.paralleljava.dependencyinjection.TestData.SomeTodo; import nl.jqno.picotest.Test; import static org.assertj.core.api.Assertions.assertThat; diff --git a/src/test/java/nl/jqno/paralleljava/app/domain/TodoTest.java b/src/test/java/nl/jqno/paralleljava/app/domain/TodoTest.java index 2e843e7..49bceaf 100644 --- a/src/test/java/nl/jqno/paralleljava/app/domain/TodoTest.java +++ b/src/test/java/nl/jqno/paralleljava/app/domain/TodoTest.java @@ -1,7 +1,7 @@ package nl.jqno.paralleljava.app.domain; import nl.jqno.equalsverifier.EqualsVerifier; -import nl.jqno.paralleljava.app.TestData.SomeTodo; +import nl.jqno.paralleljava.dependencyinjection.TestData.SomeTodo; import nl.jqno.picotest.Test; import static org.assertj.core.api.Assertions.assertThat; diff --git a/src/test/java/nl/jqno/paralleljava/app/logging/Slf4jLoggerTest.java b/src/test/java/nl/jqno/paralleljava/app/logging/Slf4jLoggerTest.java index be858eb..582cb5c 100644 --- a/src/test/java/nl/jqno/paralleljava/app/logging/Slf4jLoggerTest.java +++ b/src/test/java/nl/jqno/paralleljava/app/logging/Slf4jLoggerTest.java @@ -1,7 +1,8 @@ package nl.jqno.paralleljava.app.logging; +import nl.jqno.paralleljava.dependencyinjection.TestWiring; +import nl.jqno.paralleljava.dependencyinjection.stubs.StubLogger; import nl.jqno.picotest.Test; -import org.slf4j.helpers.SubstituteLogger; import static org.assertj.core.api.Assertions.assertThat; @@ -15,7 +16,7 @@ public class Slf4jLoggerTest extends Test { public void logger() { beforeEach(() -> { - underlying = new StubLogger(); + underlying = TestWiring.stubLogger(); logger = new Slf4jLogger(underlying); }); @@ -61,47 +62,5 @@ public void logger() { assertThat(underlying.lastThrowable).isEqualTo(SOME_EXCEPTION); }); } - - private static class StubLogger extends SubstituteLogger { - public int calledDebug = 0; - public int calledInfo = 0; - public int calledWarn = 0; - public int calledError = 0; - public Throwable lastThrowable = null; - - public StubLogger() { - super(StubLogger.class.getName()); - } - - public int calledTotal() { - return calledDebug + calledInfo + calledWarn + calledError; - } - - public void debug(String msg) { - calledDebug += 1; - } - - public void info(String msg) { - calledInfo += 1; - } - - public void warn(String msg) { - calledWarn += 1; - } - - public void warn(String msg, Throwable e) { - calledWarn += 1; - lastThrowable = e; - } - - public void error(String msg) { - calledError += 1; - } - - public void error(String msg, Throwable e) { - calledError += 1; - lastThrowable = e; - } - } } diff --git a/src/test/java/nl/jqno/paralleljava/app/persistence/InMemoryRepositoryTest.java b/src/test/java/nl/jqno/paralleljava/app/persistence/InMemoryRepositoryTest.java index 0d50423..2dd36ab 100644 --- a/src/test/java/nl/jqno/paralleljava/app/persistence/InMemoryRepositoryTest.java +++ b/src/test/java/nl/jqno/paralleljava/app/persistence/InMemoryRepositoryTest.java @@ -1,9 +1,10 @@ package nl.jqno.paralleljava.app.persistence; import io.vavr.control.Option; -import nl.jqno.paralleljava.app.TestData.AnotherTodo; -import nl.jqno.paralleljava.app.TestData.SomeTodo; -import nl.jqno.paralleljava.app.logging.NopLogger; +import nl.jqno.paralleljava.dependencyinjection.TestData.AnotherTodo; +import nl.jqno.paralleljava.dependencyinjection.TestData.SomeTodo; +import nl.jqno.paralleljava.dependencyinjection.DefaultWiring; +import nl.jqno.paralleljava.dependencyinjection.TestWiring; import nl.jqno.picotest.Test; import java.util.UUID; @@ -13,7 +14,7 @@ public class InMemoryRepositoryTest extends Test { public void repository() { - var repo = new InMemoryRepository(NopLogger.FACTORY); + var repo = DefaultWiring.inMemoryRepository(TestWiring.nopLoggerFactory()); beforeEach(() -> { repo.clearAllTodos(); diff --git a/src/test/java/nl/jqno/paralleljava/app/persistence/RandomIdGeneratorTest.java b/src/test/java/nl/jqno/paralleljava/app/persistence/RandomIdGeneratorTest.java index bea8ff9..cc9e291 100644 --- a/src/test/java/nl/jqno/paralleljava/app/persistence/RandomIdGeneratorTest.java +++ b/src/test/java/nl/jqno/paralleljava/app/persistence/RandomIdGeneratorTest.java @@ -1,5 +1,6 @@ package nl.jqno.paralleljava.app.persistence; +import nl.jqno.paralleljava.dependencyinjection.DefaultWiring; import nl.jqno.picotest.Test; import org.assertj.core.api.Assertions; @@ -8,7 +9,7 @@ public class RandomIdGeneratorTest extends Test { public void uuidGenerator() { - var generator = new RandomIdGenerator(); + var generator = DefaultWiring.randomIdGenerator(); test("generates a valid uuid", () -> { UUID actual = generator.generateId(); diff --git a/src/test/java/nl/jqno/paralleljava/app/serialization/GsonSerializerTest.java b/src/test/java/nl/jqno/paralleljava/app/serialization/GsonSerializerTest.java index ab5e6c3..fa29b6d 100644 --- a/src/test/java/nl/jqno/paralleljava/app/serialization/GsonSerializerTest.java +++ b/src/test/java/nl/jqno/paralleljava/app/serialization/GsonSerializerTest.java @@ -2,16 +2,16 @@ import io.vavr.collection.List; import io.vavr.control.Option; -import nl.jqno.paralleljava.app.logging.NopLogger; import nl.jqno.paralleljava.dependencyinjection.DefaultWiring; +import nl.jqno.paralleljava.dependencyinjection.TestWiring; import nl.jqno.picotest.Test; -import static nl.jqno.paralleljava.app.TestData.*; +import static nl.jqno.paralleljava.dependencyinjection.TestData.*; import static org.assertj.core.api.Assertions.assertThat; public class GsonSerializerTest extends Test { - private Serializer serializer = DefaultWiring.gsonSerializer(NopLogger.FACTORY); + private Serializer serializer = DefaultWiring.gsonSerializer(TestWiring.nopLoggerFactory()); public void serializationOfASingleTodo() { diff --git a/src/test/java/nl/jqno/paralleljava/app/server/SparkServerTest.java b/src/test/java/nl/jqno/paralleljava/app/server/SparkServerTest.java index cec29bc..3c45086 100644 --- a/src/test/java/nl/jqno/paralleljava/app/server/SparkServerTest.java +++ b/src/test/java/nl/jqno/paralleljava/app/server/SparkServerTest.java @@ -3,9 +3,9 @@ import io.restassured.response.Response; import io.restassured.specification.RequestSpecification; import io.vavr.collection.List; -import io.vavr.control.Try; -import nl.jqno.paralleljava.app.controller.Controller; -import nl.jqno.paralleljava.app.logging.NopLogger; +import nl.jqno.paralleljava.dependencyinjection.DefaultWiring; +import nl.jqno.paralleljava.dependencyinjection.TestWiring; +import nl.jqno.paralleljava.dependencyinjection.stubs.StubController; import nl.jqno.picotest.Test; import spark.Spark; @@ -22,12 +22,14 @@ public class SparkServerTest extends Test { private static final String ENDPOINT_WITH_ID = ENDPOINT + "/some-id"; private final RequestSpecification when = given().port(PORT).when(); private StubController underlying; + private Server server; public void server() { - underlying = new StubController(); + underlying = TestWiring.stubController(); + server = DefaultWiring.sparkServer(ENDPOINT, PORT, underlying, TestWiring.nopLoggerFactory()); beforeAll(() -> { - new SparkServer(ENDPOINT, PORT, underlying, NopLogger.FACTORY).run(); + server.run(); Spark.awaitInitialization(); }); @@ -98,75 +100,4 @@ private void assertSingleCall(IntSupplier calledEndpoint) { assertThat(calledEndpoint.getAsInt()).isEqualTo(1); assertThat(underlying.calledTotal()).isEqualTo(1); } - - private static class StubController implements Controller { - - private static final Try SUCCESS = Try.success(""); - private static final Try FAILURE_4xx = Try.failure(new IllegalArgumentException()); - private static final Try FAILURE_5xx = Try.failure(new IllegalStateException()); - - public boolean nextRequestFails4xx = false; - public boolean nextRequestFails5xx = false; - public int calledGet = 0; - public int calledGetWithId = 0; - public int calledPost = 0; - public int calledPatchWithId = 0; - public int calledDelete = 0; - public int calledDeleteWithId = 0; - - public void clear() { - nextRequestFails4xx = false; - nextRequestFails5xx = false; - calledGet = 0; - calledGetWithId = 0; - calledPost = 0; - calledPatchWithId = 0; - calledDelete = 0; - calledDeleteWithId = 0; - } - - public int calledTotal() { - return calledGet + calledGetWithId + calledPost + calledPatchWithId + calledDelete + calledDeleteWithId; - } - - public Try get() { - calledGet += 1; - return response(); - } - - public Try get(String id) { - calledGetWithId += 1; - return response(); - } - - public Try post(String json) { - calledPost += 1; - return response(); - } - - public Try patch(String id, String json) { - calledPatchWithId += 1; - return response(); - } - - public Try delete() { - calledDelete += 1; - return response(); - } - - public Try delete(String id) { - calledDeleteWithId += 1; - return response(); - } - - private Try response() { - if (nextRequestFails4xx) { - return FAILURE_4xx; - } - if (nextRequestFails5xx) { - return FAILURE_5xx; - } - return SUCCESS; - } - } } diff --git a/src/test/java/nl/jqno/paralleljava/app/TestData.java b/src/test/java/nl/jqno/paralleljava/dependencyinjection/TestData.java similarity index 97% rename from src/test/java/nl/jqno/paralleljava/app/TestData.java rename to src/test/java/nl/jqno/paralleljava/dependencyinjection/TestData.java index 3611caf..459a6d6 100644 --- a/src/test/java/nl/jqno/paralleljava/app/TestData.java +++ b/src/test/java/nl/jqno/paralleljava/dependencyinjection/TestData.java @@ -1,4 +1,4 @@ -package nl.jqno.paralleljava.app; +package nl.jqno.paralleljava.dependencyinjection; import io.vavr.collection.List; import io.vavr.control.Option; diff --git a/src/test/java/nl/jqno/paralleljava/dependencyinjection/TestWiring.java b/src/test/java/nl/jqno/paralleljava/dependencyinjection/TestWiring.java new file mode 100644 index 0000000..eb32cbf --- /dev/null +++ b/src/test/java/nl/jqno/paralleljava/dependencyinjection/TestWiring.java @@ -0,0 +1,32 @@ +package nl.jqno.paralleljava.dependencyinjection; + +import nl.jqno.paralleljava.app.logging.LoggerFactory; +import nl.jqno.paralleljava.app.persistence.IdGenerator; +import nl.jqno.paralleljava.dependencyinjection.stubs.ConstantIdGenerator; +import nl.jqno.paralleljava.dependencyinjection.stubs.NopLogger; +import nl.jqno.paralleljava.dependencyinjection.stubs.StubController; +import nl.jqno.paralleljava.dependencyinjection.stubs.StubLogger; + +import java.util.UUID; + +public class TestWiring { + private TestWiring() { + // Don't instantiate + } + + public static LoggerFactory nopLoggerFactory() { + return c -> new NopLogger(); + } + + public static StubLogger stubLogger() { + return new StubLogger(); + } + + public static IdGenerator constantIdGenerator(UUID id) { + return new ConstantIdGenerator(id); + } + + public static StubController stubController() { + return new StubController(); + } +} diff --git a/src/test/java/nl/jqno/paralleljava/app/persistence/ConstantIdGenerator.java b/src/test/java/nl/jqno/paralleljava/dependencyinjection/stubs/ConstantIdGenerator.java similarity index 67% rename from src/test/java/nl/jqno/paralleljava/app/persistence/ConstantIdGenerator.java rename to src/test/java/nl/jqno/paralleljava/dependencyinjection/stubs/ConstantIdGenerator.java index ef3e894..9ce7d82 100644 --- a/src/test/java/nl/jqno/paralleljava/app/persistence/ConstantIdGenerator.java +++ b/src/test/java/nl/jqno/paralleljava/dependencyinjection/stubs/ConstantIdGenerator.java @@ -1,4 +1,6 @@ -package nl.jqno.paralleljava.app.persistence; +package nl.jqno.paralleljava.dependencyinjection.stubs; + +import nl.jqno.paralleljava.app.persistence.IdGenerator; import java.util.UUID; diff --git a/src/test/java/nl/jqno/paralleljava/app/logging/NopLogger.java b/src/test/java/nl/jqno/paralleljava/dependencyinjection/stubs/NopLogger.java similarity index 77% rename from src/test/java/nl/jqno/paralleljava/app/logging/NopLogger.java rename to src/test/java/nl/jqno/paralleljava/dependencyinjection/stubs/NopLogger.java index 0b461f7..075ba5e 100644 --- a/src/test/java/nl/jqno/paralleljava/app/logging/NopLogger.java +++ b/src/test/java/nl/jqno/paralleljava/dependencyinjection/stubs/NopLogger.java @@ -1,8 +1,8 @@ -package nl.jqno.paralleljava.app.logging; +package nl.jqno.paralleljava.dependencyinjection.stubs; -public class NopLogger implements Logger { +import nl.jqno.paralleljava.app.logging.Logger; - public static final LoggerFactory FACTORY = c -> new NopLogger(); +public class NopLogger implements Logger { public void forDevelopment(String message) {} public void forProduction(String message) {} diff --git a/src/test/java/nl/jqno/paralleljava/dependencyinjection/stubs/StubController.java b/src/test/java/nl/jqno/paralleljava/dependencyinjection/stubs/StubController.java new file mode 100644 index 0000000..b99b6f2 --- /dev/null +++ b/src/test/java/nl/jqno/paralleljava/dependencyinjection/stubs/StubController.java @@ -0,0 +1,75 @@ +package nl.jqno.paralleljava.dependencyinjection.stubs; + +import io.vavr.control.Try; +import nl.jqno.paralleljava.app.controller.Controller; + +public class StubController implements Controller { + + private static final Try SUCCESS = Try.success(""); + private static final Try FAILURE_4xx = Try.failure(new IllegalArgumentException()); + private static final Try FAILURE_5xx = Try.failure(new IllegalStateException()); + + public boolean nextRequestFails4xx = false; + public boolean nextRequestFails5xx = false; + public int calledGet = 0; + public int calledGetWithId = 0; + public int calledPost = 0; + public int calledPatchWithId = 0; + public int calledDelete = 0; + public int calledDeleteWithId = 0; + + public void clear() { + nextRequestFails4xx = false; + nextRequestFails5xx = false; + calledGet = 0; + calledGetWithId = 0; + calledPost = 0; + calledPatchWithId = 0; + calledDelete = 0; + calledDeleteWithId = 0; + } + + public int calledTotal() { + return calledGet + calledGetWithId + calledPost + calledPatchWithId + calledDelete + calledDeleteWithId; + } + + public Try get() { + calledGet += 1; + return response(); + } + + public Try get(String id) { + calledGetWithId += 1; + return response(); + } + + public Try post(String json) { + calledPost += 1; + return response(); + } + + public Try patch(String id, String json) { + calledPatchWithId += 1; + return response(); + } + + public Try delete() { + calledDelete += 1; + return response(); + } + + public Try delete(String id) { + calledDeleteWithId += 1; + return response(); + } + + private Try response() { + if (nextRequestFails4xx) { + return FAILURE_4xx; + } + if (nextRequestFails5xx) { + return FAILURE_5xx; + } + return SUCCESS; + } +} diff --git a/src/test/java/nl/jqno/paralleljava/dependencyinjection/stubs/StubLogger.java b/src/test/java/nl/jqno/paralleljava/dependencyinjection/stubs/StubLogger.java new file mode 100644 index 0000000..12cbb23 --- /dev/null +++ b/src/test/java/nl/jqno/paralleljava/dependencyinjection/stubs/StubLogger.java @@ -0,0 +1,45 @@ +package nl.jqno.paralleljava.dependencyinjection.stubs; + +import org.slf4j.helpers.SubstituteLogger; + +public class StubLogger extends SubstituteLogger { + public int calledDebug = 0; + public int calledInfo = 0; + public int calledWarn = 0; + public int calledError = 0; + public Throwable lastThrowable = null; + + public StubLogger() { + super(StubLogger.class.getName()); + } + + public int calledTotal() { + return calledDebug + calledInfo + calledWarn + calledError; + } + + public void debug(String msg) { + calledDebug += 1; + } + + public void info(String msg) { + calledInfo += 1; + } + + public void warn(String msg) { + calledWarn += 1; + } + + public void warn(String msg, Throwable e) { + calledWarn += 1; + lastThrowable = e; + } + + public void error(String msg) { + calledError += 1; + } + + public void error(String msg, Throwable e) { + calledError += 1; + lastThrowable = e; + } +} From 57ba636973b0eb4f3b2760c9fff2deb2de8a52d5 Mon Sep 17 00:00:00 2001 From: Jan Ouwens Date: Mon, 25 Mar 2019 16:44:41 +0100 Subject: [PATCH 072/106] Renames DefaultWiring to Wiring --- src/main/java/nl/jqno/paralleljava/Main.java | 14 +++++++------- .../{DefaultWiring.java => Wiring.java} | 4 ++-- .../nl/jqno/paralleljava/ArchitectureTest.java | 4 ++-- .../app/controller/DefaultControllerTest.java | 6 +++--- .../app/persistence/InMemoryRepositoryTest.java | 4 ++-- .../app/persistence/RandomIdGeneratorTest.java | 4 ++-- .../app/serialization/GsonSerializerTest.java | 4 ++-- .../paralleljava/app/server/SparkServerTest.java | 4 ++-- 8 files changed, 22 insertions(+), 22 deletions(-) rename src/main/java/nl/jqno/paralleljava/dependencyinjection/{DefaultWiring.java => Wiring.java} (97%) diff --git a/src/main/java/nl/jqno/paralleljava/Main.java b/src/main/java/nl/jqno/paralleljava/Main.java index db19751..672886b 100644 --- a/src/main/java/nl/jqno/paralleljava/Main.java +++ b/src/main/java/nl/jqno/paralleljava/Main.java @@ -1,6 +1,6 @@ package nl.jqno.paralleljava; -import nl.jqno.paralleljava.dependencyinjection.DefaultWiring; +import nl.jqno.paralleljava.dependencyinjection.Wiring; public class Main { private static final int DEFAULT_PORT = 4567; @@ -8,16 +8,16 @@ public class Main { private static final String ENDPOINT = "/todo"; public static void main(String... args) { - var loggerFactory = DefaultWiring.slf4jLoggerFactory(); + var loggerFactory = Wiring.slf4jLoggerFactory(); - var heroku = DefaultWiring.heroku(); + var heroku = Wiring.heroku(); var fullUrl = heroku.getHostUrl().getOrElse(DEFAULT_URL) + ENDPOINT; var port = heroku.getAssignedPort().getOrElse(DEFAULT_PORT); - var repository = DefaultWiring.inMemoryRepository(loggerFactory); - var idGenerator = DefaultWiring.randomIdGenerator(); - var controller = DefaultWiring.defaultController(fullUrl, repository, idGenerator, loggerFactory); - var server = DefaultWiring.sparkServer(ENDPOINT, port, controller, loggerFactory); + var repository = Wiring.inMemoryRepository(loggerFactory); + var idGenerator = Wiring.randomIdGenerator(); + var controller = Wiring.defaultController(fullUrl, repository, idGenerator, loggerFactory); + var server = Wiring.sparkServer(ENDPOINT, port, controller, loggerFactory); server.run(); } diff --git a/src/main/java/nl/jqno/paralleljava/dependencyinjection/DefaultWiring.java b/src/main/java/nl/jqno/paralleljava/dependencyinjection/Wiring.java similarity index 97% rename from src/main/java/nl/jqno/paralleljava/dependencyinjection/DefaultWiring.java rename to src/main/java/nl/jqno/paralleljava/dependencyinjection/Wiring.java index f4edb34..138e378 100644 --- a/src/main/java/nl/jqno/paralleljava/dependencyinjection/DefaultWiring.java +++ b/src/main/java/nl/jqno/paralleljava/dependencyinjection/Wiring.java @@ -17,9 +17,9 @@ import nl.jqno.paralleljava.app.server.Server; import nl.jqno.paralleljava.app.server.SparkServer; -public class DefaultWiring { +public class Wiring { - private DefaultWiring() { + private Wiring() { // Don't instantiate } diff --git a/src/test/java/nl/jqno/paralleljava/ArchitectureTest.java b/src/test/java/nl/jqno/paralleljava/ArchitectureTest.java index 4124ffd..81ec0d5 100644 --- a/src/test/java/nl/jqno/paralleljava/ArchitectureTest.java +++ b/src/test/java/nl/jqno/paralleljava/ArchitectureTest.java @@ -5,7 +5,7 @@ import nl.jqno.paralleljava.app.logging.Slf4jLogger; import nl.jqno.paralleljava.app.serialization.GsonSerializer; import nl.jqno.paralleljava.app.server.SparkServer; -import nl.jqno.paralleljava.dependencyinjection.DefaultWiring; +import nl.jqno.paralleljava.dependencyinjection.Wiring; import nl.jqno.picotest.Test; import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.noClasses; @@ -32,7 +32,7 @@ public void architecture() { private void assertBoundary(String restrictedPackageIdentifier, Package whiteListedPackage) { var rule = noClasses() .that().resideOutsideOfPackage(whiteListedPackage.getName()) - .and().dontHaveFullyQualifiedName(DefaultWiring.class.getCanonicalName()) + .and().dontHaveFullyQualifiedName(Wiring.class.getCanonicalName()) .and().resideOutsideOfPackage("nl.jqno.paralleljava.dependencyinjection.stubs") .should().accessClassesThat().resideInAPackage(restrictedPackageIdentifier); rule.check(IMPORTED_CLASSES); diff --git a/src/test/java/nl/jqno/paralleljava/app/controller/DefaultControllerTest.java b/src/test/java/nl/jqno/paralleljava/app/controller/DefaultControllerTest.java index 372449a..a8c83f3 100644 --- a/src/test/java/nl/jqno/paralleljava/app/controller/DefaultControllerTest.java +++ b/src/test/java/nl/jqno/paralleljava/app/controller/DefaultControllerTest.java @@ -3,7 +3,7 @@ import io.vavr.control.Option; import io.vavr.control.Try; import nl.jqno.paralleljava.app.domain.Todo; -import nl.jqno.paralleljava.dependencyinjection.DefaultWiring; +import nl.jqno.paralleljava.dependencyinjection.Wiring; import nl.jqno.paralleljava.dependencyinjection.TestWiring; import nl.jqno.picotest.Test; @@ -19,9 +19,9 @@ public void controller() { var fullUrl = "/blabla/todo"; var loggerFactory = TestWiring.nopLoggerFactory(); - var repository = DefaultWiring.inMemoryRepository(loggerFactory); + var repository = Wiring.inMemoryRepository(loggerFactory); var idGenerator = TestWiring.constantIdGenerator(constantId); - var serializer = DefaultWiring.gsonSerializer(loggerFactory); + var serializer = Wiring.gsonSerializer(loggerFactory); var controller = new DefaultController(fullUrl, repository, idGenerator, serializer, loggerFactory); diff --git a/src/test/java/nl/jqno/paralleljava/app/persistence/InMemoryRepositoryTest.java b/src/test/java/nl/jqno/paralleljava/app/persistence/InMemoryRepositoryTest.java index 2dd36ab..91fa429 100644 --- a/src/test/java/nl/jqno/paralleljava/app/persistence/InMemoryRepositoryTest.java +++ b/src/test/java/nl/jqno/paralleljava/app/persistence/InMemoryRepositoryTest.java @@ -3,7 +3,7 @@ import io.vavr.control.Option; import nl.jqno.paralleljava.dependencyinjection.TestData.AnotherTodo; import nl.jqno.paralleljava.dependencyinjection.TestData.SomeTodo; -import nl.jqno.paralleljava.dependencyinjection.DefaultWiring; +import nl.jqno.paralleljava.dependencyinjection.Wiring; import nl.jqno.paralleljava.dependencyinjection.TestWiring; import nl.jqno.picotest.Test; @@ -14,7 +14,7 @@ public class InMemoryRepositoryTest extends Test { public void repository() { - var repo = DefaultWiring.inMemoryRepository(TestWiring.nopLoggerFactory()); + var repo = Wiring.inMemoryRepository(TestWiring.nopLoggerFactory()); beforeEach(() -> { repo.clearAllTodos(); diff --git a/src/test/java/nl/jqno/paralleljava/app/persistence/RandomIdGeneratorTest.java b/src/test/java/nl/jqno/paralleljava/app/persistence/RandomIdGeneratorTest.java index cc9e291..a4cc9d3 100644 --- a/src/test/java/nl/jqno/paralleljava/app/persistence/RandomIdGeneratorTest.java +++ b/src/test/java/nl/jqno/paralleljava/app/persistence/RandomIdGeneratorTest.java @@ -1,6 +1,6 @@ package nl.jqno.paralleljava.app.persistence; -import nl.jqno.paralleljava.dependencyinjection.DefaultWiring; +import nl.jqno.paralleljava.dependencyinjection.Wiring; import nl.jqno.picotest.Test; import org.assertj.core.api.Assertions; @@ -9,7 +9,7 @@ public class RandomIdGeneratorTest extends Test { public void uuidGenerator() { - var generator = DefaultWiring.randomIdGenerator(); + var generator = Wiring.randomIdGenerator(); test("generates a valid uuid", () -> { UUID actual = generator.generateId(); diff --git a/src/test/java/nl/jqno/paralleljava/app/serialization/GsonSerializerTest.java b/src/test/java/nl/jqno/paralleljava/app/serialization/GsonSerializerTest.java index fa29b6d..bd3e472 100644 --- a/src/test/java/nl/jqno/paralleljava/app/serialization/GsonSerializerTest.java +++ b/src/test/java/nl/jqno/paralleljava/app/serialization/GsonSerializerTest.java @@ -2,7 +2,7 @@ import io.vavr.collection.List; import io.vavr.control.Option; -import nl.jqno.paralleljava.dependencyinjection.DefaultWiring; +import nl.jqno.paralleljava.dependencyinjection.Wiring; import nl.jqno.paralleljava.dependencyinjection.TestWiring; import nl.jqno.picotest.Test; @@ -11,7 +11,7 @@ public class GsonSerializerTest extends Test { - private Serializer serializer = DefaultWiring.gsonSerializer(TestWiring.nopLoggerFactory()); + private Serializer serializer = Wiring.gsonSerializer(TestWiring.nopLoggerFactory()); public void serializationOfASingleTodo() { diff --git a/src/test/java/nl/jqno/paralleljava/app/server/SparkServerTest.java b/src/test/java/nl/jqno/paralleljava/app/server/SparkServerTest.java index 3c45086..b3744ed 100644 --- a/src/test/java/nl/jqno/paralleljava/app/server/SparkServerTest.java +++ b/src/test/java/nl/jqno/paralleljava/app/server/SparkServerTest.java @@ -3,7 +3,7 @@ import io.restassured.response.Response; import io.restassured.specification.RequestSpecification; import io.vavr.collection.List; -import nl.jqno.paralleljava.dependencyinjection.DefaultWiring; +import nl.jqno.paralleljava.dependencyinjection.Wiring; import nl.jqno.paralleljava.dependencyinjection.TestWiring; import nl.jqno.paralleljava.dependencyinjection.stubs.StubController; import nl.jqno.picotest.Test; @@ -26,7 +26,7 @@ public class SparkServerTest extends Test { public void server() { underlying = TestWiring.stubController(); - server = DefaultWiring.sparkServer(ENDPOINT, PORT, underlying, TestWiring.nopLoggerFactory()); + server = Wiring.sparkServer(ENDPOINT, PORT, underlying, TestWiring.nopLoggerFactory()); beforeAll(() -> { server.run(); From 341d5c34556abf3a47a658be26051a88a7aa5a25 Mon Sep 17 00:00:00 2001 From: Jan Ouwens Date: Mon, 25 Mar 2019 16:54:08 +0100 Subject: [PATCH 073/106] Turns Heroku into a proper interface with implementation --- src/main/java/nl/jqno/paralleljava/Main.java | 6 +++--- .../app/environment/Environment.java | 8 ++++++++ .../HerokuEnvironment.java} | 10 +++++----- .../dependencyinjection/Wiring.java | 7 ++++--- .../HerokuEnvironmentTest.java} | 18 +++++++++--------- .../dependencyinjection/TestWiring.java | 7 +++++++ 6 files changed, 36 insertions(+), 20 deletions(-) create mode 100644 src/main/java/nl/jqno/paralleljava/app/environment/Environment.java rename src/main/java/nl/jqno/paralleljava/app/{server/Heroku.java => environment/HerokuEnvironment.java} (66%) rename src/test/java/nl/jqno/paralleljava/app/{server/HerokuTest.java => environment/HerokuEnvironmentTest.java} (65%) diff --git a/src/main/java/nl/jqno/paralleljava/Main.java b/src/main/java/nl/jqno/paralleljava/Main.java index 672886b..d7dcae2 100644 --- a/src/main/java/nl/jqno/paralleljava/Main.java +++ b/src/main/java/nl/jqno/paralleljava/Main.java @@ -10,9 +10,9 @@ public class Main { public static void main(String... args) { var loggerFactory = Wiring.slf4jLoggerFactory(); - var heroku = Wiring.heroku(); - var fullUrl = heroku.getHostUrl().getOrElse(DEFAULT_URL) + ENDPOINT; - var port = heroku.getAssignedPort().getOrElse(DEFAULT_PORT); + var environment = Wiring.herokuEnvironment(); + var fullUrl = environment.hostUrl().getOrElse(DEFAULT_URL) + ENDPOINT; + var port = environment.port().getOrElse(DEFAULT_PORT); var repository = Wiring.inMemoryRepository(loggerFactory); var idGenerator = Wiring.randomIdGenerator(); diff --git a/src/main/java/nl/jqno/paralleljava/app/environment/Environment.java b/src/main/java/nl/jqno/paralleljava/app/environment/Environment.java new file mode 100644 index 0000000..6cb75d1 --- /dev/null +++ b/src/main/java/nl/jqno/paralleljava/app/environment/Environment.java @@ -0,0 +1,8 @@ +package nl.jqno.paralleljava.app.environment; + +import io.vavr.control.Option; + +public interface Environment { + Option port(); + Option hostUrl(); +} diff --git a/src/main/java/nl/jqno/paralleljava/app/server/Heroku.java b/src/main/java/nl/jqno/paralleljava/app/environment/HerokuEnvironment.java similarity index 66% rename from src/main/java/nl/jqno/paralleljava/app/server/Heroku.java rename to src/main/java/nl/jqno/paralleljava/app/environment/HerokuEnvironment.java index 3efcca6..37eed05 100644 --- a/src/main/java/nl/jqno/paralleljava/app/server/Heroku.java +++ b/src/main/java/nl/jqno/paralleljava/app/environment/HerokuEnvironment.java @@ -1,22 +1,22 @@ -package nl.jqno.paralleljava.app.server; +package nl.jqno.paralleljava.app.environment; import io.vavr.collection.Map; import io.vavr.control.Option; import io.vavr.control.Try; -public class Heroku { +public class HerokuEnvironment implements Environment { private final Map env; - public Heroku(Map env) { + public HerokuEnvironment(Map env) { this.env = env; } - public Option getAssignedPort() { + public Option port() { return env.get("PORT").flatMap(this::parse); } - public Option getHostUrl() { + public Option hostUrl() { // hard-coded for now return Option.some("https://parallel-java.herokuapp.com"); } diff --git a/src/main/java/nl/jqno/paralleljava/dependencyinjection/Wiring.java b/src/main/java/nl/jqno/paralleljava/dependencyinjection/Wiring.java index 138e378..d7b41ef 100644 --- a/src/main/java/nl/jqno/paralleljava/dependencyinjection/Wiring.java +++ b/src/main/java/nl/jqno/paralleljava/dependencyinjection/Wiring.java @@ -5,6 +5,8 @@ import io.vavr.gson.VavrGson; import nl.jqno.paralleljava.app.controller.Controller; import nl.jqno.paralleljava.app.controller.DefaultController; +import nl.jqno.paralleljava.app.environment.Environment; +import nl.jqno.paralleljava.app.environment.HerokuEnvironment; import nl.jqno.paralleljava.app.logging.LoggerFactory; import nl.jqno.paralleljava.app.logging.Slf4jLogger; import nl.jqno.paralleljava.app.persistence.IdGenerator; @@ -13,7 +15,6 @@ import nl.jqno.paralleljava.app.persistence.Repository; import nl.jqno.paralleljava.app.serialization.GsonSerializer; import nl.jqno.paralleljava.app.serialization.Serializer; -import nl.jqno.paralleljava.app.server.Heroku; import nl.jqno.paralleljava.app.server.Server; import nl.jqno.paralleljava.app.server.SparkServer; @@ -23,10 +24,10 @@ private Wiring() { // Don't instantiate } - public static Heroku heroku() { + public static Environment herokuEnvironment() { var processBuilder = new ProcessBuilder(); var environment = HashMap.ofAll(processBuilder.environment()); - return new Heroku(environment); + return new HerokuEnvironment(environment); } public static LoggerFactory slf4jLoggerFactory() { diff --git a/src/test/java/nl/jqno/paralleljava/app/server/HerokuTest.java b/src/test/java/nl/jqno/paralleljava/app/environment/HerokuEnvironmentTest.java similarity index 65% rename from src/test/java/nl/jqno/paralleljava/app/server/HerokuTest.java rename to src/test/java/nl/jqno/paralleljava/app/environment/HerokuEnvironmentTest.java index de54f4e..3cc40a7 100644 --- a/src/test/java/nl/jqno/paralleljava/app/server/HerokuTest.java +++ b/src/test/java/nl/jqno/paralleljava/app/environment/HerokuEnvironmentTest.java @@ -1,42 +1,42 @@ -package nl.jqno.paralleljava.app.server; +package nl.jqno.paralleljava.app.environment; import io.vavr.collection.HashMap; import io.vavr.control.Option; -import nl.jqno.paralleljava.app.server.Heroku; +import nl.jqno.paralleljava.dependencyinjection.TestWiring; import nl.jqno.picotest.Test; import static org.assertj.core.api.Assertions.assertThat; -public class HerokuTest extends Test { +public class HerokuEnvironmentTest extends Test { - private Heroku heroku = new Heroku(HashMap.empty()); + private Environment environment = TestWiring.mapBasedHerokuEnvironment(HashMap.empty()); public void port() { test("a valid port is provided", () -> { setEnvironmentVariable("PORT", "42"); - var actual = heroku.getAssignedPort(); + var actual = environment.port(); assertThat(actual).isEqualTo(Option.of(42)); }); test("an invalid port is provided", () -> { setEnvironmentVariable("PORT", "this is not the port you're looking for"); - var actual = heroku.getAssignedPort(); + var actual = environment.port(); assertThat(actual).isEqualTo(Option.none()); }); test("no port is provided", () -> { - var actual = heroku.getAssignedPort(); + var actual = environment.port(); assertThat(actual).isEqualTo(Option.none()); }); test("host url", () -> { - var actual = heroku.getHostUrl(); + var actual = environment.hostUrl(); assertThat(actual).isEqualTo(Option.some("https://parallel-java.herokuapp.com")); }); } private void setEnvironmentVariable(String key, String value) { var env = HashMap.of(key, value); - heroku = new Heroku(env); + environment = TestWiring.mapBasedHerokuEnvironment(env); } } diff --git a/src/test/java/nl/jqno/paralleljava/dependencyinjection/TestWiring.java b/src/test/java/nl/jqno/paralleljava/dependencyinjection/TestWiring.java index eb32cbf..8ba56e8 100644 --- a/src/test/java/nl/jqno/paralleljava/dependencyinjection/TestWiring.java +++ b/src/test/java/nl/jqno/paralleljava/dependencyinjection/TestWiring.java @@ -1,5 +1,8 @@ package nl.jqno.paralleljava.dependencyinjection; +import io.vavr.collection.Map; +import nl.jqno.paralleljava.app.environment.Environment; +import nl.jqno.paralleljava.app.environment.HerokuEnvironment; import nl.jqno.paralleljava.app.logging.LoggerFactory; import nl.jqno.paralleljava.app.persistence.IdGenerator; import nl.jqno.paralleljava.dependencyinjection.stubs.ConstantIdGenerator; @@ -14,6 +17,10 @@ private TestWiring() { // Don't instantiate } + public static Environment mapBasedHerokuEnvironment(Map map) { + return new HerokuEnvironment(map); + } + public static LoggerFactory nopLoggerFactory() { return c -> new NopLogger(); } From e9dd055a2226fae2a1e111d1d3a4290cc1324921 Mon Sep 17 00:00:00 2001 From: Jan Ouwens Date: Mon, 25 Mar 2019 16:59:30 +0100 Subject: [PATCH 074/106] Adds invalid data to TestData --- .../app/serialization/GsonSerializerTest.java | 10 ++++------ .../paralleljava/dependencyinjection/TestData.java | 5 +++++ 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/src/test/java/nl/jqno/paralleljava/app/serialization/GsonSerializerTest.java b/src/test/java/nl/jqno/paralleljava/app/serialization/GsonSerializerTest.java index bd3e472..d824102 100644 --- a/src/test/java/nl/jqno/paralleljava/app/serialization/GsonSerializerTest.java +++ b/src/test/java/nl/jqno/paralleljava/app/serialization/GsonSerializerTest.java @@ -2,6 +2,7 @@ import io.vavr.collection.List; import io.vavr.control.Option; +import nl.jqno.paralleljava.dependencyinjection.TestData; import nl.jqno.paralleljava.dependencyinjection.Wiring; import nl.jqno.paralleljava.dependencyinjection.TestWiring; import nl.jqno.picotest.Test; @@ -31,8 +32,7 @@ public void serializationOfASingleTodo() { }); test("Deserialization of a Todo returns none when json is invalid", () -> { - var invalidJson = "this is an invalid json document"; - var actual = serializer.deserializeTodo(invalidJson); + var actual = serializer.deserializeTodo(Invalid.JSON); assertThat(actual).isEqualTo(Option.none()); }); @@ -60,8 +60,7 @@ public void serializationOfACompletePartialTodo() { }); test("Deserialization of a complete PartialTodo returns none when json is invalid", () -> { - var invalidJson = "this is an invalid json document"; - var actual = serializer.deserializePartialTodo(invalidJson); + var actual = serializer.deserializePartialTodo(Invalid.JSON); assertThat(actual).isEqualTo(Option.none()); }); @@ -135,8 +134,7 @@ public void serializationOfAUuid() { }); test("Deserialization of a UUID returns none when json is invalid", () -> { - var invalidJson = "this is an invalid json document"; - var actual = serializer.deserializeUuid(invalidJson); + var actual = serializer.deserializeUuid(Invalid.ID); assertThat(actual).isEqualTo(Option.none()); }); diff --git a/src/test/java/nl/jqno/paralleljava/dependencyinjection/TestData.java b/src/test/java/nl/jqno/paralleljava/dependencyinjection/TestData.java index 459a6d6..cf19487 100644 --- a/src/test/java/nl/jqno/paralleljava/dependencyinjection/TestData.java +++ b/src/test/java/nl/jqno/paralleljava/dependencyinjection/TestData.java @@ -48,4 +48,9 @@ public static class ListOfTodos { public static final String SERIALIZED = "[" + SomeTodo.SERIALIZED + "," + AnotherTodo.SERIALIZED + "]"; } + + public static class Invalid { + public static final String ID = "this is an invalid uuid"; + public static final String JSON = "this is an invalid json document"; + } } From f8d4cb56c6810aed6eae3fbc3ef797b0ad6936a5 Mon Sep 17 00:00:00 2001 From: Jan Ouwens Date: Tue, 26 Mar 2019 07:44:01 +0100 Subject: [PATCH 075/106] Adds tests for failure cases in DefaultController --- .../app/controller/DefaultController.java | 31 +++---- .../app/controller/DefaultControllerTest.java | 92 +++++++++++++++++-- .../dependencyinjection/TestData.java | 1 + 3 files changed, 100 insertions(+), 24 deletions(-) diff --git a/src/main/java/nl/jqno/paralleljava/app/controller/DefaultController.java b/src/main/java/nl/jqno/paralleljava/app/controller/DefaultController.java index 4992abf..a494db8 100644 --- a/src/main/java/nl/jqno/paralleljava/app/controller/DefaultController.java +++ b/src/main/java/nl/jqno/paralleljava/app/controller/DefaultController.java @@ -65,22 +65,21 @@ public Try patch(String id, String json) { var pt = partialTodo.get(); var existingTodo = repository.get(uuid.get()); - if (existingTodo.isFailure()) { - return existingTodo.map(ignored -> ""); - } - if (existingTodo.get().isEmpty()) { - return Try.failure(new IllegalArgumentException("Can't find Todo with id " + id)); - } - - var todo = existingTodo.get().get(); - var updatedTodo = new Todo( - todo.id(), - pt.title().getOrElse(todo.title()), - todo.url(), - pt.completed().getOrElse(todo.completed()), - pt.order().getOrElse(todo.order())); - repository.updateTodo(updatedTodo); - return Try.of(() -> serializer.serializeTodo(updatedTodo)); + return existingTodo.flatMap(et -> { + if (existingTodo.get().isEmpty()) { + return Try.failure(new IllegalArgumentException("Can't find Todo with id " + id)); + } + + var todo = existingTodo.get().get(); + var updatedTodo = new Todo( + todo.id(), + pt.title().getOrElse(todo.title()), + todo.url(), + pt.completed().getOrElse(todo.completed()), + pt.order().getOrElse(todo.order())); + repository.updateTodo(updatedTodo); + return Try.of(() -> serializer.serializeTodo(updatedTodo)); + }); } public Try delete() { diff --git a/src/test/java/nl/jqno/paralleljava/app/controller/DefaultControllerTest.java b/src/test/java/nl/jqno/paralleljava/app/controller/DefaultControllerTest.java index a8c83f3..818286f 100644 --- a/src/test/java/nl/jqno/paralleljava/app/controller/DefaultControllerTest.java +++ b/src/test/java/nl/jqno/paralleljava/app/controller/DefaultControllerTest.java @@ -3,6 +3,10 @@ import io.vavr.control.Option; import io.vavr.control.Try; import nl.jqno.paralleljava.app.domain.Todo; +import nl.jqno.paralleljava.app.logging.LoggerFactory; +import nl.jqno.paralleljava.app.persistence.IdGenerator; +import nl.jqno.paralleljava.app.persistence.Repository; +import nl.jqno.paralleljava.app.serialization.Serializer; import nl.jqno.paralleljava.dependencyinjection.Wiring; import nl.jqno.paralleljava.dependencyinjection.TestWiring; import nl.jqno.picotest.Test; @@ -14,17 +18,17 @@ public class DefaultControllerTest extends Test { - public void controller() { - var constantId = UUID.randomUUID(); - var fullUrl = "/blabla/todo"; + private final UUID constantId = UUID.randomUUID(); + private final String fullUrl = "/blabla/todo"; - var loggerFactory = TestWiring.nopLoggerFactory(); - var repository = Wiring.inMemoryRepository(loggerFactory); - var idGenerator = TestWiring.constantIdGenerator(constantId); - var serializer = Wiring.gsonSerializer(loggerFactory); + private final LoggerFactory loggerFactory = TestWiring.nopLoggerFactory(); + private final Repository repository = Wiring.inMemoryRepository(loggerFactory); + private final IdGenerator idGenerator = TestWiring.constantIdGenerator(constantId); + private final Serializer serializer = Wiring.gsonSerializer(loggerFactory); - var controller = new DefaultController(fullUrl, repository, idGenerator, serializer, loggerFactory); + private final DefaultController controller = new DefaultController(fullUrl, repository, idGenerator, serializer, loggerFactory); + public void get() { beforeEach(() -> { repository.clearAllTodos(); }); @@ -41,6 +45,12 @@ public void controller() { var actual = controller.get(); assertThat(actual).isEqualTo(Try.success(ListOfTodos.SERIALIZED)); }); + } + + public void getWithId() { + beforeEach(() -> { + repository.clearAllTodos(); + }); test("get with id returns a specific serialized todo if it exists", () -> { repository.createTodo(SomeTodo.TODO); @@ -49,11 +59,23 @@ public void controller() { assertThat(actual).isEqualTo(Try.success(SomeTodo.SERIALIZED)); }); + test("get with id fails if id is invalid", () -> { + var actual = controller.get(Invalid.ID); + assertThat(actual.isFailure()).isTrue(); + assertThat(actual.getCause().getClass()).isEqualTo(IllegalArgumentException.class); + }); + test("get with id fails if it doesn't exist", () -> { var actual = controller.get(SomeTodo.ID.toString()); assertThat(actual.isFailure()).isTrue(); assertThat(actual.getCause().getClass()).isEqualTo(IllegalArgumentException.class); }); + } + + public void post() { + beforeEach(() -> { + repository.clearAllTodos(); + }); test("post adds a todo without order", () -> { var expected = new Todo(constantId, "title", fullUrl + "/" + constantId, false, 0); @@ -73,6 +95,24 @@ public void controller() { assertThat(repository.getAllTodos().get()).contains(expected); }); + test("post fails when todo is invalid", () -> { + var actual = controller.post(Invalid.JSON); + assertThat(actual.isFailure()).isTrue(); + assertThat(actual.getCause().getClass()).isEqualTo(IllegalArgumentException.class); + }); + + test("post fails when todo has no title", () -> { + var actual = controller.post(Invalid.SERIALIZED_TODO_WITH_NO_TITLE); + assertThat(actual.isFailure()).isTrue(); + assertThat(actual.getCause().getClass()).isEqualTo(IllegalArgumentException.class); + }); + } + + public void patch() { + beforeEach(() -> { + repository.clearAllTodos(); + }); + test("patch changes title", () -> { repository.createTodo(SomeTodo.TODO); var expected = SomeTodo.TODO.withTitle("another title"); @@ -106,6 +146,30 @@ public void controller() { assertThat(actual.get()).isEqualTo(Option.some(expected)); }); + test("delete with id fails if id is invalid", () -> { + var actual = controller.patch(Invalid.ID, "{\"order\":47}"); + assertThat(actual.isFailure()).isTrue(); + assertThat(actual.getCause().getClass()).isEqualTo(IllegalArgumentException.class); + }); + + test("post fails when todo is invalid", () -> { + var actual = controller.patch(SomeTodo.ID.toString(), Invalid.JSON); + assertThat(actual.isFailure()).isTrue(); + assertThat(actual.getCause().getClass()).isEqualTo(IllegalArgumentException.class); + }); + + test("patch fails if id doesn't exist", () -> { + var actual = controller.patch(SomeTodo.ID.toString(), "{\"order\":47}"); + assertThat(actual.isFailure()).isTrue(); + assertThat(actual.getCause().getClass()).isEqualTo(IllegalArgumentException.class); + }); + } + + public void delete() { + beforeEach(() -> { + repository.clearAllTodos(); + }); + test("delete clears all todos", () -> { repository.createTodo(SomeTodo.TODO); @@ -114,6 +178,12 @@ public void controller() { assertThat(actual).isEqualTo(Try.success("")); assertThat(repository.getAllTodos().get()).isEmpty(); }); + } + + public void deleteWithId() { + beforeEach(() -> { + repository.clearAllTodos(); + }); test("delete with id removes the corresponding todo", () -> { repository.createTodo(AnotherTodo.TODO); @@ -126,5 +196,11 @@ public void controller() { .doesNotContain(SomeTodo.TODO) .contains(AnotherTodo.TODO); }); + + test("delete with id fails if id is invalid", () -> { + var actual = controller.delete(Invalid.ID); + assertThat(actual.isFailure()).isTrue(); + assertThat(actual.getCause().getClass()).isEqualTo(IllegalArgumentException.class); + }); } } diff --git a/src/test/java/nl/jqno/paralleljava/dependencyinjection/TestData.java b/src/test/java/nl/jqno/paralleljava/dependencyinjection/TestData.java index cf19487..5009aab 100644 --- a/src/test/java/nl/jqno/paralleljava/dependencyinjection/TestData.java +++ b/src/test/java/nl/jqno/paralleljava/dependencyinjection/TestData.java @@ -52,5 +52,6 @@ public static class ListOfTodos { public static class Invalid { public static final String ID = "this is an invalid uuid"; public static final String JSON = "this is an invalid json document"; + public static final String SERIALIZED_TODO_WITH_NO_TITLE = "{\"order\":1337}"; } } From 16661d0d3221c29e52526e91d9529dbba5954ac1 Mon Sep 17 00:00:00 2001 From: Jan Ouwens Date: Tue, 26 Mar 2019 08:35:47 +0100 Subject: [PATCH 076/106] Sets coverage of everything except wiring to 100% --- pom.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/pom.yml b/pom.yml index e4bf497..cdf25c9 100644 --- a/pom.yml +++ b/pom.yml @@ -11,7 +11,7 @@ properties: { encoding: utf-8, maven.compiler.source: 11, maven.compiler.target: 11, - coverage.threshold: 0.88 + coverage.threshold: 1.0 } repositories: @@ -82,6 +82,10 @@ build: - groupId: org.jacoco artifactId: jacoco-maven-plugin version: 0.8.3 + configuration: + excludes: + - "nl/jqno/paralleljava/Main.class" + - "nl/jqno/paralleljava/dependencyinjection/**" executions: - id: default-prepare-angent goals: [prepare-agent] From ab14483a339de924103dc0d5c77fec41e14e15ea Mon Sep 17 00:00:00 2001 From: Jan Ouwens Date: Tue, 26 Mar 2019 16:17:32 +0100 Subject: [PATCH 077/106] Makes slf4j-api dependency explicit --- pom.yml | 1 + .../paralleljava/dependencyinjection/stubs/StubLogger.java | 4 +++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/pom.yml b/pom.yml index cdf25c9..d08e675 100644 --- a/pom.yml +++ b/pom.yml @@ -21,6 +21,7 @@ dependencies: - { groupId: io.vavr, artifactId: vavr, version: 0.10.0 } - { groupId: io.vavr, artifactId: vavr-gson, version: 0.10.0 } - { groupId: com.sparkjava, artifactId: spark-core, version: 2.7.2 } + - { groupId: org.slf4j, artifactId: slf4j-api, version: 1.7.26 } - { groupId: org.slf4j, artifactId: slf4j-simple, version: 1.7.26 } - { groupId: com.google.code.gson, artifactId: gson, version: 2.8.5 } - { groupId: nl.jqno.picotest, artifactId: picotest, version: 0.3, scope: test } diff --git a/src/test/java/nl/jqno/paralleljava/dependencyinjection/stubs/StubLogger.java b/src/test/java/nl/jqno/paralleljava/dependencyinjection/stubs/StubLogger.java index 12cbb23..9b63243 100644 --- a/src/test/java/nl/jqno/paralleljava/dependencyinjection/stubs/StubLogger.java +++ b/src/test/java/nl/jqno/paralleljava/dependencyinjection/stubs/StubLogger.java @@ -2,6 +2,8 @@ import org.slf4j.helpers.SubstituteLogger; +import java.util.concurrent.LinkedBlockingQueue; + public class StubLogger extends SubstituteLogger { public int calledDebug = 0; public int calledInfo = 0; @@ -10,7 +12,7 @@ public class StubLogger extends SubstituteLogger { public Throwable lastThrowable = null; public StubLogger() { - super(StubLogger.class.getName()); + super(StubLogger.class.getName(), new LinkedBlockingQueue<>(), false); } public int calledTotal() { From fd481ba4efab53827f1a69f270e237366cd2f1cd Mon Sep 17 00:00:00 2001 From: Jan Ouwens Date: Tue, 26 Mar 2019 16:16:40 +0100 Subject: [PATCH 078/106] Moves InMemoryRepository to a separate package --- .../app/persistence/{ => inmemory}/InMemoryRepository.java | 3 ++- .../java/nl/jqno/paralleljava/dependencyinjection/Wiring.java | 2 +- .../app/persistence/{ => inmemory}/InMemoryRepositoryTest.java | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) rename src/main/java/nl/jqno/paralleljava/app/persistence/{ => inmemory}/InMemoryRepository.java (93%) rename src/test/java/nl/jqno/paralleljava/app/persistence/{ => inmemory}/InMemoryRepositoryTest.java (98%) diff --git a/src/main/java/nl/jqno/paralleljava/app/persistence/InMemoryRepository.java b/src/main/java/nl/jqno/paralleljava/app/persistence/inmemory/InMemoryRepository.java similarity index 93% rename from src/main/java/nl/jqno/paralleljava/app/persistence/InMemoryRepository.java rename to src/main/java/nl/jqno/paralleljava/app/persistence/inmemory/InMemoryRepository.java index 3fc01dd..dd7fc85 100644 --- a/src/main/java/nl/jqno/paralleljava/app/persistence/InMemoryRepository.java +++ b/src/main/java/nl/jqno/paralleljava/app/persistence/inmemory/InMemoryRepository.java @@ -1,4 +1,4 @@ -package nl.jqno.paralleljava.app.persistence; +package nl.jqno.paralleljava.app.persistence.inmemory; import io.vavr.collection.List; import io.vavr.control.Option; @@ -6,6 +6,7 @@ import nl.jqno.paralleljava.app.domain.Todo; import nl.jqno.paralleljava.app.logging.Logger; import nl.jqno.paralleljava.app.logging.LoggerFactory; +import nl.jqno.paralleljava.app.persistence.Repository; import java.util.ArrayList; import java.util.UUID; diff --git a/src/main/java/nl/jqno/paralleljava/dependencyinjection/Wiring.java b/src/main/java/nl/jqno/paralleljava/dependencyinjection/Wiring.java index d7b41ef..75fa390 100644 --- a/src/main/java/nl/jqno/paralleljava/dependencyinjection/Wiring.java +++ b/src/main/java/nl/jqno/paralleljava/dependencyinjection/Wiring.java @@ -10,7 +10,7 @@ import nl.jqno.paralleljava.app.logging.LoggerFactory; import nl.jqno.paralleljava.app.logging.Slf4jLogger; import nl.jqno.paralleljava.app.persistence.IdGenerator; -import nl.jqno.paralleljava.app.persistence.InMemoryRepository; +import nl.jqno.paralleljava.app.persistence.inmemory.InMemoryRepository; import nl.jqno.paralleljava.app.persistence.RandomIdGenerator; import nl.jqno.paralleljava.app.persistence.Repository; import nl.jqno.paralleljava.app.serialization.GsonSerializer; diff --git a/src/test/java/nl/jqno/paralleljava/app/persistence/InMemoryRepositoryTest.java b/src/test/java/nl/jqno/paralleljava/app/persistence/inmemory/InMemoryRepositoryTest.java similarity index 98% rename from src/test/java/nl/jqno/paralleljava/app/persistence/InMemoryRepositoryTest.java rename to src/test/java/nl/jqno/paralleljava/app/persistence/inmemory/InMemoryRepositoryTest.java index 91fa429..11686b6 100644 --- a/src/test/java/nl/jqno/paralleljava/app/persistence/InMemoryRepositoryTest.java +++ b/src/test/java/nl/jqno/paralleljava/app/persistence/inmemory/InMemoryRepositoryTest.java @@ -1,4 +1,4 @@ -package nl.jqno.paralleljava.app.persistence; +package nl.jqno.paralleljava.app.persistence.inmemory; import io.vavr.control.Option; import nl.jqno.paralleljava.dependencyinjection.TestData.AnotherTodo; From c0d5f6ed81eb2a61fb9a91b1b2256e4dc8b9c0e9 Mon Sep 17 00:00:00 2001 From: Jan Ouwens Date: Tue, 26 Mar 2019 16:18:03 +0100 Subject: [PATCH 079/106] Adds Database dependencies --- pom.yml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/pom.yml b/pom.yml index d08e675..8d16eb7 100644 --- a/pom.yml +++ b/pom.yml @@ -17,6 +17,10 @@ properties: { repositories: - { id: bintray-jqno-picotest-repo, url: "https://dl.bintray.com/jqno/picotest-repo" } +dependencyManagement: + dependencies: + - { groupId: org.jdbi, artifactId: jdbi3-bom, version: 3.6.0, type: pom, scope: import } + dependencies: - { groupId: io.vavr, artifactId: vavr, version: 0.10.0 } - { groupId: io.vavr, artifactId: vavr-gson, version: 0.10.0 } @@ -24,10 +28,14 @@ dependencies: - { groupId: org.slf4j, artifactId: slf4j-api, version: 1.7.26 } - { groupId: org.slf4j, artifactId: slf4j-simple, version: 1.7.26 } - { groupId: com.google.code.gson, artifactId: gson, version: 2.8.5 } + - { groupId: org.jdbi, artifactId: jdbi3-core } + - { groupId: org.jdbi, artifactId: jdbi3-vavr } + - { groupId: nl.jqno.picotest, artifactId: picotest, version: 0.3, scope: test } - { groupId: nl.jqno.equalsverifier, artifactId: equalsverifier, version: 3.1.7, scope: test } - { groupId: org.assertj, artifactId: assertj-core, version: 3.11.1, scope: test } - { groupId: com.tngtech.archunit, artifactId: archunit, version: 0.9.3, scope: test } + - { groupId: com.h2database, artifactId: h2, version: 1.4.199, scope: test } # REST-Assured is useful but problematic on Java 11. We need to overrule Groovy and exclude JAXB-OSGI. - { groupId: org.codehaus.groovy, artifactId: groovy, version: 2.5.6, scope: test } From b68df97db4f9aafadb1b1afa9a1b1a0b293d8a5d Mon Sep 17 00:00:00 2001 From: Jan Ouwens Date: Tue, 26 Mar 2019 17:14:08 +0100 Subject: [PATCH 080/106] Adds initial empty DatabaseRepository --- src/main/java/module-info.java | 2 + src/main/java/nl/jqno/paralleljava/Main.java | 3 +- .../app/persistence/Repository.java | 1 + .../database/DatabaseRepository.java | 57 +++++++++++++++++++ .../inmemory/InMemoryRepository.java | 4 ++ .../dependencyinjection/Wiring.java | 5 ++ .../database/DatabaseRepositoryTest.java | 42 ++++++++++++++ .../inmemory/InMemoryRepositoryTest.java | 4 ++ 8 files changed, 117 insertions(+), 1 deletion(-) create mode 100644 src/main/java/nl/jqno/paralleljava/app/persistence/database/DatabaseRepository.java create mode 100644 src/test/java/nl/jqno/paralleljava/app/persistence/database/DatabaseRepositoryTest.java diff --git a/src/main/java/module-info.java b/src/main/java/module-info.java index 0db6291..af30d35 100644 --- a/src/main/java/module-info.java +++ b/src/main/java/module-info.java @@ -5,6 +5,8 @@ requires io.vavr; requires io.vavr.gson; + requires jdbi3.core; + requires jdbi3.vavr; requires java.sql; // required for gson requires gson; requires slf4j.api; diff --git a/src/main/java/nl/jqno/paralleljava/Main.java b/src/main/java/nl/jqno/paralleljava/Main.java index d7dcae2..ecb4504 100644 --- a/src/main/java/nl/jqno/paralleljava/Main.java +++ b/src/main/java/nl/jqno/paralleljava/Main.java @@ -19,6 +19,7 @@ public static void main(String... args) { var controller = Wiring.defaultController(fullUrl, repository, idGenerator, loggerFactory); var server = Wiring.sparkServer(ENDPOINT, port, controller, loggerFactory); - server.run(); + repository.initialize() + .onSuccess(ignored -> server.run()); } } diff --git a/src/main/java/nl/jqno/paralleljava/app/persistence/Repository.java b/src/main/java/nl/jqno/paralleljava/app/persistence/Repository.java index 1f40da8..8654f41 100644 --- a/src/main/java/nl/jqno/paralleljava/app/persistence/Repository.java +++ b/src/main/java/nl/jqno/paralleljava/app/persistence/Repository.java @@ -8,6 +8,7 @@ import java.util.UUID; public interface Repository { + Try initialize(); Try createTodo(Todo todo); Try> get(UUID id); Try> getAllTodos(); diff --git a/src/main/java/nl/jqno/paralleljava/app/persistence/database/DatabaseRepository.java b/src/main/java/nl/jqno/paralleljava/app/persistence/database/DatabaseRepository.java new file mode 100644 index 0000000..7e51ceb --- /dev/null +++ b/src/main/java/nl/jqno/paralleljava/app/persistence/database/DatabaseRepository.java @@ -0,0 +1,57 @@ +package nl.jqno.paralleljava.app.persistence.database; + +import io.vavr.collection.List; +import io.vavr.control.Option; +import io.vavr.control.Try; +import nl.jqno.paralleljava.app.domain.Todo; +import nl.jqno.paralleljava.app.persistence.Repository; +import org.jdbi.v3.core.HandleCallback; +import org.jdbi.v3.core.Jdbi; + +import java.util.UUID; + +public class DatabaseRepository implements Repository { + + private final Jdbi jdbi; + + public DatabaseRepository(String jdbcUrl) { + this.jdbi = Jdbi.create(jdbcUrl); + } + + public Try initialize() { + var sql = "CREATE TABLE todo (id VARCHAR(36) PRIMARY KEY, title VARCHAR, completed BOOLEAN, index INTEGER)"; + return execute(handle -> handle.execute(sql)); + } + + public Try createTodo(Todo todo) { + return null; + } + + public Try> get(UUID id) { + return null; + } + + public Try> getAllTodos() { + return null; + } + + public Try updateTodo(Todo todo) { + return null; + } + + public Try delete(UUID id) { + return null; + } + + public Try clearAllTodos() { + return null; + } + + private Try execute(HandleCallback callback) { + return query(callback).map(ignored -> null); + } + + private Try query(HandleCallback callback) { + return Try.of(() -> jdbi.withHandle(callback)); + } +} diff --git a/src/main/java/nl/jqno/paralleljava/app/persistence/inmemory/InMemoryRepository.java b/src/main/java/nl/jqno/paralleljava/app/persistence/inmemory/InMemoryRepository.java index dd7fc85..b89fa94 100644 --- a/src/main/java/nl/jqno/paralleljava/app/persistence/inmemory/InMemoryRepository.java +++ b/src/main/java/nl/jqno/paralleljava/app/persistence/inmemory/InMemoryRepository.java @@ -24,6 +24,10 @@ public InMemoryRepository(LoggerFactory loggerFactory) { this.logger = loggerFactory.create(getClass()); } + public Try initialize() { + return SUCCESS; + } + public Try createTodo(Todo todo) { logger.forProduction("Creating Todo " + todo); todos.add(todo); diff --git a/src/main/java/nl/jqno/paralleljava/dependencyinjection/Wiring.java b/src/main/java/nl/jqno/paralleljava/dependencyinjection/Wiring.java index 75fa390..fe603f9 100644 --- a/src/main/java/nl/jqno/paralleljava/dependencyinjection/Wiring.java +++ b/src/main/java/nl/jqno/paralleljava/dependencyinjection/Wiring.java @@ -10,6 +10,7 @@ import nl.jqno.paralleljava.app.logging.LoggerFactory; import nl.jqno.paralleljava.app.logging.Slf4jLogger; import nl.jqno.paralleljava.app.persistence.IdGenerator; +import nl.jqno.paralleljava.app.persistence.database.DatabaseRepository; import nl.jqno.paralleljava.app.persistence.inmemory.InMemoryRepository; import nl.jqno.paralleljava.app.persistence.RandomIdGenerator; import nl.jqno.paralleljava.app.persistence.Repository; @@ -44,6 +45,10 @@ public static Repository inMemoryRepository(LoggerFactory loggerFactory) { return new InMemoryRepository(loggerFactory); } + public static Repository databaseRepository(String jdbcUrl) { + return new DatabaseRepository(jdbcUrl); + } + public static IdGenerator randomIdGenerator() { return new RandomIdGenerator(); } diff --git a/src/test/java/nl/jqno/paralleljava/app/persistence/database/DatabaseRepositoryTest.java b/src/test/java/nl/jqno/paralleljava/app/persistence/database/DatabaseRepositoryTest.java new file mode 100644 index 0000000..9f7e887 --- /dev/null +++ b/src/test/java/nl/jqno/paralleljava/app/persistence/database/DatabaseRepositoryTest.java @@ -0,0 +1,42 @@ +package nl.jqno.paralleljava.app.persistence.database; + +import nl.jqno.paralleljava.app.persistence.Repository; +import nl.jqno.paralleljava.dependencyinjection.Wiring; +import nl.jqno.picotest.Test; + +import java.util.UUID; + +import static org.assertj.core.api.Assertions.assertThat; + +public class DatabaseRepositoryTest extends Test { + + private final Repository repo = Wiring.databaseRepository("jdbc:h2:mem:test"); + + public void initialization() { + test("a table is created", () -> { + var result = repo.initialize(); + assertThat(result.isSuccess()).isTrue(); + }); + } + + public void crud() { + test("createTodo placeholder", () -> { + repo.createTodo(null); + }); + test("get placeholder", () -> { + repo.get(UUID.randomUUID()); + }); + test("getAllTodos placeholder", () -> { + repo.getAllTodos(); + }); + test("updateTodo placeholder", () -> { + repo.updateTodo(null); + }); + test("delete placeholder", () -> { + repo.delete(null); + }); + test("clearAllTodos placeholder", () -> { + repo.clearAllTodos(); + }); + } +} diff --git a/src/test/java/nl/jqno/paralleljava/app/persistence/inmemory/InMemoryRepositoryTest.java b/src/test/java/nl/jqno/paralleljava/app/persistence/inmemory/InMemoryRepositoryTest.java index 11686b6..b91bc9c 100644 --- a/src/test/java/nl/jqno/paralleljava/app/persistence/inmemory/InMemoryRepositoryTest.java +++ b/src/test/java/nl/jqno/paralleljava/app/persistence/inmemory/InMemoryRepositoryTest.java @@ -20,6 +20,10 @@ public void repository() { repo.clearAllTodos(); }); + test("initialize does nothing", () -> { + repo.initialize(); + }); + test("create a todo", () -> { var result = repo.createTodo(SomeTodo.TODO); From 1963da33789513177fb11b1d5d166078dc443ab5 Mon Sep 17 00:00:00 2001 From: Jan Ouwens Date: Tue, 26 Mar 2019 17:21:38 +0100 Subject: [PATCH 081/106] Wires in jdbc url and logger --- src/main/java/nl/jqno/paralleljava/Main.java | 4 +++- .../nl/jqno/paralleljava/app/environment/Environment.java | 1 + .../paralleljava/app/environment/HerokuEnvironment.java | 4 ++++ .../app/persistence/database/DatabaseRepository.java | 7 ++++++- .../nl/jqno/paralleljava/dependencyinjection/Wiring.java | 8 ++++---- .../app/environment/HerokuEnvironmentTest.java | 6 ++++++ .../app/persistence/database/DatabaseRepositoryTest.java | 3 ++- 7 files changed, 26 insertions(+), 7 deletions(-) diff --git a/src/main/java/nl/jqno/paralleljava/Main.java b/src/main/java/nl/jqno/paralleljava/Main.java index ecb4504..90c8463 100644 --- a/src/main/java/nl/jqno/paralleljava/Main.java +++ b/src/main/java/nl/jqno/paralleljava/Main.java @@ -6,6 +6,7 @@ public class Main { private static final int DEFAULT_PORT = 4567; private static final String DEFAULT_URL = "http://localhost"; private static final String ENDPOINT = "/todo"; + private static final String INMEMORY_H2_JDBC_URL = "jdbc:h2:mem:test"; public static void main(String... args) { var loggerFactory = Wiring.slf4jLoggerFactory(); @@ -13,8 +14,9 @@ public static void main(String... args) { var environment = Wiring.herokuEnvironment(); var fullUrl = environment.hostUrl().getOrElse(DEFAULT_URL) + ENDPOINT; var port = environment.port().getOrElse(DEFAULT_PORT); + var jdbcUrl = environment.jdbcUrl().getOrElse(INMEMORY_H2_JDBC_URL); - var repository = Wiring.inMemoryRepository(loggerFactory); + var repository = Wiring.databaseRepository(jdbcUrl, loggerFactory); var idGenerator = Wiring.randomIdGenerator(); var controller = Wiring.defaultController(fullUrl, repository, idGenerator, loggerFactory); var server = Wiring.sparkServer(ENDPOINT, port, controller, loggerFactory); diff --git a/src/main/java/nl/jqno/paralleljava/app/environment/Environment.java b/src/main/java/nl/jqno/paralleljava/app/environment/Environment.java index 6cb75d1..3b93aec 100644 --- a/src/main/java/nl/jqno/paralleljava/app/environment/Environment.java +++ b/src/main/java/nl/jqno/paralleljava/app/environment/Environment.java @@ -5,4 +5,5 @@ public interface Environment { Option port(); Option hostUrl(); + Option jdbcUrl(); } diff --git a/src/main/java/nl/jqno/paralleljava/app/environment/HerokuEnvironment.java b/src/main/java/nl/jqno/paralleljava/app/environment/HerokuEnvironment.java index 37eed05..12dadbc 100644 --- a/src/main/java/nl/jqno/paralleljava/app/environment/HerokuEnvironment.java +++ b/src/main/java/nl/jqno/paralleljava/app/environment/HerokuEnvironment.java @@ -21,6 +21,10 @@ public Option hostUrl() { return Option.some("https://parallel-java.herokuapp.com"); } + public Option jdbcUrl() { + return env.get("JDBC_DATABASE_URL"); + } + private Option parse(String port) { return Try.of(() -> Integer.parseInt(port)).toOption(); } diff --git a/src/main/java/nl/jqno/paralleljava/app/persistence/database/DatabaseRepository.java b/src/main/java/nl/jqno/paralleljava/app/persistence/database/DatabaseRepository.java index 7e51ceb..55dee61 100644 --- a/src/main/java/nl/jqno/paralleljava/app/persistence/database/DatabaseRepository.java +++ b/src/main/java/nl/jqno/paralleljava/app/persistence/database/DatabaseRepository.java @@ -4,6 +4,8 @@ import io.vavr.control.Option; import io.vavr.control.Try; import nl.jqno.paralleljava.app.domain.Todo; +import nl.jqno.paralleljava.app.logging.Logger; +import nl.jqno.paralleljava.app.logging.LoggerFactory; import nl.jqno.paralleljava.app.persistence.Repository; import org.jdbi.v3.core.HandleCallback; import org.jdbi.v3.core.Jdbi; @@ -12,10 +14,13 @@ public class DatabaseRepository implements Repository { + private final Logger logger; private final Jdbi jdbi; - public DatabaseRepository(String jdbcUrl) { + public DatabaseRepository(String jdbcUrl, LoggerFactory loggerFactory) { this.jdbi = Jdbi.create(jdbcUrl); + this.logger = loggerFactory.create(getClass()); + logger.forProduction("Using database " + jdbcUrl); } public Try initialize() { diff --git a/src/main/java/nl/jqno/paralleljava/dependencyinjection/Wiring.java b/src/main/java/nl/jqno/paralleljava/dependencyinjection/Wiring.java index fe603f9..ead8956 100644 --- a/src/main/java/nl/jqno/paralleljava/dependencyinjection/Wiring.java +++ b/src/main/java/nl/jqno/paralleljava/dependencyinjection/Wiring.java @@ -10,10 +10,10 @@ import nl.jqno.paralleljava.app.logging.LoggerFactory; import nl.jqno.paralleljava.app.logging.Slf4jLogger; import nl.jqno.paralleljava.app.persistence.IdGenerator; -import nl.jqno.paralleljava.app.persistence.database.DatabaseRepository; -import nl.jqno.paralleljava.app.persistence.inmemory.InMemoryRepository; import nl.jqno.paralleljava.app.persistence.RandomIdGenerator; import nl.jqno.paralleljava.app.persistence.Repository; +import nl.jqno.paralleljava.app.persistence.database.DatabaseRepository; +import nl.jqno.paralleljava.app.persistence.inmemory.InMemoryRepository; import nl.jqno.paralleljava.app.serialization.GsonSerializer; import nl.jqno.paralleljava.app.serialization.Serializer; import nl.jqno.paralleljava.app.server.Server; @@ -45,8 +45,8 @@ public static Repository inMemoryRepository(LoggerFactory loggerFactory) { return new InMemoryRepository(loggerFactory); } - public static Repository databaseRepository(String jdbcUrl) { - return new DatabaseRepository(jdbcUrl); + public static Repository databaseRepository(String jdbcUrl, LoggerFactory loggerFactory) { + return new DatabaseRepository(jdbcUrl, loggerFactory); } public static IdGenerator randomIdGenerator() { diff --git a/src/test/java/nl/jqno/paralleljava/app/environment/HerokuEnvironmentTest.java b/src/test/java/nl/jqno/paralleljava/app/environment/HerokuEnvironmentTest.java index 3cc40a7..ceb0332 100644 --- a/src/test/java/nl/jqno/paralleljava/app/environment/HerokuEnvironmentTest.java +++ b/src/test/java/nl/jqno/paralleljava/app/environment/HerokuEnvironmentTest.java @@ -33,6 +33,12 @@ public void port() { var actual = environment.hostUrl(); assertThat(actual).isEqualTo(Option.some("https://parallel-java.herokuapp.com")); }); + + test("jdbc url", () -> { + setEnvironmentVariable("JDBC_DATABASE_URL", "some-jdbc"); + var actual = environment.jdbcUrl(); + assertThat(actual).isEqualTo(Option.of("some-jdbc")); + }); } private void setEnvironmentVariable(String key, String value) { diff --git a/src/test/java/nl/jqno/paralleljava/app/persistence/database/DatabaseRepositoryTest.java b/src/test/java/nl/jqno/paralleljava/app/persistence/database/DatabaseRepositoryTest.java index 9f7e887..e8b21b3 100644 --- a/src/test/java/nl/jqno/paralleljava/app/persistence/database/DatabaseRepositoryTest.java +++ b/src/test/java/nl/jqno/paralleljava/app/persistence/database/DatabaseRepositoryTest.java @@ -1,6 +1,7 @@ package nl.jqno.paralleljava.app.persistence.database; import nl.jqno.paralleljava.app.persistence.Repository; +import nl.jqno.paralleljava.dependencyinjection.TestWiring; import nl.jqno.paralleljava.dependencyinjection.Wiring; import nl.jqno.picotest.Test; @@ -10,7 +11,7 @@ public class DatabaseRepositoryTest extends Test { - private final Repository repo = Wiring.databaseRepository("jdbc:h2:mem:test"); + private final Repository repo = Wiring.databaseRepository("jdbc:h2:mem:test", TestWiring.nopLoggerFactory()); public void initialization() { test("a table is created", () -> { From 089b4407a64f6e360cebb298ca44f2b730f9d8c5 Mon Sep 17 00:00:00 2001 From: Jan Ouwens Date: Tue, 26 Mar 2019 17:25:40 +0100 Subject: [PATCH 082/106] Adds architecture test for Jdbi --- src/test/java/nl/jqno/paralleljava/ArchitectureTest.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/test/java/nl/jqno/paralleljava/ArchitectureTest.java b/src/test/java/nl/jqno/paralleljava/ArchitectureTest.java index 81ec0d5..b67fc92 100644 --- a/src/test/java/nl/jqno/paralleljava/ArchitectureTest.java +++ b/src/test/java/nl/jqno/paralleljava/ArchitectureTest.java @@ -3,6 +3,7 @@ import com.tngtech.archunit.core.domain.JavaClasses; import com.tngtech.archunit.core.importer.ClassFileImporter; import nl.jqno.paralleljava.app.logging.Slf4jLogger; +import nl.jqno.paralleljava.app.persistence.database.DatabaseRepository; import nl.jqno.paralleljava.app.serialization.GsonSerializer; import nl.jqno.paralleljava.app.server.SparkServer; import nl.jqno.paralleljava.dependencyinjection.Wiring; @@ -27,6 +28,10 @@ public void architecture() { test("only GsonSerializer accesses Gson classes", () -> { assertBoundary("com.google.gson..", GsonSerializer.class.getPackage()); }); + + test("only DatabaseRepository accesses Jdbi classes", () -> { + assertBoundary("org.jdbi..", DatabaseRepository.class.getPackage()); + }); } private void assertBoundary(String restrictedPackageIdentifier, Package whiteListedPackage) { From 69f58775095e84a3fe0a5161e5c4021907719e6c Mon Sep 17 00:00:00 2001 From: Jan Ouwens Date: Thu, 28 Mar 2019 13:37:03 +0100 Subject: [PATCH 083/106] Integrates AssertJ-vavr --- pom.yml | 1 + .../app/controller/DefaultControllerTest.java | 68 ++++++++----------- .../app/domain/PartialTodoTest.java | 22 +++--- .../environment/HerokuEnvironmentTest.java | 13 ++-- .../persistence/RandomIdGeneratorTest.java | 2 +- .../database/DatabaseRepositoryTest.java | 4 +- .../inmemory/InMemoryRepositoryTest.java | 33 +++++---- .../app/serialization/GsonSerializerTest.java | 30 ++++---- 8 files changed, 81 insertions(+), 92 deletions(-) diff --git a/pom.yml b/pom.yml index 8d16eb7..9f8458c 100644 --- a/pom.yml +++ b/pom.yml @@ -34,6 +34,7 @@ dependencies: - { groupId: nl.jqno.picotest, artifactId: picotest, version: 0.3, scope: test } - { groupId: nl.jqno.equalsverifier, artifactId: equalsverifier, version: 3.1.7, scope: test } - { groupId: org.assertj, artifactId: assertj-core, version: 3.11.1, scope: test } + - { groupId: org.assertj, artifactId: assertj-vavr, version: 0.1.0, scope: test } - { groupId: com.tngtech.archunit, artifactId: archunit, version: 0.9.3, scope: test } - { groupId: com.h2database, artifactId: h2, version: 1.4.199, scope: test } diff --git a/src/test/java/nl/jqno/paralleljava/app/controller/DefaultControllerTest.java b/src/test/java/nl/jqno/paralleljava/app/controller/DefaultControllerTest.java index 818286f..f10a653 100644 --- a/src/test/java/nl/jqno/paralleljava/app/controller/DefaultControllerTest.java +++ b/src/test/java/nl/jqno/paralleljava/app/controller/DefaultControllerTest.java @@ -1,20 +1,19 @@ package nl.jqno.paralleljava.app.controller; -import io.vavr.control.Option; -import io.vavr.control.Try; import nl.jqno.paralleljava.app.domain.Todo; import nl.jqno.paralleljava.app.logging.LoggerFactory; import nl.jqno.paralleljava.app.persistence.IdGenerator; import nl.jqno.paralleljava.app.persistence.Repository; import nl.jqno.paralleljava.app.serialization.Serializer; -import nl.jqno.paralleljava.dependencyinjection.Wiring; import nl.jqno.paralleljava.dependencyinjection.TestWiring; +import nl.jqno.paralleljava.dependencyinjection.Wiring; import nl.jqno.picotest.Test; import java.util.UUID; import static nl.jqno.paralleljava.dependencyinjection.TestData.*; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.vavr.api.VavrAssertions.assertThat; public class DefaultControllerTest extends Test { @@ -35,7 +34,7 @@ public void get() { test("get returns an empty list when no todos are present", () -> { var actual = controller.get(); - assertThat(actual).isEqualTo(Try.success("[]")); + assertThat(actual).contains("[]"); }); test("get returns all todos", () -> { @@ -43,7 +42,7 @@ public void get() { repository.createTodo(AnotherTodo.TODO); var actual = controller.get(); - assertThat(actual).isEqualTo(Try.success(ListOfTodos.SERIALIZED)); + assertThat(actual).contains(ListOfTodos.SERIALIZED); }); } @@ -56,19 +55,17 @@ public void getWithId() { repository.createTodo(SomeTodo.TODO); var actual = controller.get(SomeTodo.ID.toString()); - assertThat(actual).isEqualTo(Try.success(SomeTodo.SERIALIZED)); + assertThat(actual).contains(SomeTodo.SERIALIZED); }); test("get with id fails if id is invalid", () -> { var actual = controller.get(Invalid.ID); - assertThat(actual.isFailure()).isTrue(); - assertThat(actual.getCause().getClass()).isEqualTo(IllegalArgumentException.class); + assertThat(actual).failBecauseOf(IllegalArgumentException.class); }); test("get with id fails if it doesn't exist", () -> { var actual = controller.get(SomeTodo.ID.toString()); - assertThat(actual.isFailure()).isTrue(); - assertThat(actual.getCause().getClass()).isEqualTo(IllegalArgumentException.class); + assertThat(actual).failBecauseOf(IllegalArgumentException.class); }); } @@ -82,8 +79,8 @@ public void post() { var expectedSerialized = serializer.serializeTodo(expected); var actual = controller.post(SomeTodo.SERIALIZED_PARTIAL_POST); - assertThat(actual).isEqualTo(Try.success(expectedSerialized)); - assertThat(repository.getAllTodos().get()).contains(expected); + assertThat(actual).contains(expectedSerialized); + assertThat(repository.getAllTodos()).hasValueSatisfying(l -> assertThat(l).contains(expected)); }); test("post adds a todo with order", () -> { @@ -91,20 +88,18 @@ public void post() { var expectedSerialized = serializer.serializeTodo(expected); var actual = controller.post(SomeTodo.SERIALIZED_PARTIAL_POST_WITH_ORDER); - assertThat(actual).isEqualTo(Try.success(expectedSerialized)); - assertThat(repository.getAllTodos().get()).contains(expected); + assertThat(actual).contains(expectedSerialized); + assertThat(repository.getAllTodos()).hasValueSatisfying(l -> assertThat(l).contains(expected)); }); test("post fails when todo is invalid", () -> { var actual = controller.post(Invalid.JSON); - assertThat(actual.isFailure()).isTrue(); - assertThat(actual.getCause().getClass()).isEqualTo(IllegalArgumentException.class); + assertThat(actual).failBecauseOf(IllegalArgumentException.class); }); test("post fails when todo has no title", () -> { var actual = controller.post(Invalid.SERIALIZED_TODO_WITH_NO_TITLE); - assertThat(actual.isFailure()).isTrue(); - assertThat(actual.getCause().getClass()).isEqualTo(IllegalArgumentException.class); + assertThat(actual).failBecauseOf(IllegalArgumentException.class); }); } @@ -120,8 +115,8 @@ public void patch() { var result = controller.patch(SomeTodo.ID.toString(), "{\"title\":\"another title\"}"); var actual = repository.get(SomeTodo.ID); - assertThat(result).isEqualTo(Try.success(serializer.serializeTodo(expected))); - assertThat(actual.get()).isEqualTo(Option.some(expected)); + assertThat(result).contains(serializer.serializeTodo(expected)); + assertThat(actual).hasValueSatisfying(o -> assertThat(o).contains(expected)); }); test("patch changes completed", () -> { @@ -131,8 +126,8 @@ public void patch() { var result = controller.patch(SomeTodo.ID.toString(), "{\"completed\":false}"); var actual = repository.get(SomeTodo.ID); - assertThat(result).isEqualTo(Try.success(serializer.serializeTodo(expected))); - assertThat(actual.get()).isEqualTo(Option.some(expected)); + assertThat(result).contains(serializer.serializeTodo(expected)); + assertThat(actual).hasValueSatisfying(o -> assertThat(o).contains(expected)); }); test("patch changes order", () -> { @@ -142,26 +137,23 @@ public void patch() { var result = controller.patch(SomeTodo.ID.toString(), "{\"order\":47}"); var actual = repository.get(SomeTodo.ID); - assertThat(result).isEqualTo(Try.success(serializer.serializeTodo(expected))); - assertThat(actual.get()).isEqualTo(Option.some(expected)); + assertThat(result).contains(serializer.serializeTodo(expected)); + assertThat(actual).hasValueSatisfying(o -> assertThat(o).contains(expected)); }); test("delete with id fails if id is invalid", () -> { var actual = controller.patch(Invalid.ID, "{\"order\":47}"); - assertThat(actual.isFailure()).isTrue(); - assertThat(actual.getCause().getClass()).isEqualTo(IllegalArgumentException.class); + assertThat(actual).failBecauseOf(IllegalArgumentException.class); }); test("post fails when todo is invalid", () -> { var actual = controller.patch(SomeTodo.ID.toString(), Invalid.JSON); - assertThat(actual.isFailure()).isTrue(); - assertThat(actual.getCause().getClass()).isEqualTo(IllegalArgumentException.class); + assertThat(actual).failBecauseOf(IllegalArgumentException.class); }); test("patch fails if id doesn't exist", () -> { var actual = controller.patch(SomeTodo.ID.toString(), "{\"order\":47}"); - assertThat(actual.isFailure()).isTrue(); - assertThat(actual.getCause().getClass()).isEqualTo(IllegalArgumentException.class); + assertThat(actual).failBecauseOf(IllegalArgumentException.class); }); } @@ -175,8 +167,8 @@ public void delete() { var actual = controller.delete(); - assertThat(actual).isEqualTo(Try.success("")); - assertThat(repository.getAllTodos().get()).isEmpty(); + assertThat(actual).hasValueSatisfying(s -> assertThat(s).isEmpty()); + assertThat(repository.getAllTodos()).hasValueSatisfying(l -> assertThat(l).isEmpty()); }); } @@ -191,16 +183,16 @@ public void deleteWithId() { var actual = controller.delete(SomeTodo.ID.toString()); - assertThat(actual).isEqualTo(Try.success("")); - assertThat(repository.getAllTodos().get()) - .doesNotContain(SomeTodo.TODO) - .contains(AnotherTodo.TODO); + assertThat(actual).hasValueSatisfying(s -> assertThat(s).isEmpty()); + assertThat(repository.getAllTodos()).hasValueSatisfying(l -> { + assertThat(l).doesNotContain(SomeTodo.TODO); + assertThat(l).contains(AnotherTodo.TODO); + }); }); test("delete with id fails if id is invalid", () -> { var actual = controller.delete(Invalid.ID); - assertThat(actual.isFailure()).isTrue(); - assertThat(actual.getCause().getClass()).isEqualTo(IllegalArgumentException.class); + assertThat(actual).failBecauseOf(IllegalArgumentException.class); }); } } diff --git a/src/test/java/nl/jqno/paralleljava/app/domain/PartialTodoTest.java b/src/test/java/nl/jqno/paralleljava/app/domain/PartialTodoTest.java index e03bc9e..b568cc9 100644 --- a/src/test/java/nl/jqno/paralleljava/app/domain/PartialTodoTest.java +++ b/src/test/java/nl/jqno/paralleljava/app/domain/PartialTodoTest.java @@ -1,11 +1,11 @@ package nl.jqno.paralleljava.app.domain; -import io.vavr.control.Option; import nl.jqno.equalsverifier.EqualsVerifier; import nl.jqno.paralleljava.dependencyinjection.TestData.SomeTodo; import nl.jqno.picotest.Test; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.vavr.api.VavrAssertions.assertThat; public class PartialTodoTest extends Test { @@ -17,19 +17,19 @@ public void partialTodo() { }); test("getters (Some)", () -> { - assertThat(SomeTodo.PARTIAL_COMPLETE.id()).isEqualTo(Option.of(SomeTodo.ID)); - assertThat(SomeTodo.PARTIAL_COMPLETE.title()).isEqualTo(Option.of("title")); - assertThat(SomeTodo.PARTIAL_COMPLETE.url()).isEqualTo(Option.of("http://www.example.com")); - assertThat(SomeTodo.PARTIAL_COMPLETE.completed()).isEqualTo(Option.of(true)); - assertThat(SomeTodo.PARTIAL_COMPLETE.order()).isEqualTo(Option.of(1337)); + assertThat(SomeTodo.PARTIAL_COMPLETE.id()).contains(SomeTodo.ID); + assertThat(SomeTodo.PARTIAL_COMPLETE.title()).contains("title"); + assertThat(SomeTodo.PARTIAL_COMPLETE.url()).contains("http://www.example.com"); + assertThat(SomeTodo.PARTIAL_COMPLETE.completed()).contains(true); + assertThat(SomeTodo.PARTIAL_COMPLETE.order()).contains(1337); }); test("getters (None)", () -> { - assertThat(SomeTodo.PARTIAL_POST.id()).isEqualTo(Option.none()); - assertThat(SomeTodo.PARTIAL_POST.title()).isEqualTo(Option.of("title")); - assertThat(SomeTodo.PARTIAL_POST.url()).isEqualTo(Option.none()); - assertThat(SomeTodo.PARTIAL_POST.completed()).isEqualTo(Option.none()); - assertThat(SomeTodo.PARTIAL_POST.order()).isEqualTo(Option.none()); + assertThat(SomeTodo.PARTIAL_POST.id()).isEmpty(); + assertThat(SomeTodo.PARTIAL_POST.title()).contains("title"); + assertThat(SomeTodo.PARTIAL_POST.url()).isEmpty(); + assertThat(SomeTodo.PARTIAL_POST.completed()).isEmpty(); + assertThat(SomeTodo.PARTIAL_POST.order()).isEmpty(); }); test("toString", () -> { diff --git a/src/test/java/nl/jqno/paralleljava/app/environment/HerokuEnvironmentTest.java b/src/test/java/nl/jqno/paralleljava/app/environment/HerokuEnvironmentTest.java index ceb0332..71acf3a 100644 --- a/src/test/java/nl/jqno/paralleljava/app/environment/HerokuEnvironmentTest.java +++ b/src/test/java/nl/jqno/paralleljava/app/environment/HerokuEnvironmentTest.java @@ -1,11 +1,10 @@ package nl.jqno.paralleljava.app.environment; import io.vavr.collection.HashMap; -import io.vavr.control.Option; import nl.jqno.paralleljava.dependencyinjection.TestWiring; import nl.jqno.picotest.Test; -import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.vavr.api.VavrAssertions.assertThat; public class HerokuEnvironmentTest extends Test { @@ -15,29 +14,29 @@ public void port() { test("a valid port is provided", () -> { setEnvironmentVariable("PORT", "42"); var actual = environment.port(); - assertThat(actual).isEqualTo(Option.of(42)); + assertThat(actual).contains(42); }); test("an invalid port is provided", () -> { setEnvironmentVariable("PORT", "this is not the port you're looking for"); var actual = environment.port(); - assertThat(actual).isEqualTo(Option.none()); + assertThat(actual).isEmpty(); }); test("no port is provided", () -> { var actual = environment.port(); - assertThat(actual).isEqualTo(Option.none()); + assertThat(actual).isEmpty(); }); test("host url", () -> { var actual = environment.hostUrl(); - assertThat(actual).isEqualTo(Option.some("https://parallel-java.herokuapp.com")); + assertThat(actual).contains("https://parallel-java.herokuapp.com"); }); test("jdbc url", () -> { setEnvironmentVariable("JDBC_DATABASE_URL", "some-jdbc"); var actual = environment.jdbcUrl(); - assertThat(actual).isEqualTo(Option.of("some-jdbc")); + assertThat(actual).contains("some-jdbc"); }); } diff --git a/src/test/java/nl/jqno/paralleljava/app/persistence/RandomIdGeneratorTest.java b/src/test/java/nl/jqno/paralleljava/app/persistence/RandomIdGeneratorTest.java index a4cc9d3..cec1abc 100644 --- a/src/test/java/nl/jqno/paralleljava/app/persistence/RandomIdGeneratorTest.java +++ b/src/test/java/nl/jqno/paralleljava/app/persistence/RandomIdGeneratorTest.java @@ -12,7 +12,7 @@ public void uuidGenerator() { var generator = Wiring.randomIdGenerator(); test("generates a valid uuid", () -> { - UUID actual = generator.generateId(); + var actual = generator.generateId(); var roundTrip = UUID.fromString(actual.toString()); Assertions.assertThat(actual).isEqualTo(roundTrip); }); diff --git a/src/test/java/nl/jqno/paralleljava/app/persistence/database/DatabaseRepositoryTest.java b/src/test/java/nl/jqno/paralleljava/app/persistence/database/DatabaseRepositoryTest.java index e8b21b3..2842b80 100644 --- a/src/test/java/nl/jqno/paralleljava/app/persistence/database/DatabaseRepositoryTest.java +++ b/src/test/java/nl/jqno/paralleljava/app/persistence/database/DatabaseRepositoryTest.java @@ -7,7 +7,7 @@ import java.util.UUID; -import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.vavr.api.VavrAssertions.assertThat; public class DatabaseRepositoryTest extends Test { @@ -16,7 +16,7 @@ public class DatabaseRepositoryTest extends Test { public void initialization() { test("a table is created", () -> { var result = repo.initialize(); - assertThat(result.isSuccess()).isTrue(); + assertThat(result).isSuccess(); }); } diff --git a/src/test/java/nl/jqno/paralleljava/app/persistence/inmemory/InMemoryRepositoryTest.java b/src/test/java/nl/jqno/paralleljava/app/persistence/inmemory/InMemoryRepositoryTest.java index b91bc9c..95ddd36 100644 --- a/src/test/java/nl/jqno/paralleljava/app/persistence/inmemory/InMemoryRepositoryTest.java +++ b/src/test/java/nl/jqno/paralleljava/app/persistence/inmemory/InMemoryRepositoryTest.java @@ -1,15 +1,14 @@ package nl.jqno.paralleljava.app.persistence.inmemory; -import io.vavr.control.Option; import nl.jqno.paralleljava.dependencyinjection.TestData.AnotherTodo; import nl.jqno.paralleljava.dependencyinjection.TestData.SomeTodo; -import nl.jqno.paralleljava.dependencyinjection.Wiring; import nl.jqno.paralleljava.dependencyinjection.TestWiring; +import nl.jqno.paralleljava.dependencyinjection.Wiring; import nl.jqno.picotest.Test; import java.util.UUID; -import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.vavr.api.VavrAssertions.assertThat; public class InMemoryRepositoryTest extends Test { @@ -27,15 +26,15 @@ public void repository() { test("create a todo", () -> { var result = repo.createTodo(SomeTodo.TODO); - assertThat(result.isSuccess()).isTrue(); - assertThat(repo.getAllTodos().get()).contains(SomeTodo.TODO); + assertThat(result).isSuccess(); + assertThat(repo.getAllTodos()).hasValueSatisfying(l -> assertThat(l).contains(SomeTodo.TODO)); }); test("get a specific todo", () -> { repo.createTodo(SomeTodo.TODO); - assertThat(repo.get(SomeTodo.ID).get()).isEqualTo(Option.some(SomeTodo.TODO)); - assertThat(repo.get(UUID.randomUUID()).get()).isEqualTo(Option.none()); + assertThat(repo.get(SomeTodo.ID)).hasValueSatisfying(o -> assertThat(o).contains(SomeTodo.TODO)); + assertThat(repo.get(UUID.randomUUID())).hasValueSatisfying(o -> assertThat(o).isEmpty()); }); test("update a specific todo at index 0", () -> { @@ -46,8 +45,8 @@ public void repository() { var result = repo.updateTodo(expected); var actual = repo.get(SomeTodo.ID).get(); - assertThat(result.isSuccess()).isTrue(); - assertThat(actual).isEqualTo(Option.some(expected)); + assertThat(result).isSuccess(); + assertThat(actual).contains(expected); }); test("update a specific todo at index 1", () -> { @@ -58,8 +57,8 @@ public void repository() { var result = repo.updateTodo(expected); var actual = repo.get(SomeTodo.ID).get(); - assertThat(result.isSuccess()).isTrue(); - assertThat(actual).isEqualTo(Option.some(expected)); + assertThat(result).isSuccess(); + assertThat(actual).contains(expected); }); test("delete a specific todo at index 0", () -> { @@ -69,8 +68,8 @@ public void repository() { var result = repo.delete(SomeTodo.ID); var actual = repo.get(SomeTodo.ID).get(); - assertThat(result.isSuccess()).isTrue(); - assertThat(actual).isEqualTo(Option.none()); + assertThat(result).isSuccess(); + assertThat(actual).isEmpty(); }); test("update a specific todo at index 1", () -> { @@ -80,8 +79,8 @@ public void repository() { var result = repo.delete(SomeTodo.ID); var actual = repo.get(SomeTodo.ID).get(); - assertThat(result.isSuccess()).isTrue(); - assertThat(actual).isEqualTo(Option.none()); + assertThat(result).isSuccess(); + assertThat(actual).isEmpty(); }); test("clearing all todos", () -> { @@ -89,8 +88,8 @@ public void repository() { var result = repo.clearAllTodos(); - assertThat(result.isSuccess()).isTrue(); - assertThat(repo.getAllTodos().get()).isEmpty(); + assertThat(result).isSuccess(); + assertThat(repo.getAllTodos()).hasValueSatisfying(l -> assertThat(l).isEmpty()); }); } } diff --git a/src/test/java/nl/jqno/paralleljava/app/serialization/GsonSerializerTest.java b/src/test/java/nl/jqno/paralleljava/app/serialization/GsonSerializerTest.java index d824102..a6002ef 100644 --- a/src/test/java/nl/jqno/paralleljava/app/serialization/GsonSerializerTest.java +++ b/src/test/java/nl/jqno/paralleljava/app/serialization/GsonSerializerTest.java @@ -1,14 +1,12 @@ package nl.jqno.paralleljava.app.serialization; -import io.vavr.collection.List; -import io.vavr.control.Option; -import nl.jqno.paralleljava.dependencyinjection.TestData; -import nl.jqno.paralleljava.dependencyinjection.Wiring; import nl.jqno.paralleljava.dependencyinjection.TestWiring; +import nl.jqno.paralleljava.dependencyinjection.Wiring; import nl.jqno.picotest.Test; import static nl.jqno.paralleljava.dependencyinjection.TestData.*; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.vavr.api.VavrAssertions.assertThat; public class GsonSerializerTest extends Test { @@ -28,18 +26,18 @@ public void serializationOfASingleTodo() { test("Deserializes a Todo from json", () -> { var actual = serializer.deserializeTodo(SomeTodo.SERIALIZED); - assertThat(actual).isEqualTo(Option.of(SomeTodo.TODO)); + assertThat(actual).contains(SomeTodo.TODO); }); test("Deserialization of a Todo returns none when json is invalid", () -> { var actual = serializer.deserializeTodo(Invalid.JSON); - assertThat(actual).isEqualTo(Option.none()); + assertThat(actual).isEmpty(); }); test("Does a complete round-trip on Todo", () -> { var json = serializer.serializeTodo(SomeTodo.TODO); var actual = serializer.deserializeTodo(json); - assertThat(actual).isEqualTo(Option.of(SomeTodo.TODO)); + assertThat(actual).contains(SomeTodo.TODO); }); } @@ -56,18 +54,18 @@ public void serializationOfACompletePartialTodo() { test("Deserializes a complete PartialTodo from json", () -> { var actual = serializer.deserializePartialTodo(SomeTodo.SERIALIZED); - assertThat(actual).isEqualTo(Option.of(SomeTodo.PARTIAL_COMPLETE)); + assertThat(actual).contains(SomeTodo.PARTIAL_COMPLETE); }); test("Deserialization of a complete PartialTodo returns none when json is invalid", () -> { var actual = serializer.deserializePartialTodo(Invalid.JSON); - assertThat(actual).isEqualTo(Option.none()); + assertThat(actual).isEmpty(); }); test("Does a complete round-trip on PartialTodo", () -> { var json = serializer.serializePartialTodo(SomeTodo.PARTIAL_COMPLETE); var actual = serializer.deserializePartialTodo(json); - assertThat(actual).isEqualTo(Option.of(SomeTodo.PARTIAL_COMPLETE)); + assertThat(actual).contains(SomeTodo.PARTIAL_COMPLETE); }); } @@ -84,13 +82,13 @@ public void serializationOfAPOSTedPartialTodo() { test("Deserializes a POSTed PartialTodo from json", () -> { var actual = serializer.deserializePartialTodo(SomeTodo.SERIALIZED_PARTIAL_POST); - assertThat(actual).isEqualTo(Option.of(SomeTodo.PARTIAL_POST)); + assertThat(actual).contains(SomeTodo.PARTIAL_POST); }); test("Does a complete round-trip on a POSTed PartialTodo", () -> { var json = serializer.serializePartialTodo(SomeTodo.PARTIAL_POST); var actual = serializer.deserializePartialTodo(json); - assertThat(actual).isEqualTo(Option.of(SomeTodo.PARTIAL_POST)); + assertThat(actual).contains(SomeTodo.PARTIAL_POST); }); } @@ -111,7 +109,7 @@ public void serializationOfAListOfTodos() { test("Deserialization of a list of Todos returns an empty list when json is invalid", () -> { var invalidJson = SomeTodo.SERIALIZED; // but not a list var actual = serializer.deserializeTodos(invalidJson); - assertThat(actual).isEqualTo(List.empty()); + assertThat(actual).isEmpty(); }); test("Does a complete round-trip on lists of Todos", () -> { @@ -130,18 +128,18 @@ public void serializationOfAUuid() { test("Deserializes a UUID from json", () -> { var actual = serializer.deserializeUuid(SomeTodo.ID.toString()); - assertThat(actual).isEqualTo(Option.of(SomeTodo.ID)); + assertThat(actual).contains(SomeTodo.ID); }); test("Deserialization of a UUID returns none when json is invalid", () -> { var actual = serializer.deserializeUuid(Invalid.ID); - assertThat(actual).isEqualTo(Option.none()); + assertThat(actual).isEmpty(); }); test("Does a complete round-trip on UUID", () -> { var json = serializer.serializeUuid(SomeTodo.ID); var actual = serializer.deserializeUuid(json); - assertThat(actual).isEqualTo(Option.of(SomeTodo.ID)); + assertThat(actual).contains(SomeTodo.ID); }); } } From 4a08f10184b34f4a2916cc61cea407f37f7a8998 Mon Sep 17 00:00:00 2001 From: Jan Ouwens Date: Thu, 28 Mar 2019 16:54:24 +0100 Subject: [PATCH 084/106] Adds test to assert that repo initialization is idempotent --- src/main/java/nl/jqno/paralleljava/Main.java | 4 ++-- .../app/persistence/database/DatabaseRepository.java | 5 ++++- .../persistence/database/DatabaseRepositoryTest.java | 11 +++++++++-- 3 files changed, 15 insertions(+), 5 deletions(-) diff --git a/src/main/java/nl/jqno/paralleljava/Main.java b/src/main/java/nl/jqno/paralleljava/Main.java index 90c8463..16490ae 100644 --- a/src/main/java/nl/jqno/paralleljava/Main.java +++ b/src/main/java/nl/jqno/paralleljava/Main.java @@ -1,12 +1,12 @@ package nl.jqno.paralleljava; +import nl.jqno.paralleljava.app.persistence.database.DatabaseRepository; import nl.jqno.paralleljava.dependencyinjection.Wiring; public class Main { private static final int DEFAULT_PORT = 4567; private static final String DEFAULT_URL = "http://localhost"; private static final String ENDPOINT = "/todo"; - private static final String INMEMORY_H2_JDBC_URL = "jdbc:h2:mem:test"; public static void main(String... args) { var loggerFactory = Wiring.slf4jLoggerFactory(); @@ -14,7 +14,7 @@ public static void main(String... args) { var environment = Wiring.herokuEnvironment(); var fullUrl = environment.hostUrl().getOrElse(DEFAULT_URL) + ENDPOINT; var port = environment.port().getOrElse(DEFAULT_PORT); - var jdbcUrl = environment.jdbcUrl().getOrElse(INMEMORY_H2_JDBC_URL); + var jdbcUrl = environment.jdbcUrl().getOrElse(DatabaseRepository.DEFAULT_JDBC_URL); var repository = Wiring.databaseRepository(jdbcUrl, loggerFactory); var idGenerator = Wiring.randomIdGenerator(); diff --git a/src/main/java/nl/jqno/paralleljava/app/persistence/database/DatabaseRepository.java b/src/main/java/nl/jqno/paralleljava/app/persistence/database/DatabaseRepository.java index 55dee61..ad032da 100644 --- a/src/main/java/nl/jqno/paralleljava/app/persistence/database/DatabaseRepository.java +++ b/src/main/java/nl/jqno/paralleljava/app/persistence/database/DatabaseRepository.java @@ -14,6 +14,8 @@ public class DatabaseRepository implements Repository { + public static final String DEFAULT_JDBC_URL = "jdbc:h2:mem:test;DB_CLOSE_DELAY=-1"; + private final Logger logger; private final Jdbi jdbi; @@ -25,7 +27,8 @@ public DatabaseRepository(String jdbcUrl, LoggerFactory loggerFactory) { public Try initialize() { var sql = "CREATE TABLE todo (id VARCHAR(36) PRIMARY KEY, title VARCHAR, completed BOOLEAN, index INTEGER)"; - return execute(handle -> handle.execute(sql)); + return execute(handle -> handle.execute(sql)) + .orElse(Try.success(null)); // If it fails, the table probably already exists. } public Try createTodo(Todo todo) { diff --git a/src/test/java/nl/jqno/paralleljava/app/persistence/database/DatabaseRepositoryTest.java b/src/test/java/nl/jqno/paralleljava/app/persistence/database/DatabaseRepositoryTest.java index 2842b80..8b6228c 100644 --- a/src/test/java/nl/jqno/paralleljava/app/persistence/database/DatabaseRepositoryTest.java +++ b/src/test/java/nl/jqno/paralleljava/app/persistence/database/DatabaseRepositoryTest.java @@ -11,16 +11,23 @@ public class DatabaseRepositoryTest extends Test { - private final Repository repo = Wiring.databaseRepository("jdbc:h2:mem:test", TestWiring.nopLoggerFactory()); - public void initialization() { test("a table is created", () -> { + var repo = Wiring.databaseRepository(DatabaseRepository.DEFAULT_JDBC_URL, TestWiring.nopLoggerFactory()); var result = repo.initialize(); assertThat(result).isSuccess(); }); + + test("initializing twice is a no-op the second time", () -> { + var repo = Wiring.databaseRepository(DatabaseRepository.DEFAULT_JDBC_URL, TestWiring.nopLoggerFactory()); + assertThat(repo.initialize()).isSuccess(); + assertThat(repo.initialize()).isSuccess(); + }); } public void crud() { + var repo = Wiring.databaseRepository(DatabaseRepository.DEFAULT_JDBC_URL, TestWiring.nopLoggerFactory()); + test("createTodo placeholder", () -> { repo.createTodo(null); }); From 8987d5a4ed7c74ee9af5793b5de095555c8c70c5 Mon Sep 17 00:00:00 2001 From: Jan Ouwens Date: Thu, 28 Mar 2019 16:57:47 +0100 Subject: [PATCH 085/106] Implements initial database round-trip --- .../database/DatabaseRepository.java | 22 +++++++++++++++---- .../app/persistence/database/TodoMapper.java | 20 +++++++++++++++++ .../database/DatabaseRepositoryTest.java | 20 +++++++++++++++-- 3 files changed, 56 insertions(+), 6 deletions(-) create mode 100644 src/main/java/nl/jqno/paralleljava/app/persistence/database/TodoMapper.java diff --git a/src/main/java/nl/jqno/paralleljava/app/persistence/database/DatabaseRepository.java b/src/main/java/nl/jqno/paralleljava/app/persistence/database/DatabaseRepository.java index ad032da..5a00a88 100644 --- a/src/main/java/nl/jqno/paralleljava/app/persistence/database/DatabaseRepository.java +++ b/src/main/java/nl/jqno/paralleljava/app/persistence/database/DatabaseRepository.java @@ -8,6 +8,7 @@ import nl.jqno.paralleljava.app.logging.LoggerFactory; import nl.jqno.paralleljava.app.persistence.Repository; import org.jdbi.v3.core.HandleCallback; +import org.jdbi.v3.core.HandleConsumer; import org.jdbi.v3.core.Jdbi; import java.util.UUID; @@ -21,6 +22,7 @@ public class DatabaseRepository implements Repository { public DatabaseRepository(String jdbcUrl, LoggerFactory loggerFactory) { this.jdbi = Jdbi.create(jdbcUrl); + this.jdbi.registerRowMapper(Todo.class, new TodoMapper()); this.logger = loggerFactory.create(getClass()); logger.forProduction("Using database " + jdbcUrl); } @@ -32,7 +34,13 @@ public Try initialize() { } public Try createTodo(Todo todo) { - return null; + return execute(handle -> + handle.createUpdate("INSERT INTO todo (id, title, completed, index) VALUES (?, ?, ?, ?)") + .bind(0, todo.id()) + .bind(1, todo.title()) + .bind(2, todo.completed()) + .bind(3, todo.order()) + .execute()); } public Try> get(UUID id) { @@ -40,7 +48,10 @@ public Try> get(UUID id) { } public Try> getAllTodos() { - return null; + return query(handle -> + handle.createQuery("SELECT id, title, completed, index FROM todo") + .mapTo(Todo.class) + .collect(List.collector())); } public Try updateTodo(Todo todo) { @@ -55,8 +66,11 @@ public Try clearAllTodos() { return null; } - private Try execute(HandleCallback callback) { - return query(callback).map(ignored -> null); + private Try execute(HandleConsumer consumer) { + return Try.of(() -> { + jdbi.useHandle(consumer); + return null; + }); } private Try query(HandleCallback callback) { diff --git a/src/main/java/nl/jqno/paralleljava/app/persistence/database/TodoMapper.java b/src/main/java/nl/jqno/paralleljava/app/persistence/database/TodoMapper.java new file mode 100644 index 0000000..364c417 --- /dev/null +++ b/src/main/java/nl/jqno/paralleljava/app/persistence/database/TodoMapper.java @@ -0,0 +1,20 @@ +package nl.jqno.paralleljava.app.persistence.database; + +import nl.jqno.paralleljava.app.domain.Todo; +import org.jdbi.v3.core.mapper.RowMapper; +import org.jdbi.v3.core.statement.StatementContext; + +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.UUID; + +public class TodoMapper implements RowMapper { + public Todo map(ResultSet rs, StatementContext ctx) throws SQLException { + return new Todo( + UUID.fromString(rs.getString("id")), + rs.getString("title"), + "", + rs.getBoolean("completed"), + rs.getInt("index")); + } +} diff --git a/src/test/java/nl/jqno/paralleljava/app/persistence/database/DatabaseRepositoryTest.java b/src/test/java/nl/jqno/paralleljava/app/persistence/database/DatabaseRepositoryTest.java index 8b6228c..5ed9723 100644 --- a/src/test/java/nl/jqno/paralleljava/app/persistence/database/DatabaseRepositoryTest.java +++ b/src/test/java/nl/jqno/paralleljava/app/persistence/database/DatabaseRepositoryTest.java @@ -1,6 +1,6 @@ package nl.jqno.paralleljava.app.persistence.database; -import nl.jqno.paralleljava.app.persistence.Repository; +import nl.jqno.paralleljava.dependencyinjection.TestData; import nl.jqno.paralleljava.dependencyinjection.TestWiring; import nl.jqno.paralleljava.dependencyinjection.Wiring; import nl.jqno.picotest.Test; @@ -12,6 +12,7 @@ public class DatabaseRepositoryTest extends Test { public void initialization() { + test("a table is created", () -> { var repo = Wiring.databaseRepository(DatabaseRepository.DEFAULT_JDBC_URL, TestWiring.nopLoggerFactory()); var result = repo.initialize(); @@ -25,7 +26,7 @@ public void initialization() { }); } - public void crud() { + public void placeholders() { var repo = Wiring.databaseRepository(DatabaseRepository.DEFAULT_JDBC_URL, TestWiring.nopLoggerFactory()); test("createTodo placeholder", () -> { @@ -47,4 +48,19 @@ public void crud() { repo.clearAllTodos(); }); } + + public void repository() { + var repo = Wiring.databaseRepository(DatabaseRepository.DEFAULT_JDBC_URL, TestWiring.nopLoggerFactory()); + + beforeAll(() -> { + assertThat(repo.initialize()).isSuccess(); + }); + + test("create a todo", () -> { + var result = repo.createTodo(TestData.SomeTodo.TODO); + + assertThat(result).isSuccess(); + assertThat(repo.getAllTodos()).hasValueSatisfying(l -> l.contains(TestData.SomeTodo.TODO)); + }); + } } From 04d72b1554ed43dd3b5c7ad3a6fa42773ea61d86 Mon Sep 17 00:00:00 2001 From: Jan Ouwens Date: Thu, 28 Mar 2019 17:19:39 +0100 Subject: [PATCH 086/106] Generates URL from todo and implements GET --- src/main/java/nl/jqno/paralleljava/Main.java | 4 +++- .../database/DatabaseRepository.java | 12 +++++++++--- .../app/persistence/database/TodoMapper.java | 8 +++++++- .../dependencyinjection/Wiring.java | 5 +++-- .../app/domain/PartialTodoTest.java | 4 ++-- .../jqno/paralleljava/app/domain/TodoTest.java | 4 ++-- .../database/DatabaseRepositoryTest.java | 17 +++++++++++++---- .../app/serialization/GsonSerializerTest.java | 4 ++-- .../dependencyinjection/TestData.java | 16 +++++++++++----- 9 files changed, 52 insertions(+), 22 deletions(-) diff --git a/src/main/java/nl/jqno/paralleljava/Main.java b/src/main/java/nl/jqno/paralleljava/Main.java index 16490ae..d10a9a3 100644 --- a/src/main/java/nl/jqno/paralleljava/Main.java +++ b/src/main/java/nl/jqno/paralleljava/Main.java @@ -1,6 +1,7 @@ package nl.jqno.paralleljava; import nl.jqno.paralleljava.app.persistence.database.DatabaseRepository; +import nl.jqno.paralleljava.app.persistence.database.TodoMapper; import nl.jqno.paralleljava.dependencyinjection.Wiring; public class Main { @@ -16,7 +17,8 @@ public static void main(String... args) { var port = environment.port().getOrElse(DEFAULT_PORT); var jdbcUrl = environment.jdbcUrl().getOrElse(DatabaseRepository.DEFAULT_JDBC_URL); - var repository = Wiring.databaseRepository(jdbcUrl, loggerFactory); + var todoMapper = new TodoMapper(fullUrl); + var repository = Wiring.databaseRepository(jdbcUrl, todoMapper, loggerFactory); var idGenerator = Wiring.randomIdGenerator(); var controller = Wiring.defaultController(fullUrl, repository, idGenerator, loggerFactory); var server = Wiring.sparkServer(ENDPOINT, port, controller, loggerFactory); diff --git a/src/main/java/nl/jqno/paralleljava/app/persistence/database/DatabaseRepository.java b/src/main/java/nl/jqno/paralleljava/app/persistence/database/DatabaseRepository.java index 5a00a88..bee27cc 100644 --- a/src/main/java/nl/jqno/paralleljava/app/persistence/database/DatabaseRepository.java +++ b/src/main/java/nl/jqno/paralleljava/app/persistence/database/DatabaseRepository.java @@ -20,9 +20,9 @@ public class DatabaseRepository implements Repository { private final Logger logger; private final Jdbi jdbi; - public DatabaseRepository(String jdbcUrl, LoggerFactory loggerFactory) { + public DatabaseRepository(String jdbcUrl, TodoMapper todoMapper, LoggerFactory loggerFactory) { this.jdbi = Jdbi.create(jdbcUrl); - this.jdbi.registerRowMapper(Todo.class, new TodoMapper()); + this.jdbi.registerRowMapper(Todo.class, todoMapper); this.logger = loggerFactory.create(getClass()); logger.forProduction("Using database " + jdbcUrl); } @@ -44,7 +44,13 @@ public Try createTodo(Todo todo) { } public Try> get(UUID id) { - return null; + return query(handle -> { + var o = handle.createQuery("SELECT id, title, completed, index FROM todo WHERE id = ?") + .bind(0, id.toString()) + .mapTo(Todo.class) + .findFirst(); + return Option.ofOptional(o); + }); } public Try> getAllTodos() { diff --git a/src/main/java/nl/jqno/paralleljava/app/persistence/database/TodoMapper.java b/src/main/java/nl/jqno/paralleljava/app/persistence/database/TodoMapper.java index 364c417..b49461b 100644 --- a/src/main/java/nl/jqno/paralleljava/app/persistence/database/TodoMapper.java +++ b/src/main/java/nl/jqno/paralleljava/app/persistence/database/TodoMapper.java @@ -9,11 +9,17 @@ import java.util.UUID; public class TodoMapper implements RowMapper { + private String urlPrefix; + + public TodoMapper(String urlPrefix) { + this.urlPrefix = urlPrefix; + } + public Todo map(ResultSet rs, StatementContext ctx) throws SQLException { return new Todo( UUID.fromString(rs.getString("id")), rs.getString("title"), - "", + urlPrefix + "/" + rs.getString("id"), rs.getBoolean("completed"), rs.getInt("index")); } diff --git a/src/main/java/nl/jqno/paralleljava/dependencyinjection/Wiring.java b/src/main/java/nl/jqno/paralleljava/dependencyinjection/Wiring.java index ead8956..23b7a5b 100644 --- a/src/main/java/nl/jqno/paralleljava/dependencyinjection/Wiring.java +++ b/src/main/java/nl/jqno/paralleljava/dependencyinjection/Wiring.java @@ -13,6 +13,7 @@ import nl.jqno.paralleljava.app.persistence.RandomIdGenerator; import nl.jqno.paralleljava.app.persistence.Repository; import nl.jqno.paralleljava.app.persistence.database.DatabaseRepository; +import nl.jqno.paralleljava.app.persistence.database.TodoMapper; import nl.jqno.paralleljava.app.persistence.inmemory.InMemoryRepository; import nl.jqno.paralleljava.app.serialization.GsonSerializer; import nl.jqno.paralleljava.app.serialization.Serializer; @@ -45,8 +46,8 @@ public static Repository inMemoryRepository(LoggerFactory loggerFactory) { return new InMemoryRepository(loggerFactory); } - public static Repository databaseRepository(String jdbcUrl, LoggerFactory loggerFactory) { - return new DatabaseRepository(jdbcUrl, loggerFactory); + public static Repository databaseRepository(String jdbcUrl, TodoMapper todoMapper, LoggerFactory loggerFactory) { + return new DatabaseRepository(jdbcUrl, todoMapper, loggerFactory); } public static IdGenerator randomIdGenerator() { diff --git a/src/test/java/nl/jqno/paralleljava/app/domain/PartialTodoTest.java b/src/test/java/nl/jqno/paralleljava/app/domain/PartialTodoTest.java index b568cc9..8ec9217 100644 --- a/src/test/java/nl/jqno/paralleljava/app/domain/PartialTodoTest.java +++ b/src/test/java/nl/jqno/paralleljava/app/domain/PartialTodoTest.java @@ -19,7 +19,7 @@ public void partialTodo() { test("getters (Some)", () -> { assertThat(SomeTodo.PARTIAL_COMPLETE.id()).contains(SomeTodo.ID); assertThat(SomeTodo.PARTIAL_COMPLETE.title()).contains("title"); - assertThat(SomeTodo.PARTIAL_COMPLETE.url()).contains("http://www.example.com"); + assertThat(SomeTodo.PARTIAL_COMPLETE.url()).contains(SomeTodo.URL); assertThat(SomeTodo.PARTIAL_COMPLETE.completed()).contains(true); assertThat(SomeTodo.PARTIAL_COMPLETE.order()).contains(1337); }); @@ -34,7 +34,7 @@ public void partialTodo() { test("toString", () -> { assertThat(SomeTodo.PARTIAL_COMPLETE.toString()) - .isEqualTo("PartialTodo: [id=Some(" + SomeTodo.ID + "), title=Some(title), url=Some(http://www.example.com), completed=Some(true), order=Some(1337)]"); + .isEqualTo("PartialTodo: [id=Some(" + SomeTodo.ID + "), title=Some(title), url=Some(" + SomeTodo.URL + "), completed=Some(true), order=Some(1337)]"); assertThat(SomeTodo.PARTIAL_POST.toString()) .isEqualTo("PartialTodo: [id=None, title=Some(title), url=None, completed=None, order=None]"); }); diff --git a/src/test/java/nl/jqno/paralleljava/app/domain/TodoTest.java b/src/test/java/nl/jqno/paralleljava/app/domain/TodoTest.java index 49bceaf..bbfd6a4 100644 --- a/src/test/java/nl/jqno/paralleljava/app/domain/TodoTest.java +++ b/src/test/java/nl/jqno/paralleljava/app/domain/TodoTest.java @@ -18,14 +18,14 @@ public void todo() { test("getters", () -> { assertThat(SomeTodo.TODO.id()).isEqualTo(SomeTodo.ID); assertThat(SomeTodo.TODO.title()).isEqualTo("title"); - assertThat(SomeTodo.TODO.url()).isEqualTo("http://www.example.com"); + assertThat(SomeTodo.TODO.url()).isEqualTo("http://localhost/blabla/" + SomeTodo.ID.toString()); assertThat(SomeTodo.TODO.completed()).isEqualTo(true); assertThat(SomeTodo.TODO.order()).isEqualTo(1337); }); test("toString", () -> { assertThat(SomeTodo.TODO.toString()) - .isEqualTo("Todo: [id=" + SomeTodo.ID + ", title=title, url=http://www.example.com, completed=true, order=1337]"); + .isEqualTo("Todo: [id=" + SomeTodo.ID + ", title=title, url=" + SomeTodo.URL + ", completed=true, order=1337]"); }); test("withTitle", () -> { diff --git a/src/test/java/nl/jqno/paralleljava/app/persistence/database/DatabaseRepositoryTest.java b/src/test/java/nl/jqno/paralleljava/app/persistence/database/DatabaseRepositoryTest.java index 5ed9723..7906dbc 100644 --- a/src/test/java/nl/jqno/paralleljava/app/persistence/database/DatabaseRepositoryTest.java +++ b/src/test/java/nl/jqno/paralleljava/app/persistence/database/DatabaseRepositoryTest.java @@ -11,23 +11,25 @@ public class DatabaseRepositoryTest extends Test { + private final TodoMapper todoMapper = new TodoMapper(TestData.URL_PREFIX); + public void initialization() { test("a table is created", () -> { - var repo = Wiring.databaseRepository(DatabaseRepository.DEFAULT_JDBC_URL, TestWiring.nopLoggerFactory()); + var repo = Wiring.databaseRepository(DatabaseRepository.DEFAULT_JDBC_URL, todoMapper, TestWiring.nopLoggerFactory()); var result = repo.initialize(); assertThat(result).isSuccess(); }); test("initializing twice is a no-op the second time", () -> { - var repo = Wiring.databaseRepository(DatabaseRepository.DEFAULT_JDBC_URL, TestWiring.nopLoggerFactory()); + var repo = Wiring.databaseRepository(DatabaseRepository.DEFAULT_JDBC_URL, todoMapper, TestWiring.nopLoggerFactory()); assertThat(repo.initialize()).isSuccess(); assertThat(repo.initialize()).isSuccess(); }); } public void placeholders() { - var repo = Wiring.databaseRepository(DatabaseRepository.DEFAULT_JDBC_URL, TestWiring.nopLoggerFactory()); + var repo = Wiring.databaseRepository(DatabaseRepository.DEFAULT_JDBC_URL, todoMapper, TestWiring.nopLoggerFactory()); test("createTodo placeholder", () -> { repo.createTodo(null); @@ -50,7 +52,7 @@ public void placeholders() { } public void repository() { - var repo = Wiring.databaseRepository(DatabaseRepository.DEFAULT_JDBC_URL, TestWiring.nopLoggerFactory()); + var repo = Wiring.databaseRepository(DatabaseRepository.DEFAULT_JDBC_URL, todoMapper, TestWiring.nopLoggerFactory()); beforeAll(() -> { assertThat(repo.initialize()).isSuccess(); @@ -62,5 +64,12 @@ public void repository() { assertThat(result).isSuccess(); assertThat(repo.getAllTodos()).hasValueSatisfying(l -> l.contains(TestData.SomeTodo.TODO)); }); + + test("get a specific todo", () -> { + repo.createTodo(TestData.SomeTodo.TODO); + + assertThat(repo.get(TestData.SomeTodo.ID)).hasValueSatisfying(o -> assertThat(o).contains(TestData.SomeTodo.TODO)); + assertThat(repo.get(UUID.randomUUID())).hasValueSatisfying(o -> assertThat(o).isEmpty()); + }); } } diff --git a/src/test/java/nl/jqno/paralleljava/app/serialization/GsonSerializerTest.java b/src/test/java/nl/jqno/paralleljava/app/serialization/GsonSerializerTest.java index a6002ef..3908be5 100644 --- a/src/test/java/nl/jqno/paralleljava/app/serialization/GsonSerializerTest.java +++ b/src/test/java/nl/jqno/paralleljava/app/serialization/GsonSerializerTest.java @@ -19,7 +19,7 @@ public void serializationOfASingleTodo() { assertThat(actual) .contains("\"id\":\"" + SomeTodo.ID + "\"") .contains("\"title\":\"title\"") - .contains("\"url\":\"http://www.example.com\"") + .contains("\"url\":\"" + SomeTodo.URL + "\"") .contains("\"completed\":true") .contains("\"order\":1337"); }); @@ -47,7 +47,7 @@ public void serializationOfACompletePartialTodo() { assertThat(actual) .contains("\"id\":\"" + SomeTodo.ID + "\"") .contains("\"title\":\"title\"") - .contains("\"url\":\"http://www.example.com\"") + .contains("\"url\":\"" + SomeTodo.URL + "\"") .contains("\"completed\":true") .contains("\"order\":1337"); }); diff --git a/src/test/java/nl/jqno/paralleljava/dependencyinjection/TestData.java b/src/test/java/nl/jqno/paralleljava/dependencyinjection/TestData.java index 5009aab..ead31a1 100644 --- a/src/test/java/nl/jqno/paralleljava/dependencyinjection/TestData.java +++ b/src/test/java/nl/jqno/paralleljava/dependencyinjection/TestData.java @@ -9,20 +9,24 @@ public class TestData { + public static final String URL_PREFIX = "http://localhost/blabla"; + public static class SomeTodo { public static final UUID ID = UUID.randomUUID(); + public static final String URL = URL_PREFIX + "/" + ID.toString(); + public static final Todo TODO = - new Todo(ID, "title", "http://www.example.com", true, 1337); + new Todo(ID, "title", URL, true, 1337); public static final PartialTodo PARTIAL_COMPLETE = - new PartialTodo(Option.of(ID), Option.of("title"), Option.of("http://www.example.com"), Option.of(true), Option.of(1337)); + new PartialTodo(Option.of(ID), Option.of("title"), Option.of(URL), Option.of(true), Option.of(1337)); public static final PartialTodo PARTIAL_POST = new PartialTodo(Option.none(), Option.of("title"), Option.none(), Option.none(), Option.none()); public static final String SERIALIZED = - "{\"id\":\"" + ID + "\",\"title\":\"title\",\"url\":\"http://www.example.com\",\"completed\":true,\"order\":1337}"; + "{\"id\":\"" + ID + "\",\"title\":\"title\",\"url\":\"" + URL + "\",\"completed\":true,\"order\":1337}"; public static final String SERIALIZED_PARTIAL_POST = "{\"title\":\"title\"}"; @@ -34,11 +38,13 @@ public static class SomeTodo { public static class AnotherTodo { public static final UUID ID = UUID.randomUUID(); + public static final String URL = URL_PREFIX + "/" + ID.toString(); + public static final Todo TODO = - new Todo(ID, "something", "http://www.todobackend.com", false, 1); + new Todo(ID, "something", URL, false, 1); public static final String SERIALIZED = - "{\"id\":\"" + ID + "\",\"title\":\"something\",\"url\":\"http://www.todobackend.com\",\"completed\":false,\"order\":1}"; + "{\"id\":\"" + ID + "\",\"title\":\"something\",\"url\":\"" + URL + "\",\"completed\":false,\"order\":1}"; } public static class ListOfTodos { From 6ec72ca79a42dc3a8607461348d736aa3c3277fc Mon Sep 17 00:00:00 2001 From: Jan Ouwens Date: Thu, 28 Mar 2019 17:28:41 +0100 Subject: [PATCH 087/106] Implements the rest of the database queries --- .../database/DatabaseRepository.java | 15 +++++-- .../database/DatabaseRepositoryTest.java | 43 +++++++++++++++++-- .../inmemory/InMemoryRepositoryTest.java | 2 +- 3 files changed, 52 insertions(+), 8 deletions(-) diff --git a/src/main/java/nl/jqno/paralleljava/app/persistence/database/DatabaseRepository.java b/src/main/java/nl/jqno/paralleljava/app/persistence/database/DatabaseRepository.java index bee27cc..1264b41 100644 --- a/src/main/java/nl/jqno/paralleljava/app/persistence/database/DatabaseRepository.java +++ b/src/main/java/nl/jqno/paralleljava/app/persistence/database/DatabaseRepository.java @@ -61,15 +61,24 @@ public Try> getAllTodos() { } public Try updateTodo(Todo todo) { - return null; + return execute(handle -> + handle.createUpdate("UPDATE todo SET title = ?, completed = ?, index = ? WHERE id = ?") + .bind(0, todo.title()) + .bind(1, todo.completed()) + .bind(2, todo.order()) + .bind(3, todo.id()) + .execute()); } public Try delete(UUID id) { - return null; + return execute(handle -> + handle.createUpdate("DELETE FROM todo WHERE id = ?") + .bind(0, id.toString()) + .execute()); } public Try clearAllTodos() { - return null; + return execute(handle -> handle.execute("DELETE FROM todo")); } private Try execute(HandleConsumer consumer) { diff --git a/src/test/java/nl/jqno/paralleljava/app/persistence/database/DatabaseRepositoryTest.java b/src/test/java/nl/jqno/paralleljava/app/persistence/database/DatabaseRepositoryTest.java index 7906dbc..e82bcfb 100644 --- a/src/test/java/nl/jqno/paralleljava/app/persistence/database/DatabaseRepositoryTest.java +++ b/src/test/java/nl/jqno/paralleljava/app/persistence/database/DatabaseRepositoryTest.java @@ -1,6 +1,8 @@ package nl.jqno.paralleljava.app.persistence.database; import nl.jqno.paralleljava.dependencyinjection.TestData; +import nl.jqno.paralleljava.dependencyinjection.TestData.AnotherTodo; +import nl.jqno.paralleljava.dependencyinjection.TestData.SomeTodo; import nl.jqno.paralleljava.dependencyinjection.TestWiring; import nl.jqno.paralleljava.dependencyinjection.Wiring; import nl.jqno.picotest.Test; @@ -56,20 +58,53 @@ public void repository() { beforeAll(() -> { assertThat(repo.initialize()).isSuccess(); + assertThat(repo.clearAllTodos()).isSuccess(); }); test("create a todo", () -> { - var result = repo.createTodo(TestData.SomeTodo.TODO); + var result = repo.createTodo(SomeTodo.TODO); assertThat(result).isSuccess(); - assertThat(repo.getAllTodos()).hasValueSatisfying(l -> l.contains(TestData.SomeTodo.TODO)); + assertThat(repo.getAllTodos()).hasValueSatisfying(l -> l.contains(SomeTodo.TODO)); }); test("get a specific todo", () -> { - repo.createTodo(TestData.SomeTodo.TODO); + repo.createTodo(SomeTodo.TODO); - assertThat(repo.get(TestData.SomeTodo.ID)).hasValueSatisfying(o -> assertThat(o).contains(TestData.SomeTodo.TODO)); + assertThat(repo.get(SomeTodo.ID)).hasValueSatisfying(o -> assertThat(o).contains(SomeTodo.TODO)); assertThat(repo.get(UUID.randomUUID())).hasValueSatisfying(o -> assertThat(o).isEmpty()); }); + + test("update a specific todo", () -> { + var expected = SomeTodo.TODO.withTitle("another title"); + repo.createTodo(SomeTodo.TODO); + repo.createTodo(AnotherTodo.TODO); + + var result = repo.updateTodo(expected); + var actual = repo.get(SomeTodo.ID).get(); + + assertThat(result).isSuccess(); + assertThat(actual).contains(expected); + }); + + test("delete a specific todo", () -> { + repo.createTodo(SomeTodo.TODO); + repo.createTodo(AnotherTodo.TODO); + + var result = repo.delete(SomeTodo.ID); + var actual = repo.get(SomeTodo.ID).get(); + + assertThat(result).isSuccess(); + assertThat(actual).isEmpty(); + }); + + test("clearing all todos", () -> { + repo.createTodo(SomeTodo.TODO); + + var result = repo.clearAllTodos(); + + assertThat(result).isSuccess(); + assertThat(repo.getAllTodos()).hasValueSatisfying(l -> assertThat(l).isEmpty()); + }); } } diff --git a/src/test/java/nl/jqno/paralleljava/app/persistence/inmemory/InMemoryRepositoryTest.java b/src/test/java/nl/jqno/paralleljava/app/persistence/inmemory/InMemoryRepositoryTest.java index 95ddd36..5942fd7 100644 --- a/src/test/java/nl/jqno/paralleljava/app/persistence/inmemory/InMemoryRepositoryTest.java +++ b/src/test/java/nl/jqno/paralleljava/app/persistence/inmemory/InMemoryRepositoryTest.java @@ -72,7 +72,7 @@ public void repository() { assertThat(actual).isEmpty(); }); - test("update a specific todo at index 1", () -> { + test("delete a specific todo at index 1", () -> { repo.createTodo(AnotherTodo.TODO); repo.createTodo(SomeTodo.TODO); From 5c21db459dc4a8ab4deb7b3442fa2a7b9e1ab0d1 Mon Sep 17 00:00:00 2001 From: Jan Ouwens Date: Thu, 28 Mar 2019 17:28:56 +0100 Subject: [PATCH 088/106] Removes placeholder tests --- .../database/DatabaseRepositoryTest.java | 23 ------------------- 1 file changed, 23 deletions(-) diff --git a/src/test/java/nl/jqno/paralleljava/app/persistence/database/DatabaseRepositoryTest.java b/src/test/java/nl/jqno/paralleljava/app/persistence/database/DatabaseRepositoryTest.java index e82bcfb..8f0f3f1 100644 --- a/src/test/java/nl/jqno/paralleljava/app/persistence/database/DatabaseRepositoryTest.java +++ b/src/test/java/nl/jqno/paralleljava/app/persistence/database/DatabaseRepositoryTest.java @@ -30,29 +30,6 @@ public void initialization() { }); } - public void placeholders() { - var repo = Wiring.databaseRepository(DatabaseRepository.DEFAULT_JDBC_URL, todoMapper, TestWiring.nopLoggerFactory()); - - test("createTodo placeholder", () -> { - repo.createTodo(null); - }); - test("get placeholder", () -> { - repo.get(UUID.randomUUID()); - }); - test("getAllTodos placeholder", () -> { - repo.getAllTodos(); - }); - test("updateTodo placeholder", () -> { - repo.updateTodo(null); - }); - test("delete placeholder", () -> { - repo.delete(null); - }); - test("clearAllTodos placeholder", () -> { - repo.clearAllTodos(); - }); - } - public void repository() { var repo = Wiring.databaseRepository(DatabaseRepository.DEFAULT_JDBC_URL, todoMapper, TestWiring.nopLoggerFactory()); From 6fc48f9b5eacd9c3eb6936ac4102e9babcf1619c Mon Sep 17 00:00:00 2001 From: Jan Ouwens Date: Thu, 28 Mar 2019 17:33:55 +0100 Subject: [PATCH 089/106] Adds some failure logging to DatabaseRepository --- .../app/persistence/database/DatabaseRepository.java | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/main/java/nl/jqno/paralleljava/app/persistence/database/DatabaseRepository.java b/src/main/java/nl/jqno/paralleljava/app/persistence/database/DatabaseRepository.java index 1264b41..2b93951 100644 --- a/src/main/java/nl/jqno/paralleljava/app/persistence/database/DatabaseRepository.java +++ b/src/main/java/nl/jqno/paralleljava/app/persistence/database/DatabaseRepository.java @@ -82,13 +82,14 @@ public Try clearAllTodos() { } private Try execute(HandleConsumer consumer) { - return Try.of(() -> { + return Try.of(() -> { jdbi.useHandle(consumer); return null; - }); + }).onFailure(f -> logger.wakeMeUp("Failed to execute statement", f)); } private Try query(HandleCallback callback) { - return Try.of(() -> jdbi.withHandle(callback)); + return Try.of(() -> jdbi.withHandle(callback)) + .onFailure(f -> logger.wakeMeUp("Failed to execute query", f)); } } From ad1d2113148048b07604ed566bbdfd8cd990ff28 Mon Sep 17 00:00:00 2001 From: Jan Ouwens Date: Fri, 29 Mar 2019 06:54:29 +0100 Subject: [PATCH 090/106] Adds PostgreSQL dependency --- pom.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/pom.yml b/pom.yml index 9f8458c..666dc34 100644 --- a/pom.yml +++ b/pom.yml @@ -30,6 +30,7 @@ dependencies: - { groupId: com.google.code.gson, artifactId: gson, version: 2.8.5 } - { groupId: org.jdbi, artifactId: jdbi3-core } - { groupId: org.jdbi, artifactId: jdbi3-vavr } + - { groupId: org.postgresql, artifactId: postgresql, version: 42.2.5 } - { groupId: nl.jqno.picotest, artifactId: picotest, version: 0.3, scope: test } - { groupId: nl.jqno.equalsverifier, artifactId: equalsverifier, version: 3.1.7, scope: test } From a65ba50509a6e54e096eb73bd8d36915369ed79f Mon Sep 17 00:00:00 2001 From: Jan Ouwens Date: Fri, 29 Mar 2019 06:59:21 +0100 Subject: [PATCH 091/106] Fixes PostgreSQL conversion issue --- .../app/persistence/database/DatabaseRepository.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/nl/jqno/paralleljava/app/persistence/database/DatabaseRepository.java b/src/main/java/nl/jqno/paralleljava/app/persistence/database/DatabaseRepository.java index 2b93951..875dbd1 100644 --- a/src/main/java/nl/jqno/paralleljava/app/persistence/database/DatabaseRepository.java +++ b/src/main/java/nl/jqno/paralleljava/app/persistence/database/DatabaseRepository.java @@ -36,7 +36,7 @@ public Try initialize() { public Try createTodo(Todo todo) { return execute(handle -> handle.createUpdate("INSERT INTO todo (id, title, completed, index) VALUES (?, ?, ?, ?)") - .bind(0, todo.id()) + .bind(0, todo.id().toString()) .bind(1, todo.title()) .bind(2, todo.completed()) .bind(3, todo.order()) @@ -66,7 +66,7 @@ public Try updateTodo(Todo todo) { .bind(0, todo.title()) .bind(1, todo.completed()) .bind(2, todo.order()) - .bind(3, todo.id()) + .bind(3, todo.id().toString()) .execute()); } From 94d407672bacec3ae5a859bf6a60b1427867c3ee Mon Sep 17 00:00:00 2001 From: Jan Ouwens Date: Fri, 29 Mar 2019 07:15:09 +0100 Subject: [PATCH 092/106] Adds test for failure case --- .../database/DatabaseRepositoryTest.java | 31 +++++++++++++++++-- 1 file changed, 28 insertions(+), 3 deletions(-) diff --git a/src/test/java/nl/jqno/paralleljava/app/persistence/database/DatabaseRepositoryTest.java b/src/test/java/nl/jqno/paralleljava/app/persistence/database/DatabaseRepositoryTest.java index 8f0f3f1..bb39787 100644 --- a/src/test/java/nl/jqno/paralleljava/app/persistence/database/DatabaseRepositoryTest.java +++ b/src/test/java/nl/jqno/paralleljava/app/persistence/database/DatabaseRepositoryTest.java @@ -1,37 +1,42 @@ package nl.jqno.paralleljava.app.persistence.database; +import nl.jqno.paralleljava.app.domain.Todo; import nl.jqno.paralleljava.dependencyinjection.TestData; import nl.jqno.paralleljava.dependencyinjection.TestData.AnotherTodo; import nl.jqno.paralleljava.dependencyinjection.TestData.SomeTodo; import nl.jqno.paralleljava.dependencyinjection.TestWiring; import nl.jqno.paralleljava.dependencyinjection.Wiring; import nl.jqno.picotest.Test; +import org.jdbi.v3.core.statement.StatementContext; +import java.sql.ResultSet; +import java.sql.SQLException; import java.util.UUID; import static org.assertj.vavr.api.VavrAssertions.assertThat; public class DatabaseRepositoryTest extends Test { + private static final String IN_MEMORY_DATABASE = DatabaseRepository.DEFAULT_JDBC_URL; private final TodoMapper todoMapper = new TodoMapper(TestData.URL_PREFIX); public void initialization() { test("a table is created", () -> { - var repo = Wiring.databaseRepository(DatabaseRepository.DEFAULT_JDBC_URL, todoMapper, TestWiring.nopLoggerFactory()); + var repo = Wiring.databaseRepository(IN_MEMORY_DATABASE, todoMapper, TestWiring.nopLoggerFactory()); var result = repo.initialize(); assertThat(result).isSuccess(); }); test("initializing twice is a no-op the second time", () -> { - var repo = Wiring.databaseRepository(DatabaseRepository.DEFAULT_JDBC_URL, todoMapper, TestWiring.nopLoggerFactory()); + var repo = Wiring.databaseRepository(IN_MEMORY_DATABASE, todoMapper, TestWiring.nopLoggerFactory()); assertThat(repo.initialize()).isSuccess(); assertThat(repo.initialize()).isSuccess(); }); } public void repository() { - var repo = Wiring.databaseRepository(DatabaseRepository.DEFAULT_JDBC_URL, todoMapper, TestWiring.nopLoggerFactory()); + var repo = Wiring.databaseRepository(IN_MEMORY_DATABASE, todoMapper, TestWiring.nopLoggerFactory()); beforeAll(() -> { assertThat(repo.initialize()).isSuccess(); @@ -84,4 +89,24 @@ public void repository() { assertThat(repo.getAllTodos()).hasValueSatisfying(l -> assertThat(l).isEmpty()); }); } + + public void failures() { + var failingMapper = new TodoMapper("") { + public Todo map(ResultSet rs, StatementContext ctx) throws SQLException { + throw new SQLException("Intentional failure"); + } + }; + var repo = Wiring.databaseRepository(IN_MEMORY_DATABASE, failingMapper, TestWiring.nopLoggerFactory()); + + beforeAll(() -> { + repo.initialize(); + }); + + test("Query failures cause failed results", () -> { + repo.createTodo(SomeTodo.TODO); + + var result = repo.getAllTodos(); + assertThat(result).isFailure(); + }); + } } From 25b90a038f1d401d9282d897b514fc13aa0910a2 Mon Sep 17 00:00:00 2001 From: Jan Ouwens Date: Fri, 29 Mar 2019 07:25:45 +0100 Subject: [PATCH 093/106] Consolidates environment default constants --- src/main/java/nl/jqno/paralleljava/Main.java | 14 +++++--------- .../paralleljava/app/environment/Environment.java | 6 ++++++ .../persistence/database/DatabaseRepository.java | 2 -- .../database/DatabaseRepositoryTest.java | 3 ++- 4 files changed, 13 insertions(+), 12 deletions(-) diff --git a/src/main/java/nl/jqno/paralleljava/Main.java b/src/main/java/nl/jqno/paralleljava/Main.java index d10a9a3..e46ed65 100644 --- a/src/main/java/nl/jqno/paralleljava/Main.java +++ b/src/main/java/nl/jqno/paralleljava/Main.java @@ -1,27 +1,23 @@ package nl.jqno.paralleljava; -import nl.jqno.paralleljava.app.persistence.database.DatabaseRepository; +import nl.jqno.paralleljava.app.environment.Environment; import nl.jqno.paralleljava.app.persistence.database.TodoMapper; import nl.jqno.paralleljava.dependencyinjection.Wiring; public class Main { - private static final int DEFAULT_PORT = 4567; - private static final String DEFAULT_URL = "http://localhost"; - private static final String ENDPOINT = "/todo"; - public static void main(String... args) { var loggerFactory = Wiring.slf4jLoggerFactory(); var environment = Wiring.herokuEnvironment(); - var fullUrl = environment.hostUrl().getOrElse(DEFAULT_URL) + ENDPOINT; - var port = environment.port().getOrElse(DEFAULT_PORT); - var jdbcUrl = environment.jdbcUrl().getOrElse(DatabaseRepository.DEFAULT_JDBC_URL); + var fullUrl = environment.hostUrl().getOrElse(Environment.DEFAULT_URL) + Environment.ENDPOINT; + var port = environment.port().getOrElse(Environment.DEFAULT_PORT); + var jdbcUrl = environment.jdbcUrl().getOrElse(Environment.DEFAULT_JDBC_URL); var todoMapper = new TodoMapper(fullUrl); var repository = Wiring.databaseRepository(jdbcUrl, todoMapper, loggerFactory); var idGenerator = Wiring.randomIdGenerator(); var controller = Wiring.defaultController(fullUrl, repository, idGenerator, loggerFactory); - var server = Wiring.sparkServer(ENDPOINT, port, controller, loggerFactory); + var server = Wiring.sparkServer(Environment.ENDPOINT, port, controller, loggerFactory); repository.initialize() .onSuccess(ignored -> server.run()); diff --git a/src/main/java/nl/jqno/paralleljava/app/environment/Environment.java b/src/main/java/nl/jqno/paralleljava/app/environment/Environment.java index 3b93aec..cce5d23 100644 --- a/src/main/java/nl/jqno/paralleljava/app/environment/Environment.java +++ b/src/main/java/nl/jqno/paralleljava/app/environment/Environment.java @@ -3,6 +3,12 @@ import io.vavr.control.Option; public interface Environment { + + int DEFAULT_PORT = 4567; + String DEFAULT_URL = "http://localhost"; + String ENDPOINT = "/todo"; + String DEFAULT_JDBC_URL = "jdbc:h2:mem:test;DB_CLOSE_DELAY=-1"; + Option port(); Option hostUrl(); Option jdbcUrl(); diff --git a/src/main/java/nl/jqno/paralleljava/app/persistence/database/DatabaseRepository.java b/src/main/java/nl/jqno/paralleljava/app/persistence/database/DatabaseRepository.java index 875dbd1..a0214f3 100644 --- a/src/main/java/nl/jqno/paralleljava/app/persistence/database/DatabaseRepository.java +++ b/src/main/java/nl/jqno/paralleljava/app/persistence/database/DatabaseRepository.java @@ -15,8 +15,6 @@ public class DatabaseRepository implements Repository { - public static final String DEFAULT_JDBC_URL = "jdbc:h2:mem:test;DB_CLOSE_DELAY=-1"; - private final Logger logger; private final Jdbi jdbi; diff --git a/src/test/java/nl/jqno/paralleljava/app/persistence/database/DatabaseRepositoryTest.java b/src/test/java/nl/jqno/paralleljava/app/persistence/database/DatabaseRepositoryTest.java index bb39787..9b1cefb 100644 --- a/src/test/java/nl/jqno/paralleljava/app/persistence/database/DatabaseRepositoryTest.java +++ b/src/test/java/nl/jqno/paralleljava/app/persistence/database/DatabaseRepositoryTest.java @@ -1,6 +1,7 @@ package nl.jqno.paralleljava.app.persistence.database; import nl.jqno.paralleljava.app.domain.Todo; +import nl.jqno.paralleljava.app.environment.Environment; import nl.jqno.paralleljava.dependencyinjection.TestData; import nl.jqno.paralleljava.dependencyinjection.TestData.AnotherTodo; import nl.jqno.paralleljava.dependencyinjection.TestData.SomeTodo; @@ -17,7 +18,7 @@ public class DatabaseRepositoryTest extends Test { - private static final String IN_MEMORY_DATABASE = DatabaseRepository.DEFAULT_JDBC_URL; + private static final String IN_MEMORY_DATABASE = Environment.DEFAULT_JDBC_URL; private final TodoMapper todoMapper = new TodoMapper(TestData.URL_PREFIX); public void initialization() { From 1b579b30450d59d703a6960123baff1c07ff5cac Mon Sep 17 00:00:00 2001 From: Jan Ouwens Date: Fri, 29 Mar 2019 08:20:09 +0100 Subject: [PATCH 094/106] Factors out Wired class --- pom.yml | 1 - src/main/java/nl/jqno/paralleljava/Main.java | 26 ++++++-- .../app/serialization/GsonSerializer.java | 8 +++ .../dependencyinjection/Wiring.java | 64 ------------------- .../jqno/paralleljava/ArchitectureTest.java | 3 - .../{dependencyinjection => }/TestData.java | 2 +- .../app/controller/DefaultControllerTest.java | 16 +++-- .../controller}/StubController.java | 2 +- .../app/domain/PartialTodoTest.java | 2 +- .../paralleljava/app/domain/TodoTest.java | 2 +- .../environment/HerokuEnvironmentTest.java | 5 +- .../stubs => app/logging}/NopLogger.java | 2 +- .../app/logging/Slf4jLoggerTest.java | 4 +- .../stubs => app/logging}/StubLogger.java | 2 +- .../persistence}/ConstantIdGenerator.java | 2 +- .../persistence/RandomIdGeneratorTest.java | 3 +- .../database/DatabaseRepositoryTest.java | 19 +++--- .../inmemory/InMemoryRepositoryTest.java | 9 ++- .../app/serialization/GsonSerializerTest.java | 7 +- .../app/server/SparkServerTest.java | 9 ++- .../dependencyinjection/TestWiring.java | 39 ----------- 21 files changed, 68 insertions(+), 159 deletions(-) delete mode 100644 src/main/java/nl/jqno/paralleljava/dependencyinjection/Wiring.java rename src/test/java/nl/jqno/paralleljava/{dependencyinjection => }/TestData.java (97%) rename src/test/java/nl/jqno/paralleljava/{dependencyinjection/stubs => app/controller}/StubController.java (97%) rename src/test/java/nl/jqno/paralleljava/{dependencyinjection/stubs => app/logging}/NopLogger.java (88%) rename src/test/java/nl/jqno/paralleljava/{dependencyinjection/stubs => app/logging}/StubLogger.java (94%) rename src/test/java/nl/jqno/paralleljava/{dependencyinjection/stubs => app/persistence}/ConstantIdGenerator.java (84%) delete mode 100644 src/test/java/nl/jqno/paralleljava/dependencyinjection/TestWiring.java diff --git a/pom.yml b/pom.yml index 666dc34..3ecf313 100644 --- a/pom.yml +++ b/pom.yml @@ -96,7 +96,6 @@ build: configuration: excludes: - "nl/jqno/paralleljava/Main.class" - - "nl/jqno/paralleljava/dependencyinjection/**" executions: - id: default-prepare-angent goals: [prepare-agent] diff --git a/src/main/java/nl/jqno/paralleljava/Main.java b/src/main/java/nl/jqno/paralleljava/Main.java index e46ed65..0b3ae2a 100644 --- a/src/main/java/nl/jqno/paralleljava/Main.java +++ b/src/main/java/nl/jqno/paralleljava/Main.java @@ -1,23 +1,35 @@ package nl.jqno.paralleljava; +import io.vavr.collection.HashMap; +import nl.jqno.paralleljava.app.controller.DefaultController; import nl.jqno.paralleljava.app.environment.Environment; +import nl.jqno.paralleljava.app.environment.HerokuEnvironment; +import nl.jqno.paralleljava.app.logging.LoggerFactory; +import nl.jqno.paralleljava.app.logging.Slf4jLogger; +import nl.jqno.paralleljava.app.persistence.RandomIdGenerator; +import nl.jqno.paralleljava.app.persistence.database.DatabaseRepository; import nl.jqno.paralleljava.app.persistence.database.TodoMapper; -import nl.jqno.paralleljava.dependencyinjection.Wiring; +import nl.jqno.paralleljava.app.serialization.GsonSerializer; +import nl.jqno.paralleljava.app.server.SparkServer; public class Main { public static void main(String... args) { - var loggerFactory = Wiring.slf4jLoggerFactory(); + LoggerFactory loggerFactory = c -> new Slf4jLogger(org.slf4j.LoggerFactory.getLogger(c)); + + var processBuilder = new ProcessBuilder(); + var environmentMap = HashMap.ofAll(processBuilder.environment()); + var environment = new HerokuEnvironment(environmentMap); - var environment = Wiring.herokuEnvironment(); var fullUrl = environment.hostUrl().getOrElse(Environment.DEFAULT_URL) + Environment.ENDPOINT; var port = environment.port().getOrElse(Environment.DEFAULT_PORT); var jdbcUrl = environment.jdbcUrl().getOrElse(Environment.DEFAULT_JDBC_URL); var todoMapper = new TodoMapper(fullUrl); - var repository = Wiring.databaseRepository(jdbcUrl, todoMapper, loggerFactory); - var idGenerator = Wiring.randomIdGenerator(); - var controller = Wiring.defaultController(fullUrl, repository, idGenerator, loggerFactory); - var server = Wiring.sparkServer(Environment.ENDPOINT, port, controller, loggerFactory); + var repository = new DatabaseRepository(jdbcUrl, todoMapper, loggerFactory); + var idGenerator = new RandomIdGenerator(); + var serializer = GsonSerializer.create(loggerFactory); + var controller = new DefaultController(fullUrl, repository, idGenerator, serializer, loggerFactory); + var server = new SparkServer(Environment.ENDPOINT, port, controller, loggerFactory); repository.initialize() .onSuccess(ignored -> server.run()); diff --git a/src/main/java/nl/jqno/paralleljava/app/serialization/GsonSerializer.java b/src/main/java/nl/jqno/paralleljava/app/serialization/GsonSerializer.java index 8a0ddf5..6908b1e 100644 --- a/src/main/java/nl/jqno/paralleljava/app/serialization/GsonSerializer.java +++ b/src/main/java/nl/jqno/paralleljava/app/serialization/GsonSerializer.java @@ -1,11 +1,13 @@ package nl.jqno.paralleljava.app.serialization; import com.google.gson.Gson; +import com.google.gson.GsonBuilder; import com.google.gson.reflect.TypeToken; import io.vavr.CheckedFunction0; import io.vavr.collection.List; import io.vavr.control.Option; import io.vavr.control.Try; +import io.vavr.gson.VavrGson; import nl.jqno.paralleljava.app.domain.PartialTodo; import nl.jqno.paralleljava.app.domain.Todo; import nl.jqno.paralleljava.app.logging.Logger; @@ -20,6 +22,12 @@ public class GsonSerializer implements Serializer { private final Gson gson; private final Logger logger; + public static Serializer create(LoggerFactory loggerFactory) { + var gsonBuilder = new GsonBuilder(); + VavrGson.registerAll(gsonBuilder); + return new GsonSerializer(gsonBuilder.create(), loggerFactory); + } + public GsonSerializer(Gson gson, LoggerFactory loggerFactory) { this.gson = gson; this.logger = loggerFactory.create(getClass()); diff --git a/src/main/java/nl/jqno/paralleljava/dependencyinjection/Wiring.java b/src/main/java/nl/jqno/paralleljava/dependencyinjection/Wiring.java deleted file mode 100644 index 23b7a5b..0000000 --- a/src/main/java/nl/jqno/paralleljava/dependencyinjection/Wiring.java +++ /dev/null @@ -1,64 +0,0 @@ -package nl.jqno.paralleljava.dependencyinjection; - -import com.google.gson.GsonBuilder; -import io.vavr.collection.HashMap; -import io.vavr.gson.VavrGson; -import nl.jqno.paralleljava.app.controller.Controller; -import nl.jqno.paralleljava.app.controller.DefaultController; -import nl.jqno.paralleljava.app.environment.Environment; -import nl.jqno.paralleljava.app.environment.HerokuEnvironment; -import nl.jqno.paralleljava.app.logging.LoggerFactory; -import nl.jqno.paralleljava.app.logging.Slf4jLogger; -import nl.jqno.paralleljava.app.persistence.IdGenerator; -import nl.jqno.paralleljava.app.persistence.RandomIdGenerator; -import nl.jqno.paralleljava.app.persistence.Repository; -import nl.jqno.paralleljava.app.persistence.database.DatabaseRepository; -import nl.jqno.paralleljava.app.persistence.database.TodoMapper; -import nl.jqno.paralleljava.app.persistence.inmemory.InMemoryRepository; -import nl.jqno.paralleljava.app.serialization.GsonSerializer; -import nl.jqno.paralleljava.app.serialization.Serializer; -import nl.jqno.paralleljava.app.server.Server; -import nl.jqno.paralleljava.app.server.SparkServer; - -public class Wiring { - - private Wiring() { - // Don't instantiate - } - - public static Environment herokuEnvironment() { - var processBuilder = new ProcessBuilder(); - var environment = HashMap.ofAll(processBuilder.environment()); - return new HerokuEnvironment(environment); - } - - public static LoggerFactory slf4jLoggerFactory() { - return c -> new Slf4jLogger(org.slf4j.LoggerFactory.getLogger(c)); - } - - public static Serializer gsonSerializer(LoggerFactory loggerFactory) { - var gsonBuilder = new GsonBuilder(); - VavrGson.registerAll(gsonBuilder); - return new GsonSerializer(gsonBuilder.create(), loggerFactory); - } - - public static Repository inMemoryRepository(LoggerFactory loggerFactory) { - return new InMemoryRepository(loggerFactory); - } - - public static Repository databaseRepository(String jdbcUrl, TodoMapper todoMapper, LoggerFactory loggerFactory) { - return new DatabaseRepository(jdbcUrl, todoMapper, loggerFactory); - } - - public static IdGenerator randomIdGenerator() { - return new RandomIdGenerator(); - } - - public static Controller defaultController(String fullUrl, Repository repository, IdGenerator idGenerator, LoggerFactory loggerFactory) { - return new DefaultController(fullUrl, repository, idGenerator, gsonSerializer(loggerFactory), loggerFactory); - } - - public static Server sparkServer(String endpoint, int port, Controller controller, LoggerFactory loggerFactory) { - return new SparkServer(endpoint, port, controller, loggerFactory); - } -} diff --git a/src/test/java/nl/jqno/paralleljava/ArchitectureTest.java b/src/test/java/nl/jqno/paralleljava/ArchitectureTest.java index b67fc92..e65905f 100644 --- a/src/test/java/nl/jqno/paralleljava/ArchitectureTest.java +++ b/src/test/java/nl/jqno/paralleljava/ArchitectureTest.java @@ -6,7 +6,6 @@ import nl.jqno.paralleljava.app.persistence.database.DatabaseRepository; import nl.jqno.paralleljava.app.serialization.GsonSerializer; import nl.jqno.paralleljava.app.server.SparkServer; -import nl.jqno.paralleljava.dependencyinjection.Wiring; import nl.jqno.picotest.Test; import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.noClasses; @@ -37,8 +36,6 @@ public void architecture() { private void assertBoundary(String restrictedPackageIdentifier, Package whiteListedPackage) { var rule = noClasses() .that().resideOutsideOfPackage(whiteListedPackage.getName()) - .and().dontHaveFullyQualifiedName(Wiring.class.getCanonicalName()) - .and().resideOutsideOfPackage("nl.jqno.paralleljava.dependencyinjection.stubs") .should().accessClassesThat().resideInAPackage(restrictedPackageIdentifier); rule.check(IMPORTED_CLASSES); } diff --git a/src/test/java/nl/jqno/paralleljava/dependencyinjection/TestData.java b/src/test/java/nl/jqno/paralleljava/TestData.java similarity index 97% rename from src/test/java/nl/jqno/paralleljava/dependencyinjection/TestData.java rename to src/test/java/nl/jqno/paralleljava/TestData.java index ead31a1..999cb1b 100644 --- a/src/test/java/nl/jqno/paralleljava/dependencyinjection/TestData.java +++ b/src/test/java/nl/jqno/paralleljava/TestData.java @@ -1,4 +1,4 @@ -package nl.jqno.paralleljava.dependencyinjection; +package nl.jqno.paralleljava; import io.vavr.collection.List; import io.vavr.control.Option; diff --git a/src/test/java/nl/jqno/paralleljava/app/controller/DefaultControllerTest.java b/src/test/java/nl/jqno/paralleljava/app/controller/DefaultControllerTest.java index f10a653..7d1e7f9 100644 --- a/src/test/java/nl/jqno/paralleljava/app/controller/DefaultControllerTest.java +++ b/src/test/java/nl/jqno/paralleljava/app/controller/DefaultControllerTest.java @@ -4,14 +4,16 @@ import nl.jqno.paralleljava.app.logging.LoggerFactory; import nl.jqno.paralleljava.app.persistence.IdGenerator; import nl.jqno.paralleljava.app.persistence.Repository; +import nl.jqno.paralleljava.app.persistence.inmemory.InMemoryRepository; +import nl.jqno.paralleljava.app.serialization.GsonSerializer; import nl.jqno.paralleljava.app.serialization.Serializer; -import nl.jqno.paralleljava.dependencyinjection.TestWiring; -import nl.jqno.paralleljava.dependencyinjection.Wiring; +import nl.jqno.paralleljava.app.persistence.ConstantIdGenerator; +import nl.jqno.paralleljava.app.logging.NopLogger; import nl.jqno.picotest.Test; import java.util.UUID; -import static nl.jqno.paralleljava.dependencyinjection.TestData.*; +import static nl.jqno.paralleljava.TestData.*; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.vavr.api.VavrAssertions.assertThat; @@ -20,10 +22,10 @@ public class DefaultControllerTest extends Test { private final UUID constantId = UUID.randomUUID(); private final String fullUrl = "/blabla/todo"; - private final LoggerFactory loggerFactory = TestWiring.nopLoggerFactory(); - private final Repository repository = Wiring.inMemoryRepository(loggerFactory); - private final IdGenerator idGenerator = TestWiring.constantIdGenerator(constantId); - private final Serializer serializer = Wiring.gsonSerializer(loggerFactory); + private final LoggerFactory loggerFactory = c -> new NopLogger(); + private final Repository repository = new InMemoryRepository(loggerFactory); + private final IdGenerator idGenerator = new ConstantIdGenerator(constantId); + private final Serializer serializer = GsonSerializer.create(loggerFactory); private final DefaultController controller = new DefaultController(fullUrl, repository, idGenerator, serializer, loggerFactory); diff --git a/src/test/java/nl/jqno/paralleljava/dependencyinjection/stubs/StubController.java b/src/test/java/nl/jqno/paralleljava/app/controller/StubController.java similarity index 97% rename from src/test/java/nl/jqno/paralleljava/dependencyinjection/stubs/StubController.java rename to src/test/java/nl/jqno/paralleljava/app/controller/StubController.java index b99b6f2..2267e1d 100644 --- a/src/test/java/nl/jqno/paralleljava/dependencyinjection/stubs/StubController.java +++ b/src/test/java/nl/jqno/paralleljava/app/controller/StubController.java @@ -1,4 +1,4 @@ -package nl.jqno.paralleljava.dependencyinjection.stubs; +package nl.jqno.paralleljava.app.controller; import io.vavr.control.Try; import nl.jqno.paralleljava.app.controller.Controller; diff --git a/src/test/java/nl/jqno/paralleljava/app/domain/PartialTodoTest.java b/src/test/java/nl/jqno/paralleljava/app/domain/PartialTodoTest.java index 8ec9217..678c1e0 100644 --- a/src/test/java/nl/jqno/paralleljava/app/domain/PartialTodoTest.java +++ b/src/test/java/nl/jqno/paralleljava/app/domain/PartialTodoTest.java @@ -1,7 +1,7 @@ package nl.jqno.paralleljava.app.domain; import nl.jqno.equalsverifier.EqualsVerifier; -import nl.jqno.paralleljava.dependencyinjection.TestData.SomeTodo; +import nl.jqno.paralleljava.TestData.SomeTodo; import nl.jqno.picotest.Test; import static org.assertj.core.api.Assertions.assertThat; diff --git a/src/test/java/nl/jqno/paralleljava/app/domain/TodoTest.java b/src/test/java/nl/jqno/paralleljava/app/domain/TodoTest.java index bbfd6a4..470a868 100644 --- a/src/test/java/nl/jqno/paralleljava/app/domain/TodoTest.java +++ b/src/test/java/nl/jqno/paralleljava/app/domain/TodoTest.java @@ -1,7 +1,7 @@ package nl.jqno.paralleljava.app.domain; import nl.jqno.equalsverifier.EqualsVerifier; -import nl.jqno.paralleljava.dependencyinjection.TestData.SomeTodo; +import nl.jqno.paralleljava.TestData.SomeTodo; import nl.jqno.picotest.Test; import static org.assertj.core.api.Assertions.assertThat; diff --git a/src/test/java/nl/jqno/paralleljava/app/environment/HerokuEnvironmentTest.java b/src/test/java/nl/jqno/paralleljava/app/environment/HerokuEnvironmentTest.java index 71acf3a..bb74193 100644 --- a/src/test/java/nl/jqno/paralleljava/app/environment/HerokuEnvironmentTest.java +++ b/src/test/java/nl/jqno/paralleljava/app/environment/HerokuEnvironmentTest.java @@ -1,14 +1,13 @@ package nl.jqno.paralleljava.app.environment; import io.vavr.collection.HashMap; -import nl.jqno.paralleljava.dependencyinjection.TestWiring; import nl.jqno.picotest.Test; import static org.assertj.vavr.api.VavrAssertions.assertThat; public class HerokuEnvironmentTest extends Test { - private Environment environment = TestWiring.mapBasedHerokuEnvironment(HashMap.empty()); + private Environment environment = new HerokuEnvironment(HashMap.empty()); public void port() { test("a valid port is provided", () -> { @@ -42,6 +41,6 @@ public void port() { private void setEnvironmentVariable(String key, String value) { var env = HashMap.of(key, value); - environment = TestWiring.mapBasedHerokuEnvironment(env); + environment = new HerokuEnvironment(env); } } diff --git a/src/test/java/nl/jqno/paralleljava/dependencyinjection/stubs/NopLogger.java b/src/test/java/nl/jqno/paralleljava/app/logging/NopLogger.java similarity index 88% rename from src/test/java/nl/jqno/paralleljava/dependencyinjection/stubs/NopLogger.java rename to src/test/java/nl/jqno/paralleljava/app/logging/NopLogger.java index 075ba5e..8736a63 100644 --- a/src/test/java/nl/jqno/paralleljava/dependencyinjection/stubs/NopLogger.java +++ b/src/test/java/nl/jqno/paralleljava/app/logging/NopLogger.java @@ -1,4 +1,4 @@ -package nl.jqno.paralleljava.dependencyinjection.stubs; +package nl.jqno.paralleljava.app.logging; import nl.jqno.paralleljava.app.logging.Logger; diff --git a/src/test/java/nl/jqno/paralleljava/app/logging/Slf4jLoggerTest.java b/src/test/java/nl/jqno/paralleljava/app/logging/Slf4jLoggerTest.java index 582cb5c..d0d644f 100644 --- a/src/test/java/nl/jqno/paralleljava/app/logging/Slf4jLoggerTest.java +++ b/src/test/java/nl/jqno/paralleljava/app/logging/Slf4jLoggerTest.java @@ -1,7 +1,5 @@ package nl.jqno.paralleljava.app.logging; -import nl.jqno.paralleljava.dependencyinjection.TestWiring; -import nl.jqno.paralleljava.dependencyinjection.stubs.StubLogger; import nl.jqno.picotest.Test; import static org.assertj.core.api.Assertions.assertThat; @@ -16,7 +14,7 @@ public class Slf4jLoggerTest extends Test { public void logger() { beforeEach(() -> { - underlying = TestWiring.stubLogger(); + underlying = new StubLogger(); logger = new Slf4jLogger(underlying); }); diff --git a/src/test/java/nl/jqno/paralleljava/dependencyinjection/stubs/StubLogger.java b/src/test/java/nl/jqno/paralleljava/app/logging/StubLogger.java similarity index 94% rename from src/test/java/nl/jqno/paralleljava/dependencyinjection/stubs/StubLogger.java rename to src/test/java/nl/jqno/paralleljava/app/logging/StubLogger.java index 9b63243..9445a2d 100644 --- a/src/test/java/nl/jqno/paralleljava/dependencyinjection/stubs/StubLogger.java +++ b/src/test/java/nl/jqno/paralleljava/app/logging/StubLogger.java @@ -1,4 +1,4 @@ -package nl.jqno.paralleljava.dependencyinjection.stubs; +package nl.jqno.paralleljava.app.logging; import org.slf4j.helpers.SubstituteLogger; diff --git a/src/test/java/nl/jqno/paralleljava/dependencyinjection/stubs/ConstantIdGenerator.java b/src/test/java/nl/jqno/paralleljava/app/persistence/ConstantIdGenerator.java similarity index 84% rename from src/test/java/nl/jqno/paralleljava/dependencyinjection/stubs/ConstantIdGenerator.java rename to src/test/java/nl/jqno/paralleljava/app/persistence/ConstantIdGenerator.java index 9ce7d82..491c4db 100644 --- a/src/test/java/nl/jqno/paralleljava/dependencyinjection/stubs/ConstantIdGenerator.java +++ b/src/test/java/nl/jqno/paralleljava/app/persistence/ConstantIdGenerator.java @@ -1,4 +1,4 @@ -package nl.jqno.paralleljava.dependencyinjection.stubs; +package nl.jqno.paralleljava.app.persistence; import nl.jqno.paralleljava.app.persistence.IdGenerator; diff --git a/src/test/java/nl/jqno/paralleljava/app/persistence/RandomIdGeneratorTest.java b/src/test/java/nl/jqno/paralleljava/app/persistence/RandomIdGeneratorTest.java index cec1abc..bf63ef0 100644 --- a/src/test/java/nl/jqno/paralleljava/app/persistence/RandomIdGeneratorTest.java +++ b/src/test/java/nl/jqno/paralleljava/app/persistence/RandomIdGeneratorTest.java @@ -1,6 +1,5 @@ package nl.jqno.paralleljava.app.persistence; -import nl.jqno.paralleljava.dependencyinjection.Wiring; import nl.jqno.picotest.Test; import org.assertj.core.api.Assertions; @@ -9,7 +8,7 @@ public class RandomIdGeneratorTest extends Test { public void uuidGenerator() { - var generator = Wiring.randomIdGenerator(); + var generator = new RandomIdGenerator(); test("generates a valid uuid", () -> { var actual = generator.generateId(); diff --git a/src/test/java/nl/jqno/paralleljava/app/persistence/database/DatabaseRepositoryTest.java b/src/test/java/nl/jqno/paralleljava/app/persistence/database/DatabaseRepositoryTest.java index 9b1cefb..f5c907f 100644 --- a/src/test/java/nl/jqno/paralleljava/app/persistence/database/DatabaseRepositoryTest.java +++ b/src/test/java/nl/jqno/paralleljava/app/persistence/database/DatabaseRepositoryTest.java @@ -2,11 +2,11 @@ import nl.jqno.paralleljava.app.domain.Todo; import nl.jqno.paralleljava.app.environment.Environment; -import nl.jqno.paralleljava.dependencyinjection.TestData; -import nl.jqno.paralleljava.dependencyinjection.TestData.AnotherTodo; -import nl.jqno.paralleljava.dependencyinjection.TestData.SomeTodo; -import nl.jqno.paralleljava.dependencyinjection.TestWiring; -import nl.jqno.paralleljava.dependencyinjection.Wiring; +import nl.jqno.paralleljava.app.logging.LoggerFactory; +import nl.jqno.paralleljava.TestData; +import nl.jqno.paralleljava.TestData.AnotherTodo; +import nl.jqno.paralleljava.TestData.SomeTodo; +import nl.jqno.paralleljava.app.logging.NopLogger; import nl.jqno.picotest.Test; import org.jdbi.v3.core.statement.StatementContext; @@ -19,25 +19,26 @@ public class DatabaseRepositoryTest extends Test { private static final String IN_MEMORY_DATABASE = Environment.DEFAULT_JDBC_URL; + private static final LoggerFactory NOP_LOGGER = c -> new NopLogger(); private final TodoMapper todoMapper = new TodoMapper(TestData.URL_PREFIX); public void initialization() { test("a table is created", () -> { - var repo = Wiring.databaseRepository(IN_MEMORY_DATABASE, todoMapper, TestWiring.nopLoggerFactory()); + var repo = new DatabaseRepository(IN_MEMORY_DATABASE, todoMapper, NOP_LOGGER); var result = repo.initialize(); assertThat(result).isSuccess(); }); test("initializing twice is a no-op the second time", () -> { - var repo = Wiring.databaseRepository(IN_MEMORY_DATABASE, todoMapper, TestWiring.nopLoggerFactory()); + var repo = new DatabaseRepository(IN_MEMORY_DATABASE, todoMapper, NOP_LOGGER); assertThat(repo.initialize()).isSuccess(); assertThat(repo.initialize()).isSuccess(); }); } public void repository() { - var repo = Wiring.databaseRepository(IN_MEMORY_DATABASE, todoMapper, TestWiring.nopLoggerFactory()); + var repo = new DatabaseRepository(IN_MEMORY_DATABASE, todoMapper, NOP_LOGGER); beforeAll(() -> { assertThat(repo.initialize()).isSuccess(); @@ -97,7 +98,7 @@ public Todo map(ResultSet rs, StatementContext ctx) throws SQLException { throw new SQLException("Intentional failure"); } }; - var repo = Wiring.databaseRepository(IN_MEMORY_DATABASE, failingMapper, TestWiring.nopLoggerFactory()); + var repo = new DatabaseRepository(IN_MEMORY_DATABASE, failingMapper, NOP_LOGGER); beforeAll(() -> { repo.initialize(); diff --git a/src/test/java/nl/jqno/paralleljava/app/persistence/inmemory/InMemoryRepositoryTest.java b/src/test/java/nl/jqno/paralleljava/app/persistence/inmemory/InMemoryRepositoryTest.java index 5942fd7..ee05ce1 100644 --- a/src/test/java/nl/jqno/paralleljava/app/persistence/inmemory/InMemoryRepositoryTest.java +++ b/src/test/java/nl/jqno/paralleljava/app/persistence/inmemory/InMemoryRepositoryTest.java @@ -1,9 +1,8 @@ package nl.jqno.paralleljava.app.persistence.inmemory; -import nl.jqno.paralleljava.dependencyinjection.TestData.AnotherTodo; -import nl.jqno.paralleljava.dependencyinjection.TestData.SomeTodo; -import nl.jqno.paralleljava.dependencyinjection.TestWiring; -import nl.jqno.paralleljava.dependencyinjection.Wiring; +import nl.jqno.paralleljava.TestData.AnotherTodo; +import nl.jqno.paralleljava.TestData.SomeTodo; +import nl.jqno.paralleljava.app.logging.NopLogger; import nl.jqno.picotest.Test; import java.util.UUID; @@ -13,7 +12,7 @@ public class InMemoryRepositoryTest extends Test { public void repository() { - var repo = Wiring.inMemoryRepository(TestWiring.nopLoggerFactory()); + var repo = new InMemoryRepository(c -> new NopLogger()); beforeEach(() -> { repo.clearAllTodos(); diff --git a/src/test/java/nl/jqno/paralleljava/app/serialization/GsonSerializerTest.java b/src/test/java/nl/jqno/paralleljava/app/serialization/GsonSerializerTest.java index 3908be5..0a65abc 100644 --- a/src/test/java/nl/jqno/paralleljava/app/serialization/GsonSerializerTest.java +++ b/src/test/java/nl/jqno/paralleljava/app/serialization/GsonSerializerTest.java @@ -1,16 +1,15 @@ package nl.jqno.paralleljava.app.serialization; -import nl.jqno.paralleljava.dependencyinjection.TestWiring; -import nl.jqno.paralleljava.dependencyinjection.Wiring; +import nl.jqno.paralleljava.app.logging.NopLogger; import nl.jqno.picotest.Test; -import static nl.jqno.paralleljava.dependencyinjection.TestData.*; +import static nl.jqno.paralleljava.TestData.*; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.vavr.api.VavrAssertions.assertThat; public class GsonSerializerTest extends Test { - private Serializer serializer = Wiring.gsonSerializer(TestWiring.nopLoggerFactory()); + private Serializer serializer = GsonSerializer.create(c -> new NopLogger()); public void serializationOfASingleTodo() { diff --git a/src/test/java/nl/jqno/paralleljava/app/server/SparkServerTest.java b/src/test/java/nl/jqno/paralleljava/app/server/SparkServerTest.java index b3744ed..cf836be 100644 --- a/src/test/java/nl/jqno/paralleljava/app/server/SparkServerTest.java +++ b/src/test/java/nl/jqno/paralleljava/app/server/SparkServerTest.java @@ -3,9 +3,8 @@ import io.restassured.response.Response; import io.restassured.specification.RequestSpecification; import io.vavr.collection.List; -import nl.jqno.paralleljava.dependencyinjection.Wiring; -import nl.jqno.paralleljava.dependencyinjection.TestWiring; -import nl.jqno.paralleljava.dependencyinjection.stubs.StubController; +import nl.jqno.paralleljava.app.logging.NopLogger; +import nl.jqno.paralleljava.app.controller.StubController; import nl.jqno.picotest.Test; import spark.Spark; @@ -25,8 +24,8 @@ public class SparkServerTest extends Test { private Server server; public void server() { - underlying = TestWiring.stubController(); - server = Wiring.sparkServer(ENDPOINT, PORT, underlying, TestWiring.nopLoggerFactory()); + underlying = new StubController(); + server = new SparkServer(ENDPOINT, PORT, underlying, c -> new NopLogger()); beforeAll(() -> { server.run(); diff --git a/src/test/java/nl/jqno/paralleljava/dependencyinjection/TestWiring.java b/src/test/java/nl/jqno/paralleljava/dependencyinjection/TestWiring.java deleted file mode 100644 index 8ba56e8..0000000 --- a/src/test/java/nl/jqno/paralleljava/dependencyinjection/TestWiring.java +++ /dev/null @@ -1,39 +0,0 @@ -package nl.jqno.paralleljava.dependencyinjection; - -import io.vavr.collection.Map; -import nl.jqno.paralleljava.app.environment.Environment; -import nl.jqno.paralleljava.app.environment.HerokuEnvironment; -import nl.jqno.paralleljava.app.logging.LoggerFactory; -import nl.jqno.paralleljava.app.persistence.IdGenerator; -import nl.jqno.paralleljava.dependencyinjection.stubs.ConstantIdGenerator; -import nl.jqno.paralleljava.dependencyinjection.stubs.NopLogger; -import nl.jqno.paralleljava.dependencyinjection.stubs.StubController; -import nl.jqno.paralleljava.dependencyinjection.stubs.StubLogger; - -import java.util.UUID; - -public class TestWiring { - private TestWiring() { - // Don't instantiate - } - - public static Environment mapBasedHerokuEnvironment(Map map) { - return new HerokuEnvironment(map); - } - - public static LoggerFactory nopLoggerFactory() { - return c -> new NopLogger(); - } - - public static StubLogger stubLogger() { - return new StubLogger(); - } - - public static IdGenerator constantIdGenerator(UUID id) { - return new ConstantIdGenerator(id); - } - - public static StubController stubController() { - return new StubController(); - } -} From d92b924c247370d06b888af25df66214a8e43b95 Mon Sep 17 00:00:00 2001 From: Jan Ouwens Date: Fri, 29 Mar 2019 08:31:56 +0100 Subject: [PATCH 095/106] Names Repository methods more consistently --- .../app/controller/DefaultController.java | 8 ++-- .../app/persistence/Repository.java | 8 ++-- .../database/DatabaseRepository.java | 8 ++-- .../inmemory/InMemoryRepository.java | 8 ++-- .../app/controller/DefaultControllerTest.java | 38 +++++++++---------- .../database/DatabaseRepositoryTest.java | 30 +++++++-------- .../inmemory/InMemoryRepositoryTest.java | 36 +++++++++--------- 7 files changed, 68 insertions(+), 68 deletions(-) diff --git a/src/main/java/nl/jqno/paralleljava/app/controller/DefaultController.java b/src/main/java/nl/jqno/paralleljava/app/controller/DefaultController.java index a494db8..38e1920 100644 --- a/src/main/java/nl/jqno/paralleljava/app/controller/DefaultController.java +++ b/src/main/java/nl/jqno/paralleljava/app/controller/DefaultController.java @@ -26,7 +26,7 @@ public DefaultController(String url, Repository repository, IdGenerator generato } public Try get() { - return repository.getAllTodos() + return repository.getAll() .map(serializer::serializeTodos); } @@ -51,7 +51,7 @@ public Try post(String json) { var pt = partialTodo.get(); var id = generator.generateId(); var todo = new Todo(id, pt.title().get(), buildUrlFor(id), false, pt.order().getOrElse(0)); - return repository.createTodo(todo) + return repository.create(todo) .map(ignored -> serializer.serializeTodo(todo)); } @@ -77,13 +77,13 @@ public Try patch(String id, String json) { todo.url(), pt.completed().getOrElse(todo.completed()), pt.order().getOrElse(todo.order())); - repository.updateTodo(updatedTodo); + repository.update(updatedTodo); return Try.of(() -> serializer.serializeTodo(updatedTodo)); }); } public Try delete() { - return repository.clearAllTodos() + return repository.deleteAll() .map(ignored -> ""); } diff --git a/src/main/java/nl/jqno/paralleljava/app/persistence/Repository.java b/src/main/java/nl/jqno/paralleljava/app/persistence/Repository.java index 8654f41..86ee202 100644 --- a/src/main/java/nl/jqno/paralleljava/app/persistence/Repository.java +++ b/src/main/java/nl/jqno/paralleljava/app/persistence/Repository.java @@ -9,10 +9,10 @@ public interface Repository { Try initialize(); - Try createTodo(Todo todo); + Try create(Todo todo); Try> get(UUID id); - Try> getAllTodos(); - Try updateTodo(Todo todo); + Try> getAll(); + Try update(Todo todo); Try delete(UUID id); - Try clearAllTodos(); + Try deleteAll(); } diff --git a/src/main/java/nl/jqno/paralleljava/app/persistence/database/DatabaseRepository.java b/src/main/java/nl/jqno/paralleljava/app/persistence/database/DatabaseRepository.java index a0214f3..7ba42bb 100644 --- a/src/main/java/nl/jqno/paralleljava/app/persistence/database/DatabaseRepository.java +++ b/src/main/java/nl/jqno/paralleljava/app/persistence/database/DatabaseRepository.java @@ -31,7 +31,7 @@ public Try initialize() { .orElse(Try.success(null)); // If it fails, the table probably already exists. } - public Try createTodo(Todo todo) { + public Try create(Todo todo) { return execute(handle -> handle.createUpdate("INSERT INTO todo (id, title, completed, index) VALUES (?, ?, ?, ?)") .bind(0, todo.id().toString()) @@ -51,14 +51,14 @@ public Try> get(UUID id) { }); } - public Try> getAllTodos() { + public Try> getAll() { return query(handle -> handle.createQuery("SELECT id, title, completed, index FROM todo") .mapTo(Todo.class) .collect(List.collector())); } - public Try updateTodo(Todo todo) { + public Try update(Todo todo) { return execute(handle -> handle.createUpdate("UPDATE todo SET title = ?, completed = ?, index = ? WHERE id = ?") .bind(0, todo.title()) @@ -75,7 +75,7 @@ public Try delete(UUID id) { .execute()); } - public Try clearAllTodos() { + public Try deleteAll() { return execute(handle -> handle.execute("DELETE FROM todo")); } diff --git a/src/main/java/nl/jqno/paralleljava/app/persistence/inmemory/InMemoryRepository.java b/src/main/java/nl/jqno/paralleljava/app/persistence/inmemory/InMemoryRepository.java index b89fa94..37193eb 100644 --- a/src/main/java/nl/jqno/paralleljava/app/persistence/inmemory/InMemoryRepository.java +++ b/src/main/java/nl/jqno/paralleljava/app/persistence/inmemory/InMemoryRepository.java @@ -28,7 +28,7 @@ public Try initialize() { return SUCCESS; } - public Try createTodo(Todo todo) { + public Try create(Todo todo) { logger.forProduction("Creating Todo " + todo); todos.add(todo); return SUCCESS; @@ -40,11 +40,11 @@ public Try> get(UUID id) { return Try.success(result); } - public Try> getAllTodos() { + public Try> getAll() { return Try.success(List.ofAll(todos)); } - public Try updateTodo(Todo todo) { + public Try update(Todo todo) { var index = List.ofAll(todos) .map(Todo::id) .indexOf(todo.id()); @@ -61,7 +61,7 @@ public Try delete(UUID id) { return SUCCESS; } - public Try clearAllTodos() { + public Try deleteAll() { logger.forProduction("Clearing all Todos"); todos.clear(); return SUCCESS; diff --git a/src/test/java/nl/jqno/paralleljava/app/controller/DefaultControllerTest.java b/src/test/java/nl/jqno/paralleljava/app/controller/DefaultControllerTest.java index 7d1e7f9..476efb5 100644 --- a/src/test/java/nl/jqno/paralleljava/app/controller/DefaultControllerTest.java +++ b/src/test/java/nl/jqno/paralleljava/app/controller/DefaultControllerTest.java @@ -31,7 +31,7 @@ public class DefaultControllerTest extends Test { public void get() { beforeEach(() -> { - repository.clearAllTodos(); + repository.deleteAll(); }); test("get returns an empty list when no todos are present", () -> { @@ -40,8 +40,8 @@ public void get() { }); test("get returns all todos", () -> { - repository.createTodo(SomeTodo.TODO); - repository.createTodo(AnotherTodo.TODO); + repository.create(SomeTodo.TODO); + repository.create(AnotherTodo.TODO); var actual = controller.get(); assertThat(actual).contains(ListOfTodos.SERIALIZED); @@ -50,11 +50,11 @@ public void get() { public void getWithId() { beforeEach(() -> { - repository.clearAllTodos(); + repository.deleteAll(); }); test("get with id returns a specific serialized todo if it exists", () -> { - repository.createTodo(SomeTodo.TODO); + repository.create(SomeTodo.TODO); var actual = controller.get(SomeTodo.ID.toString()); assertThat(actual).contains(SomeTodo.SERIALIZED); @@ -73,7 +73,7 @@ public void getWithId() { public void post() { beforeEach(() -> { - repository.clearAllTodos(); + repository.deleteAll(); }); test("post adds a todo without order", () -> { @@ -82,7 +82,7 @@ public void post() { var actual = controller.post(SomeTodo.SERIALIZED_PARTIAL_POST); assertThat(actual).contains(expectedSerialized); - assertThat(repository.getAllTodos()).hasValueSatisfying(l -> assertThat(l).contains(expected)); + assertThat(repository.getAll()).hasValueSatisfying(l -> assertThat(l).contains(expected)); }); test("post adds a todo with order", () -> { @@ -91,7 +91,7 @@ public void post() { var actual = controller.post(SomeTodo.SERIALIZED_PARTIAL_POST_WITH_ORDER); assertThat(actual).contains(expectedSerialized); - assertThat(repository.getAllTodos()).hasValueSatisfying(l -> assertThat(l).contains(expected)); + assertThat(repository.getAll()).hasValueSatisfying(l -> assertThat(l).contains(expected)); }); test("post fails when todo is invalid", () -> { @@ -107,11 +107,11 @@ public void post() { public void patch() { beforeEach(() -> { - repository.clearAllTodos(); + repository.deleteAll(); }); test("patch changes title", () -> { - repository.createTodo(SomeTodo.TODO); + repository.create(SomeTodo.TODO); var expected = SomeTodo.TODO.withTitle("another title"); var result = controller.patch(SomeTodo.ID.toString(), "{\"title\":\"another title\"}"); @@ -122,7 +122,7 @@ public void patch() { }); test("patch changes completed", () -> { - repository.createTodo(SomeTodo.TODO); + repository.create(SomeTodo.TODO); var expected = SomeTodo.TODO.withCompleted(false); var result = controller.patch(SomeTodo.ID.toString(), "{\"completed\":false}"); @@ -133,7 +133,7 @@ public void patch() { }); test("patch changes order", () -> { - repository.createTodo(SomeTodo.TODO); + repository.create(SomeTodo.TODO); var expected = SomeTodo.TODO.withOrder(47); var result = controller.patch(SomeTodo.ID.toString(), "{\"order\":47}"); @@ -161,32 +161,32 @@ public void patch() { public void delete() { beforeEach(() -> { - repository.clearAllTodos(); + repository.deleteAll(); }); test("delete clears all todos", () -> { - repository.createTodo(SomeTodo.TODO); + repository.create(SomeTodo.TODO); var actual = controller.delete(); assertThat(actual).hasValueSatisfying(s -> assertThat(s).isEmpty()); - assertThat(repository.getAllTodos()).hasValueSatisfying(l -> assertThat(l).isEmpty()); + assertThat(repository.getAll()).hasValueSatisfying(l -> assertThat(l).isEmpty()); }); } public void deleteWithId() { beforeEach(() -> { - repository.clearAllTodos(); + repository.deleteAll(); }); test("delete with id removes the corresponding todo", () -> { - repository.createTodo(AnotherTodo.TODO); - repository.createTodo(SomeTodo.TODO); + repository.create(AnotherTodo.TODO); + repository.create(SomeTodo.TODO); var actual = controller.delete(SomeTodo.ID.toString()); assertThat(actual).hasValueSatisfying(s -> assertThat(s).isEmpty()); - assertThat(repository.getAllTodos()).hasValueSatisfying(l -> { + assertThat(repository.getAll()).hasValueSatisfying(l -> { assertThat(l).doesNotContain(SomeTodo.TODO); assertThat(l).contains(AnotherTodo.TODO); }); diff --git a/src/test/java/nl/jqno/paralleljava/app/persistence/database/DatabaseRepositoryTest.java b/src/test/java/nl/jqno/paralleljava/app/persistence/database/DatabaseRepositoryTest.java index f5c907f..2a97f8b 100644 --- a/src/test/java/nl/jqno/paralleljava/app/persistence/database/DatabaseRepositoryTest.java +++ b/src/test/java/nl/jqno/paralleljava/app/persistence/database/DatabaseRepositoryTest.java @@ -42,18 +42,18 @@ public void repository() { beforeAll(() -> { assertThat(repo.initialize()).isSuccess(); - assertThat(repo.clearAllTodos()).isSuccess(); + assertThat(repo.deleteAll()).isSuccess(); }); test("create a todo", () -> { - var result = repo.createTodo(SomeTodo.TODO); + var result = repo.create(SomeTodo.TODO); assertThat(result).isSuccess(); - assertThat(repo.getAllTodos()).hasValueSatisfying(l -> l.contains(SomeTodo.TODO)); + assertThat(repo.getAll()).hasValueSatisfying(l -> l.contains(SomeTodo.TODO)); }); test("get a specific todo", () -> { - repo.createTodo(SomeTodo.TODO); + repo.create(SomeTodo.TODO); assertThat(repo.get(SomeTodo.ID)).hasValueSatisfying(o -> assertThat(o).contains(SomeTodo.TODO)); assertThat(repo.get(UUID.randomUUID())).hasValueSatisfying(o -> assertThat(o).isEmpty()); @@ -61,10 +61,10 @@ public void repository() { test("update a specific todo", () -> { var expected = SomeTodo.TODO.withTitle("another title"); - repo.createTodo(SomeTodo.TODO); - repo.createTodo(AnotherTodo.TODO); + repo.create(SomeTodo.TODO); + repo.create(AnotherTodo.TODO); - var result = repo.updateTodo(expected); + var result = repo.update(expected); var actual = repo.get(SomeTodo.ID).get(); assertThat(result).isSuccess(); @@ -72,8 +72,8 @@ public void repository() { }); test("delete a specific todo", () -> { - repo.createTodo(SomeTodo.TODO); - repo.createTodo(AnotherTodo.TODO); + repo.create(SomeTodo.TODO); + repo.create(AnotherTodo.TODO); var result = repo.delete(SomeTodo.ID); var actual = repo.get(SomeTodo.ID).get(); @@ -82,13 +82,13 @@ public void repository() { assertThat(actual).isEmpty(); }); - test("clearing all todos", () -> { - repo.createTodo(SomeTodo.TODO); + test("delete all todos", () -> { + repo.create(SomeTodo.TODO); - var result = repo.clearAllTodos(); + var result = repo.deleteAll(); assertThat(result).isSuccess(); - assertThat(repo.getAllTodos()).hasValueSatisfying(l -> assertThat(l).isEmpty()); + assertThat(repo.getAll()).hasValueSatisfying(l -> assertThat(l).isEmpty()); }); } @@ -105,9 +105,9 @@ public Todo map(ResultSet rs, StatementContext ctx) throws SQLException { }); test("Query failures cause failed results", () -> { - repo.createTodo(SomeTodo.TODO); + repo.create(SomeTodo.TODO); - var result = repo.getAllTodos(); + var result = repo.getAll(); assertThat(result).isFailure(); }); } diff --git a/src/test/java/nl/jqno/paralleljava/app/persistence/inmemory/InMemoryRepositoryTest.java b/src/test/java/nl/jqno/paralleljava/app/persistence/inmemory/InMemoryRepositoryTest.java index ee05ce1..f00cb55 100644 --- a/src/test/java/nl/jqno/paralleljava/app/persistence/inmemory/InMemoryRepositoryTest.java +++ b/src/test/java/nl/jqno/paralleljava/app/persistence/inmemory/InMemoryRepositoryTest.java @@ -15,7 +15,7 @@ public void repository() { var repo = new InMemoryRepository(c -> new NopLogger()); beforeEach(() -> { - repo.clearAllTodos(); + repo.deleteAll(); }); test("initialize does nothing", () -> { @@ -23,14 +23,14 @@ public void repository() { }); test("create a todo", () -> { - var result = repo.createTodo(SomeTodo.TODO); + var result = repo.create(SomeTodo.TODO); assertThat(result).isSuccess(); - assertThat(repo.getAllTodos()).hasValueSatisfying(l -> assertThat(l).contains(SomeTodo.TODO)); + assertThat(repo.getAll()).hasValueSatisfying(l -> assertThat(l).contains(SomeTodo.TODO)); }); test("get a specific todo", () -> { - repo.createTodo(SomeTodo.TODO); + repo.create(SomeTodo.TODO); assertThat(repo.get(SomeTodo.ID)).hasValueSatisfying(o -> assertThat(o).contains(SomeTodo.TODO)); assertThat(repo.get(UUID.randomUUID())).hasValueSatisfying(o -> assertThat(o).isEmpty()); @@ -38,10 +38,10 @@ public void repository() { test("update a specific todo at index 0", () -> { var expected = SomeTodo.TODO.withTitle("another title"); - repo.createTodo(SomeTodo.TODO); - repo.createTodo(AnotherTodo.TODO); + repo.create(SomeTodo.TODO); + repo.create(AnotherTodo.TODO); - var result = repo.updateTodo(expected); + var result = repo.update(expected); var actual = repo.get(SomeTodo.ID).get(); assertThat(result).isSuccess(); @@ -50,10 +50,10 @@ public void repository() { test("update a specific todo at index 1", () -> { var expected = SomeTodo.TODO.withTitle("another title"); - repo.createTodo(AnotherTodo.TODO); - repo.createTodo(SomeTodo.TODO); + repo.create(AnotherTodo.TODO); + repo.create(SomeTodo.TODO); - var result = repo.updateTodo(expected); + var result = repo.update(expected); var actual = repo.get(SomeTodo.ID).get(); assertThat(result).isSuccess(); @@ -61,8 +61,8 @@ public void repository() { }); test("delete a specific todo at index 0", () -> { - repo.createTodo(SomeTodo.TODO); - repo.createTodo(AnotherTodo.TODO); + repo.create(SomeTodo.TODO); + repo.create(AnotherTodo.TODO); var result = repo.delete(SomeTodo.ID); var actual = repo.get(SomeTodo.ID).get(); @@ -72,8 +72,8 @@ public void repository() { }); test("delete a specific todo at index 1", () -> { - repo.createTodo(AnotherTodo.TODO); - repo.createTodo(SomeTodo.TODO); + repo.create(AnotherTodo.TODO); + repo.create(SomeTodo.TODO); var result = repo.delete(SomeTodo.ID); var actual = repo.get(SomeTodo.ID).get(); @@ -82,13 +82,13 @@ public void repository() { assertThat(actual).isEmpty(); }); - test("clearing all todos", () -> { - repo.createTodo(SomeTodo.TODO); + test("delete all todos", () -> { + repo.create(SomeTodo.TODO); - var result = repo.clearAllTodos(); + var result = repo.deleteAll(); assertThat(result).isSuccess(); - assertThat(repo.getAllTodos()).hasValueSatisfying(l -> assertThat(l).isEmpty()); + assertThat(repo.getAll()).hasValueSatisfying(l -> assertThat(l).isEmpty()); }); } } From 27501720617400283302161cda0152e9890f986d Mon Sep 17 00:00:00 2001 From: Jan Ouwens Date: Fri, 29 Mar 2019 08:50:50 +0100 Subject: [PATCH 096/106] Adds Runner --- src/main/java/nl/jqno/paralleljava/Main.java | 5 +- .../java/nl/jqno/paralleljava/app/Runner.java | 19 +++++ .../nl/jqno/paralleljava/app/RunnerTest.java | 37 ++++++++++ .../app/persistence/StubRepository.java | 74 +++++++++++++++++++ .../paralleljava/app/server/StubServer.java | 13 ++++ 5 files changed, 146 insertions(+), 2 deletions(-) create mode 100644 src/main/java/nl/jqno/paralleljava/app/Runner.java create mode 100644 src/test/java/nl/jqno/paralleljava/app/RunnerTest.java create mode 100644 src/test/java/nl/jqno/paralleljava/app/persistence/StubRepository.java create mode 100644 src/test/java/nl/jqno/paralleljava/app/server/StubServer.java diff --git a/src/main/java/nl/jqno/paralleljava/Main.java b/src/main/java/nl/jqno/paralleljava/Main.java index 0b3ae2a..d180f65 100644 --- a/src/main/java/nl/jqno/paralleljava/Main.java +++ b/src/main/java/nl/jqno/paralleljava/Main.java @@ -1,6 +1,7 @@ package nl.jqno.paralleljava; import io.vavr.collection.HashMap; +import nl.jqno.paralleljava.app.Runner; import nl.jqno.paralleljava.app.controller.DefaultController; import nl.jqno.paralleljava.app.environment.Environment; import nl.jqno.paralleljava.app.environment.HerokuEnvironment; @@ -31,7 +32,7 @@ public static void main(String... args) { var controller = new DefaultController(fullUrl, repository, idGenerator, serializer, loggerFactory); var server = new SparkServer(Environment.ENDPOINT, port, controller, loggerFactory); - repository.initialize() - .onSuccess(ignored -> server.run()); + var runner = new Runner(repository, server); + runner.startup(); } } diff --git a/src/main/java/nl/jqno/paralleljava/app/Runner.java b/src/main/java/nl/jqno/paralleljava/app/Runner.java new file mode 100644 index 0000000..fdd148e --- /dev/null +++ b/src/main/java/nl/jqno/paralleljava/app/Runner.java @@ -0,0 +1,19 @@ +package nl.jqno.paralleljava.app; + +import nl.jqno.paralleljava.app.persistence.Repository; +import nl.jqno.paralleljava.app.server.Server; + +public class Runner { + private final Repository repository; + private final Server server; + + public Runner(Repository repository, Server server) { + this.repository = repository; + this.server = server; + } + + public void startup() { + repository.initialize() + .onSuccess(ignored -> server.run()); + } +} diff --git a/src/test/java/nl/jqno/paralleljava/app/RunnerTest.java b/src/test/java/nl/jqno/paralleljava/app/RunnerTest.java new file mode 100644 index 0000000..c00bddf --- /dev/null +++ b/src/test/java/nl/jqno/paralleljava/app/RunnerTest.java @@ -0,0 +1,37 @@ +package nl.jqno.paralleljava.app; + +import nl.jqno.paralleljava.app.persistence.StubRepository; +import nl.jqno.paralleljava.app.server.StubServer; +import nl.jqno.picotest.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +public class RunnerTest extends Test { + + public void runner() { + var repo = new StubRepository(); + var server = new StubServer(); + var runner = new Runner(repo, server); + + beforeEach(() -> { + repo.clear(); + server.clear(); + }); + + test("happy path", () -> { + runner.startup(); + + assertThat(repo.calledInitialize).isEqualTo(1); + assertThat(server.calledRun).isEqualTo(1); + }); + + test("repo initialization fails", () -> { + repo.failNextCall = true; + + runner.startup(); + + assertThat(repo.calledInitialize).isEqualTo(1); + assertThat(server.calledRun).isEqualTo(0); + }); + } +} diff --git a/src/test/java/nl/jqno/paralleljava/app/persistence/StubRepository.java b/src/test/java/nl/jqno/paralleljava/app/persistence/StubRepository.java new file mode 100644 index 0000000..0005758 --- /dev/null +++ b/src/test/java/nl/jqno/paralleljava/app/persistence/StubRepository.java @@ -0,0 +1,74 @@ +package nl.jqno.paralleljava.app.persistence; + +import io.vavr.collection.List; +import io.vavr.control.Option; +import io.vavr.control.Try; +import nl.jqno.paralleljava.app.domain.Todo; + +import java.util.UUID; + +public class StubRepository implements Repository { + + public boolean failNextCall = false; + public int calledInitialize = 0; + public int calledCreate = 0; + public int calledGet = 0; + public int calledGetAll = 0; + public int calledUpdate = 0; + public int calledDelete = 0; + public int calledDeleteAll = 0; + + public void clear() { + failNextCall = false; + calledInitialize = 0; + calledCreate = 0; + calledGet = 0; + calledGetAll = 0; + calledUpdate = 0; + calledDelete = 0; + calledDeleteAll = 0; + } + + public Try initialize() { + calledInitialize += 1; + return returnValue(null); + } + + public Try create(Todo todo) { + calledCreate += 1; + return returnValue(null); + } + + public Try> get(UUID id) { + calledGet += 1; + return returnValue(Option.none()); + } + + public Try> getAll() { + calledGetAll += 1; + return returnValue(List.empty()); + } + + public Try update(Todo todo) { + calledUpdate += 1; + return returnValue(null); + } + + public Try delete(UUID id) { + calledDelete += 1; + return returnValue(null); + } + + public Try deleteAll() { + calledDeleteAll += 1; + return returnValue(null); + } + + private Try returnValue(T value) { + if (failNextCall) { + failNextCall = false; + return Try.failure(new IllegalStateException()); + } + return Try.success(value); + } +} diff --git a/src/test/java/nl/jqno/paralleljava/app/server/StubServer.java b/src/test/java/nl/jqno/paralleljava/app/server/StubServer.java new file mode 100644 index 0000000..3681aab --- /dev/null +++ b/src/test/java/nl/jqno/paralleljava/app/server/StubServer.java @@ -0,0 +1,13 @@ +package nl.jqno.paralleljava.app.server; + +public class StubServer implements Server { + public int calledRun = 0; + + public void clear() { + calledRun = 0; + } + + public void run() { + calledRun += 1; + } +} From 721e1ca7811a6a4ba538662407caad288d53310b Mon Sep 17 00:00:00 2001 From: Jan Ouwens Date: Fri, 29 Mar 2019 09:07:06 +0100 Subject: [PATCH 097/106] Adds a thread safety warning --- .../app/persistence/database/DatabaseRepository.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/main/java/nl/jqno/paralleljava/app/persistence/database/DatabaseRepository.java b/src/main/java/nl/jqno/paralleljava/app/persistence/database/DatabaseRepository.java index 7ba42bb..d9be321 100644 --- a/src/main/java/nl/jqno/paralleljava/app/persistence/database/DatabaseRepository.java +++ b/src/main/java/nl/jqno/paralleljava/app/persistence/database/DatabaseRepository.java @@ -13,6 +13,9 @@ import java.util.UUID; +/** + * NOTE: this class is totally not thread-safe! It totally ignores database transactions. + */ public class DatabaseRepository implements Repository { private final Logger logger; From d64fe9e1aff9b7ab801189a4d79610c5ee7fc659 Mon Sep 17 00:00:00 2001 From: Jan Ouwens Date: Fri, 29 Mar 2019 09:07:14 +0100 Subject: [PATCH 098/106] Adds a README.md --- README.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 0000000..4ae3000 --- /dev/null +++ b/README.md @@ -0,0 +1,13 @@ +# Parallel Java + +[![Build Status](https://img.shields.io/travis/jqno/paralleljava.svg?style=plastic)](https://travis-ci.org/jqno/paralleljava) + +This app is written in Java from a Parallel Universe where annotations were never invented. You can check: there isn't a single annotation in this codebase! + +It requires a JDK 11 to build and run. + +It's a showcase for my talk, Java from a Parallel Universe. It's also a fully functioning [Todo Backend](https://www.todobackend.com/) (you can [run the Todo Backend test suite](https://www.todobackend.com/specs/index.html?https://parallel-java.herokuapp.com/todo +)!), using the [Spark web framework](http://sparkjava.com/), the [Jdbi database framework](http://jdbi.org/), and **no framework** for dependency injection because you really really don't need one. It also uses Java 11 `var` declarations, [Vavr](http://www.vavr.io/) and [Polyglot for Maven](https://github.com/takari/polyglot-maven) because I think they're pretty nifty and because they make the code look a little different, as if, I dunno, as if it came from a Parallel Universe or something? Also, the application is fully modularized and has 100% test coverage because why not. + +Note, however, that this is still a demo app that is not production-ready. Some corners have definitely been cut. For example, neither the [InMemoryRepository](https://github.com/jqno/paralleljava/blob/master/src/main/java/nl/jqno/paralleljava/app/persistence/inmemory/InMemoryRepository.java) nor the [DatabaseRepository](https://github.com/jqno/paralleljava/blob/master/src/main/java/nl/jqno/paralleljava/app/persistence/database/DatabaseRepository.java) are thread-safe. + From 31ab93893052c8775179bc3b9734b7b4c605b64b Mon Sep 17 00:00:00 2001 From: Jan Ouwens Date: Fri, 29 Mar 2019 11:51:18 +0100 Subject: [PATCH 099/106] Fixes architecture test when run from IntelliJ --- src/test/java/nl/jqno/paralleljava/ArchitectureTest.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/test/java/nl/jqno/paralleljava/ArchitectureTest.java b/src/test/java/nl/jqno/paralleljava/ArchitectureTest.java index e65905f..3cfc20c 100644 --- a/src/test/java/nl/jqno/paralleljava/ArchitectureTest.java +++ b/src/test/java/nl/jqno/paralleljava/ArchitectureTest.java @@ -36,6 +36,7 @@ public void architecture() { private void assertBoundary(String restrictedPackageIdentifier, Package whiteListedPackage) { var rule = noClasses() .that().resideOutsideOfPackage(whiteListedPackage.getName()) + .and().dontHaveFullyQualifiedName(Main.class.getCanonicalName()) .should().accessClassesThat().resideInAPackage(restrictedPackageIdentifier); rule.check(IMPORTED_CLASSES); } From 2a8e82e2b1686221d2294bdc83ac10c0feef1f73 Mon Sep 17 00:00:00 2001 From: Jan Ouwens Date: Fri, 29 Mar 2019 11:51:35 +0100 Subject: [PATCH 100/106] Makes it possible to run locally with H2 --- pom.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.yml b/pom.yml index 3ecf313..e5c7103 100644 --- a/pom.yml +++ b/pom.yml @@ -31,13 +31,13 @@ dependencies: - { groupId: org.jdbi, artifactId: jdbi3-core } - { groupId: org.jdbi, artifactId: jdbi3-vavr } - { groupId: org.postgresql, artifactId: postgresql, version: 42.2.5 } + - { groupId: com.h2database, artifactId: h2, version: 1.4.199 } - { groupId: nl.jqno.picotest, artifactId: picotest, version: 0.3, scope: test } - { groupId: nl.jqno.equalsverifier, artifactId: equalsverifier, version: 3.1.7, scope: test } - { groupId: org.assertj, artifactId: assertj-core, version: 3.11.1, scope: test } - { groupId: org.assertj, artifactId: assertj-vavr, version: 0.1.0, scope: test } - { groupId: com.tngtech.archunit, artifactId: archunit, version: 0.9.3, scope: test } - - { groupId: com.h2database, artifactId: h2, version: 1.4.199, scope: test } # REST-Assured is useful but problematic on Java 11. We need to overrule Groovy and exclude JAXB-OSGI. - { groupId: org.codehaus.groovy, artifactId: groovy, version: 2.5.6, scope: test } From af90ac295d31dc9c74123c57f8f1908bea4637fe Mon Sep 17 00:00:00 2001 From: Jan Ouwens Date: Mon, 1 Apr 2019 07:48:40 +0200 Subject: [PATCH 101/106] Makes various small improvements --- .../app/controller/DefaultController.java | 12 ++-- .../app/environment/Environment.java | 3 +- .../database/DatabaseRepository.java | 28 ++++---- .../java/nl/jqno/paralleljava/TestData.java | 65 +++++++------------ 4 files changed, 45 insertions(+), 63 deletions(-) diff --git a/src/main/java/nl/jqno/paralleljava/app/controller/DefaultController.java b/src/main/java/nl/jqno/paralleljava/app/controller/DefaultController.java index 38e1920..aaa75b8 100644 --- a/src/main/java/nl/jqno/paralleljava/app/controller/DefaultController.java +++ b/src/main/java/nl/jqno/paralleljava/app/controller/DefaultController.java @@ -13,14 +13,14 @@ public class DefaultController implements Controller { private final String url; private final Repository repository; - private final IdGenerator generator; + private final IdGenerator idGenerator; private final Serializer serializer; private final Logger logger; - public DefaultController(String url, Repository repository, IdGenerator generator, Serializer serializer, LoggerFactory loggerFactory) { + public DefaultController(String url, Repository repository, IdGenerator idGenerator, Serializer serializer, LoggerFactory loggerFactory) { this.url = url; this.repository = repository; - this.generator = generator; + this.idGenerator = idGenerator; this.serializer = serializer; this.logger = loggerFactory.create(getClass()); } @@ -49,7 +49,7 @@ public Try post(String json) { } var pt = partialTodo.get(); - var id = generator.generateId(); + var id = idGenerator.generateId(); var todo = new Todo(id, pt.title().get(), buildUrlFor(id), false, pt.order().getOrElse(0)); return repository.create(todo) .map(ignored -> serializer.serializeTodo(todo)); @@ -66,11 +66,11 @@ public Try patch(String id, String json) { var pt = partialTodo.get(); var existingTodo = repository.get(uuid.get()); return existingTodo.flatMap(et -> { - if (existingTodo.get().isEmpty()) { + if (et.isEmpty()) { return Try.failure(new IllegalArgumentException("Can't find Todo with id " + id)); } - var todo = existingTodo.get().get(); + var todo = et.get(); var updatedTodo = new Todo( todo.id(), pt.title().getOrElse(todo.title()), diff --git a/src/main/java/nl/jqno/paralleljava/app/environment/Environment.java b/src/main/java/nl/jqno/paralleljava/app/environment/Environment.java index cce5d23..b9105ae 100644 --- a/src/main/java/nl/jqno/paralleljava/app/environment/Environment.java +++ b/src/main/java/nl/jqno/paralleljava/app/environment/Environment.java @@ -6,9 +6,10 @@ public interface Environment { int DEFAULT_PORT = 4567; String DEFAULT_URL = "http://localhost"; - String ENDPOINT = "/todo"; String DEFAULT_JDBC_URL = "jdbc:h2:mem:test;DB_CLOSE_DELAY=-1"; + String ENDPOINT = "/todo"; + Option port(); Option hostUrl(); Option jdbcUrl(); diff --git a/src/main/java/nl/jqno/paralleljava/app/persistence/database/DatabaseRepository.java b/src/main/java/nl/jqno/paralleljava/app/persistence/database/DatabaseRepository.java index d9be321..f859a9d 100644 --- a/src/main/java/nl/jqno/paralleljava/app/persistence/database/DatabaseRepository.java +++ b/src/main/java/nl/jqno/paralleljava/app/persistence/database/DatabaseRepository.java @@ -36,18 +36,18 @@ public Try initialize() { public Try create(Todo todo) { return execute(handle -> - handle.createUpdate("INSERT INTO todo (id, title, completed, index) VALUES (?, ?, ?, ?)") - .bind(0, todo.id().toString()) - .bind(1, todo.title()) - .bind(2, todo.completed()) - .bind(3, todo.order()) + handle.createUpdate("INSERT INTO todo (id, title, completed, index) VALUES (:id, :title, :completed, :order)") + .bind("id", todo.id().toString()) + .bind("title", todo.title()) + .bind("completed", todo.completed()) + .bind("order", todo.order()) .execute()); } public Try> get(UUID id) { return query(handle -> { - var o = handle.createQuery("SELECT id, title, completed, index FROM todo WHERE id = ?") - .bind(0, id.toString()) + var o = handle.createQuery("SELECT id, title, completed, index FROM todo WHERE id = :id") + .bind("id", id.toString()) .mapTo(Todo.class) .findFirst(); return Option.ofOptional(o); @@ -63,18 +63,18 @@ public Try> getAll() { public Try update(Todo todo) { return execute(handle -> - handle.createUpdate("UPDATE todo SET title = ?, completed = ?, index = ? WHERE id = ?") - .bind(0, todo.title()) - .bind(1, todo.completed()) - .bind(2, todo.order()) - .bind(3, todo.id().toString()) + handle.createUpdate("UPDATE todo SET title = :title, completed = :completed, index = :order WHERE id = :id") + .bind("title", todo.title()) + .bind("completed", todo.completed()) + .bind("order", todo.order()) + .bind("id", todo.id().toString()) .execute()); } public Try delete(UUID id) { return execute(handle -> - handle.createUpdate("DELETE FROM todo WHERE id = ?") - .bind(0, id.toString()) + handle.createUpdate("DELETE FROM todo WHERE id = :id") + .bind("id", id.toString()) .execute()); } diff --git a/src/test/java/nl/jqno/paralleljava/TestData.java b/src/test/java/nl/jqno/paralleljava/TestData.java index 999cb1b..a2a734a 100644 --- a/src/test/java/nl/jqno/paralleljava/TestData.java +++ b/src/test/java/nl/jqno/paralleljava/TestData.java @@ -7,57 +7,38 @@ import java.util.UUID; -public class TestData { +public interface TestData { - public static final String URL_PREFIX = "http://localhost/blabla"; + String URL_PREFIX = "http://localhost/blabla"; - public static class SomeTodo { - public static final UUID ID = UUID.randomUUID(); + interface SomeTodo { + UUID ID = UUID.randomUUID(); + String URL = URL_PREFIX + "/" + ID.toString(); + Todo TODO = new Todo(ID, "title", URL, true, 1337); - public static final String URL = URL_PREFIX + "/" + ID.toString(); + PartialTodo PARTIAL_COMPLETE = new PartialTodo(Option.of(ID), Option.of("title"), Option.of(URL), Option.of(true), Option.of(1337)); + PartialTodo PARTIAL_POST = new PartialTodo(Option.none(), Option.of("title"), Option.none(), Option.none(), Option.none()); - public static final Todo TODO = - new Todo(ID, "title", URL, true, 1337); - - public static final PartialTodo PARTIAL_COMPLETE = - new PartialTodo(Option.of(ID), Option.of("title"), Option.of(URL), Option.of(true), Option.of(1337)); - - public static final PartialTodo PARTIAL_POST = - new PartialTodo(Option.none(), Option.of("title"), Option.none(), Option.none(), Option.none()); - - public static final String SERIALIZED = - "{\"id\":\"" + ID + "\",\"title\":\"title\",\"url\":\"" + URL + "\",\"completed\":true,\"order\":1337}"; - - public static final String SERIALIZED_PARTIAL_POST = - "{\"title\":\"title\"}"; - - public static final String SERIALIZED_PARTIAL_POST_WITH_ORDER = - "{\"title\":\"title\",\"order\":1337}"; + String SERIALIZED = "{\"id\":\"" + ID + "\",\"title\":\"title\",\"url\":\"" + URL + "\",\"completed\":true,\"order\":1337}"; + String SERIALIZED_PARTIAL_POST = "{\"title\":\"title\"}"; + String SERIALIZED_PARTIAL_POST_WITH_ORDER = "{\"title\":\"title\",\"order\":1337}"; } - public static class AnotherTodo { - public static final UUID ID = UUID.randomUUID(); - - public static final String URL = URL_PREFIX + "/" + ID.toString(); - - public static final Todo TODO = - new Todo(ID, "something", URL, false, 1); - - public static final String SERIALIZED = - "{\"id\":\"" + ID + "\",\"title\":\"something\",\"url\":\"" + URL + "\",\"completed\":false,\"order\":1}"; + interface AnotherTodo { + UUID ID = UUID.randomUUID(); + String URL = URL_PREFIX + "/" + ID.toString(); + Todo TODO = new Todo(ID, "something", URL, false, 1); + String SERIALIZED = "{\"id\":\"" + ID + "\",\"title\":\"something\",\"url\":\"" + URL + "\",\"completed\":false,\"order\":1}"; } - public static class ListOfTodos { - public static final List LIST = - List.of(SomeTodo.TODO, AnotherTodo.TODO); - - public static final String SERIALIZED = - "[" + SomeTodo.SERIALIZED + "," + AnotherTodo.SERIALIZED + "]"; + interface ListOfTodos { + List LIST = List.of(SomeTodo.TODO, AnotherTodo.TODO); + String SERIALIZED = "[" + SomeTodo.SERIALIZED + "," + AnotherTodo.SERIALIZED + "]"; } - public static class Invalid { - public static final String ID = "this is an invalid uuid"; - public static final String JSON = "this is an invalid json document"; - public static final String SERIALIZED_TODO_WITH_NO_TITLE = "{\"order\":1337}"; + interface Invalid { + String ID = "this is an invalid uuid"; + String JSON = "this is an invalid json document"; + String SERIALIZED_TODO_WITH_NO_TITLE = "{\"order\":1337}"; } } From cb4c8f8c253a8bcbfe25972bcc16dc34d77cf12b Mon Sep 17 00:00:00 2001 From: Jan Ouwens Date: Mon, 1 Apr 2019 13:13:30 +0200 Subject: [PATCH 102/106] Makes repo initialization failures testable --- src/main/java/nl/jqno/paralleljava/Main.java | 5 +- .../database/DatabaseRepository.java | 50 ++++++--------- .../app/persistence/database/DefaultJdbi.java | 31 +++++++++ .../app/persistence/database/Jdbi.java | 10 +++ .../database/DatabaseRepositoryTest.java | 63 ++++++++++++++----- .../app/persistence/database/FailingJdbi.java | 25 ++++++++ 6 files changed, 136 insertions(+), 48 deletions(-) create mode 100644 src/main/java/nl/jqno/paralleljava/app/persistence/database/DefaultJdbi.java create mode 100644 src/main/java/nl/jqno/paralleljava/app/persistence/database/Jdbi.java create mode 100644 src/test/java/nl/jqno/paralleljava/app/persistence/database/FailingJdbi.java diff --git a/src/main/java/nl/jqno/paralleljava/Main.java b/src/main/java/nl/jqno/paralleljava/Main.java index d180f65..388dbc5 100644 --- a/src/main/java/nl/jqno/paralleljava/Main.java +++ b/src/main/java/nl/jqno/paralleljava/Main.java @@ -9,6 +9,7 @@ import nl.jqno.paralleljava.app.logging.Slf4jLogger; import nl.jqno.paralleljava.app.persistence.RandomIdGenerator; import nl.jqno.paralleljava.app.persistence.database.DatabaseRepository; +import nl.jqno.paralleljava.app.persistence.database.DefaultJdbi; import nl.jqno.paralleljava.app.persistence.database.TodoMapper; import nl.jqno.paralleljava.app.serialization.GsonSerializer; import nl.jqno.paralleljava.app.server.SparkServer; @@ -26,7 +27,9 @@ public static void main(String... args) { var jdbcUrl = environment.jdbcUrl().getOrElse(Environment.DEFAULT_JDBC_URL); var todoMapper = new TodoMapper(fullUrl); - var repository = new DatabaseRepository(jdbcUrl, todoMapper, loggerFactory); + var jdbi = new DefaultJdbi(jdbcUrl, todoMapper, loggerFactory); + var repository = new DatabaseRepository(jdbi); + var idGenerator = new RandomIdGenerator(); var serializer = GsonSerializer.create(loggerFactory); var controller = new DefaultController(fullUrl, repository, idGenerator, serializer, loggerFactory); diff --git a/src/main/java/nl/jqno/paralleljava/app/persistence/database/DatabaseRepository.java b/src/main/java/nl/jqno/paralleljava/app/persistence/database/DatabaseRepository.java index f859a9d..1c29f6b 100644 --- a/src/main/java/nl/jqno/paralleljava/app/persistence/database/DatabaseRepository.java +++ b/src/main/java/nl/jqno/paralleljava/app/persistence/database/DatabaseRepository.java @@ -4,12 +4,7 @@ import io.vavr.control.Option; import io.vavr.control.Try; import nl.jqno.paralleljava.app.domain.Todo; -import nl.jqno.paralleljava.app.logging.Logger; -import nl.jqno.paralleljava.app.logging.LoggerFactory; import nl.jqno.paralleljava.app.persistence.Repository; -import org.jdbi.v3.core.HandleCallback; -import org.jdbi.v3.core.HandleConsumer; -import org.jdbi.v3.core.Jdbi; import java.util.UUID; @@ -18,24 +13,27 @@ */ public class DatabaseRepository implements Repository { - private final Logger logger; - private final Jdbi jdbi; + private Jdbi jdbi; - public DatabaseRepository(String jdbcUrl, TodoMapper todoMapper, LoggerFactory loggerFactory) { - this.jdbi = Jdbi.create(jdbcUrl); - this.jdbi.registerRowMapper(Todo.class, todoMapper); - this.logger = loggerFactory.create(getClass()); - logger.forProduction("Using database " + jdbcUrl); + public DatabaseRepository(Jdbi jdbi) { + this.jdbi = jdbi; } public Try initialize() { var sql = "CREATE TABLE todo (id VARCHAR(36) PRIMARY KEY, title VARCHAR, completed BOOLEAN, index INTEGER)"; - return execute(handle -> handle.execute(sql)) - .orElse(Try.success(null)); // If it fails, the table probably already exists. + return jdbi.execute(handle -> handle.execute(sql)) + .recoverWith(f -> { + if (f.getMessage() != null && f.getMessage().toLowerCase().contains("\"todo\" already exists")) { + return Try.success(null); + } + else { + return Try.failure(f); + } + }); } public Try create(Todo todo) { - return execute(handle -> + return jdbi.execute(handle -> handle.createUpdate("INSERT INTO todo (id, title, completed, index) VALUES (:id, :title, :completed, :order)") .bind("id", todo.id().toString()) .bind("title", todo.title()) @@ -45,7 +43,7 @@ public Try create(Todo todo) { } public Try> get(UUID id) { - return query(handle -> { + return jdbi.query(handle -> { var o = handle.createQuery("SELECT id, title, completed, index FROM todo WHERE id = :id") .bind("id", id.toString()) .mapTo(Todo.class) @@ -55,14 +53,14 @@ public Try> get(UUID id) { } public Try> getAll() { - return query(handle -> + return jdbi.query(handle -> handle.createQuery("SELECT id, title, completed, index FROM todo") .mapTo(Todo.class) .collect(List.collector())); } public Try update(Todo todo) { - return execute(handle -> + return jdbi.execute(handle -> handle.createUpdate("UPDATE todo SET title = :title, completed = :completed, index = :order WHERE id = :id") .bind("title", todo.title()) .bind("completed", todo.completed()) @@ -72,25 +70,13 @@ public Try update(Todo todo) { } public Try delete(UUID id) { - return execute(handle -> + return jdbi.execute(handle -> handle.createUpdate("DELETE FROM todo WHERE id = :id") .bind("id", id.toString()) .execute()); } public Try deleteAll() { - return execute(handle -> handle.execute("DELETE FROM todo")); - } - - private Try execute(HandleConsumer consumer) { - return Try.of(() -> { - jdbi.useHandle(consumer); - return null; - }).onFailure(f -> logger.wakeMeUp("Failed to execute statement", f)); - } - - private Try query(HandleCallback callback) { - return Try.of(() -> jdbi.withHandle(callback)) - .onFailure(f -> logger.wakeMeUp("Failed to execute query", f)); + return jdbi.execute(handle -> handle.execute("DELETE FROM todo")); } } diff --git a/src/main/java/nl/jqno/paralleljava/app/persistence/database/DefaultJdbi.java b/src/main/java/nl/jqno/paralleljava/app/persistence/database/DefaultJdbi.java new file mode 100644 index 0000000..1ad95cd --- /dev/null +++ b/src/main/java/nl/jqno/paralleljava/app/persistence/database/DefaultJdbi.java @@ -0,0 +1,31 @@ +package nl.jqno.paralleljava.app.persistence.database; + +import io.vavr.control.Try; +import nl.jqno.paralleljava.app.logging.Logger; +import nl.jqno.paralleljava.app.logging.LoggerFactory; +import org.jdbi.v3.core.HandleCallback; +import org.jdbi.v3.core.HandleConsumer; + +public class DefaultJdbi implements Jdbi { + private final org.jdbi.v3.core.Jdbi jdbi; + private final Logger logger; + + public DefaultJdbi(String jdbcUrl, TodoMapper todoMapper, LoggerFactory loggerFactory) { + this.jdbi = org.jdbi.v3.core.Jdbi + .create(jdbcUrl) + .registerRowMapper(todoMapper); + this.logger = loggerFactory.create(getClass()); + } + + public Try execute(HandleConsumer consumer) { + return Try.of(() -> { + jdbi.useHandle(consumer); + return null; + }).onFailure(f -> logger.wakeMeUp("Failed to execute statement", f)); + } + + public Try query(HandleCallback callback) { + return Try.of(() -> jdbi.withHandle(callback)) + .onFailure(f -> logger.wakeMeUp("Failed to execute query", f)); + } +} diff --git a/src/main/java/nl/jqno/paralleljava/app/persistence/database/Jdbi.java b/src/main/java/nl/jqno/paralleljava/app/persistence/database/Jdbi.java new file mode 100644 index 0000000..65be4fe --- /dev/null +++ b/src/main/java/nl/jqno/paralleljava/app/persistence/database/Jdbi.java @@ -0,0 +1,10 @@ +package nl.jqno.paralleljava.app.persistence.database; + +import io.vavr.control.Try; +import org.jdbi.v3.core.HandleCallback; +import org.jdbi.v3.core.HandleConsumer; + +public interface Jdbi { + Try execute(HandleConsumer consumer); + Try query(HandleCallback callback); +} diff --git a/src/test/java/nl/jqno/paralleljava/app/persistence/database/DatabaseRepositoryTest.java b/src/test/java/nl/jqno/paralleljava/app/persistence/database/DatabaseRepositoryTest.java index 2a97f8b..76bd12c 100644 --- a/src/test/java/nl/jqno/paralleljava/app/persistence/database/DatabaseRepositoryTest.java +++ b/src/test/java/nl/jqno/paralleljava/app/persistence/database/DatabaseRepositoryTest.java @@ -1,11 +1,11 @@ package nl.jqno.paralleljava.app.persistence.database; -import nl.jqno.paralleljava.app.domain.Todo; -import nl.jqno.paralleljava.app.environment.Environment; -import nl.jqno.paralleljava.app.logging.LoggerFactory; import nl.jqno.paralleljava.TestData; import nl.jqno.paralleljava.TestData.AnotherTodo; import nl.jqno.paralleljava.TestData.SomeTodo; +import nl.jqno.paralleljava.app.domain.Todo; +import nl.jqno.paralleljava.app.environment.Environment; +import nl.jqno.paralleljava.app.logging.LoggerFactory; import nl.jqno.paralleljava.app.logging.NopLogger; import nl.jqno.picotest.Test; import org.jdbi.v3.core.statement.StatementContext; @@ -20,25 +20,46 @@ public class DatabaseRepositoryTest extends Test { private static final String IN_MEMORY_DATABASE = Environment.DEFAULT_JDBC_URL; private static final LoggerFactory NOP_LOGGER = c -> new NopLogger(); - private final TodoMapper todoMapper = new TodoMapper(TestData.URL_PREFIX); + private static final TodoMapper todoMapper = new TodoMapper(TestData.URL_PREFIX); public void initialization() { test("a table is created", () -> { - var repo = new DatabaseRepository(IN_MEMORY_DATABASE, todoMapper, NOP_LOGGER); + var jdbi = new DefaultJdbi(IN_MEMORY_DATABASE, todoMapper, NOP_LOGGER); + var repo = new DatabaseRepository(jdbi); var result = repo.initialize(); assertThat(result).isSuccess(); }); test("initializing twice is a no-op the second time", () -> { - var repo = new DatabaseRepository(IN_MEMORY_DATABASE, todoMapper, NOP_LOGGER); + var jdbi = new DefaultJdbi(IN_MEMORY_DATABASE, todoMapper, NOP_LOGGER); + var repo = new DatabaseRepository(jdbi); assertThat(repo.initialize()).isSuccess(); assertThat(repo.initialize()).isSuccess(); }); + + test("a failure with no message while creating is propagated", () -> { + var jdbi = new FailingJdbi(); + var repo = new DatabaseRepository(jdbi); + assertThat(repo.initialize()).isFailure(); + }); + + test("a failure with a message while creating is propagated", () -> { + var jdbi = new FailingJdbi(new IllegalStateException("Something went wrong")); + var repo = new DatabaseRepository(jdbi); + assertThat(repo.initialize()).isFailure(); + }); + + test("a failure to create the table is not propagated if the table already exists", () -> { + var jdbi = new FailingJdbi(new IllegalStateException("Table \"TODO\" already exists")); + var repo = new DatabaseRepository(jdbi); + assertThat(repo.initialize()).isSuccess(); + }); } public void repository() { - var repo = new DatabaseRepository(IN_MEMORY_DATABASE, todoMapper, NOP_LOGGER); + var jdbi = new DefaultJdbi(IN_MEMORY_DATABASE, todoMapper, NOP_LOGGER); + var repo = new DatabaseRepository(jdbi); beforeAll(() -> { assertThat(repo.initialize()).isSuccess(); @@ -93,18 +114,30 @@ public void repository() { } public void failures() { - var failingMapper = new TodoMapper("") { - public Todo map(ResultSet rs, StatementContext ctx) throws SQLException { - throw new SQLException("Intentional failure"); - } - }; - var repo = new DatabaseRepository(IN_MEMORY_DATABASE, failingMapper, NOP_LOGGER); + test("Execute failures cause failed results", () -> { + var jdbi = new FailingJdbi(); + var repo = new DatabaseRepository(jdbi); - beforeAll(() -> { - repo.initialize(); + var result = repo.create(SomeTodo.TODO); + assertThat(result).isFailure(); }); test("Query failures cause failed results", () -> { + var jdbi = new FailingJdbi(); + var repo = new DatabaseRepository(jdbi); + + var result = repo.getAll(); + assertThat(result).isFailure(); + }); + + test("Mapping failures cause failed results", () -> { + var failingMapper = new TodoMapper("") { + public Todo map(ResultSet rs, StatementContext ctx) throws SQLException { + throw new SQLException("Intentional failure"); + } + }; + var jdbi = new DefaultJdbi(IN_MEMORY_DATABASE, failingMapper, NOP_LOGGER); + var repo = new DatabaseRepository(jdbi); repo.create(SomeTodo.TODO); var result = repo.getAll(); diff --git a/src/test/java/nl/jqno/paralleljava/app/persistence/database/FailingJdbi.java b/src/test/java/nl/jqno/paralleljava/app/persistence/database/FailingJdbi.java new file mode 100644 index 0000000..4e1ae65 --- /dev/null +++ b/src/test/java/nl/jqno/paralleljava/app/persistence/database/FailingJdbi.java @@ -0,0 +1,25 @@ +package nl.jqno.paralleljava.app.persistence.database; + +import io.vavr.control.Try; +import org.jdbi.v3.core.HandleCallback; +import org.jdbi.v3.core.HandleConsumer; + +public class FailingJdbi implements Jdbi { + private final Throwable exception; + + public FailingJdbi() { + this(new IllegalStateException()); + } + + public FailingJdbi(Throwable exception) { + this.exception = exception; + } + + public Try execute(HandleConsumer consumer) { + return Try.failure(exception); + } + + public Try query(HandleCallback callback) { + return Try.failure(exception); + } +} From a16bb9125fa308f8aef1dd7304cdb9dd5c278019 Mon Sep 17 00:00:00 2001 From: Jan Ouwens Date: Sun, 7 Apr 2019 15:24:12 +0200 Subject: [PATCH 103/106] Adds a banner --- src/main/java/nl/jqno/paralleljava/Main.java | 2 +- .../java/nl/jqno/paralleljava/app/Runner.java | 21 +++++++++++++++++-- .../nl/jqno/paralleljava/app/RunnerTest.java | 6 +++++- 3 files changed, 25 insertions(+), 4 deletions(-) diff --git a/src/main/java/nl/jqno/paralleljava/Main.java b/src/main/java/nl/jqno/paralleljava/Main.java index 388dbc5..f6695d7 100644 --- a/src/main/java/nl/jqno/paralleljava/Main.java +++ b/src/main/java/nl/jqno/paralleljava/Main.java @@ -35,7 +35,7 @@ public static void main(String... args) { var controller = new DefaultController(fullUrl, repository, idGenerator, serializer, loggerFactory); var server = new SparkServer(Environment.ENDPOINT, port, controller, loggerFactory); - var runner = new Runner(repository, server); + var runner = new Runner(repository, server, loggerFactory); runner.startup(); } } diff --git a/src/main/java/nl/jqno/paralleljava/app/Runner.java b/src/main/java/nl/jqno/paralleljava/app/Runner.java index fdd148e..46acae7 100644 --- a/src/main/java/nl/jqno/paralleljava/app/Runner.java +++ b/src/main/java/nl/jqno/paralleljava/app/Runner.java @@ -1,19 +1,36 @@ package nl.jqno.paralleljava.app; +import nl.jqno.paralleljava.app.logging.Logger; +import nl.jqno.paralleljava.app.logging.LoggerFactory; import nl.jqno.paralleljava.app.persistence.Repository; import nl.jqno.paralleljava.app.server.Server; public class Runner { private final Repository repository; private final Server server; + private final Logger logger; - public Runner(Repository repository, Server server) { + public Runner(Repository repository, Server server, LoggerFactory loggerFactory) { this.repository = repository; this.server = server; + this.logger = loggerFactory.create(getClass()); } public void startup() { repository.initialize() - .onSuccess(ignored -> server.run()); + .onSuccess(ignored -> { + printBanner(); + server.run(); + }); + } + + private void printBanner() { + logger.forProduction(" _ _ _____ _ _ __ _ _"); + logger.forProduction("| \\ | | ___ | ___| __ __ _ _ __ ___ _____ _____ _ __| | _| |\\ \\ \\ \\"); + logger.forProduction("| \\| |/ _ \\ | |_ | '__/ _` | '_ ` _ \\ / _ \\ \\ /\\ / / _ \\| '__| |/ / | \\ \\ \\ \\"); + logger.forProduction("| |\\ | (_) | | _|| | | (_| | | | | | | __/\\ V V / (_) | | | <|_| ) ) ) )"); + logger.forProduction("|_| \\_|\\___/ |_| |_| \\__,_|_| |_| |_|\\___| \\_/\\_/ \\___/|_| |_|\\_(_) / / / /"); + logger.forProduction("=======================================================================/_/_/_/"); + logger.forProduction(" :: Built with Plain Java! :: \uD83C\uDF89"); } } diff --git a/src/test/java/nl/jqno/paralleljava/app/RunnerTest.java b/src/test/java/nl/jqno/paralleljava/app/RunnerTest.java index c00bddf..225dd80 100644 --- a/src/test/java/nl/jqno/paralleljava/app/RunnerTest.java +++ b/src/test/java/nl/jqno/paralleljava/app/RunnerTest.java @@ -1,5 +1,7 @@ package nl.jqno.paralleljava.app; +import nl.jqno.paralleljava.app.logging.Slf4jLogger; +import nl.jqno.paralleljava.app.logging.StubLogger; import nl.jqno.paralleljava.app.persistence.StubRepository; import nl.jqno.paralleljava.app.server.StubServer; import nl.jqno.picotest.Test; @@ -11,7 +13,8 @@ public class RunnerTest extends Test { public void runner() { var repo = new StubRepository(); var server = new StubServer(); - var runner = new Runner(repo, server); + var logger = new StubLogger(); + var runner = new Runner(repo, server, c -> new Slf4jLogger(logger)); beforeEach(() -> { repo.clear(); @@ -23,6 +26,7 @@ public void runner() { assertThat(repo.calledInitialize).isEqualTo(1); assertThat(server.calledRun).isEqualTo(1); + assertThat(logger.calledInfo).isEqualTo(7); }); test("repo initialization fails", () -> { From 44ba2a51d0243751d976852bfca58b5daac45011 Mon Sep 17 00:00:00 2001 From: Jan Ouwens Date: Thu, 11 Apr 2019 17:05:14 +0200 Subject: [PATCH 104/106] Adds transaction support for updates --- .../app/controller/DefaultController.java | 26 ++++----- .../nl/jqno/paralleljava/app/domain/Todo.java | 4 ++ .../app/persistence/Repository.java | 2 + .../database/DatabaseRepository.java | 54 ++++++++++++------- .../app/persistence/database/DefaultJdbi.java | 4 +- .../inmemory/InMemoryRepository.java | 13 +++++ .../paralleljava/app/domain/TodoTest.java | 12 +++++ .../app/persistence/StubRepository.java | 14 +++-- .../database/DatabaseRepositoryTest.java | 32 +++++++++++ .../inmemory/InMemoryRepositoryTest.java | 30 +++++++++++ 10 files changed, 151 insertions(+), 40 deletions(-) diff --git a/src/main/java/nl/jqno/paralleljava/app/controller/DefaultController.java b/src/main/java/nl/jqno/paralleljava/app/controller/DefaultController.java index aaa75b8..4a2199b 100644 --- a/src/main/java/nl/jqno/paralleljava/app/controller/DefaultController.java +++ b/src/main/java/nl/jqno/paralleljava/app/controller/DefaultController.java @@ -1,5 +1,6 @@ package nl.jqno.paralleljava.app.controller; +import io.vavr.Function1; import io.vavr.control.Try; import nl.jqno.paralleljava.app.domain.Todo; import nl.jqno.paralleljava.app.logging.Logger; @@ -64,22 +65,15 @@ public Try patch(String id, String json) { } var pt = partialTodo.get(); - var existingTodo = repository.get(uuid.get()); - return existingTodo.flatMap(et -> { - if (et.isEmpty()) { - return Try.failure(new IllegalArgumentException("Can't find Todo with id " + id)); - } - - var todo = et.get(); - var updatedTodo = new Todo( - todo.id(), - pt.title().getOrElse(todo.title()), - todo.url(), - pt.completed().getOrElse(todo.completed()), - pt.order().getOrElse(todo.order())); - repository.update(updatedTodo); - return Try.of(() -> serializer.serializeTodo(updatedTodo)); - }); + Function1 updater = todo -> new Todo( + todo.id(), + pt.title().getOrElse(todo.title()), + todo.url(), + pt.completed().getOrElse(todo.completed()), + pt.order().getOrElse(todo.order()) + ); + return repository.update(uuid.get(), updater) + .map(serializer::serializeTodo); } public Try delete() { diff --git a/src/main/java/nl/jqno/paralleljava/app/domain/Todo.java b/src/main/java/nl/jqno/paralleljava/app/domain/Todo.java index 85917dc..9701da0 100644 --- a/src/main/java/nl/jqno/paralleljava/app/domain/Todo.java +++ b/src/main/java/nl/jqno/paralleljava/app/domain/Todo.java @@ -38,6 +38,10 @@ public int order() { return order; } + public Todo withId(UUID id) { + return new Todo(id, title(), url(), completed(), order()); + } + public Todo withTitle(String title) { return new Todo(id(), title, url(), completed(), order()); } diff --git a/src/main/java/nl/jqno/paralleljava/app/persistence/Repository.java b/src/main/java/nl/jqno/paralleljava/app/persistence/Repository.java index 86ee202..3ba16ce 100644 --- a/src/main/java/nl/jqno/paralleljava/app/persistence/Repository.java +++ b/src/main/java/nl/jqno/paralleljava/app/persistence/Repository.java @@ -1,5 +1,6 @@ package nl.jqno.paralleljava.app.persistence; +import io.vavr.Function1; import io.vavr.collection.List; import io.vavr.control.Option; import io.vavr.control.Try; @@ -13,6 +14,7 @@ public interface Repository { Try> get(UUID id); Try> getAll(); Try update(Todo todo); + Try update(UUID id, Function1 f); Try delete(UUID id); Try deleteAll(); } diff --git a/src/main/java/nl/jqno/paralleljava/app/persistence/database/DatabaseRepository.java b/src/main/java/nl/jqno/paralleljava/app/persistence/database/DatabaseRepository.java index 1c29f6b..05cfaaa 100644 --- a/src/main/java/nl/jqno/paralleljava/app/persistence/database/DatabaseRepository.java +++ b/src/main/java/nl/jqno/paralleljava/app/persistence/database/DatabaseRepository.java @@ -1,16 +1,15 @@ package nl.jqno.paralleljava.app.persistence.database; +import io.vavr.Function1; import io.vavr.collection.List; import io.vavr.control.Option; import io.vavr.control.Try; import nl.jqno.paralleljava.app.domain.Todo; import nl.jqno.paralleljava.app.persistence.Repository; +import org.jdbi.v3.core.Handle; import java.util.UUID; -/** - * NOTE: this class is totally not thread-safe! It totally ignores database transactions. - */ public class DatabaseRepository implements Repository { private Jdbi jdbi; @@ -25,8 +24,7 @@ public Try initialize() { .recoverWith(f -> { if (f.getMessage() != null && f.getMessage().toLowerCase().contains("\"todo\" already exists")) { return Try.success(null); - } - else { + } else { return Try.failure(f); } }); @@ -43,13 +41,7 @@ public Try create(Todo todo) { } public Try> get(UUID id) { - return jdbi.query(handle -> { - var o = handle.createQuery("SELECT id, title, completed, index FROM todo WHERE id = :id") - .bind("id", id.toString()) - .mapTo(Todo.class) - .findFirst(); - return Option.ofOptional(o); - }); + return jdbi.query(handle -> handleGet(handle, id)); } public Try> getAll() { @@ -60,13 +52,20 @@ public Try> getAll() { } public Try update(Todo todo) { - return jdbi.execute(handle -> - handle.createUpdate("UPDATE todo SET title = :title, completed = :completed, index = :order WHERE id = :id") - .bind("title", todo.title()) - .bind("completed", todo.completed()) - .bind("order", todo.order()) - .bind("id", todo.id().toString()) - .execute()); + return jdbi.execute(handle -> handleUpdate(handle, todo)); + } + + public Try update(UUID id, Function1 f) { + return jdbi.query(handle -> { + var option = handleGet(handle, id); + if (option.isEmpty()) { + throw new IllegalArgumentException("Can't find Todo with id " + id); + } + var oldTodo = option.get(); + var newTodo = f.apply(oldTodo).withId(oldTodo.id()); + handleUpdate(handle, newTodo); + return newTodo; + }); } public Try delete(UUID id) { @@ -79,4 +78,21 @@ public Try delete(UUID id) { public Try deleteAll() { return jdbi.execute(handle -> handle.execute("DELETE FROM todo")); } + + private Option handleGet(Handle handle, UUID id) { + var o = handle.createQuery("SELECT id, title, completed, index FROM todo WHERE id = :id") + .bind("id", id.toString()) + .mapTo(Todo.class) + .findFirst(); + return Option.ofOptional(o); + } + + private int handleUpdate(Handle handle, Todo todo) { + return handle.createUpdate("UPDATE todo SET title = :title, completed = :completed, index = :order WHERE id = :id") + .bind("title", todo.title()) + .bind("completed", todo.completed()) + .bind("order", todo.order()) + .bind("id", todo.id().toString()) + .execute(); + } } diff --git a/src/main/java/nl/jqno/paralleljava/app/persistence/database/DefaultJdbi.java b/src/main/java/nl/jqno/paralleljava/app/persistence/database/DefaultJdbi.java index 1ad95cd..bda8df5 100644 --- a/src/main/java/nl/jqno/paralleljava/app/persistence/database/DefaultJdbi.java +++ b/src/main/java/nl/jqno/paralleljava/app/persistence/database/DefaultJdbi.java @@ -19,13 +19,13 @@ public DefaultJdbi(String jdbcUrl, TodoMapper todoMapper, LoggerFactory loggerFa public Try execute(HandleConsumer consumer) { return Try.of(() -> { - jdbi.useHandle(consumer); + jdbi.useHandle(h -> h.useTransaction(consumer)); return null; }).onFailure(f -> logger.wakeMeUp("Failed to execute statement", f)); } public Try query(HandleCallback callback) { - return Try.of(() -> jdbi.withHandle(callback)) + return Try.of(() -> jdbi.withHandle(h -> h.inTransaction(callback))) .onFailure(f -> logger.wakeMeUp("Failed to execute query", f)); } } diff --git a/src/main/java/nl/jqno/paralleljava/app/persistence/inmemory/InMemoryRepository.java b/src/main/java/nl/jqno/paralleljava/app/persistence/inmemory/InMemoryRepository.java index 37193eb..9fe8fbb 100644 --- a/src/main/java/nl/jqno/paralleljava/app/persistence/inmemory/InMemoryRepository.java +++ b/src/main/java/nl/jqno/paralleljava/app/persistence/inmemory/InMemoryRepository.java @@ -1,5 +1,6 @@ package nl.jqno.paralleljava.app.persistence.inmemory; +import io.vavr.Function1; import io.vavr.collection.List; import io.vavr.control.Option; import io.vavr.control.Try; @@ -53,6 +54,18 @@ public Try update(Todo todo) { return SUCCESS; } + public Try update(UUID id, Function1 f) { + return get(id).flatMap(option -> { + if (option.isEmpty()) { + return Try.failure(new IllegalArgumentException("Can't find Todo with id " + id)); + } else { + var oldTodo = option.get(); + var newTodo = f.apply(oldTodo).withId(oldTodo.id()); + return update(newTodo).map(ignored -> newTodo); + } + }); + } + public Try delete(UUID id) { var index = List.ofAll(todos) .map(Todo::id) diff --git a/src/test/java/nl/jqno/paralleljava/app/domain/TodoTest.java b/src/test/java/nl/jqno/paralleljava/app/domain/TodoTest.java index 470a868..932c885 100644 --- a/src/test/java/nl/jqno/paralleljava/app/domain/TodoTest.java +++ b/src/test/java/nl/jqno/paralleljava/app/domain/TodoTest.java @@ -4,6 +4,8 @@ import nl.jqno.paralleljava.TestData.SomeTodo; import nl.jqno.picotest.Test; +import java.util.UUID; + import static org.assertj.core.api.Assertions.assertThat; public class TodoTest extends Test { @@ -28,6 +30,16 @@ public void todo() { .isEqualTo("Todo: [id=" + SomeTodo.ID + ", title=title, url=" + SomeTodo.URL + ", completed=true, order=1337]"); }); + test("withId", () -> { + var anotherId = UUID.randomUUID(); + var actual = SomeTodo.TODO.withId(anotherId); + assertThat(actual.id()).isEqualTo(anotherId); + assertThat(actual.title()).isEqualTo(SomeTodo.TODO.title()); + assertThat(actual.url()).isEqualTo(SomeTodo.TODO.url()); + assertThat(actual.completed()).isEqualTo(SomeTodo.TODO.completed()); + assertThat(actual.order()).isEqualTo(SomeTodo.TODO.order()); + }); + test("withTitle", () -> { var actual = SomeTodo.TODO.withTitle("another title"); assertThat(actual.title()).isEqualTo("another title"); diff --git a/src/test/java/nl/jqno/paralleljava/app/persistence/StubRepository.java b/src/test/java/nl/jqno/paralleljava/app/persistence/StubRepository.java index 0005758..c98de42 100644 --- a/src/test/java/nl/jqno/paralleljava/app/persistence/StubRepository.java +++ b/src/test/java/nl/jqno/paralleljava/app/persistence/StubRepository.java @@ -1,5 +1,6 @@ package nl.jqno.paralleljava.app.persistence; +import io.vavr.Function1; import io.vavr.collection.List; import io.vavr.control.Option; import io.vavr.control.Try; @@ -14,7 +15,8 @@ public class StubRepository implements Repository { public int calledCreate = 0; public int calledGet = 0; public int calledGetAll = 0; - public int calledUpdate = 0; + public int calledUpdateDirectly = 0; + public int calledUpdateModify = 0; public int calledDelete = 0; public int calledDeleteAll = 0; @@ -24,7 +26,8 @@ public void clear() { calledCreate = 0; calledGet = 0; calledGetAll = 0; - calledUpdate = 0; + calledUpdateDirectly = 0; + calledUpdateModify = 0; calledDelete = 0; calledDeleteAll = 0; } @@ -50,7 +53,12 @@ public Try> getAll() { } public Try update(Todo todo) { - calledUpdate += 1; + calledUpdateDirectly += 1; + return returnValue(null); + } + + public Try update(UUID id, Function1 f) { + calledUpdateModify += 1; return returnValue(null); } diff --git a/src/test/java/nl/jqno/paralleljava/app/persistence/database/DatabaseRepositoryTest.java b/src/test/java/nl/jqno/paralleljava/app/persistence/database/DatabaseRepositoryTest.java index 76bd12c..b2cc836 100644 --- a/src/test/java/nl/jqno/paralleljava/app/persistence/database/DatabaseRepositoryTest.java +++ b/src/test/java/nl/jqno/paralleljava/app/persistence/database/DatabaseRepositoryTest.java @@ -14,6 +14,7 @@ import java.sql.SQLException; import java.util.UUID; +import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.vavr.api.VavrAssertions.assertThat; public class DatabaseRepositoryTest extends Test { @@ -63,6 +64,8 @@ public void repository() { beforeAll(() -> { assertThat(repo.initialize()).isSuccess(); + }); + beforeEach(() -> { assertThat(repo.deleteAll()).isSuccess(); }); @@ -92,6 +95,35 @@ public void repository() { assertThat(actual).contains(expected); }); + test("update a todo with a specific id", () -> { + repo.create(SomeTodo.TODO); + + var result = repo.update(SomeTodo.ID, t -> t.withTitle("updated")); + var actual = repo.get(SomeTodo.ID).get(); + + assertThat(result).isSuccess(); + assertThat(actual).hasValueSatisfying(t -> assertThat(t.title()).isEqualTo("updated")); + }); + + test("update a todo with a specific id that doesn't exist fails", () -> { + var result = repo.update(SomeTodo.ID, t -> t); + assertThat(result).isFailure(); + }); + + test("update a todo with a specific id doesn't change the id", () -> { + repo.create(SomeTodo.TODO); + + var result = repo.update(SomeTodo.ID, t -> AnotherTodo.TODO); + var actualOriginal = repo.get(SomeTodo.ID); + var actualNew = repo.get(AnotherTodo.ID); + + assertThat(result).isSuccess(); + assertThat(actualOriginal).hasValueSatisfying( + o -> assertThat(o).hasValueSatisfying( + t -> assertThat(t.title()).isEqualTo(AnotherTodo.TODO.title()))); + assertThat(actualNew).hasValueSatisfying(o -> assertThat(o).isEmpty()); + }); + test("delete a specific todo", () -> { repo.create(SomeTodo.TODO); repo.create(AnotherTodo.TODO); diff --git a/src/test/java/nl/jqno/paralleljava/app/persistence/inmemory/InMemoryRepositoryTest.java b/src/test/java/nl/jqno/paralleljava/app/persistence/inmemory/InMemoryRepositoryTest.java index f00cb55..dbc03c2 100644 --- a/src/test/java/nl/jqno/paralleljava/app/persistence/inmemory/InMemoryRepositoryTest.java +++ b/src/test/java/nl/jqno/paralleljava/app/persistence/inmemory/InMemoryRepositoryTest.java @@ -7,6 +7,7 @@ import java.util.UUID; +import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.vavr.api.VavrAssertions.assertThat; public class InMemoryRepositoryTest extends Test { @@ -60,6 +61,35 @@ public void repository() { assertThat(actual).contains(expected); }); + test("update a todo with a specific id", () -> { + repo.create(SomeTodo.TODO); + + var result = repo.update(SomeTodo.ID, t -> t.withTitle("updated")); + var actual = repo.get(SomeTodo.ID).get(); + + assertThat(result).isSuccess(); + assertThat(actual).hasValueSatisfying(t -> assertThat(t.title()).isEqualTo("updated")); + }); + + test("update a todo with a specific id that doesn't exist fails", () -> { + var result = repo.update(SomeTodo.ID, t -> t); + assertThat(result).isFailure(); + }); + + test("update a todo with a specific id doesn't change the id", () -> { + repo.create(SomeTodo.TODO); + + var result = repo.update(SomeTodo.ID, t -> AnotherTodo.TODO); + var actualOriginal = repo.get(SomeTodo.ID); + var actualNew = repo.get(AnotherTodo.ID); + + assertThat(result).isSuccess(); + assertThat(actualOriginal).hasValueSatisfying( + o -> assertThat(o).hasValueSatisfying( + t -> assertThat(t.title()).isEqualTo(AnotherTodo.TODO.title()))); + assertThat(actualNew).hasValueSatisfying(o -> assertThat(o).isEmpty()); + }); + test("delete a specific todo at index 0", () -> { repo.create(SomeTodo.TODO); repo.create(AnotherTodo.TODO); From 81ab5dfc236ecececd6434bd58f3522817110431 Mon Sep 17 00:00:00 2001 From: Jan Ouwens Date: Sun, 14 Apr 2019 10:59:59 +0200 Subject: [PATCH 105/106] Renames Jdbi to Engine --- src/main/java/nl/jqno/paralleljava/Main.java | 6 ++-- .../database/DatabaseRepository.java | 22 ++++++------ .../database/{Jdbi.java => Engine.java} | 2 +- .../{DefaultJdbi.java => JdbiEngine.java} | 9 ++--- .../jqno/paralleljava/ArchitectureTest.java | 2 +- .../database/DatabaseRepositoryTest.java | 36 +++++++++---------- .../{FailingJdbi.java => FailingEngine.java} | 6 ++-- 7 files changed, 42 insertions(+), 41 deletions(-) rename src/main/java/nl/jqno/paralleljava/app/persistence/database/{Jdbi.java => Engine.java} (92%) rename src/main/java/nl/jqno/paralleljava/app/persistence/database/{DefaultJdbi.java => JdbiEngine.java} (81%) rename src/test/java/nl/jqno/paralleljava/app/persistence/database/{FailingJdbi.java => FailingEngine.java} (81%) diff --git a/src/main/java/nl/jqno/paralleljava/Main.java b/src/main/java/nl/jqno/paralleljava/Main.java index f6695d7..e973bb5 100644 --- a/src/main/java/nl/jqno/paralleljava/Main.java +++ b/src/main/java/nl/jqno/paralleljava/Main.java @@ -9,7 +9,7 @@ import nl.jqno.paralleljava.app.logging.Slf4jLogger; import nl.jqno.paralleljava.app.persistence.RandomIdGenerator; import nl.jqno.paralleljava.app.persistence.database.DatabaseRepository; -import nl.jqno.paralleljava.app.persistence.database.DefaultJdbi; +import nl.jqno.paralleljava.app.persistence.database.JdbiEngine; import nl.jqno.paralleljava.app.persistence.database.TodoMapper; import nl.jqno.paralleljava.app.serialization.GsonSerializer; import nl.jqno.paralleljava.app.server.SparkServer; @@ -27,8 +27,8 @@ public static void main(String... args) { var jdbcUrl = environment.jdbcUrl().getOrElse(Environment.DEFAULT_JDBC_URL); var todoMapper = new TodoMapper(fullUrl); - var jdbi = new DefaultJdbi(jdbcUrl, todoMapper, loggerFactory); - var repository = new DatabaseRepository(jdbi); + var dbEngine = new JdbiEngine(jdbcUrl, todoMapper, loggerFactory); + var repository = new DatabaseRepository(dbEngine); var idGenerator = new RandomIdGenerator(); var serializer = GsonSerializer.create(loggerFactory); diff --git a/src/main/java/nl/jqno/paralleljava/app/persistence/database/DatabaseRepository.java b/src/main/java/nl/jqno/paralleljava/app/persistence/database/DatabaseRepository.java index 05cfaaa..239a0c6 100644 --- a/src/main/java/nl/jqno/paralleljava/app/persistence/database/DatabaseRepository.java +++ b/src/main/java/nl/jqno/paralleljava/app/persistence/database/DatabaseRepository.java @@ -12,15 +12,15 @@ public class DatabaseRepository implements Repository { - private Jdbi jdbi; + private Engine engine; - public DatabaseRepository(Jdbi jdbi) { - this.jdbi = jdbi; + public DatabaseRepository(Engine engine) { + this.engine = engine; } public Try initialize() { var sql = "CREATE TABLE todo (id VARCHAR(36) PRIMARY KEY, title VARCHAR, completed BOOLEAN, index INTEGER)"; - return jdbi.execute(handle -> handle.execute(sql)) + return engine.execute(handle -> handle.execute(sql)) .recoverWith(f -> { if (f.getMessage() != null && f.getMessage().toLowerCase().contains("\"todo\" already exists")) { return Try.success(null); @@ -31,7 +31,7 @@ public Try initialize() { } public Try create(Todo todo) { - return jdbi.execute(handle -> + return engine.execute(handle -> handle.createUpdate("INSERT INTO todo (id, title, completed, index) VALUES (:id, :title, :completed, :order)") .bind("id", todo.id().toString()) .bind("title", todo.title()) @@ -41,22 +41,22 @@ public Try create(Todo todo) { } public Try> get(UUID id) { - return jdbi.query(handle -> handleGet(handle, id)); + return engine.query(handle -> handleGet(handle, id)); } public Try> getAll() { - return jdbi.query(handle -> + return engine.query(handle -> handle.createQuery("SELECT id, title, completed, index FROM todo") .mapTo(Todo.class) .collect(List.collector())); } public Try update(Todo todo) { - return jdbi.execute(handle -> handleUpdate(handle, todo)); + return engine.execute(handle -> handleUpdate(handle, todo)); } public Try update(UUID id, Function1 f) { - return jdbi.query(handle -> { + return engine.query(handle -> { var option = handleGet(handle, id); if (option.isEmpty()) { throw new IllegalArgumentException("Can't find Todo with id " + id); @@ -69,14 +69,14 @@ public Try update(UUID id, Function1 f) { } public Try delete(UUID id) { - return jdbi.execute(handle -> + return engine.execute(handle -> handle.createUpdate("DELETE FROM todo WHERE id = :id") .bind("id", id.toString()) .execute()); } public Try deleteAll() { - return jdbi.execute(handle -> handle.execute("DELETE FROM todo")); + return engine.execute(handle -> handle.execute("DELETE FROM todo")); } private Option handleGet(Handle handle, UUID id) { diff --git a/src/main/java/nl/jqno/paralleljava/app/persistence/database/Jdbi.java b/src/main/java/nl/jqno/paralleljava/app/persistence/database/Engine.java similarity index 92% rename from src/main/java/nl/jqno/paralleljava/app/persistence/database/Jdbi.java rename to src/main/java/nl/jqno/paralleljava/app/persistence/database/Engine.java index 65be4fe..e10950e 100644 --- a/src/main/java/nl/jqno/paralleljava/app/persistence/database/Jdbi.java +++ b/src/main/java/nl/jqno/paralleljava/app/persistence/database/Engine.java @@ -4,7 +4,7 @@ import org.jdbi.v3.core.HandleCallback; import org.jdbi.v3.core.HandleConsumer; -public interface Jdbi { +public interface Engine { Try execute(HandleConsumer consumer); Try query(HandleCallback callback); } diff --git a/src/main/java/nl/jqno/paralleljava/app/persistence/database/DefaultJdbi.java b/src/main/java/nl/jqno/paralleljava/app/persistence/database/JdbiEngine.java similarity index 81% rename from src/main/java/nl/jqno/paralleljava/app/persistence/database/DefaultJdbi.java rename to src/main/java/nl/jqno/paralleljava/app/persistence/database/JdbiEngine.java index bda8df5..f0039b5 100644 --- a/src/main/java/nl/jqno/paralleljava/app/persistence/database/DefaultJdbi.java +++ b/src/main/java/nl/jqno/paralleljava/app/persistence/database/JdbiEngine.java @@ -5,13 +5,14 @@ import nl.jqno.paralleljava.app.logging.LoggerFactory; import org.jdbi.v3.core.HandleCallback; import org.jdbi.v3.core.HandleConsumer; +import org.jdbi.v3.core.Jdbi; -public class DefaultJdbi implements Jdbi { - private final org.jdbi.v3.core.Jdbi jdbi; +public class JdbiEngine implements Engine { + private final Jdbi jdbi; private final Logger logger; - public DefaultJdbi(String jdbcUrl, TodoMapper todoMapper, LoggerFactory loggerFactory) { - this.jdbi = org.jdbi.v3.core.Jdbi + public JdbiEngine(String jdbcUrl, TodoMapper todoMapper, LoggerFactory loggerFactory) { + this.jdbi = Jdbi .create(jdbcUrl) .registerRowMapper(todoMapper); this.logger = loggerFactory.create(getClass()); diff --git a/src/test/java/nl/jqno/paralleljava/ArchitectureTest.java b/src/test/java/nl/jqno/paralleljava/ArchitectureTest.java index 3cfc20c..a90c17d 100644 --- a/src/test/java/nl/jqno/paralleljava/ArchitectureTest.java +++ b/src/test/java/nl/jqno/paralleljava/ArchitectureTest.java @@ -28,7 +28,7 @@ public void architecture() { assertBoundary("com.google.gson..", GsonSerializer.class.getPackage()); }); - test("only DatabaseRepository accesses Jdbi classes", () -> { + test("only DatabaseRepository accesses Engine classes", () -> { assertBoundary("org.jdbi..", DatabaseRepository.class.getPackage()); }); } diff --git a/src/test/java/nl/jqno/paralleljava/app/persistence/database/DatabaseRepositoryTest.java b/src/test/java/nl/jqno/paralleljava/app/persistence/database/DatabaseRepositoryTest.java index b2cc836..9c81d34 100644 --- a/src/test/java/nl/jqno/paralleljava/app/persistence/database/DatabaseRepositoryTest.java +++ b/src/test/java/nl/jqno/paralleljava/app/persistence/database/DatabaseRepositoryTest.java @@ -26,41 +26,41 @@ public class DatabaseRepositoryTest extends Test { public void initialization() { test("a table is created", () -> { - var jdbi = new DefaultJdbi(IN_MEMORY_DATABASE, todoMapper, NOP_LOGGER); - var repo = new DatabaseRepository(jdbi); + var engine = new JdbiEngine(IN_MEMORY_DATABASE, todoMapper, NOP_LOGGER); + var repo = new DatabaseRepository(engine); var result = repo.initialize(); assertThat(result).isSuccess(); }); test("initializing twice is a no-op the second time", () -> { - var jdbi = new DefaultJdbi(IN_MEMORY_DATABASE, todoMapper, NOP_LOGGER); - var repo = new DatabaseRepository(jdbi); + var engine = new JdbiEngine(IN_MEMORY_DATABASE, todoMapper, NOP_LOGGER); + var repo = new DatabaseRepository(engine); assertThat(repo.initialize()).isSuccess(); assertThat(repo.initialize()).isSuccess(); }); test("a failure with no message while creating is propagated", () -> { - var jdbi = new FailingJdbi(); - var repo = new DatabaseRepository(jdbi); + var engine = new FailingEngine(); + var repo = new DatabaseRepository(engine); assertThat(repo.initialize()).isFailure(); }); test("a failure with a message while creating is propagated", () -> { - var jdbi = new FailingJdbi(new IllegalStateException("Something went wrong")); - var repo = new DatabaseRepository(jdbi); + var engine = new FailingEngine(new IllegalStateException("Something went wrong")); + var repo = new DatabaseRepository(engine); assertThat(repo.initialize()).isFailure(); }); test("a failure to create the table is not propagated if the table already exists", () -> { - var jdbi = new FailingJdbi(new IllegalStateException("Table \"TODO\" already exists")); - var repo = new DatabaseRepository(jdbi); + var engine = new FailingEngine(new IllegalStateException("Table \"TODO\" already exists")); + var repo = new DatabaseRepository(engine); assertThat(repo.initialize()).isSuccess(); }); } public void repository() { - var jdbi = new DefaultJdbi(IN_MEMORY_DATABASE, todoMapper, NOP_LOGGER); - var repo = new DatabaseRepository(jdbi); + var engine = new JdbiEngine(IN_MEMORY_DATABASE, todoMapper, NOP_LOGGER); + var repo = new DatabaseRepository(engine); beforeAll(() -> { assertThat(repo.initialize()).isSuccess(); @@ -147,16 +147,16 @@ public void repository() { public void failures() { test("Execute failures cause failed results", () -> { - var jdbi = new FailingJdbi(); - var repo = new DatabaseRepository(jdbi); + var engine = new FailingEngine(); + var repo = new DatabaseRepository(engine); var result = repo.create(SomeTodo.TODO); assertThat(result).isFailure(); }); test("Query failures cause failed results", () -> { - var jdbi = new FailingJdbi(); - var repo = new DatabaseRepository(jdbi); + var engine = new FailingEngine(); + var repo = new DatabaseRepository(engine); var result = repo.getAll(); assertThat(result).isFailure(); @@ -168,8 +168,8 @@ public Todo map(ResultSet rs, StatementContext ctx) throws SQLException { throw new SQLException("Intentional failure"); } }; - var jdbi = new DefaultJdbi(IN_MEMORY_DATABASE, failingMapper, NOP_LOGGER); - var repo = new DatabaseRepository(jdbi); + var engine = new JdbiEngine(IN_MEMORY_DATABASE, failingMapper, NOP_LOGGER); + var repo = new DatabaseRepository(engine); repo.create(SomeTodo.TODO); var result = repo.getAll(); diff --git a/src/test/java/nl/jqno/paralleljava/app/persistence/database/FailingJdbi.java b/src/test/java/nl/jqno/paralleljava/app/persistence/database/FailingEngine.java similarity index 81% rename from src/test/java/nl/jqno/paralleljava/app/persistence/database/FailingJdbi.java rename to src/test/java/nl/jqno/paralleljava/app/persistence/database/FailingEngine.java index 4e1ae65..00cd972 100644 --- a/src/test/java/nl/jqno/paralleljava/app/persistence/database/FailingJdbi.java +++ b/src/test/java/nl/jqno/paralleljava/app/persistence/database/FailingEngine.java @@ -4,14 +4,14 @@ import org.jdbi.v3.core.HandleCallback; import org.jdbi.v3.core.HandleConsumer; -public class FailingJdbi implements Jdbi { +public class FailingEngine implements Engine { private final Throwable exception; - public FailingJdbi() { + public FailingEngine() { this(new IllegalStateException()); } - public FailingJdbi(Throwable exception) { + public FailingEngine(Throwable exception) { this.exception = exception; } From fe7004f363754e269d0b3d33f1963e2adc0dd4fb Mon Sep 17 00:00:00 2001 From: Jan Ouwens Date: Thu, 25 Apr 2019 08:35:41 +0200 Subject: [PATCH 106/106] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 4ae3000..7a6b9de 100644 --- a/README.md +++ b/README.md @@ -9,5 +9,5 @@ It requires a JDK 11 to build and run. It's a showcase for my talk, Java from a Parallel Universe. It's also a fully functioning [Todo Backend](https://www.todobackend.com/) (you can [run the Todo Backend test suite](https://www.todobackend.com/specs/index.html?https://parallel-java.herokuapp.com/todo )!), using the [Spark web framework](http://sparkjava.com/), the [Jdbi database framework](http://jdbi.org/), and **no framework** for dependency injection because you really really don't need one. It also uses Java 11 `var` declarations, [Vavr](http://www.vavr.io/) and [Polyglot for Maven](https://github.com/takari/polyglot-maven) because I think they're pretty nifty and because they make the code look a little different, as if, I dunno, as if it came from a Parallel Universe or something? Also, the application is fully modularized and has 100% test coverage because why not. -Note, however, that this is still a demo app that is not production-ready. Some corners have definitely been cut. For example, neither the [InMemoryRepository](https://github.com/jqno/paralleljava/blob/master/src/main/java/nl/jqno/paralleljava/app/persistence/inmemory/InMemoryRepository.java) nor the [DatabaseRepository](https://github.com/jqno/paralleljava/blob/master/src/main/java/nl/jqno/paralleljava/app/persistence/database/DatabaseRepository.java) are thread-safe. +Note, however, that this is still a demo app that is not production-ready. Some corners have definitely been cut. For example, the [InMemoryRepository](https://github.com/jqno/paralleljava/blob/master/src/main/java/nl/jqno/paralleljava/app/persistence/inmemory/InMemoryRepository.java) is not thread-safe.