diff --git a/application/application-api/src/main/java/io/github/bmarwell/keyserver/application/api/VerificationService.java b/application/application-api/src/main/java/io/github/bmarwell/keyserver/application/api/VerificationService.java index ca09f19..3502055 100644 --- a/application/application-api/src/main/java/io/github/bmarwell/keyserver/application/api/VerificationService.java +++ b/application/application-api/src/main/java/io/github/bmarwell/keyserver/application/api/VerificationService.java @@ -5,9 +5,6 @@ */ package io.github.bmarwell.keyserver.application.api; -import io.github.bmarwell.keyserver.application.api.ex.TokenExpiredException; -import io.github.bmarwell.keyserver.application.api.ex.TokenInvalidException; - /// Primary (inbound) port for synchronous UID verification. /// /// Implementations handle the full token-to-publication flow synchronously, diff --git a/ear/pom.xml b/ear/pom.xml new file mode 100644 index 0000000..4fbe26a --- /dev/null +++ b/ear/pom.xml @@ -0,0 +1,45 @@ + + + 4.0.0 + + + io.github.bmarwell.keyserver + keyserver + 0.1.0-SNAPSHOT + + + io.github.bmarwell.keyserver + keyserver-ear + 0.1.0-SNAPSHOT + ear + + Java Keyserver :: EAR + + + + io.github.bmarwell.keyserver + keyserver-openpgp-keyserver-protocol + 0.1.0-SNAPSHOT + war + + + io.github.bmarwell.keyserver + keyserver-web-rest + 0.1.0-SNAPSHOT + war + + + + + + + org.apache.maven.plugins + maven-ear-plugin + + true + lib + + + + + diff --git a/ear/src/main/application/META-INF/application.xml b/ear/src/main/application/META-INF/application.xml new file mode 100644 index 0000000..e3dffd4 --- /dev/null +++ b/ear/src/main/application/META-INF/application.xml @@ -0,0 +1,21 @@ + + + Java Keyserver + + + + keyserver-openpgp-keyserver-protocol-0.1.0-SNAPSHOT.war + /pks + + + + + + keyserver-web-rest-0.1.0-SNAPSHOT.war + /api + + + diff --git a/integration-tests/helper/pom.xml b/integration-tests/helper/pom.xml new file mode 100644 index 0000000..4fd6a40 --- /dev/null +++ b/integration-tests/helper/pom.xml @@ -0,0 +1,112 @@ + + + 4.0.0 + + + io.github.bmarwell.keyserver + keyserver-integration-tests + 0.1.0-SNAPSHOT + + + keyserver-integration-tests-helper + Java Keyserver :: Integration Tests :: helper + + + + org.testcontainers + testcontainers + compile + + + org.testcontainers + postgresql + compile + + + + org.junit.jupiter + junit-jupiter-api + compile + + + + + org.postgresql + postgresql + runtime + + + + + ch.qos.logback + logback-classic + runtime + + + + + jakarta.ws.rs + jakarta.ws.rs-api + provided + + + org.apache.cxf + cxf-rt-rs-client + runtime + + + org.bouncycastle + bcprov-jdk18on + compile + + + org.bouncycastle + bcpg-jdk18on + compile + + + + + + + run-its + + + + + org.apache.maven.plugins + maven-dependency-plugin + + + copy-pgsql-driver + + copy + + pre-integration-test + + + + org.postgresql + postgresql + ${postgresql.version} + ${project.build.directory}/libs + postgresql.jar + + + + + + + + + + + + + diff --git a/integration-tests/helper/src/main/java/io/github/bmarwell/keyserver/it/extension/DatabaseSeed.java b/integration-tests/helper/src/main/java/io/github/bmarwell/keyserver/it/extension/DatabaseSeed.java new file mode 100644 index 0000000..292a2ae --- /dev/null +++ b/integration-tests/helper/src/main/java/io/github/bmarwell/keyserver/it/extension/DatabaseSeed.java @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2023-2026 The java-keyserver project team. + * + * SPDX-License-Identifier: EUPL-1.2 OR Apache-2.0 + */ +package io.github.bmarwell.keyserver.it.extension; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Seeds the shared test database before a test class runs. + * + *

When omitted, the shared PostgreSQL instance starts empty and the test class + * is responsible for creating its own data. + */ +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +@Inherited +public @interface DatabaseSeed { + + String[] value() default {}; + + String[] truncateAfter() default {}; +} diff --git a/integration-tests/helper/src/main/java/io/github/bmarwell/keyserver/it/extension/KeyserverAccess.java b/integration-tests/helper/src/main/java/io/github/bmarwell/keyserver/it/extension/KeyserverAccess.java new file mode 100644 index 0000000..b2d05da --- /dev/null +++ b/integration-tests/helper/src/main/java/io/github/bmarwell/keyserver/it/extension/KeyserverAccess.java @@ -0,0 +1,21 @@ +/* + * Copyright (C) 2023-2026 The java-keyserver project team. + * + * SPDX-License-Identifier: EUPL-1.2 OR Apache-2.0 + */ +package io.github.bmarwell.keyserver.it.extension; + +import java.net.URI; + +public interface KeyserverAccess { + + URI pksBaseUri(); + + URI apiBaseUri(); + + String jdbcUrl(); + + String dbUser(); + + String dbPassword(); +} diff --git a/integration-tests/src/test/java/io/github/bmarwell/keyserver/it/extension/KeyserverContainerExtension.java b/integration-tests/helper/src/main/java/io/github/bmarwell/keyserver/it/extension/KeyserverContainerExtension.java similarity index 64% rename from integration-tests/src/test/java/io/github/bmarwell/keyserver/it/extension/KeyserverContainerExtension.java rename to integration-tests/helper/src/main/java/io/github/bmarwell/keyserver/it/extension/KeyserverContainerExtension.java index eada346..7915883 100644 --- a/integration-tests/src/test/java/io/github/bmarwell/keyserver/it/extension/KeyserverContainerExtension.java +++ b/integration-tests/helper/src/main/java/io/github/bmarwell/keyserver/it/extension/KeyserverContainerExtension.java @@ -19,6 +19,7 @@ import java.util.ArrayList; import java.util.List; import java.util.Optional; +import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.extension.AfterAllCallback; import org.junit.jupiter.api.extension.BeforeAllCallback; import org.junit.jupiter.api.extension.ExtensionContext; @@ -35,74 +36,31 @@ import org.testcontainers.containers.wait.strategy.Wait; import org.testcontainers.images.builder.ImageFromDockerfile; -/** - * JUnit 5 extension that starts a shared PostgreSQL container and an Open Liberty container - * for the duration of the entire test session. - * - *

Containers are created once per JVM (stored at the root extension-context level) and - * shut down automatically when all tests finish because {@link ContainerHolder} implements - * {@link Store.CloseableResource}. - * - *

The extension implements {@link ParameterResolver}: any {@code @Test}, - * {@code @BeforeEach}, or {@code @BeforeAll} method that declares a {@link KeyserverAccess} - * (or {@link KeyserverInstance}) parameter will receive the shared running instance. - * - *

Parallel test classes: all classes in the same JVM share one set of containers. - * This avoids repeated Liberty startups (each takes ~2 minutes). Tests that require a clean - * database state should annotate their class with {@link DatabaseSeed}; the extension will - * execute the seed SQL before the class and truncate the declared tables afterwards. - * - *

This extension is normally activated via the {@link KeyserverIntegrationTest} meta- - * annotation rather than being referenced directly. - */ public class KeyserverContainerExtension implements BeforeAllCallback, AfterAllCallback, ParameterResolver { private static final Logger LOG = LoggerFactory.getLogger(KeyserverContainerExtension.class); - private static final Logger WEBSPHERE_LIBERTY_LOGGER = LoggerFactory.getLogger("websphere_liberty"); - private static final Namespace NS = Namespace.create(KeyserverContainerExtension.class); private static final String HOLDER_KEY = "containers"; - /** Liberty image pulled from IBM Container Registry. */ - private static final String LIBERTY_BASE_IMAGE = - "icr.io/appcafe/open-liberty:kernel-slim-java25-openj9-ubi-minimal"; - - /** Liberty's application server ready log message (CWWKF0011I). */ - private static final String LIBERTY_READY_LOG = ".*CWWKF0011I.*"; - - /** Postgres network alias — matches KEYSERVER_DB_SERVER env var fed to Liberty. */ private static final String PG_NETWORK_ALIAS = "postgres"; - private static final String PG_DATABASE = "keyserver"; private static final String PG_USER = "keyserver"; private static final String PG_PASSWORD = "keyserver"; - // ------------------------------------------------------------------------- - // JUnit 5 lifecycle callbacks - // ------------------------------------------------------------------------- - @Override public void beforeAll(ExtensionContext context) { - // Obtain or create the shared holder at the root context so it is shared - // across all test classes in the JVM. ContainerHolder holder = context.getRoot() .getStore(NS) .getOrComputeIfAbsent(HOLDER_KEY, _key -> startContainers(), ContainerHolder.class); - // Apply @DatabaseSeed SQL if present on the test class. findSeedAnnotation(context).ifPresent(seed -> applySeed(holder, seed)); } @Override public void afterAll(ExtensionContext context) { - // Truncate tables declared in @DatabaseSeed so the next class starts clean. findSeedAnnotation(context).ifPresent(seed -> truncateTables(requireHolder(context), seed)); } - // ------------------------------------------------------------------------- - // ParameterResolver — injects KeyserverAccess / KeyserverInstance into tests - // ------------------------------------------------------------------------- - @Override public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext) { Class type = parameterContext.getParameter().getType(); @@ -114,16 +72,10 @@ public Object resolveParameter(ParameterContext parameterContext, ExtensionConte return requireHolder(extensionContext).toKeyserverInstance(); } - // ------------------------------------------------------------------------- - // Container startup - // ------------------------------------------------------------------------- - - @SuppressWarnings("resource") // resources are closed via ContainerHolder.close() + @SuppressWarnings("resource") private static ContainerHolder startContainers() { LOG.info("Starting shared keyserver test containers..."); - // Network is tracked in ContainerHolder so it is closed in close() even if - // Liberty startup throws after PostgreSQL has already started. Network network = Network.newNetwork(); PostgreSQLContainer postgres = null; GenericContainer liberty = null; @@ -134,13 +86,11 @@ private static ContainerHolder startContainers() { .withDatabaseName(PG_DATABASE) .withUsername(PG_USER) .withPassword(PG_PASSWORD); - postgres.start(); liberty = buildLibertyContainer(network); liberty.start(); } catch (RuntimeException ex) { - // Clean up anything that started before the failure. if (liberty != null) { liberty.stop(); } @@ -168,37 +118,23 @@ private static ContainerHolder startContainers() { } private static GenericContainer buildLibertyContainer(Network network) { - String pksWarPath = requireSystemProperty("pks.war.path"); - String restWarPath = requireSystemProperty("rest.war.path"); - String serverXmlPath = requireSystemProperty("liberty.server.xml.path"); - String pgsqlJarPath = requireSystemProperty("pgsql.jar.path"); - - /* - * Build a custom Liberty image using ImageFromDockerfile so we can run - * features.sh before copying the WARs (required for kernel-slim). - * - * Build order: - * 1. Copy server.xml -> /config/server.xml - * 2. Run features.sh -> installs only the features declared in server.xml - * 3. Copy WARs -> /config/dropins/ (auto-deployed by Liberty) - * 4. Copy JDBC driver -> /config/lib/global/postgresql.jar - * 5. Run configure.sh -> final Liberty image prep step - */ + String deployablePath = requireSystemProperty("keyserver.deployable.path"); + String serverXmlPath = requireSystemProperty("keyserver.server.xml.path"); + String pgsqlJarPath = requireSystemProperty("keyserver.pgsql.jar.path"); + + String deployableName = Path.of(deployablePath).getFileName().toString(); String dockerfile = """ FROM %s COPY --chown=1001:0 server.xml /config/server.xml - RUN features.sh - COPY --chown=1001:0 pks.war /config/dropins/pks.war - COPY --chown=1001:0 rest.war /config/dropins/rest.war + COPY --chown=1001:0 deployable /config/dropins/%s COPY --chown=1001:0 postgresql.jar /config/lib/global/postgresql.jar RUN configure.sh - """.formatted(LIBERTY_BASE_IMAGE); + """.formatted(KeyserverTestImage.LIBERTY_BASE_IMAGE, deployableName); ImageFromDockerfile image = new ImageFromDockerfile("keyserver-liberty-it", true) .withFileFromString("Dockerfile", dockerfile) .withFileFromPath("server.xml", Path.of(serverXmlPath)) - .withFileFromPath("pks.war", Path.of(pksWarPath)) - .withFileFromPath("rest.war", Path.of(restWarPath)) + .withFileFromPath("deployable", Path.of(deployablePath)) .withFileFromPath("postgresql.jar", Path.of(pgsqlJarPath)); return new GenericContainer<>(image) @@ -208,14 +144,11 @@ private static GenericContainer buildLibertyContainer(Network network) { .withEnv("KEYSERVER_DB_NAME", PG_DATABASE) .withEnv("KEYSERVER_DB_USER", PG_USER) .withEnv("KEYSERVER_DB_PASSWORD", PG_PASSWORD) - .waitingFor(Wait.forLogMessage(LIBERTY_READY_LOG, 1).withStartupTimeout(Duration.ofMinutes(3))) - .withLogConsumer(new Slf4jLogConsumer(WEBSPHERE_LIBERTY_LOGGER)); + .waitingFor(Wait.forLogMessage(KeyserverTestImage.LIBERTY_READY_LOG, 1) + .withStartupTimeout(Duration.ofMinutes(3))) + .withLogConsumer(new Slf4jLogConsumer(LoggerFactory.getLogger("liberty"))); } - // ------------------------------------------------------------------------- - // Database seeding helpers - // ------------------------------------------------------------------------- - private static void applySeed(ContainerHolder holder, DatabaseSeed seed) { if (seed.value().length == 0) { return; @@ -259,16 +192,18 @@ private static String readClasspathResource(String resource) { } } - // ------------------------------------------------------------------------- - // Internal helpers - // ------------------------------------------------------------------------- - private static Optional findSeedAnnotation(ExtensionContext context) { return context.getTestClass().map(cls -> cls.getAnnotation(DatabaseSeed.class)); } private static ContainerHolder requireHolder(ExtensionContext context) { - return context.getRoot().getStore(NS).get(HOLDER_KEY, ContainerHolder.class); + @Nullable ContainerHolder holder = context.getRoot().getStore(NS).get(HOLDER_KEY, ContainerHolder.class); + if (holder == null) { + throw new IllegalStateException("KeyserverContainerExtension containers are not initialised. " + + "Ensure beforeAll has run before resolving parameters. " + + "Did you use @KeyserverIntegrationTest on the test class?"); + } + return holder; } private static String requireSystemProperty(String key) { @@ -282,18 +217,6 @@ private static String requireSystemProperty(String key) { return value; } - // ------------------------------------------------------------------------- - // Holder record — closed by JUnit after the last test in the session - // ------------------------------------------------------------------------- - - /** - * Holds the running containers and their derived URLs. JUnit automatically calls - * {@link #close()} after all tests in the root context finish. - * - *

The {@link Network} is stored here so it can be closed after both - * containers have stopped. With {@code ryuk.disabled=true} nothing else would - * remove the dangling network from the Docker/Podman daemon. - */ record ContainerHolder( PostgreSQLContainer postgres, GenericContainer liberty, @@ -313,8 +236,6 @@ KeyserverInstance toKeyserverInstance() { @Override public void close() { - // Best-effort shutdown: every resource is attempted even if a previous step throws. - // All exceptions are collected; the first is rethrown with the rest as suppressed. List errors = new ArrayList<>(); try { diff --git a/integration-tests/helper/src/main/java/io/github/bmarwell/keyserver/it/extension/KeyserverInstance.java b/integration-tests/helper/src/main/java/io/github/bmarwell/keyserver/it/extension/KeyserverInstance.java new file mode 100644 index 0000000..afc8029 --- /dev/null +++ b/integration-tests/helper/src/main/java/io/github/bmarwell/keyserver/it/extension/KeyserverInstance.java @@ -0,0 +1,11 @@ +/* + * Copyright (C) 2023-2026 The java-keyserver project team. + * + * SPDX-License-Identifier: EUPL-1.2 OR Apache-2.0 + */ +package io.github.bmarwell.keyserver.it.extension; + +import java.net.URI; + +public record KeyserverInstance(URI pksBaseUri, URI apiBaseUri, String jdbcUrl, String dbUser, String dbPassword) + implements KeyserverAccess {} diff --git a/integration-tests/helper/src/main/java/io/github/bmarwell/keyserver/it/extension/KeyserverIntegrationTest.java b/integration-tests/helper/src/main/java/io/github/bmarwell/keyserver/it/extension/KeyserverIntegrationTest.java new file mode 100644 index 0000000..c51fac9 --- /dev/null +++ b/integration-tests/helper/src/main/java/io/github/bmarwell/keyserver/it/extension/KeyserverIntegrationTest.java @@ -0,0 +1,21 @@ +/* + * Copyright (C) 2023-2026 The java-keyserver project team. + * + * SPDX-License-Identifier: EUPL-1.2 OR Apache-2.0 + */ +package io.github.bmarwell.keyserver.it.extension; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.extension.ExtendWith; + +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +@Inherited +@Tag("integration") +@ExtendWith(KeyserverContainerExtension.class) +public @interface KeyserverIntegrationTest {} diff --git a/integration-tests/helper/src/main/java/io/github/bmarwell/keyserver/it/extension/KeyserverTestImage.java b/integration-tests/helper/src/main/java/io/github/bmarwell/keyserver/it/extension/KeyserverTestImage.java new file mode 100644 index 0000000..cf25490 --- /dev/null +++ b/integration-tests/helper/src/main/java/io/github/bmarwell/keyserver/it/extension/KeyserverTestImage.java @@ -0,0 +1,14 @@ +/* + * Copyright (C) 2023-2026 The java-keyserver project team. + * + * SPDX-License-Identifier: EUPL-1.2 OR Apache-2.0 + */ +package io.github.bmarwell.keyserver.it.extension; + +public final class KeyserverTestImage { + + public static final String LIBERTY_BASE_IMAGE = "icr.io/appcafe/open-liberty:full-java25-openj9-ubi-minimal"; + public static final String LIBERTY_READY_LOG = ".*CWWKF0011I.*"; + + private KeyserverTestImage() {} +} diff --git a/integration-tests/src/test/java/io/github/bmarwell/keyserver/it/extension/package-info.java b/integration-tests/helper/src/main/java/io/github/bmarwell/keyserver/it/extension/package-info.java similarity index 100% rename from integration-tests/src/test/java/io/github/bmarwell/keyserver/it/extension/package-info.java rename to integration-tests/helper/src/main/java/io/github/bmarwell/keyserver/it/extension/package-info.java diff --git a/integration-tests/src/test/java/io/github/bmarwell/keyserver/it/support/TestPgpKeyGenerator.java b/integration-tests/helper/src/main/java/io/github/bmarwell/keyserver/it/support/TestPgpKeyGenerator.java similarity index 100% rename from integration-tests/src/test/java/io/github/bmarwell/keyserver/it/support/TestPgpKeyGenerator.java rename to integration-tests/helper/src/main/java/io/github/bmarwell/keyserver/it/support/TestPgpKeyGenerator.java diff --git a/integration-tests/pom.xml b/integration-tests/pom.xml index 54a9483..2a8266b 100644 --- a/integration-tests/pom.xml +++ b/integration-tests/pom.xml @@ -8,177 +8,20 @@ 0.1.0-SNAPSHOT - io.github.bmarwell.keyserver keyserver-integration-tests - 0.1.0-SNAPSHOT - Java Keyserver :: Integration Tests - - - - - - org.testcontainers - testcontainers - test - - - org.testcontainers - postgresql - test - - - org.testcontainers - junit-jupiter - test - - - org.junit.jupiter - junit-jupiter - test - - - org.assertj - assertj-core - test - - - - org.postgresql - postgresql - test - - - - - ch.qos.logback - logback-classic - test - - - - - jakarta.ws.rs - jakarta.ws.rs-api - test - - - - org.apache.cxf - cxf-rt-rs-client - test - - - org.bouncycastle - bcprov-jdk18on - test - - - org.bouncycastle - bcpg-jdk18on - test - - + pom + Java Keyserver :: Integration Tests - - - - - org.apache.maven.plugins - maven-surefire-plugin - - true - - - - + - run-its - - - - - org.apache.maven.plugins - maven-dependency-plugin - - - copy-pgsql-driver - - copy - - pre-integration-test - - - - org.postgresql - postgresql - ${postgresql.version} - ${project.build.directory}/libs - postgresql.jar - - - - - - - - - org.apache.maven.plugins - maven-failsafe-plugin - - - - ${project.parent.basedir}/web/openpgp-keyserver-protocol/target/keyserver-openpgp-keyserver-protocol-${project.version}.war - ${project.parent.basedir}/web/rest/target/keyserver-web-rest-${project.version}.war - ${project.parent.basedir}/web/openpgp-keyserver-protocol/src/main/liberty/config/server.xml - ${project.build.directory}/libs/postgresql.jar - - - - - - integration-test - verify - - - - - - + + helper + suite + diff --git a/integration-tests/src/test/java/io/github/bmarwell/keyserver/it/extension/DatabaseSeed.java b/integration-tests/src/test/java/io/github/bmarwell/keyserver/it/extension/DatabaseSeed.java deleted file mode 100644 index a7bd325..0000000 --- a/integration-tests/src/test/java/io/github/bmarwell/keyserver/it/extension/DatabaseSeed.java +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Copyright (C) 2023-2026 The java-keyserver project team. - * - * SPDX-License-Identifier: EUPL-1.2 OR Apache-2.0 - */ -package io.github.bmarwell.keyserver.it.extension; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Inherited; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -/** - * Marks a test class that needs a specific database state before tests run. - * - *

The {@link KeyserverContainerExtension} reads this annotation in {@code beforeAll}. - * SQL files listed in {@link #value()} are executed in order against the shared - * PostgreSQL container before any test in the annotated class. - * - *

After all tests in the class finish, the tables listed in {@link #truncateAfter()} - * are truncated in a single statement (CASCADE) so the next test class starts clean. - * - *

Not required: test classes that do not declare {@code @DatabaseSeed} are - * perfectly valid. They use the shared PostgreSQL instance as-is. At the start of - * a fresh test session the database is empty (schema only, created by Flyway migrations - * on Liberty start-up). Tests without seeding must therefore be self-contained: either - * they write their own data and assert with flexible matchers (e.g. - * {@code isGreaterThanOrEqualTo(1)}), or they assert on data written earlier in the - * same session. If strict isolation is needed, add {@code @DatabaseSeed} with an - * empty {@link #value()} array to get the automatic truncation without any seed SQL. - * - *

Example with seed SQL: - *

{@code
- * @DatabaseSeed(value = {"sql/some-keys.sql"}, truncateAfter = {"keys", "uids"})
- * class LookupIT {
- *     // tests here can assume that some-keys.sql data is present
- * }
- * }
- * - *

Example with truncation only (no seed SQL): - *

{@code
- * @DatabaseSeed
- * class IsolatedWriteIT {
- *     // starts each class with the default tables truncated
- * }
- * }
- */ -@Target(ElementType.TYPE) -@Retention(RetentionPolicy.RUNTIME) -@Inherited -public @interface DatabaseSeed { - - /** - * Classpath resources containing SQL INSERT/COPY statements to execute before - * the test class runs. Paths are resolved relative to the classpath root. - */ - String[] value() default {}; - - /** - * Table names to TRUNCATE CASCADE after all tests in the annotated class finish. - * Defaults to the full set of application tables. - */ - String[] truncateAfter() default {"uids", "keys", "verification_queue", "business_transactions"}; -} diff --git a/integration-tests/src/test/java/io/github/bmarwell/keyserver/it/extension/KeyserverAccess.java b/integration-tests/src/test/java/io/github/bmarwell/keyserver/it/extension/KeyserverAccess.java deleted file mode 100644 index 35b69ac..0000000 --- a/integration-tests/src/test/java/io/github/bmarwell/keyserver/it/extension/KeyserverAccess.java +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright (C) 2023-2026 The java-keyserver project team. - * - * SPDX-License-Identifier: EUPL-1.2 OR Apache-2.0 - */ -package io.github.bmarwell.keyserver.it.extension; - -import java.net.URI; - -/** - * Read-only view of a running keyserver test instance. - * - *

Implementations are injected by {@link KeyserverContainerExtension} as a - * {@code ParameterResolver} argument into any {@code @Test}, {@code @BeforeEach}, - * or {@code @BeforeAll} method that declares a parameter of this type. - * - *

Example: - *

{@code
- * @KeyserverIntegrationTest
- * class MyIT {
- *
- *     @Test
- *     void lookup(KeyserverAccess keyserver) throws Exception {
- *         URI endpoint = keyserver.pksBaseUri().resolve("/pks/lookup?op=get&search=...");
- *         // ...
- *     }
- * }
- * }
- * - *

The concrete type injected is {@link KeyserverInstance}. Test code should - * declare the parameter as {@code KeyserverAccess} so tests remain decoupled from - * the container implementation. - * - *

Parallel test classes: all classes that use {@code @KeyserverIntegrationTest} - * in the same JVM share the same running containers. Tests that need an isolated - * database state should annotate their class with {@link DatabaseSeed}. - */ -public interface KeyserverAccess { - - /** Base URI of the HKP (PKS) endpoint, e.g. {@code http://localhost:9080/pks}. */ - URI pksBaseUri(); - - /** Base URI of the REST (JSON) endpoint, e.g. {@code http://localhost:9080/api}. */ - URI apiBaseUri(); - - /** JDBC URL for direct database access during seed/verify steps. */ - String jdbcUrl(); - - /** Database user. */ - String dbUser(); - - /** Database password. */ - String dbPassword(); -} diff --git a/integration-tests/src/test/java/io/github/bmarwell/keyserver/it/extension/KeyserverInstance.java b/integration-tests/src/test/java/io/github/bmarwell/keyserver/it/extension/KeyserverInstance.java deleted file mode 100644 index 05765c7..0000000 --- a/integration-tests/src/test/java/io/github/bmarwell/keyserver/it/extension/KeyserverInstance.java +++ /dev/null @@ -1,21 +0,0 @@ -/* - * Copyright (C) 2023-2026 The java-keyserver project team. - * - * SPDX-License-Identifier: EUPL-1.2 OR Apache-2.0 - */ -package io.github.bmarwell.keyserver.it.extension; - -import java.net.URI; - -/** - * Immutable snapshot of a running keyserver instance, injected by - * {@link KeyserverContainerExtension} into test methods. - * - *

All coordinates (URLs, JDBC URL, credentials) are resolved from the live containers - * at injection time, so ports are always correct regardless of host mapping. - * - *

Test code should declare parameters as {@link KeyserverAccess} rather than - * {@code KeyserverInstance} for better decoupling from the container implementation. - */ -public record KeyserverInstance(URI pksBaseUri, URI apiBaseUri, String jdbcUrl, String dbUser, String dbPassword) - implements KeyserverAccess {} diff --git a/integration-tests/src/test/java/io/github/bmarwell/keyserver/it/extension/KeyserverIntegrationTest.java b/integration-tests/src/test/java/io/github/bmarwell/keyserver/it/extension/KeyserverIntegrationTest.java deleted file mode 100644 index bd4ba95..0000000 --- a/integration-tests/src/test/java/io/github/bmarwell/keyserver/it/extension/KeyserverIntegrationTest.java +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright (C) 2023-2026 The java-keyserver project team. - * - * SPDX-License-Identifier: EUPL-1.2 OR Apache-2.0 - */ -package io.github.bmarwell.keyserver.it.extension; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Inherited; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; -import org.junit.jupiter.api.Tag; -import org.junit.jupiter.api.extension.ExtendWith; - -/** - * Composed annotation for keyserver integration tests. - * - *

Applying {@code @KeyserverIntegrationTest} to a test class is equivalent to: - *

{@code
- * @Tag("integration")
- * @ExtendWith(KeyserverContainerExtension.class)
- * }
- * - *

The {@link KeyserverContainerExtension} starts a shared Open Liberty container and - * a PostgreSQL container (once per JVM) and injects a {@link KeyserverAccess} argument - * into any test method that declares one. - * - *

Example: - *

{@code
- * @KeyserverIntegrationTest
- * class AddKeyIT {
- *
- *     @Test
- *     void add_key_returns_202(KeyserverAccess keyserver) throws Exception {
- *         URI endpoint = keyserver.pksBaseUri().resolve("/pks/add");
- *         // ...
- *     }
- * }
- * }
- * - *

For database seeding before a test class, additionally annotate with - * {@link DatabaseSeed}. - * - *

The {@code "integration"} tag lets CI selectively include or exclude these tests: - *

{@code
- * ./mvnw verify -pl integration-tests -am -P run-its -Dgroups=integration
- * }
- */ -@Target(ElementType.TYPE) -@Retention(RetentionPolicy.RUNTIME) -@Inherited -@Tag("integration") -@ExtendWith(KeyserverContainerExtension.class) -public @interface KeyserverIntegrationTest {} diff --git a/integration-tests/suite/pom.xml b/integration-tests/suite/pom.xml new file mode 100644 index 0000000..eb9c1ce --- /dev/null +++ b/integration-tests/suite/pom.xml @@ -0,0 +1,98 @@ + + + 4.0.0 + + + io.github.bmarwell.keyserver + keyserver-integration-tests + 0.1.0-SNAPSHOT + + + keyserver-integration-tests-suite + Java Keyserver :: Integration Tests :: Suite + + + + io.github.bmarwell.keyserver + keyserver-ear + 0.1.0-SNAPSHOT + ear + test + + + + io.github.bmarwell.keyserver + keyserver-integration-tests-helper + 0.1.0-SNAPSHOT + test + + + + + + + + org.apache.maven.plugins + maven-surefire-plugin + + true + + + + + org.apache.maven.plugins + maven-dependency-plugin + + + copy-ear + + copy-dependencies + + pre-integration-test + + ear + + + + + + + + + + + run-its + + + + org.apache.maven.plugins + maven-failsafe-plugin + + + + ${project.build.directory}/dependency/keyserver-ear-${project.version}.ear + ${maven.multiModuleProjectDirectory}/web/openpgp-keyserver-protocol/src/main/liberty/config/server.xml + ${project.build.directory}/libs/postgresql.jar + + + + + + integration-test + verify + + + + + + + + + + diff --git a/integration-tests/src/test/java/io/github/bmarwell/keyserver/it/AddKeyIT.java b/integration-tests/suite/src/test/java/io/github/bmarwell/keyserver/it/AddKeyIT.java similarity index 100% rename from integration-tests/src/test/java/io/github/bmarwell/keyserver/it/AddKeyIT.java rename to integration-tests/suite/src/test/java/io/github/bmarwell/keyserver/it/AddKeyIT.java diff --git a/integration-tests/src/test/java/io/github/bmarwell/keyserver/it/package-info.java b/integration-tests/suite/src/test/java/io/github/bmarwell/keyserver/it/package-info.java similarity index 100% rename from integration-tests/src/test/java/io/github/bmarwell/keyserver/it/package-info.java rename to integration-tests/suite/src/test/java/io/github/bmarwell/keyserver/it/package-info.java diff --git a/integration-tests/src/test/java/io/github/bmarwell/keyserver/it/support/package-info.java b/integration-tests/suite/src/test/java/io/github/bmarwell/keyserver/it/support/package-info.java similarity index 100% rename from integration-tests/src/test/java/io/github/bmarwell/keyserver/it/support/package-info.java rename to integration-tests/suite/src/test/java/io/github/bmarwell/keyserver/it/support/package-info.java diff --git a/integration-tests/src/test/resources/logback-test.xml b/integration-tests/suite/src/test/resources/logback-test.xml similarity index 100% rename from integration-tests/src/test/resources/logback-test.xml rename to integration-tests/suite/src/test/resources/logback-test.xml diff --git a/integration-tests/src/test/resources/testcontainers.properties b/integration-tests/suite/src/test/resources/testcontainers.properties similarity index 100% rename from integration-tests/src/test/resources/testcontainers.properties rename to integration-tests/suite/src/test/resources/testcontainers.properties diff --git a/pom.xml b/pom.xml index 919b48c..8345bb1 100644 --- a/pom.xml +++ b/pom.xml @@ -34,6 +34,7 @@ repository mail web + ear integration-tests @@ -337,6 +338,12 @@ + + org.apache.maven.plugins + maven-ear-plugin + 3.4.0 + + org.jacoco diff --git a/web/openpgp-keyserver-protocol/pom.xml b/web/openpgp-keyserver-protocol/pom.xml index a5bc5d8..93fcb70 100644 --- a/web/openpgp-keyserver-protocol/pom.xml +++ b/web/openpgp-keyserver-protocol/pom.xml @@ -8,9 +8,7 @@ 0.1.0-SNAPSHOT - io.github.bmarwell.keyserver keyserver-openpgp-keyserver-protocol - 0.1.0-SNAPSHOT war Java Keyserver :: web :: OpenPGP Keyserver Protocol @@ -118,4 +116,46 @@ + + + run-its + + + + + + io.github.bmarwell.keyserver + keyserver-integration-tests-helper + 0.1.0-SNAPSHOT + test + + + + + + + + org.codehaus.mojo + build-helper-maven-plugin + 3.6.1 + + + add-test-source + + add-test-source + + generate-test-sources + + + src/it/java + + + + + + + + + + diff --git a/web/openpgp-keyserver-protocol/src/main/java/io/github/bmarwell/keyserver/web/pks/AbstractFreemarkerRenderer.java b/web/openpgp-keyserver-protocol/src/main/java/io/github/bmarwell/keyserver/web/pks/AbstractFreemarkerRenderer.java index 85f4780..a8c6ccb 100644 --- a/web/openpgp-keyserver-protocol/src/main/java/io/github/bmarwell/keyserver/web/pks/AbstractFreemarkerRenderer.java +++ b/web/openpgp-keyserver-protocol/src/main/java/io/github/bmarwell/keyserver/web/pks/AbstractFreemarkerRenderer.java @@ -5,7 +5,6 @@ */ package io.github.bmarwell.keyserver.web.pks; -import freemarker.template.Configuration; import freemarker.template.Template; import jakarta.inject.Inject; import java.io.StringWriter; diff --git a/web/openpgp-keyserver-protocol/src/main/java/io/github/bmarwell/keyserver/web/pks/FreemarkerConfigurationProducer.java b/web/openpgp-keyserver-protocol/src/main/java/io/github/bmarwell/keyserver/web/pks/FreemarkerConfigurationProducer.java index 7e084ca..8ba1944 100644 --- a/web/openpgp-keyserver-protocol/src/main/java/io/github/bmarwell/keyserver/web/pks/FreemarkerConfigurationProducer.java +++ b/web/openpgp-keyserver-protocol/src/main/java/io/github/bmarwell/keyserver/web/pks/FreemarkerConfigurationProducer.java @@ -6,8 +6,6 @@ package io.github.bmarwell.keyserver.web.pks; import freemarker.cache.ClassTemplateLoader; -import freemarker.core.HTMLOutputFormat; -import freemarker.core.PlainTextOutputFormat; import freemarker.template.Configuration; import freemarker.template.TemplateExceptionHandler; import jakarta.enterprise.context.ApplicationScoped; diff --git a/web/openpgp-keyserver-protocol/src/test/java/io/github/bmarwell/keyserver/web/pks/FreemarkerConfigurationProducerTest.java b/web/openpgp-keyserver-protocol/src/test/java/io/github/bmarwell/keyserver/web/pks/FreemarkerConfigurationProducerTest.java index 4a4154c..aa2f114 100644 --- a/web/openpgp-keyserver-protocol/src/test/java/io/github/bmarwell/keyserver/web/pks/FreemarkerConfigurationProducerTest.java +++ b/web/openpgp-keyserver-protocol/src/test/java/io/github/bmarwell/keyserver/web/pks/FreemarkerConfigurationProducerTest.java @@ -7,7 +7,6 @@ import static org.assertj.core.api.Assertions.assertThat; -import freemarker.template.Configuration; import freemarker.template.Template; import java.io.StringWriter; import java.util.Map;