From f2250bd30251a0d652f5e370581e012c8989a5a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Manteaux?= Date: Fri, 20 Oct 2023 13:27:18 +0200 Subject: [PATCH] Update sample project --- .../configuration/ConfigurationService.java | 15 ++--- .../internal/InternalApiAuthenticator.java | 25 +++++++ .../webservices/internal/MonitoringWs.java | 14 ++-- .../webservices/internal/SwaggerWs.java | 24 ++----- src/test/java/com/coreoz/SampleTest.java | 12 ++++ .../java/com/coreoz/guice/TestModule.java | 24 ++++--- .../com/coreoz/guice/TimeProviderForTest.java | 66 +++++++++++++++---- .../integration/SampleIntegrationTest.java | 15 +++++ 8 files changed, 140 insertions(+), 55 deletions(-) create mode 100644 src/main/java/com/coreoz/webservices/internal/InternalApiAuthenticator.java diff --git a/src/main/java/com/coreoz/services/configuration/ConfigurationService.java b/src/main/java/com/coreoz/services/configuration/ConfigurationService.java index d018ed8..0b0ff6c 100644 --- a/src/main/java/com/coreoz/services/configuration/ConfigurationService.java +++ b/src/main/java/com/coreoz/services/configuration/ConfigurationService.java @@ -7,7 +7,6 @@ @Singleton public class ConfigurationService { - private final Config config; @Inject @@ -19,13 +18,11 @@ public String hello() { return config.getString("hello"); } - public String swaggerAccessUsername() { - return config.getString("swagger.access.username"); - } - - public String swaggerAccessPassword() { - return config.getString("swagger.access.password"); - } + public String internalApiAuthUsername() { + return config.getString("internal-api.auth-username"); + } + public String internalApiAuthPassword() { + return config.getString("internal-api.auth-password"); + } } - diff --git a/src/main/java/com/coreoz/webservices/internal/InternalApiAuthenticator.java b/src/main/java/com/coreoz/webservices/internal/InternalApiAuthenticator.java new file mode 100644 index 0000000..edb6460 --- /dev/null +++ b/src/main/java/com/coreoz/webservices/internal/InternalApiAuthenticator.java @@ -0,0 +1,25 @@ +package com.coreoz.webservices.internal; + +import javax.inject.Inject; +import javax.inject.Singleton; + +import com.coreoz.plume.jersey.security.basic.BasicAuthenticator; +import com.coreoz.services.configuration.ConfigurationService; + +@Singleton +public class InternalApiAuthenticator { + private final BasicAuthenticator basicAuthenticator; + + @Inject + public InternalApiAuthenticator(ConfigurationService configurationService) { + this.basicAuthenticator = BasicAuthenticator.fromSingleCredentials( + configurationService.internalApiAuthUsername(), + configurationService.internalApiAuthPassword(), + "API plume-showcase" + ); + } + + public BasicAuthenticator get() { + return this.basicAuthenticator; + } +} diff --git a/src/main/java/com/coreoz/webservices/internal/MonitoringWs.java b/src/main/java/com/coreoz/webservices/internal/MonitoringWs.java index c2dbb18..fb0261d 100644 --- a/src/main/java/com/coreoz/webservices/internal/MonitoringWs.java +++ b/src/main/java/com/coreoz/webservices/internal/MonitoringWs.java @@ -13,11 +13,11 @@ import javax.ws.rs.core.MediaType; import com.codahale.metrics.Metric; +import com.coreoz.plume.db.transaction.TransactionManager; import com.coreoz.plume.jersey.monitoring.utils.health.HealthCheckBuilder; import com.coreoz.plume.jersey.monitoring.utils.health.beans.HealthStatus; import com.coreoz.plume.jersey.monitoring.utils.info.ApplicationInfoProvider; -import com.coreoz.plume.db.transaction.TransactionManager; import com.coreoz.plume.jersey.monitoring.utils.info.beans.ApplicationInfo; import com.coreoz.plume.jersey.monitoring.utils.metrics.MetricsCheckBuilder; import com.coreoz.plume.jersey.security.basic.BasicAuthenticator; @@ -36,7 +36,11 @@ public class MonitoringWs { private final BasicAuthenticator basicAuthenticator; @Inject - public MonitoringWs(ApplicationInfoProvider applicationInfoProvider, TransactionManager transactionManager) { + public MonitoringWs( + ApplicationInfoProvider applicationInfoProvider, + TransactionManager transactionManager, + InternalApiAuthenticator apiAuthenticator + ) { this.applicationInfo = applicationInfoProvider.get(); // Registering health checks this.healthStatusProvider = new HealthCheckBuilder() @@ -49,11 +53,7 @@ public MonitoringWs(ApplicationInfoProvider applicationInfoProvider, Transaction .build(); // Require authentication to access monitoring endpoints - this.basicAuthenticator = BasicAuthenticator.fromSingleCredentials( - "plume", - "rocks", - "Plume showcase" - ); + this.basicAuthenticator = apiAuthenticator.get(); } @GET diff --git a/src/main/java/com/coreoz/webservices/internal/SwaggerWs.java b/src/main/java/com/coreoz/webservices/internal/SwaggerWs.java index a64196c..2d6ee5c 100644 --- a/src/main/java/com/coreoz/webservices/internal/SwaggerWs.java +++ b/src/main/java/com/coreoz/webservices/internal/SwaggerWs.java @@ -14,28 +14,25 @@ import com.coreoz.plume.jersey.security.basic.BasicAuthenticator; import com.coreoz.plume.jersey.security.permission.PublicApi; -import com.coreoz.services.configuration.ConfigurationService; import com.fasterxml.jackson.core.JsonProcessingException; import io.swagger.v3.core.util.Yaml; import io.swagger.v3.jaxrs2.integration.JaxrsOpenApiContextBuilder; -import io.swagger.v3.oas.integration.OpenApiConfigurationException; import io.swagger.v3.oas.integration.SwaggerConfiguration; import io.swagger.v3.oas.integration.api.OpenApiContext; import io.swagger.v3.oas.models.OpenAPI; import io.swagger.v3.oas.models.servers.Server; -import lombok.SneakyThrows; @Path("/swagger") @Singleton @PublicApi public class SwaggerWs { - private final String swaggerDefinition; private final BasicAuthenticator basicAuthenticator; @Inject - public SwaggerWs(ConfigurationService configurationService) throws OpenApiConfigurationException { + public SwaggerWs(InternalApiAuthenticator apiAuthenticator) throws Exception { + // Basic configuration SwaggerConfiguration openApiConfig = new SwaggerConfiguration() .resourcePackages(Set.of("com.coreoz.webservices.api")) .sortOutput(true) @@ -45,24 +42,19 @@ public SwaggerWs(ConfigurationService configurationService) throws OpenApiConfig .description("API plume-showcase") ))); + // Generation of the OpenApi object OpenApiContext context = new JaxrsOpenApiContextBuilder<>() .openApiConfiguration(openApiConfig) .buildContext(true); + // the OpenAPI object can be changed to add security definition + // or to alter the generated mapping OpenAPI openApi = context.read(); // serialization of the Swagger definition - try { - this.swaggerDefinition = Yaml.mapper().writeValueAsString(openApi); - } catch (JsonProcessingException e) { - throw new RuntimeException(e); - } + this.swaggerDefinition = Yaml.mapper().writeValueAsString(openApi); // require authentication to access the API documentation - this.basicAuthenticator = BasicAuthenticator.fromSingleCredentials( - configurationService.swaggerAccessUsername(), - configurationService.swaggerAccessPassword(), - "API plume-showcase" - ); + this.basicAuthenticator = apiAuthenticator.get(); } @Produces(MediaType.APPLICATION_JSON) @@ -72,6 +64,4 @@ public String get(@Context ContainerRequestContext requestContext) throws JsonPr return swaggerDefinition; } - } - diff --git a/src/test/java/com/coreoz/SampleTest.java b/src/test/java/com/coreoz/SampleTest.java index 23469f5..89acabb 100644 --- a/src/test/java/com/coreoz/SampleTest.java +++ b/src/test/java/com/coreoz/SampleTest.java @@ -3,6 +3,18 @@ import org.assertj.core.api.Assertions; import org.junit.Test; +/** + * A unit test sample. + * + * Unit tests are a great tool for: + * - Testing exhaustively a function by changing all the parameters to verify that is fully respects its specification + * - Testing a function that does not have a lot of dependencies + * + * To test something that has interactions with the database, or not only one function but a chain of services, + * integration tests are preferred. See {@link SampleIntegrationTest} for an example. + * + * Once there are other unit tests in the project, this sample should be deleted. + */ public class SampleTest { @Test public void methodToTest__test_scenario_description() { diff --git a/src/test/java/com/coreoz/guice/TestModule.java b/src/test/java/com/coreoz/guice/TestModule.java index c676812..a7704b2 100644 --- a/src/test/java/com/coreoz/guice/TestModule.java +++ b/src/test/java/com/coreoz/guice/TestModule.java @@ -5,15 +5,21 @@ import com.google.inject.AbstractModule; import com.google.inject.util.Modules; +/** + * The Guice module that will be used for integration tests. + * + * In this module, it is possible to override the behaviors of some services as it is shown with the {@link TimeProvider} + * module. + */ public class TestModule extends AbstractModule { - @Override - protected void configure() { - install(Modules.override(new ApplicationModule()).with(new AbstractModule() { - @Override - protected void configure() { - bind(TimeProvider.class).to(TimeProviderForTest.class); - } - })); + @Override + protected void configure() { + install(Modules.override(new ApplicationModule()).with(new AbstractModule() { + @Override + protected void configure() { + bind(TimeProvider.class).to(TimeProviderForTest.class); + } + })); install(new GuiceDbTestModule()); - } + } } diff --git a/src/test/java/com/coreoz/guice/TimeProviderForTest.java b/src/test/java/com/coreoz/guice/TimeProviderForTest.java index 7b22cc4..776691d 100644 --- a/src/test/java/com/coreoz/guice/TimeProviderForTest.java +++ b/src/test/java/com/coreoz/guice/TimeProviderForTest.java @@ -4,28 +4,68 @@ import javax.inject.Singleton; import java.time.Clock; +import java.time.Instant; +import java.time.ZoneId; +/** + * Override the default {@link TimeProvider} for testing purposes. + * This adds the possibility to change how time flows during a test. + * This works only for code that relies on the {@link TimeProvider} + */ @Singleton public class TimeProviderForTest implements TimeProvider { - private Clock clock; + private Clock clock; - public TimeProviderForTest() { - this.clock = Clock.systemDefaultZone(); - } + public TimeProviderForTest() { + this.clock = Clock.systemDefaultZone(); + } - @Override - public Clock clock() { - return clock; - } + /** + * Returns the current clock used + */ + @Override + public Clock clock() { + return clock; + } - public void changeClock(Clock clock) { - this.clock = clock; - } + /** + * Changes the current clock used. This is generally a temporary measure that should be reverted. + * See {@link #executeWithClock(Clock, Runnable)} for usage + * @param newClock The new clock to use + */ + public void changeClock(Clock newClock) { + this.clock = newClock; + } - public void withClock(Clock clock, Runnable toExecuteWithClock) { + /** + * Execute a function with a custom clock. If unsure, use {@link #executeWithInstant(Instant, Runnable)} or {@link #executeWithConstantTime(Runnable)} instead + * @param newClock The custom clock + * @param toExecuteWithClock The function to execute + */ + public void executeWithClock(Clock newClock, Runnable toExecuteWithClock) { Clock oldClock = this.clock; - changeClock(clock); + changeClock(newClock); toExecuteWithClock.run(); changeClock(oldClock); } + + /** + * Execute a function for which for time does not change + * @param fixedInstantForExecution The instant that will be used to execute the function + * @param toExecuteWithInstant The function to execute + */ + public void executeWithInstant(Instant fixedInstantForExecution, Runnable toExecuteWithInstant) { + executeWithClock( + Clock.fixed(fixedInstantForExecution, ZoneId.systemDefault()), + toExecuteWithInstant + ); + } + + /** + * Execute a function for which for time does not change + * @param toExecuteWithConstantTime The function to execute + */ + public void executeWithConstantTime(Runnable toExecuteWithConstantTime) { + executeWithInstant(Instant.now(), toExecuteWithConstantTime); + } } diff --git a/src/test/java/com/coreoz/integration/SampleIntegrationTest.java b/src/test/java/com/coreoz/integration/SampleIntegrationTest.java index 5580232..47878ee 100644 --- a/src/test/java/com/coreoz/integration/SampleIntegrationTest.java +++ b/src/test/java/com/coreoz/integration/SampleIntegrationTest.java @@ -10,6 +10,21 @@ import javax.inject.Inject; +/** + * An integration test sample. + * + * This tests differs from an unit tests, cf {@link SampleTest}, because: + * - It will initialize and rely on the dependency injection, see {@link TestModule} for tests specific overrides + * - Other services can be referenced for this tests + * - These other services can be altered for tests, see {@link TimeProviderForTest} for an example + * - If a database is used in the project, an H2 in memory database will be available to run queries and verify that data is correctly being inserted/updated in the database + * - The H2 in memory database will be created by playing Flyway initialization scripts: these scripts must be correctly setup + * + * Integration tests are a great tool to test the whole chain of services with one automated test. + * Although, to test intensively a function, a unit test is preferred, see {@link TimeProviderForTest} for an example. + * + * Once there are other integration tests in the project, this sample should be deleted. + */ @RunWith(GuiceTestRunner.class) @GuiceModules(TestModule.class) public class SampleIntegrationTest {