Skip to content

Commit 4c26e53

Browse files
author
Matias Schilling
committed
Add integration test
1 parent 4477c2a commit 4c26e53

File tree

66 files changed

+2689
-2290
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

66 files changed

+2689
-2290
lines changed

.github/workflows/build.yml

+18-2
Original file line numberDiff line numberDiff line change
@@ -13,14 +13,30 @@ env:
1313
JAVA_VERSION: 21
1414

1515
jobs:
16-
test:
16+
unit-test:
1717
runs-on: [ ubuntu-latest ]
1818
steps:
1919
- uses: actions/checkout@v4
20+
with:
21+
ref: ${{ inputs.version }}
2022
- uses: actions/setup-java@v4
2123
with:
2224
cache: gradle
2325
distribution: ${{ env.JAVA_DISTRIBUTION }}
2426
java-version: ${{ env.JAVA_VERSION }}
27+
- run: ./gradlew clean test spotlessCheck --exclude-task :chucknorris-integration-test:test
28+
29+
integration-test:
30+
runs-on: [ ubuntu-latest ]
31+
steps:
32+
- uses: actions/checkout@v4
33+
with:
2534
ref: ${{ inputs.version }}
26-
- run: ./gradlew test spotlessCheck
35+
- uses: actions/setup-java@v4
36+
with:
37+
cache: gradle
38+
distribution: ${{ env.JAVA_DISTRIBUTION }}
39+
java-version: ${{ env.JAVA_VERSION }}
40+
- name: Run Integration Test
41+
shell: bash
42+
run: ./chucknorris-integration-test/script/run-integration-test.sh

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ out/
1515
hs_err_pid*
1616

1717
# Gradle
18+
bin/
1819
.gradle
1920
build/
2021
gradle-app.setting

build.gradle

+1-4
Original file line numberDiff line numberDiff line change
@@ -10,16 +10,13 @@ buildscript {
1010
}
1111
}
1212

13-
version = Git.commitHash()
14-
1513
subprojects { Project project ->
1614
apply plugin: 'com.diffplug.spotless'
1715
apply plugin: 'io.spring.dependency-management'
1816
apply plugin: 'java'
1917

2018
sourceCompatibility = 17
2119
targetCompatibility = 17
22-
version Git.commitHash()
2320

2421
dependencyManagement {
2522
imports { mavenBom("org.springframework.boot:spring-boot-dependencies:${Version.SPRING_BOOT}") }
@@ -39,7 +36,7 @@ subprojects { Project project ->
3936

4037
spotless {
4138
java {
42-
googleJavaFormat()
39+
eclipse().configFile('../format.xml')
4340
}
4441
}
4542
}
+4-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
11
interface Version {
2-
public static final String SPRING_BOOT = '2.7.18'
2+
public static final String LOMBOK = '1.18.30'
3+
public static final String JUNIT = '5.10.2'
4+
public static final String REST_ASSURED = '5.4.0'
35
public static final String SPOTLESS = '7.0.0.BETA1'
6+
public static final String SPRING_BOOT = '2.7.18'
47
}
+12
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,14 @@
11
dependencies {
2+
testImplementation 'commons-logging:commons-logging:1.3.2'
3+
4+
testImplementation "org.junit.jupiter:junit-jupiter-api:${Version.JUNIT}"
5+
testImplementation "org.junit.jupiter:junit-jupiter:${Version.JUNIT}"
6+
testImplementation("io.rest-assured:json-path:${Version.REST_ASSURED}") {
7+
exclude group: "org.apache.groovy", module: "groovy"
8+
exclude group: "org.apache.groovy", module: "groovy-xml"
9+
}
10+
testImplementation("io.rest-assured:rest-assured:${Version.REST_ASSURED}") {
11+
exclude group: "org.apache.groovy", module: "groovy"
12+
exclude group: "org.apache.groovy", module: "groovy-xml"
13+
}
214
}

chucknorris-integration-test/docker-compose.yml

+12-2
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,15 @@ services:
55
image: chucknorrisio/postgres
66
container_name: chucknorris-database
77
healthcheck:
8-
interval: 30s
8+
interval: 5s
99
retries: 5
1010
start_period: 80s
1111
test: [ "CMD-SHELL", "pg_isready", "-d", "db_prod" ]
1212
timeout: 60s
1313
ports:
1414
- '5432:5432'
1515

16-
service:
16+
subject:
1717
environment:
1818
- APPLICATION_EVENT_SNS_TOPIC_ARN=my-application-event-sns-topic-arn
1919
- AWS_ACCESS_KEY_ID=my-aws-access-key-id
@@ -36,3 +36,13 @@ services:
3636
condition: service_healthy
3737
ports:
3838
- '8080:8080'
39+
40+
integration-test:
41+
command: "./gradlew :chucknorris-integration-test:test --stacktrace"
42+
container_name: "integration-test"
43+
depends_on:
44+
- subject
45+
image: "azul/zulu-openjdk-alpine:21-jre-headless-latest"
46+
volumes:
47+
- "../:/home/gradle/project"
48+
working_dir: /home/gradle/project

chucknorris-integration-test/script/run-integration-test.sh

+5-9
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,9 @@ readonly SCRIPT_DIR=$(cd "$(dirname "$0")" && pwd)
44
readonly MODULE_DIR=$(cd "${SCRIPT_DIR}/.." && pwd)
55
readonly PROJECT_DIR=$(cd "${SCRIPT_DIR}/../.." && pwd)
66

7-
./gradlew --build-file "$PROJECT_DIR/build.gradle" clean dockerTagCurrent
7+
./gradlew --build-file "$PROJECT_DIR/chucknorris-web/build.gradle" clean dockerTagLatest
88

9-
# docker-compose --file "$MODULE_DIR/docker-compose.yml" up \
10-
# --exit-code-from "integration-test" \
11-
# --quiet-pull \
12-
# --remove-orphans
13-
14-
docker-compose --file "$MODULE_DIR/docker-compose.yml" up \
15-
--quiet-pull \
16-
--remove-orphans
9+
docker-compose --file "$MODULE_DIR/docker-compose.yml" up \
10+
--exit-code-from "integration-test" \
11+
--quiet-pull \
12+
--remove-orphans
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
package io.chucknorris.integration_test.joke;
2+
3+
import static io.restassured.RestAssured.given;
4+
5+
import io.restassured.RestAssured;
6+
import java.util.List;
7+
import org.hamcrest.Matchers;
8+
import org.junit.jupiter.api.BeforeAll;
9+
import org.junit.jupiter.api.DisplayName;
10+
import org.junit.jupiter.api.Test;
11+
12+
class JokeTest {
13+
14+
@BeforeAll
15+
public static void beforeAll() {
16+
RestAssured.baseURI = "http://subject";
17+
RestAssured.port = 8080;
18+
}
19+
20+
@DisplayName("Should return joke")
21+
@Test
22+
void shouldReturnJoke() {
23+
var specification = given()
24+
.log().all()
25+
.accept("application/json");
26+
27+
var response = specification.when().get("/jokes/random");
28+
29+
response.then()
30+
.log().all()
31+
.statusCode(200)
32+
.assertThat()
33+
.header("Content-Type", Matchers.equalTo("application/json"))
34+
.body("categories", Matchers.isA(List.class))
35+
.body("created_at", Matchers.isA(String.class))
36+
.body("icon_url", Matchers.isA(String.class))
37+
.body("id", Matchers.isA(String.class))
38+
.body("updated_at", Matchers.isA(String.class))
39+
.body("url", Matchers.isA(String.class))
40+
.body("value", Matchers.isA(String.class));
41+
}
42+
43+
@DisplayName("Should include CORS headers")
44+
@Test
45+
void shouldIncludeCORSHeaders() {
46+
var specification = given()
47+
.log().all()
48+
.accept("application/json")
49+
.header("Origin", "http://localhost:3000");
50+
51+
var response = specification.when().get("/jokes/random");
52+
53+
response.then()
54+
.log().all()
55+
.statusCode(200)
56+
.assertThat()
57+
.header("Access-Control-Allow-Origin", Matchers.equalTo("*"))
58+
.header("Access-Control-Expose-Headers", Matchers.equalTo("Access-Control-Allow-Origin Access-Control-Allow-Credentials"))
59+
.header("Content-Type", Matchers.equalTo("application/json"));
60+
}
61+
}

chucknorris-web/build.gradle

+2-21
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,8 @@ plugins {
33
id 'org.springframework.boot'
44
}
55

6-
repositories {
7-
mavenCentral()
8-
}
9-
10-
// versions
11-
def lombokVersion = "1.18.30"
12-
136
dependencies {
14-
annotationProcessor "org.projectlombok:lombok:${lombokVersion}"
7+
annotationProcessor "org.projectlombok:lombok:${Version.LOMBOK}"
158

169
implementation "com.amazonaws:aws-java-sdk:1.11.561"
1710
implementation "com.fasterxml.jackson.core:jackson-core:2.17.1"
@@ -20,7 +13,7 @@ dependencies {
2013
implementation "nz.net.ultraq.thymeleaf:thymeleaf-layout-dialect:2.3.0"
2114
implementation "org.hibernate:hibernate-validator:6.0.16.Final"
2215
implementation "org.postgresql:postgresql:42.2.9"
23-
implementation "org.projectlombok:lombok:${lombokVersion}"
16+
implementation "org.projectlombok:lombok:${Version.LOMBOK}"
2417
implementation "org.springframework.boot:spring-boot-starter-data-jpa"
2518
implementation "org.springframework.boot:spring-boot-starter-thymeleaf"
2619
implementation "org.springframework.boot:spring-boot-starter-web"
@@ -35,7 +28,6 @@ tasks {
3528
manifest.attributes("Multi-Release": "true")
3629

3730
archiveBaseName.set(project.name)
38-
archiveVersion.set(project.version)
3931

4032
if (project.hasProperty("archiveName")) {
4133
archiveFileName.set(project.properties["archiveName"] as String)
@@ -48,22 +40,11 @@ tasks {
4840
def imageName = "${project.properties.group}/${project.name}"
4941
name = "${imageName}:latest"
5042

51-
tag("current", "${imageName}:${project.version}")
5243
tag("latest", "${imageName}:latest")
5344
tag("herokuProduction", "registry.heroku.com/chucky/web")
5445

5546
dockerfile file("${projectDir}/src/main/docker/Dockerfile")
5647
files tasks.bootJar.outputs
5748
buildArgs([JAR_FILE: bootJar.getArchiveFileName().get()])
5849
}
59-
60-
springBoot {
61-
buildInfo {
62-
properties {
63-
artifact = "${project.name}-${project.version}.jar"
64-
version = project.version
65-
name = project.name
66-
}
67-
}
68-
}
6950
}

chucknorris-web/src/main/java/io/chucknorris/api/Application.java

+4-4
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,12 @@
55
import org.springframework.context.annotation.ComponentScan;
66
import org.springframework.data.jpa.repository.config.EnableJpaAuditing;
77

8-
@ComponentScan(basePackages = {"io.chucknorris"})
8+
@ComponentScan(basePackages = { "io.chucknorris" })
99
@EnableJpaAuditing
1010
@SpringBootApplication
1111
public class Application {
1212

13-
public static void main(String[] args) {
14-
SpringApplication.run(Application.class, args);
15-
}
13+
public static void main(String[] args) {
14+
SpringApplication.run(Application.class, args);
15+
}
1616
}

chucknorris-web/src/main/java/io/chucknorris/api/ApplicationExceptionHandler.java

+30-30
Original file line numberDiff line numberDiff line change
@@ -16,38 +16,38 @@
1616
@ControllerAdvice
1717
public class ApplicationExceptionHandler extends ResponseEntityExceptionHandler {
1818

19-
@ExceptionHandler(value = {ConstraintViolationException.class})
20-
protected ResponseEntity<Object> handleConstraintViolationException(
21-
ConstraintViolationException exception, ServletWebRequest request) {
22-
switch (request.getHeader(HttpHeaders.ACCEPT)) {
23-
case MediaType.TEXT_PLAIN_VALUE:
24-
StringBuilder stringBuilder = new StringBuilder();
25-
for (ConstraintViolation violation : exception.getConstraintViolations()) {
26-
stringBuilder.append(
27-
violation.getPropertyPath().toString() + ": " + violation.getMessage() + '\n');
28-
}
19+
@ExceptionHandler(value = { ConstraintViolationException.class })
20+
protected ResponseEntity<Object> handleConstraintViolationException(
21+
ConstraintViolationException exception, ServletWebRequest request) {
22+
switch (request.getHeader(HttpHeaders.ACCEPT)) {
23+
case MediaType.TEXT_PLAIN_VALUE:
24+
StringBuilder stringBuilder = new StringBuilder();
25+
for (ConstraintViolation violation : exception.getConstraintViolations()) {
26+
stringBuilder.append(
27+
violation.getPropertyPath().toString() + ": " + violation.getMessage() + '\n');
28+
}
2929

30-
return handleExceptionInternal(
31-
exception,
32-
stringBuilder.toString(),
33-
new HttpHeaders(),
34-
HttpStatus.BAD_REQUEST,
35-
request);
36-
default:
37-
LinkedHashMap<String, Object> constraintViolations = new LinkedHashMap<>();
38-
for (ConstraintViolation violation : exception.getConstraintViolations()) {
39-
constraintViolations.put(violation.getPropertyPath().toString(), violation.getMessage());
40-
}
30+
return handleExceptionInternal(
31+
exception,
32+
stringBuilder.toString(),
33+
new HttpHeaders(),
34+
HttpStatus.BAD_REQUEST,
35+
request);
36+
default:
37+
LinkedHashMap<String, Object> constraintViolations = new LinkedHashMap<>();
38+
for (ConstraintViolation violation : exception.getConstraintViolations()) {
39+
constraintViolations.put(violation.getPropertyPath().toString(), violation.getMessage());
40+
}
4141

42-
LinkedHashMap<String, Object> body = new LinkedHashMap<>();
43-
body.put("timestamp", new Date());
44-
body.put("status", HttpStatus.BAD_REQUEST.value());
45-
body.put("error", HttpStatus.BAD_REQUEST.getReasonPhrase());
46-
body.put("message", exception.getMessage());
47-
body.put("violations", constraintViolations);
42+
LinkedHashMap<String, Object> body = new LinkedHashMap<>();
43+
body.put("timestamp", new Date());
44+
body.put("status", HttpStatus.BAD_REQUEST.value());
45+
body.put("error", HttpStatus.BAD_REQUEST.getReasonPhrase());
46+
body.put("message", exception.getMessage());
47+
body.put("violations", constraintViolations);
4848

49-
return handleExceptionInternal(
50-
exception, body, new HttpHeaders(), HttpStatus.BAD_REQUEST, request);
49+
return handleExceptionInternal(
50+
exception, body, new HttpHeaders(), HttpStatus.BAD_REQUEST, request);
51+
}
5152
}
52-
}
5353
}

0 commit comments

Comments
 (0)