diff --git a/.gitignore b/.gitignore index 549e00a..f2e78f2 100644 --- a/.gitignore +++ b/.gitignore @@ -31,3 +31,5 @@ build/ ### VS Code ### .vscode/ + +.envrc \ No newline at end of file diff --git a/.mvn/extensions.xml b/.mvn/extensions.xml index 896770a..a58d883 100644 --- a/.mvn/extensions.xml +++ b/.mvn/extensions.xml @@ -8,6 +8,6 @@ kr.motd.maven os-maven-plugin - 1.7.0 + 1.7.1 diff --git a/pom.xml b/pom.xml index 8ae9ca5..8af79f4 100644 --- a/pom.xml +++ b/pom.xml @@ -19,7 +19,7 @@ com.github.loki4j loki-logback-appender - 1.4.1 + 1.5.0-m1 com.rometools @@ -69,6 +69,12 @@ 2.0.1 test + + org.apache.maven.shared + maven-invoker + 3.2.0 + test + org.springframework.boot spring-boot-testcontainers @@ -89,6 +95,12 @@ spring-boot-starter-test test + + fr.brouillard.oss + jgitver-maven-plugin + 1.9.0 + test + @@ -159,6 +171,19 @@ + + org.apache.maven.plugins + maven-failsafe-plugin + 3.2.3 + + + + integration-test + verify + + + + diff --git a/src/main/java/com/javagrunt/listener/youtube/Application.java b/src/main/java/com/javagrunt/listener/youtube/Application.java index 2f25aee..8ae3550 100644 --- a/src/main/java/com/javagrunt/listener/youtube/Application.java +++ b/src/main/java/com/javagrunt/listener/youtube/Application.java @@ -16,11 +16,8 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.data.annotation.Id; -import org.springframework.data.redis.connection.RedisConnectionFactory; -import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory; import org.springframework.data.redis.connection.lettuce.observability.MicrometerTracingAdapter; import org.springframework.data.redis.core.RedisHash; -import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.repository.configuration.EnableRedisRepositories; import org.springframework.data.repository.CrudRepository; import org.springframework.web.bind.annotation.*; @@ -88,25 +85,13 @@ record YouTubeEvent(@Id String id, String entryXml){} @Configuration @EnableRedisRepositories class ApplicationConfig { - -// @Bean -// public RedisConnectionFactory connectionFactory() { -// return new LettuceConnectionFactory(); -// } -// -// @Bean -// public RedisTemplate redisTemplate(RedisConnectionFactory redisConnectionFactory) { -// RedisTemplate template = new RedisTemplate(); -// template.setConnectionFactory(redisConnectionFactory); -// return template; -// } -// -// @Bean -// public ClientResources clientResources(ObservationRegistry observationRegistry) { -// return ClientResources.builder() -// .tracing(new MicrometerTracingAdapter(observationRegistry, "youtube-listener")) -// .build(); -// } + + @Bean + public ClientResources clientResources(ObservationRegistry observationRegistry) { + return ClientResources.builder() + .tracing(new MicrometerTracingAdapter(observationRegistry, "youtube-listener")) + .build(); + } } interface EventRepository extends CrudRepository { } \ No newline at end of file diff --git a/src/test/java/com/javagrunt/listener/youtube/AbstractAppTests.java b/src/test/java/com/javagrunt/listener/youtube/AbstractAppTests.java new file mode 100644 index 0000000..430c944 --- /dev/null +++ b/src/test/java/com/javagrunt/listener/youtube/AbstractAppTests.java @@ -0,0 +1,152 @@ +package com.javagrunt.listener.youtube; + +import com.redis.testcontainers.RedisContainer; +import io.restassured.builder.RequestSpecBuilder; +import io.restassured.specification.RequestSpecification; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.boot.testcontainers.service.connection.ServiceConnection; +import org.springframework.restdocs.RestDocumentationContextProvider; +import org.springframework.restdocs.RestDocumentationExtension; +import org.testcontainers.containers.Network; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; + +import static io.restassured.RestAssured.given; +import static org.hamcrest.core.Is.is; +import static org.springframework.restdocs.operation.preprocess.Preprocessors.modifyUris; +import static org.springframework.restdocs.operation.preprocess.Preprocessors.preprocessRequest; +import static org.springframework.restdocs.restassured.RestAssuredRestDocumentation.document; +import static org.springframework.restdocs.restassured.RestAssuredRestDocumentation.documentationConfiguration; + +@ExtendWith(RestDocumentationExtension.class) +@Testcontainers +public abstract class AbstractAppTests { + + static Logger logger = LoggerFactory.getLogger(AbstractAppTests.class); + + abstract int getPort(); + + private RequestSpecification spec; + private static final Network network = Network.newNetwork(); + + static Network getNetwork(){ + return network; + } + + @Container + @ServiceConnection(name = "redis") + static final RedisContainer redis = new RedisContainer( + RedisContainer.DEFAULT_IMAGE_NAME.withTag(RedisContainer.DEFAULT_TAG)) + .withExposedPorts(6379) + .withNetworkAliases("redis") + .withNetwork(network); + + @BeforeEach + void setUp(RestDocumentationContextProvider restDocumentation) { + this.spec = new RequestSpecBuilder() + .addFilter(documentationConfiguration(restDocumentation)) + .build(); + } + @Test + public void getShouldEchoHubChallenge() { + given(this.spec) + .filter(document("hello", + preprocessRequest(modifyUris() + .scheme("https") + .host("youtube-listener.javagrunt.com") + .removePort()))) + .when() + .port(getPort()) + .get("/api/?hub.mode=subscribe&hub.challenge=CHALLENGE_STRING&hub.topic=somthingcool") + .then() + .assertThat().statusCode(is(200)) + .assertThat().body(is("CHALLENGE_STRING")); + } + + @Test + void postShouldReturnSuccess() throws Exception { + String exampleEvent = """ + + + + YouTube video feed + 2015-04-01T19:05:24.552394234+00:00 + + yt:video:VIDEO_ID + VIDEO_ID + CHANNEL_ID + Video title + + + Channel title + http://www.youtube.com/channel/CHANNEL_ID + + 2015-03-06T21:40:57+00:00 + 2015-03-09T19:05:24.552394234+00:00 + + + """; + given(this.spec) + .filter(document("listen", + preprocessRequest(modifyUris() + .scheme("https") + .host("youtube-listener.javagrunt.com") + .removePort()))) + .contentType("application/atom+xml") + .body(exampleEvent) + .when() + .port(getPort()) + .post("/api/") + .then() + .assertThat() + .statusCode(is(200)); + } + + @Test + public void actuatorInfo() { + given(this.spec) + .filter(document("info", + preprocessRequest(modifyUris() + .scheme("https") + .host("youtube-listener.javagrunt.com") + .removePort()))) + .when() + .port(getPort()) + .get("/actuator/info") + .then() + .assertThat().statusCode(is(200)); + } + + @Test + public void actuatorHealth() { + given(this.spec) + .filter(document("health", + preprocessRequest(modifyUris() + .scheme("https") + .host("youtube-listener.javagrunt.com") + .removePort()))) + .when() + .port(getPort()) + .get("/actuator/health") + .then() + .assertThat().statusCode(is(200)); + } + + @Test + void redisShouldBeRunning() { + Assertions.assertTrue(redis.isRunning()); + } + + @AfterAll + static void tearDown() { + redis.stop(); + } + +} diff --git a/src/test/java/com/javagrunt/listener/youtube/JavaVirtualMachineTest.java b/src/test/java/com/javagrunt/listener/youtube/JavaVirtualMachineTest.java new file mode 100644 index 0000000..9eaabbf --- /dev/null +++ b/src/test/java/com/javagrunt/listener/youtube/JavaVirtualMachineTest.java @@ -0,0 +1,16 @@ +package com.javagrunt.listener.youtube; + +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.web.server.LocalServerPort; + +@SpringBootTest(webEnvironment= SpringBootTest.WebEnvironment.RANDOM_PORT) +class JavaVirtualMachineTest extends AbstractAppTests { + + @LocalServerPort + private int port; + + @Override + int getPort() { + return this.port; + } +} diff --git a/src/test/java/com/javagrunt/listener/youtube/NativeImageIT.java b/src/test/java/com/javagrunt/listener/youtube/NativeImageIT.java new file mode 100644 index 0000000..d206273 --- /dev/null +++ b/src/test/java/com/javagrunt/listener/youtube/NativeImageIT.java @@ -0,0 +1,93 @@ +package com.javagrunt.listener.youtube; + +import fr.brouillard.oss.jgitver.GitVersionCalculator; +import org.apache.maven.shared.invoker.DefaultInvocationRequest; +import org.apache.maven.shared.invoker.DefaultInvoker; +import org.apache.maven.shared.invoker.InvocationResult; +import org.apache.maven.shared.invoker.MavenInvocationException; +import org.junit.jupiter.api.BeforeAll; +import org.testcontainers.DockerClientFactory; +import org.testcontainers.containers.GenericContainer; +import org.testcontainers.containers.wait.strategy.Wait; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.utility.LazyFuture; + +import java.io.File; +import java.time.Duration; +import java.time.temporal.ChronoUnit; +import java.util.List; +import java.util.concurrent.Future; + +public class NativeImageIT extends AbstractAppTests { + + private static final Future IMAGE_FUTURE = new LazyFuture<>() { + @Override + protected String resolve() { + // Find project's root dir + File cwd; + cwd = new File("."); + while (!new File(cwd, "mvnw").isFile()) { + cwd = cwd.getParentFile(); + } + + var request = new DefaultInvocationRequest() + .addShellEnvironment("DOCKER_HOST", DockerClientFactory.instance().getTransportConfig().getDockerHost().toString()) + .setPomFile(new File(cwd, "pom.xml")) + .setGoals(List.of("spring-boot:build-image")) + .setMavenExecutable(new File(cwd, "mvnw")) + .setProfiles(List.of("native")); + + InvocationResult invocationResult; + try { + invocationResult = new DefaultInvoker().execute(request); + } catch (MavenInvocationException e) { + throw new RuntimeException(e); + } + + if (invocationResult.getExitCode() != 0) { + throw new RuntimeException(invocationResult.getExecutionException()); + } + + String semanticVersion = null; + File workDir = new File(System.getProperty("user.dir")); + + try (GitVersionCalculator jgitver = GitVersionCalculator.location(workDir)) { + semanticVersion = jgitver.getVersion().split("-")[0]; + } catch (Exception e) { + logger.error("Error getting semantic version", e); + } + + if (System.getProperty("os.arch").contains("aarch")) { + return String.format("dashaun/com.javagrunt.listener.youtube:v%s-aarch_64", semanticVersion); + } else { + return String.format("dashaun/com.javagrunt.listener.youtube:v%s-amd_64", semanticVersion); + } + } + }; + + private static int port; + + @Override + int getPort() { + return port; + } + + + @Container + static final GenericContainer APP = new GenericContainer<>(IMAGE_FUTURE) + .withExposedPorts(8080) + .withNetworkAliases("app") + .withNetwork(getNetwork()) + .withEnv("REDIS_PORT", "6379") + .withEnv("REDIS_HOST", "redis") + .waitingFor(Wait.forHttp("/actuator/health")) + .withStartupTimeout(Duration.of(600, ChronoUnit.SECONDS)) + .dependsOn(redis); + + @BeforeAll + static void setUp() { + APP.start(); + port = APP.getFirstMappedPort(); + } + +} \ No newline at end of file diff --git a/src/test/java/com/javagrunt/listener/youtube/WebLayerTest.java b/src/test/java/com/javagrunt/listener/youtube/WebLayerTest.java deleted file mode 100644 index ec78935..0000000 --- a/src/test/java/com/javagrunt/listener/youtube/WebLayerTest.java +++ /dev/null @@ -1,146 +0,0 @@ -package com.javagrunt.listener.youtube; - -import com.redis.testcontainers.RedisContainer; -import io.restassured.builder.RequestSpecBuilder; -import io.restassured.specification.RequestSpecification; -import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.boot.test.context.TestConfiguration; -import org.springframework.boot.test.web.server.LocalServerPort; -import org.springframework.boot.testcontainers.service.connection.ServiceConnection; -import org.springframework.context.annotation.Bean; -import org.springframework.restdocs.RestDocumentationContextProvider; -import org.springframework.restdocs.RestDocumentationExtension; -import org.testcontainers.containers.GenericContainer; -import org.testcontainers.junit.jupiter.Container; -import org.testcontainers.junit.jupiter.Testcontainers; -import org.testcontainers.utility.DockerImageName; - - -import static io.restassured.RestAssured.given; -import static org.hamcrest.core.Is.is; -import static org.junit.Assert.assertTrue; -import static org.springframework.restdocs.operation.preprocess.Preprocessors.modifyUris; -import static org.springframework.restdocs.operation.preprocess.Preprocessors.preprocessRequest; -import static org.springframework.restdocs.restassured.RestAssuredRestDocumentation.document; -import static org.springframework.restdocs.restassured.RestAssuredRestDocumentation.documentationConfiguration; - - -@SuppressWarnings("resource") -@ExtendWith(RestDocumentationExtension.class) -@SpringBootTest(webEnvironment= SpringBootTest.WebEnvironment.RANDOM_PORT) -@Testcontainers -class WebLayerTest { - - private RequestSpecification spec; - - @LocalServerPort - private int port; - - @Container - @ServiceConnection - private static final RedisContainer redis = new RedisContainer( - RedisContainer.DEFAULT_IMAGE_NAME.withTag(RedisContainer.DEFAULT_TAG)); - - @BeforeEach - void setUp(RestDocumentationContextProvider restDocumentation) { - this.spec = new RequestSpecBuilder() - .addFilter(documentationConfiguration(restDocumentation)) - .build(); - } - - @Test - void redisShouldBeRunning() { - Assertions.assertTrue(redis.isRunning()); - } - - @Test - public void getShouldEchoHubChallenge() { - given(this.spec) - .filter(document("hello", - preprocessRequest(modifyUris() - .scheme("https") - .host("youtube-listener.javagrunt.com") - .removePort()))) - .when() - .port(this.port) - .get("/api/?hub.mode=subscribe&hub.challenge=CHALLENGE_STRING&hub.topic=somthingcool") - .then() - .assertThat().statusCode(is(200)) - .assertThat().body(is("CHALLENGE_STRING")); - } - - @Test - void postShouldReturnSuccess() throws Exception { - String exampleEvent = """ - - - - YouTube video feed - 2015-04-01T19:05:24.552394234+00:00 - - yt:video:VIDEO_ID - VIDEO_ID - CHANNEL_ID - Video title - - - Channel title - http://www.youtube.com/channel/CHANNEL_ID - - 2015-03-06T21:40:57+00:00 - 2015-03-09T19:05:24.552394234+00:00 - - - """; - given(this.spec) - .filter(document("listen", - preprocessRequest(modifyUris() - .scheme("https") - .host("youtube-listener.javagrunt.com") - .removePort()))) - .contentType("application/atom+xml") - .body(exampleEvent) - .when() - .port(this.port) - .post("/api/") - .then() - .assertThat() - .statusCode(is(200)); - } - - @Test - public void actuatorHealth() { - given(this.spec) - .filter(document("health", - preprocessRequest(modifyUris() - .scheme("https") - .host("youtube-listener.javagrunt.com") - .removePort()))) - .when() - .port(this.port) - .get("/actuator/health") - .then() - .assertThat().statusCode(is(200)); - } - - @Test - public void actuatorInfo() { - given(this.spec) - .filter(document("info", - preprocessRequest(modifyUris() - .scheme("https") - .host("youtube-listener.javagrunt.com") - .removePort()))) - .when() - .port(this.port) - .get("/actuator/info") - .then() - .assertThat().statusCode(is(200)); - } - -}