From 7446bd506aca36c5358d5e31a713462cea06ef24 Mon Sep 17 00:00:00 2001 From: Fabrice Lecomte Date: Sat, 27 Feb 2021 23:20:57 +0100 Subject: [PATCH] create gradle tasks "test", "testSql", "migrations" and run docker before tasks --- .idea/dataSources.xml | 2 +- .idea/runConfigurations/Migration.xml | 23 ++ .idea/runConfigurations/TestSql.xml | 23 ++ .idea/runConfigurations/Test_All_SQL.xml | 2 + build.gradle.kts | 203 +++++++++++++++--- docker-compose-sonar.yml | 48 +++++ docker-compose-test.yml | 44 ++++ docker-compose.yml | 47 +--- docker/app/Dockerfile | 4 +- gradle.properties | 6 - .../functional/NotificationConsumerTest.kt | 4 +- src/test/resources/application-test.conf | 13 +- src/test/sql/test.sh | 8 +- 13 files changed, 325 insertions(+), 102 deletions(-) create mode 100644 .idea/runConfigurations/Migration.xml create mode 100644 .idea/runConfigurations/TestSql.xml create mode 100644 docker-compose-sonar.yml create mode 100644 docker-compose-test.yml diff --git a/.idea/dataSources.xml b/.idea/dataSources.xml index c5bca50..2b2a567 100644 --- a/.idea/dataSources.xml +++ b/.idea/dataSources.xml @@ -11,7 +11,7 @@ postgresql true org.postgresql.Driver - jdbc:postgresql://localhost:5432/test + jdbc:postgresql://localhost:5433/test postgresql diff --git a/.idea/runConfigurations/Migration.xml b/.idea/runConfigurations/Migration.xml new file mode 100644 index 0000000..3159aef --- /dev/null +++ b/.idea/runConfigurations/Migration.xml @@ -0,0 +1,23 @@ + + + + + + + true + true + false + + + \ No newline at end of file diff --git a/.idea/runConfigurations/TestSql.xml b/.idea/runConfigurations/TestSql.xml new file mode 100644 index 0000000..8003a29 --- /dev/null +++ b/.idea/runConfigurations/TestSql.xml @@ -0,0 +1,23 @@ + + + + + + + true + true + false + + + \ No newline at end of file diff --git a/.idea/runConfigurations/Test_All_SQL.xml b/.idea/runConfigurations/Test_All_SQL.xml index 326058e..06d1935 100644 --- a/.idea/runConfigurations/Test_All_SQL.xml +++ b/.idea/runConfigurations/Test_All_SQL.xml @@ -1,5 +1,6 @@ + diff --git a/build.gradle.kts b/build.gradle.kts index a0ae987..7ffec68 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -3,13 +3,17 @@ import io.gitlab.arturbosch.detekt.Detekt import org.jetbrains.kotlin.gradle.tasks.KotlinCompile import org.owasp.dependencycheck.reporting.ReportGenerator import org.slf4j.LoggerFactory +import fr.postgresjson.connexion.Connection +import fr.postgresjson.migration.Migrations +import com.typesafe.config.ConfigFactory +import fr.postgresjson.connexion.Requester -val ktor_version: String by project -val kotlin_version: String by project -val coroutinesVersion: String by project -val logback_version: String by project -val koinVersion: String by project -val jackson_version: String by project +val ktorVersion = "1.5.0" +val kotlinVersion = "1.4.30" +val coroutinesVersion = "1.4.2" +val logbackVersion = "1.2.3" +val koinVersion = "2.0.1" +val jacksonVersion = "2.12.1" group = "com.github.flecomte" version = versioning.info.run { @@ -36,17 +40,121 @@ plugins { id("net.nemerosa.versioning") version "2.14.0" id("io.gitlab.arturbosch.detekt") version "1.16.0-RC1" id("info.solidsoft.pitest") version "1.5.2" + id("com.avast.gradle.docker-compose") version "0.14.0" } application { mainClassName = "io.ktor.server.jetty.EngineMain" } +buildscript { + repositories { + mavenLocal() + mavenCentral() + jcenter() + maven { url = uri("https://jitpack.io") } + } + dependencies { + classpath("com.typesafe:config:1.4.1") + classpath("com.github.flecomte:postgres-json:2.1.1") + } +} + tasks.withType { kotlinOptions { jvmTarget = "11" } } + +val migration by tasks.registering { + group = "application" + dependsOn(tasks.named("composeUp")) + + doLast { + val config = ConfigFactory.parseFile(file("${buildDir}/../src/main/resources/application.conf")).resolve() + val connection = Connection( + host = config.getString("db.host"), + port = config.getInt("db.port"), + database = config.getString("db.database"), + username = config.getString("db.username"), + password = config.getString("db.password") + ) + Migrations( + connection, + file("$buildDir/../src/main/resources/sql/migrations").toURI(), + file("$buildDir/../src/main/resources/sql/functions").toURI() + ).run { + run() + } + } +} + +val migrationTest by tasks.registering { + group = "verification" + dependsOn(tasks.named("testComposeUp")) + finalizedBy(tasks.named("testComposeDown")) + doLast { + val config = ConfigFactory.parseFile(file("${buildDir}/../src/test/resources/application-test.conf")).resolve() + val connection = Connection( + host = config.getString("db.host"), + port = config.getInt("db.port"), + database = config.getString("db.database"), + username = config.getString("db.username"), + password = config.getString("db.password") + ) + Migrations( + connection, + file("$buildDir/../src/main/resources/sql/migrations").toURI(), + file("$buildDir/../src/main/resources/sql/functions").toURI() + ).run { + run() + connection.disconnect() + } + } +} + +val testSql by tasks.registering { + group = "verification" + dependsOn(tasks.named("testComposeUp")) + finalizedBy(tasks.named("testComposeDown")) + + doLast { + val config = ConfigFactory.parseFile(file("${buildDir}/../src/test/resources/application-test.conf")).resolve() + + val connection = Connection( + host = config.getString("db.host"), + port = config.getInt("db.port"), + database = config.getString("db.database"), + username = config.getString("db.username"), + password = config.getString("db.password") + ) + + Migrations( + connection, + file("$buildDir/../src/main/resources/sql/migrations").toURI(), + file("$buildDir/../src/main/resources/sql/functions").toURI(), + file("$buildDir/../src/test/sql/fixtures").toURI() + ).run { + run() + } + + Requester.RequesterFactory( + connection = connection, + queriesDirectory = file("${buildDir}/../src/test/sql").toURI() + ).createRequester().run { + getQueries().map { + try { + it.sendQuery() == 0 + } catch (e: Exception) { + false + } + } + } + + connection.disconnect() + } +} + tasks.withType { manifest { attributes( @@ -57,23 +165,48 @@ tasks.withType { } } -tasks { - named("shadowJar") { - mergeServiceFiles("META-INF/services") - archiveFileName.set("${archiveBaseName.get()}-latest-all.${archiveExtension.get()}") - } +tasks.named("shadowJar") { + mergeServiceFiles("META-INF/services") + archiveFileName.set("${archiveBaseName.get()}-latest-all.${archiveExtension.get()}") } -val sourcesJar by tasks.creating(Jar::class) { +tasks.sonarqube.configure { dependsOn(tasks.jacocoTestReport) } + +val sourcesJar by tasks.registering(Jar::class) { + group = "build" archiveClassifier.set("sources") from(sourceSets.getByName("main").allSource) } + tasks.test { useJUnit() useJUnitPlatform() systemProperty("junit.jupiter.execution.parallel.enabled", true) + dependsOn(testSql) + dependsOn(tasks.pitest) finalizedBy(tasks.jacocoTestReport) // report is always generated after tests run -// maxHeapSize = "1G" +} + +apply(plugin = "docker-compose") +dockerCompose { + projectName = "dc-project" + useComposeFiles = listOf("docker-compose.yml") + startedServices = listOf("db", "elasticsearch", "rabbitmq", "redis") + stopContainers = false + isRequiredBy(project.tasks.run) + createNested("test").apply { + projectName = "dc-project_test" + useComposeFiles = listOf("docker-compose-test.yml") + stopContainers = false + isRequiredBy(project.tasks.test) + isRequiredBy(project.tasks.named("testSql")) + } + createNested("sonarqube").apply { + projectName = "dc-project" + useComposeFiles = listOf("docker-compose-sonar.yml") + stopContainers = false +// isRequiredBy(project.tasks.sonarqube) + } } publishing { @@ -106,10 +239,12 @@ jacoco { toolVersion = "0.8.6" applyTo(tasks.run.get()) } + tasks.register("applicationCodeCoverageReport") { executionData(tasks.run.get()) sourceSets(sourceSets.main.get()) } + tasks.jacocoTestReport { dependsOn(tasks.test) reports { @@ -159,43 +294,45 @@ repositories { } dependencies { - implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version") + implementation(gradleApi()) + implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlinVersion") implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutinesVersion") implementation("org.jetbrains.kotlinx:kotlinx-coroutines-reactor:$coroutinesVersion") implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.0.1") - implementation("io.ktor:ktor-server-jetty:$ktor_version") - implementation("io.ktor:ktor-client-jetty:$ktor_version") - implementation("ch.qos.logback:logback-classic:$logback_version") - implementation("io.ktor:ktor-server-core:$ktor_version") - implementation("io.ktor:ktor-locations:$ktor_version") - implementation("io.ktor:ktor-auth:$ktor_version") - implementation("io.ktor:ktor-auth-jwt:$ktor_version") - implementation("io.ktor:ktor-websockets:$ktor_version") + implementation("io.ktor:ktor-server-jetty:$ktorVersion") + implementation("io.ktor:ktor-client-jetty:$ktorVersion") + implementation("ch.qos.logback:logback-classic:$logbackVersion") + implementation("io.ktor:ktor-server-core:$ktorVersion") + implementation("io.ktor:ktor-locations:$ktorVersion") + implementation("io.ktor:ktor-auth:$ktorVersion") + implementation("io.ktor:ktor-auth-jwt:$ktorVersion") + implementation("io.ktor:ktor-websockets:$ktorVersion") implementation("org.koin:koin-ktor:$koinVersion") - implementation("io.ktor:ktor-jackson:$ktor_version") - implementation("com.fasterxml.jackson.module:jackson-module-kotlin:$jackson_version") - implementation("com.fasterxml.jackson.datatype:jackson-datatype-joda:$jackson_version") + implementation("io.ktor:ktor-jackson:$ktorVersion") + implementation("com.fasterxml.jackson.module:jackson-module-kotlin:$jacksonVersion") + implementation("com.fasterxml.jackson.datatype:jackson-datatype-joda:$jacksonVersion") implementation("net.pearx.kasechange:kasechange-jvm:1.3.0") implementation("com.auth0:java-jwt:3.12.0") implementation("com.github.jasync-sql:jasync-postgresql:1.1.6") - implementation("com.github.flecomte:postgres-json:2.0.0") + implementation("com.github.flecomte:postgres-json:2.1.1") implementation("com.sendgrid:sendgrid-java:4.7.1") implementation("io.lettuce:lettuce-core:5.3.6.RELEASE") // TODO update to 6.0.2 implementation("com.rabbitmq:amqp-client:5.10.0") implementation("org.elasticsearch.client:elasticsearch-rest-client:6.7.1") implementation("com.jayway.jsonpath:json-path:2.5.0") + implementation("com.avast.gradle:gradle-docker-compose-plugin:0.14.0") - testImplementation("io.ktor:ktor-server-tests:$ktor_version") - testImplementation("io.ktor:ktor-client-mock:$ktor_version") - testImplementation("io.ktor:ktor-client-mock-jvm:$ktor_version") + testImplementation("io.ktor:ktor-server-tests:$ktorVersion") + testImplementation("io.ktor:ktor-client-mock:$ktorVersion") + testImplementation("io.ktor:ktor-client-mock-jvm:$ktorVersion") testImplementation("org.koin:koin-test:$koinVersion") testImplementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:$coroutinesVersion") - testImplementation("io.mockk:mockk:1.10.5") + testImplementation("io.mockk:mockk:1.10.6") testImplementation("org.junit.jupiter:junit-jupiter:5.7.0") testImplementation("org.amshove.kluent:kluent:1.61") pitest("org.pitest:pitest-junit5-plugin:0.5") - testImplementation("io.mockk:mockk-agent-api:1.10.5") - testImplementation("io.mockk:mockk-agent-jvm:1.10.5") - testImplementation("org.jetbrains.kotlin:kotlin-reflect:$kotlin_version") + testImplementation("io.mockk:mockk-agent-api:1.10.6") + testImplementation("io.mockk:mockk-agent-jvm:1.10.6") + testImplementation("org.jetbrains.kotlin:kotlin-reflect:$kotlinVersion") testImplementation("com.thedeanda:lorem:2.1") } diff --git a/docker-compose-sonar.yml b/docker-compose-sonar.yml new file mode 100644 index 0000000..978c9c9 --- /dev/null +++ b/docker-compose-sonar.yml @@ -0,0 +1,48 @@ +version: '3.8' +services: + sonarqube: + container_name: ${APP_NAME}_sonarqube + image: sonarqube:community + depends_on: + - sonarqube_db + ports: + - ${SONARQUBE_PORT}:9000 + networks: + - sonarnet + environment: + SONAR_JDBC_URL: jdbc:postgresql://sonarqube_db:5432/sonar + SONAR_JDBC_USERNAME: sonar + SONAR_JDBC_PASSWORD: sonar + volumes: + - sonarqube_data:/opt/sonarqube/data + - sonarqube_extensions:/opt/sonarqube/extensions + - sonarqube_logs:/opt/sonarqube/logs + - sonarqube_temp:/opt/sonarqube/temp + + sonarqube_db: + container_name: ${APP_NAME}_sonarqube_db + image: postgres:alpine + networks: + - sonarnet + environment: + POSTGRES_USER: sonar + POSTGRES_PASSWORD: sonar + ports: + - ${SONARQUBE_DB_PORT}:5432 + volumes: + - sonarqube_postgresql:/var/lib/postgresql + # This needs explicit mapping due to https://github.com/docker-library/postgres/blob/4e48e3228a30763913ece952c611e5e9b95c8759/Dockerfile.template#L52 + - sonarqube_postgresql_data:/var/lib/postgresql/data + + +networks: + sonarnet: + driver: bridge + +volumes: + sonarqube_data: + sonarqube_extensions: + sonarqube_logs: + sonarqube_temp: + sonarqube_postgresql: + sonarqube_postgresql_data: \ No newline at end of file diff --git a/docker-compose-test.yml b/docker-compose-test.yml new file mode 100644 index 0000000..7819168 --- /dev/null +++ b/docker-compose-test.yml @@ -0,0 +1,44 @@ +version: '3.8' +services: + rabbitmq: + container_name: ${APP_NAME}_rabbitmq_test + image: rabbitmq:management-alpine + ports: + - 5672:5672 + - 15672:15672 + + redis: + container_name: ${APP_NAME}_redis_test + image: redis:6-alpine + ports: + - 6380:6379 + + elasticsearch: + container_name: ${APP_NAME}_elasticsearch_test + image: elasticsearch:6.7.1 + ports: + - 9201:9200 + - 9301:9300 + healthcheck: + test: ["CMD", "curl", "-f", "http://elasticsearch:9200"] + interval: 3s + timeout: 2s + retries: 20 + + db: + container_name: ${APP_NAME}_postgresql_test + build: + context: docker/postgresql + ports: + - 5433:5432 + environment: + POSTGRES_PASSWORD: ${DB_NAME} + POSTGRES_USER: ${DB_USER} + POSTGRES_DB: ${DB_PWD} + depends_on: + - elasticsearch + healthcheck: + test: [ "CMD", "pg_isready", "-q", "-d", "${DB_NAME}", "-U", "${DB_USER}" ] + interval: 3s + timeout: 2s + retries: 20 diff --git a/docker-compose.yml b/docker-compose.yml index 3e00b61..4d93bf9 100755 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,40 +1,7 @@ # To execute this docker-compose yml file use docker-compose -f up # Add the "-d" flag at the end for detached execution -version: '3.7' +version: '3.8' services: - sonarqube: - container_name: ${APP_NAME}_sonarqube - image: sonarqube:community - depends_on: - - sonarqube_db - ports: - - ${SONARQUBE_PORT}:9000 - networks: - - sonarnet - environment: - SONAR_JDBC_URL: jdbc:postgresql://sonarqube_db:5432/sonar - SONAR_JDBC_USERNAME: sonar - SONAR_JDBC_PASSWORD: sonar - volumes: - - sonarqube_data:/opt/sonarqube/data - - sonarqube_extensions:/opt/sonarqube/extensions - - sonarqube_logs:/opt/sonarqube/logs - - sonarqube_temp:/opt/sonarqube/temp - sonarqube_db: - container_name: ${APP_NAME}_sonarqube_db - image: postgres:alpine - networks: - - sonarnet - environment: - POSTGRES_USER: sonar - POSTGRES_PASSWORD: sonar - ports: - - ${SONARQUBE_DB_PORT}:5432 - volumes: - - sonarqube_postgresql:/var/lib/postgresql - # This needs explicit mapping due to https://github.com/docker-library/postgres/blob/4e48e3228a30763913ece952c611e5e9b95c8759/Dockerfile.template#L52 - - sonarqube_postgresql_data:/var/lib/postgresql/data - openapi: container_name: ${APP_NAME}_openapi image: swaggerapi/swagger-ui @@ -110,16 +77,6 @@ services: timeout: 2s retries: 20 -networks: - sonarnet: - driver: bridge - volumes: db-data: - redis-data: - sonarqube_data: - sonarqube_extensions: - sonarqube_logs: - sonarqube_temp: - sonarqube_postgresql: - sonarqube_postgresql_data: \ No newline at end of file + redis-data: \ No newline at end of file diff --git a/docker/app/Dockerfile b/docker/app/Dockerfile index 2afe808..8d8fb06 100644 --- a/docker/app/Dockerfile +++ b/docker/app/Dockerfile @@ -1,5 +1,5 @@ #### BUILD #### -FROM gradle:5.6.4-jdk11 AS build +FROM gradle:6.8-jdk11 AS build COPY --chown=gradle:gradle . /home/gradle/src WORKDIR /home/gradle/src @@ -7,7 +7,7 @@ RUN gradle build -x test -x ktlintKotlinScriptCheck -x ktlintTestSourceSetCheck RUN gradle shadowJar #### RUN #### -FROM adoptopenjdk/openjdk11:jre-11.0.4_11-alpine +FROM amazoncorretto:11-alpine as run ENV APPLICATION_USER ktor RUN adduser -D -g '' $APPLICATION_USER diff --git a/gradle.properties b/gradle.properties index bc21501..c799b29 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,10 +1,4 @@ -ktor_version=1.5.0 kotlin.code.style=official -kotlin_version=1.4.21-2 -coroutinesVersion=1.4.2 -logback_version=1.2.3 -koinVersion=2.0.1 -jackson_version=2.12.1 systemProp.sonar.host.url=http://localhost:9002 systemProp.sonar.login=admin systemProp.sonar.password=sonar diff --git a/src/test/kotlin/functional/NotificationConsumerTest.kt b/src/test/kotlin/functional/NotificationConsumerTest.kt index 9c706a1..b291564 100644 --- a/src/test/kotlin/functional/NotificationConsumerTest.kt +++ b/src/test/kotlin/functional/NotificationConsumerTest.kt @@ -86,14 +86,14 @@ class NotificationConsumerTest { followArticleRepo = followArticleRepo, followConstitutionRepo = mockk(), notificationEmailSender = emailSender, - exchangeName = "notification_test", + exchangeName = "notification", ).apply { start() } verify { rabbitFactory.newConnection() } /* Push message */ Publisher( factory = rabbitFactory, - exchangeName = "notification_test", + exchangeName = "notification", ).publish( ArticleUpdateNotification( ArticleForView( diff --git a/src/test/resources/application-test.conf b/src/test/resources/application-test.conf index 0c5f0fd..fe6fc92 100644 --- a/src/test/resources/application-test.conf +++ b/src/test/resources/application-test.conf @@ -1,7 +1,6 @@ ktor { deployment { - port = 8080 - port = ${?PORT} + port = 8181 } application { modules = [ fr.dcproject.ApplicationKt.module ] @@ -15,26 +14,22 @@ app { db { host = localhost - host = ${?DB_HOST} database = test username = test password = test - port = 5432 + port = 5433 } redis { - connection = "redis://localhost:6379" - connection = ${?REDIS_CONNECTION} + connection = "redis://localhost:6380" } rabbitmq { connection = "amqp://localhost:5672" - connection = ${?RABBITMQ_CONNECTION} } elasticsearch { - connection = "http://localhost:9200" - connection = ${?ELASTICSEARCH_CONNECTION} + connection = "http://localhost:9201" } mail { diff --git a/src/test/sql/test.sh b/src/test/sql/test.sh index 65a0dbe..83767dd 100644 --- a/src/test/sql/test.sh +++ b/src/test/sql/test.sh @@ -1,6 +1,6 @@ #!/bin/bash -options=("All" "article" "citizen" "comment" "constitution" "follow" "opinion" "user" "vote" "workgroup" "RESET DB" "Quit") +options=("All" "RESET DB" "article" "citizen" "comment" "constitution" "follow" "opinion" "user" "vote" "workgroup" "Quit") if [ -z "$1" ]; then PS3='Please enter your choice: ' select ch in "${options[@]}" @@ -17,7 +17,7 @@ case $opt in awk 'FNR==1{print "--------------------"}1' \ ../../main/resources/sql/migrations/*.down.sql \ ../../main/resources/sql/migrations/*.up.sql > ./allSQL.sql - docker exec -i dc-project_postgresql psql test test -q -b -v "ON_ERROR_STOP=1" < ./allSQL.sql + docker exec -i dc-project_postgresql_test psql test test -q -b -v "ON_ERROR_STOP=1" < ./allSQL.sql rm ./allSQL.sql ;; "All") @@ -26,7 +26,7 @@ case $opt in ../../main/resources/sql/functions/*/*.sql \ ./fixtures/*.sql \ ./*.sql > ./allSQL.sql - docker exec -i dc-project_postgresql psql test test -q -b -v "ON_ERROR_STOP=1" < ./allSQL.sql + docker exec -i dc-project_postgresql_test psql test test -q -b -v "ON_ERROR_STOP=1" < ./allSQL.sql rm ./allSQL.sql ;; "Quit") @@ -37,7 +37,7 @@ case $opt in ../../main/resources/sql/functions/*/*.sql \ ./fixtures/*.sql \ ./"$opt".sql > ./allSQL.sql - docker exec -i dc-project_postgresql psql test test -q -b -v "ON_ERROR_STOP=1" < ./allSQL.sql + docker exec -i dc-project_postgresql_test psql test test -q -b -v "ON_ERROR_STOP=1" < ./allSQL.sql rm ./allSQL.sql ;; esac \ No newline at end of file