From f78be0c5a2d48292a6fd2035ac6eb06d666d597e Mon Sep 17 00:00:00 2001 From: dtuchs Date: Thu, 10 Oct 2024 23:21:00 +0500 Subject: [PATCH 1/2] ci --- .github/workflows/e2e.yml | 73 +++++++++++++ build.gradle | 11 ++ docker-compose-dev.sh | 18 ++++ docker-compose-test.yml | 100 ++++++++++++++++++ docker-compose.yml | 66 ++++++++++++ rangiffler-api/build.gradle | 35 ++++++ .../src/main/resources/application.yml | 39 +++++-- rangiffler-auth/build.gradle | 35 ++++++ .../src/main/resources/application.yaml | 34 ++++-- rangiffler-e2e/Dockerfile | 21 ++++ rangiffler-e2e/build.gradle | 7 +- .../org/rangiffler/api/AllureDockerApi.java | 33 ++++++ .../rangiffler/api/AllureDockerApiClient.java | 60 +++++++++++ .../java/org/rangiffler/config/Config.java | 6 +- .../org/rangiffler/config/DockerConfig.java | 47 ++++++++ .../org/rangiffler/config/LocalConfig.java | 5 + .../jupiter/AllureDockerExtension.java | 73 +++++++++++++ .../rangiffler/jupiter/SuiteExtension.java | 24 +++++ .../org/rangiffler/model/AllureProject.java | 4 + .../org/rangiffler/model/AllureResults.java | 8 ++ .../rangiffler/model/DecodedAllureFile.java | 7 ++ .../java/org/rangiffler/page/LoginPage.java | 22 ++++ .../java/org/rangiffler/page/MainPage.java | 20 ++++ .../org/rangiffler/page/RegisterPage.java | 31 ++++++ .../java/org/rangiffler/page/WelcomePage.java | 21 ++++ .../test/{ => gql}/BaseGraphQLTest.java | 2 +- .../test/{ => gql}/CountriesTest.java | 4 +- .../rangiffler/test/web/RegistrationTest.java | 25 +++++ .../org.junit.jupiter.api.extension.Extension | 1 + rangiffler-gql-client/.dockerignore | 3 + rangiffler-gql-client/.env.docker | 6 ++ rangiffler-gql-client/Dockerfile | 16 +++ rangiffler-gql-client/nginx.conf | 15 +++ rangiffler-gql-client/package.json | 3 +- selenoid/browsers.json | 15 +++ 35 files changed, 863 insertions(+), 27 deletions(-) create mode 100644 .github/workflows/e2e.yml create mode 100644 docker-compose-dev.sh create mode 100644 docker-compose-test.yml create mode 100644 docker-compose.yml create mode 100644 rangiffler-e2e/Dockerfile create mode 100644 rangiffler-e2e/src/test/java/org/rangiffler/api/AllureDockerApi.java create mode 100644 rangiffler-e2e/src/test/java/org/rangiffler/api/AllureDockerApiClient.java create mode 100644 rangiffler-e2e/src/test/java/org/rangiffler/config/DockerConfig.java create mode 100644 rangiffler-e2e/src/test/java/org/rangiffler/jupiter/AllureDockerExtension.java create mode 100644 rangiffler-e2e/src/test/java/org/rangiffler/jupiter/SuiteExtension.java create mode 100644 rangiffler-e2e/src/test/java/org/rangiffler/model/AllureProject.java create mode 100644 rangiffler-e2e/src/test/java/org/rangiffler/model/AllureResults.java create mode 100644 rangiffler-e2e/src/test/java/org/rangiffler/model/DecodedAllureFile.java create mode 100644 rangiffler-e2e/src/test/java/org/rangiffler/page/LoginPage.java create mode 100644 rangiffler-e2e/src/test/java/org/rangiffler/page/MainPage.java create mode 100644 rangiffler-e2e/src/test/java/org/rangiffler/page/RegisterPage.java create mode 100644 rangiffler-e2e/src/test/java/org/rangiffler/page/WelcomePage.java rename rangiffler-e2e/src/test/java/org/rangiffler/test/{ => gql}/BaseGraphQLTest.java (95%) rename rangiffler-e2e/src/test/java/org/rangiffler/test/{ => gql}/CountriesTest.java (96%) create mode 100644 rangiffler-e2e/src/test/java/org/rangiffler/test/web/RegistrationTest.java create mode 100644 rangiffler-gql-client/.dockerignore create mode 100644 rangiffler-gql-client/.env.docker create mode 100644 rangiffler-gql-client/Dockerfile create mode 100644 rangiffler-gql-client/nginx.conf create mode 100644 selenoid/browsers.json diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml new file mode 100644 index 0000000..f0a5ea0 --- /dev/null +++ b/.github/workflows/e2e.yml @@ -0,0 +1,73 @@ +name: e2e + +on: + pull_request: + types: [ opened, reopened, synchronize ] + +jobs: + e2e-tests: + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v4 + - name: Setup java + uses: actions/setup-java@v4 + with: + distribution: 'temurin' + java-version: '21' + - name: Setup gradle + uses: gradle/actions/setup-gradle@v4 + with: + gradle-version: '8.6' + - name: Pull browsers + run: | + docker pull selenoid/vnc_chrome:127.0 + - name: Run tests + env: + ARCH: amd64 + ALLURE_DOCKER_API: ${{ secrets.ALLURE_DOCKER_API }} + BUILD_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} + HEAD_COMMIT_MESSAGE: ${{ github.event.pull_request.head.sha || github.sha }} + EXECUTION_TYPE: github + run: | + : # build backends with profile `docker`, only for testing + bash ./gradlew jibDockerBuild -x :rangiffler-e2e:test || exit 1 + : # run e2e tests + docker compose -f docker-compose-test.yml up -d + docker ps -a + docker wait rangiffler-e2e + exit_code=$(docker inspect -f '{{.State.ExitCode}}' rangiffler-e2e) + echo "e2e_exit_code=$exit_code" >> $GITHUB_OUTPUT + echo "### Test logs ###" + docker logs rangiffler-e2e + docker compose -f docker-compose-test.yml down + docker system prune -a -f + if [ "$exit_code" -eq "0" ]; then + echo "Tests passed successfully!" + exit 0 + else + echo "Tests failed!" + exit 1 + fi + - name: Add comment to PR with link to allure + if: always() + uses: actions/github-script@v6 + with: + script: | + const issues = await github.rest.pulls.list({ + owner: context.repo.owner, + repo: context.repo.repo, + state: 'open', + head: `${context.repo.owner}:${context.ref.replace('refs/heads/', '')}` + }) + const pr = context.issue.number || issues.data[0].number + const exitCode = ${{ steps.e2e.outputs.e2e_exit_code }} + const message = exitCode == '0' ? + 'āœ… TEST RUN PASSED āœ… There is the [report](https://allure.niffler-stage.qa.guru/api/allure-docker-service/projects/rangiffler/reports/latest/index.html)\nšŸ•“ All reports [history](https://allure.niffler-stage.qa.guru/allure-docker-service-ui/projects/rangiffler)' : + 'šŸ”“ TEST RUN FAILED šŸ”“ There is the [report](https://allure.niffler-stage.qa.guru/api/allure-docker-service/projects/rangiffler/reports/latest/index.html)\nšŸ•“ All reports [history](https://allure.niffler-stage.qa.guru/allure-docker-service-ui/projects/rangiffler)' + github.rest.issues.createComment({ + issue_number: pr, + owner: context.repo.owner, + repo: context.repo.repo, + body: message + }) diff --git a/build.gradle b/build.gradle index 68d6931..e2a1759 100644 --- a/build.gradle +++ b/build.gradle @@ -17,3 +17,14 @@ allprojects { } } } + +subprojects { + ext { + dockerImage = System.getProperty("os.arch") == "aarch64" || System.getProperty("os.arch") == "arm64" + ? "arm64v8/eclipse-temurin:21-jdk" + : "eclipse-temurin:21-jdk" + dockerArch = System.getProperty("os.arch") == "aarch64" || System.getProperty("os.arch") == "arm64" + ? "arm64" + : "amd64" + } +} diff --git a/docker-compose-dev.sh b/docker-compose-dev.sh new file mode 100644 index 0000000..c25cf9d --- /dev/null +++ b/docker-compose-dev.sh @@ -0,0 +1,18 @@ +#!/bin/bash +export FRONT_VERSION="1.0" +export ARCH=$(uname -m) + +docker compose down +bash ./gradlew clean +if [ "$1" = "push" ]; then + echo "### Build & push images ###" + bash ./gradlew jib -x :rangiffler-e2e:test + docker compose push client.rangiffler.dc +else + echo "### Build images ###" + bash ./gradlew jibDockerBuild -x :rangiffler-e2e:test +fi + +docker image prune -f +docker compose up -d +docker ps -a diff --git a/docker-compose-test.yml b/docker-compose-test.yml new file mode 100644 index 0000000..43710a8 --- /dev/null +++ b/docker-compose-test.yml @@ -0,0 +1,100 @@ +services: + rangiffler-mysql: + container_name: rangiffler-mysql + image: mysql:8.3.0 + environment: + - MYSQL_ROOT_PASSWORD=secret + volumes: + - rangiffler:/var/lib/mysql + healthcheck: + test: ["CMD", "mysqladmin" ,"ping", "-h", "localhost"] + interval: 3s + timeout: 3s + retries: 5 + expose: + - 3306 + networks: + - rangiffler-network + dns_search: . + + auth.rangiffler.dc: + container_name: auth.rangiffler.dc + image: dtuchs/rangiffler-auth:latest + ports: + - 9000:9000 + depends_on: + rangiffler-mysql: + condition: service_healthy + networks: + - rangiffler-network + dns_search: . + + api.rangiffler.dc: + container_name: api.rangiffler.dc + image: dtuchs/rangiffler-api:latest + ports: + - 8080:8080 + depends_on: + auth.rangiffler.dc: + condition: service_started + networks: + - rangiffler-network + dns_search: . + + client.rangiffler.dc: + container_name: client.rangiffler.dc + image: dtuchs/rangiffler-client:latest + build: + context: ./rangiffler-gql-client + dockerfile: ./Dockerfile + args: + VERSION: ${FRONT_VERSION} + ports: + - 80:80 + depends_on: + api.rangiffler.dc: + condition: service_started + networks: + - rangiffler-network + + rangiffler-e2e: + container_name: rangiffler-e2e + image: dtuchs/rangiffler-e2e:latest + build: + context: ./ + dockerfile: ./rangiffler-e2e/Dockerfile + args: + ALLURE_DOCKER_API: ${ALLURE_DOCKER_API} + BUILD_URL: ${BUILD_URL} + HEAD_COMMIT_MESSAGE: ${HEAD_COMMIT_MESSAGE} + EXECUTION_TYPE: ${EXECUTION_TYPE} + environment: + - ALLURE_DOCKER_API=${ALLURE_DOCKER_API} + - BUILD_URL=${BUILD_URL} + - HEAD_COMMIT_MESSAGE=${HEAD_COMMIT_MESSAGE} + - EXECUTION_TYPE=${EXECUTION_TYPE} + depends_on: + client.rangiffler.dc: + condition: service_started + networks: + - rangiffler-network + + selenoid: + image: aerokube/selenoid:latest-release + platform: linux/${ARCH} + volumes: + - /selenoid:/etc/selenoid + - /var/run/docker.sock:/var/run/docker.sock + command: ["-conf", "/etc/selenoid/browsers.json", "-limit", "3", "-video-output-dir", "/opt/selenoid/video", "-log-output-dir", "/opt/selenoid/logs", "-container-network", "rangiffler_rangiffler-network"] + ports: + - "4444:4444" + networks: + - rangiffler-network + +volumes: + rangiffler: + external: true + +networks: + rangiffler-network: + driver: bridge diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..209df1e --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,66 @@ +services: + rangiffler-mysql: + container_name: rangiffler-mysql + image: mysql:8.3.0 + environment: + - MYSQL_ROOT_PASSWORD=secret + volumes: + - rangiffler:/var/lib/mysql + healthcheck: + test: ["CMD", "mysqladmin" ,"ping", "-h", "localhost"] + interval: 3s + timeout: 3s + retries: 5 + expose: + - 3306 + networks: + - rangiffler-network + dns_search: . + + auth.rangiffler.dc: + container_name: auth.rangiffler.dc + image: dtuchs/rangiffler-auth:latest + ports: + - 9000:9000 + depends_on: + rangiffler-mysql: + condition: service_healthy + networks: + - rangiffler-network + dns_search: . + + api.rangiffler.dc: + container_name: api.rangiffler.dc + image: dtuchs/rangiffler-api:latest + ports: + - 8080:8080 + depends_on: + auth.rangiffler.dc: + condition: service_started + networks: + - rangiffler-network + dns_search: . + + client.rangiffler.dc: + container_name: client.rangiffler.dc + image: dtuchs/rangiffler-client:latest + build: + context: ./rangiffler-gql-client + dockerfile: ./Dockerfile + args: + VERSION: ${FRONT_VERSION} + ports: + - 80:80 + depends_on: + api.rangiffler.dc: + condition: service_started + networks: + - rangiffler-network + +volumes: + rangiffler: + external: true + +networks: + rangiffler-network: + driver: bridge diff --git a/rangiffler-api/build.gradle b/rangiffler-api/build.gradle index 4364606..1c2b22f 100644 --- a/rangiffler-api/build.gradle +++ b/rangiffler-api/build.gradle @@ -1,6 +1,7 @@ plugins { id 'org.springframework.boot' version '3.3.4' id 'io.spring.dependency-management' version '1.1.6' + id 'com.google.cloud.tools.jib' version '3.4.1' } group = 'org.rangiffler' @@ -26,6 +27,40 @@ dependencies { annotationProcessor 'org.projectlombok:lombok:1.18.34' } +jib { + container { + ports = ['8080'] + jvmFlags = ['-Dspring.profiles.active=docker'] + creationTime = 'USE_CURRENT_TIMESTAMP' + labels = [ + 'maintainer': 'Dmitrii Tuchs @dtuchs', + 'version' : "${project.version}".toString() + ] + } + from { + image = "${project.ext.dockerImage}" + platforms { + platform { + architecture = "${project.ext.dockerArch}" + os = 'linux' + } + } + } + to { + image = "dtuchs/${project.name}" + tags = ['latest', "${project.version}"] + } +} + +tasks.jib.dependsOn test +tasks.jibDockerBuild.dependsOn test + +tasks.register('printVersion') { + doLast { + println project.version + } +} + test { useJUnitPlatform() } diff --git a/rangiffler-api/src/main/resources/application.yml b/rangiffler-api/src/main/resources/application.yml index af977fe..0063f98 100644 --- a/rangiffler-api/src/main/resources/application.yml +++ b/rangiffler-api/src/main/resources/application.yml @@ -12,13 +12,7 @@ spring: enabled: true websocket: path: /graphql - security: - oauth2: - resourceserver: - jwt: - issuer-uri: 'http://127.0.0.1:9000' datasource: - url: 'jdbc:mysql://127.0.0.1:3306/rangiffler-api?serverTimezone=UTC&createDatabaseIfNotExist=true' hikari: connection-timeout: 20000 minimum-idle: 10 @@ -50,9 +44,36 @@ spring: logging: level: root: INFO - org.springframework.web: DEBUG - org.springframework.security: DEBUG - org.springframework.security.oauth2: DEBUG + org.springframework.web: INFO + org.springframework.security: INFO + org.springframework.security.oauth2: INFO +--- +spring: + config: + activate: + on-profile: 'local' + security: + oauth2: + resourceserver: + jwt: + issuer-uri: 'http://127.0.0.1:9000' + datasource: + url: 'jdbc:mysql://127.0.0.1:3306/rangiffler-api?serverTimezone=UTC&createDatabaseIfNotExist=true' rangiffler-front: base-uri: 'http://127.0.0.1:3001' +--- +spring: + config: + activate: + on-profile: 'docker' + security: + oauth2: + resourceserver: + jwt: + issuer-uri: 'http://auth.rangiffler.dc:9000' + datasource: + url: 'jdbc:mysql://rangiffler-mysql:3306/rangiffler-api?serverTimezone=UTC&createDatabaseIfNotExist=true' +rangiffler-front: + base-uri: 'http://client.rangiffler.dc' +--- \ No newline at end of file diff --git a/rangiffler-auth/build.gradle b/rangiffler-auth/build.gradle index dc4d21a..11c04e5 100644 --- a/rangiffler-auth/build.gradle +++ b/rangiffler-auth/build.gradle @@ -1,6 +1,7 @@ plugins { id 'org.springframework.boot' version '3.3.4' id 'io.spring.dependency-management' version '1.1.6' + id 'com.google.cloud.tools.jib' version '3.4.1' } group = 'org.rangiffler' @@ -25,6 +26,40 @@ dependencies { annotationProcessor 'org.projectlombok:lombok:1.18.34' } +jib { + container { + ports = ['9000'] + jvmFlags = ['-Dspring.profiles.active=docker'] + creationTime = 'USE_CURRENT_TIMESTAMP' + labels = [ + 'maintainer': 'Dmitrii Tuchs @dtuchs', + 'version' : "${project.version}".toString() + ] + } + from { + image = "${project.ext.dockerImage}" + platforms { + platform { + architecture = "${project.ext.dockerArch}" + os = 'linux' + } + } + } + to { + image = "dtuchs/${project.name}" + tags = ['latest', "${project.version}"] + } +} + +tasks.jib.dependsOn test +tasks.jibDockerBuild.dependsOn test + +tasks.register('printVersion') { + doLast { + println project.version + } +} + test { useJUnitPlatform() } diff --git a/rangiffler-auth/src/main/resources/application.yaml b/rangiffler-auth/src/main/resources/application.yaml index 8ce99d6..18a4902 100644 --- a/rangiffler-auth/src/main/resources/application.yaml +++ b/rangiffler-auth/src/main/resources/application.yaml @@ -1,11 +1,5 @@ server: port: 9000 - # ONLY FOR TESTING - servlet: - session: - cookie: - http-only: false - secure: false spring: application: @@ -13,7 +7,6 @@ spring: mvc: log-request-details: true datasource: - url: 'jdbc:mysql://127.0.0.1:3306/rangiffler-auth?serverTimezone=UTC&createDatabaseIfNotExist=true' hikari: connection-timeout: 20000 minimum-idle: 10 @@ -45,12 +38,31 @@ spring: logging: level: root: INFO - org.springframework.web: DEBUG - org.springframework.security: DEBUG - org.springframework.security.oauth2: DEBUG -# org.springframework.boot.autoconfigure: DEBUG + org.springframework.web: INFO + org.springframework.security: INFO + org.springframework.security.oauth2: INFO + org.springframework.boot.autoconfigure: INFO +--- +spring: + config: + activate: + on-profile: 'local' + datasource: + url: 'jdbc:mysql://127.0.0.1:3306/rangiffler-auth?serverTimezone=UTC&createDatabaseIfNotExist=true' rangiffler-front: base-uri: 'http://127.0.0.1:3001' rangiffler-auth: base-uri: 'http://127.0.0.1:9000' +--- +spring: + config: + activate: + on-profile: 'docker' + datasource: + url: 'jdbc:mysql://rangiffler-mysql:3306/rangiffler-auth?serverTimezone=UTC&createDatabaseIfNotExist=true' +rangiffler-front: + base-uri: 'http://client.rangiffler.dc' +rangiffler-auth: + base-uri: 'http://auth.rangiffler.dc:9000' +--- diff --git a/rangiffler-e2e/Dockerfile b/rangiffler-e2e/Dockerfile new file mode 100644 index 0000000..4441439 --- /dev/null +++ b/rangiffler-e2e/Dockerfile @@ -0,0 +1,21 @@ +ARG ALLURE_DOCKER_API +ARG BUILD_URL +ARG HEAD_COMMIT_MESSAGE +ARG EXECUTION_TYPE + +FROM eclipse-temurin:21-jdk + +ENV ALLURE_DOCKER_API=${ALLURE_DOCKER_API} +ENV BUILD_URL=${BUILD_URL} +ENV HEAD_COMMIT_MESSAGE=${HEAD_COMMIT_MESSAGE} +ENV EXECUTION_TYPE=${EXECUTION_TYPE} + +WORKDIR /rangiffler +COPY ./gradle ./gradle +COPY ./rangiffler-e2e ./rangiffler-e2e +COPY ./gradlew ./ +COPY ./build.gradle ./ +COPY ./settings.gradle ./ +COPY ./gradle.properties ./ + +CMD ./gradlew test -Dtest.env=docker diff --git a/rangiffler-e2e/build.gradle b/rangiffler-e2e/build.gradle index faf0036..13c7a99 100644 --- a/rangiffler-e2e/build.gradle +++ b/rangiffler-e2e/build.gradle @@ -2,6 +2,7 @@ buildscript { ext { junitVersion = '5.11.1' allureVersion = '2.29.0' + selenideVersion = '7.4.2' okhttp3Version = '4.11.0' retrofitVersion = '2.11.0' logbackVersion = '1.4.11' @@ -54,6 +55,8 @@ dependencies { testImplementation "org.slf4j:slf4j-api:${slf4jVersion}" // JUnit testImplementation "org.junit.jupiter:junit-jupiter:${junitVersion}" + // Web + testImplementation "com.codeborne:selenide:${selenideVersion}" // GraphQL testImplementation 'com.apollographql.java:client:0.0.2' testImplementation 'com.apollographql.java:rx2:0.0.2' @@ -74,9 +77,7 @@ dependencies { exclude group: "com.squareup.okhttp3" } // Utils - testImplementation("com.github.javafaker:javafaker:${fakerVersion}") { - exclude group: 'org.yaml' - } + testImplementation "com.github.javafaker:javafaker:${fakerVersion}" testImplementation 'commons-io:commons-io:2.16.1' testImplementation 'com.google.code.findbugs:jsr305:3.0.2' testAnnotationProcessor 'org.projectlombok:lombok:1.18.34' diff --git a/rangiffler-e2e/src/test/java/org/rangiffler/api/AllureDockerApi.java b/rangiffler-e2e/src/test/java/org/rangiffler/api/AllureDockerApi.java new file mode 100644 index 0000000..33ffe38 --- /dev/null +++ b/rangiffler-e2e/src/test/java/org/rangiffler/api/AllureDockerApi.java @@ -0,0 +1,33 @@ +package org.rangiffler.api; + +import com.fasterxml.jackson.databind.JsonNode; +import org.rangiffler.model.AllureProject; +import org.rangiffler.model.AllureResults; +import retrofit2.Call; +import retrofit2.http.Body; +import retrofit2.http.GET; +import retrofit2.http.POST; +import retrofit2.http.Path; +import retrofit2.http.Query; + +public interface AllureDockerApi { + + @POST("allure-docker-service/send-results") + Call uploadResults(@Query("project_id") String projectId, + @Body AllureResults results); + + @GET("allure-docker-service/projects/{project_id}") + Call project(@Path("project_id") String projectId); + + @GET("allure-docker-service/clean-results") + Call cleanResults(@Query("project_id") String projectId); + + @GET("allure-docker-service/generate-report") + Call generateReport(@Query("project_id") String projectId, + @Query("execution_name") String executionName, + @Query(value = "execution_from", encoded = true) String executionFrom, + @Query("execution_type") String executionType); + + @POST("allure-docker-service/projects") + Call createProject(@Body AllureProject project); +} \ No newline at end of file diff --git a/rangiffler-e2e/src/test/java/org/rangiffler/api/AllureDockerApiClient.java b/rangiffler-e2e/src/test/java/org/rangiffler/api/AllureDockerApiClient.java new file mode 100644 index 0000000..99335cf --- /dev/null +++ b/rangiffler-e2e/src/test/java/org/rangiffler/api/AllureDockerApiClient.java @@ -0,0 +1,60 @@ +package org.rangiffler.api; + +import org.junit.jupiter.api.Assertions; +import org.rangiffler.config.Config; +import org.rangiffler.model.AllureProject; +import org.rangiffler.model.AllureResults; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import retrofit2.Retrofit; +import retrofit2.converter.jackson.JacksonConverterFactory; + +import java.io.IOException; + +public class AllureDockerApiClient { + + private static final Logger LOG = LoggerFactory.getLogger(AllureDockerApiClient.class); + private static final Config CFG = Config.getInstance(); + + private static final Retrofit retrofit = new Retrofit.Builder() + .baseUrl(CFG.allureDockerUrl()) + .addConverterFactory(JacksonConverterFactory.create()) + .build(); + + private final AllureDockerApi allureDockerApi; + + public AllureDockerApiClient() { + this.allureDockerApi = retrofit.create(AllureDockerApi.class); + } + + public void clean(String projectId) throws IOException { + allureDockerApi.cleanResults(projectId).execute(); + } + + public void generateReport(String projectId, + String executionName, + String executionFrom, + String executionType) throws IOException { + allureDockerApi.generateReport(projectId, executionName, executionFrom, executionType).execute(); + } + + public void sendResultsToAllure(String projectId, AllureResults allureResults) throws IOException { + int code = allureDockerApi.uploadResults( + projectId, + allureResults + ).execute().code(); + Assertions.assertEquals(200, code); + } + + public void createProjectIfNotExist(String projectId) throws IOException { + int code = allureDockerApi.project( + projectId + ).execute().code(); + if (code == 404) { + code = allureDockerApi.createProject(new AllureProject(projectId)).execute().code(); + Assertions.assertEquals(201, code); + } else { + Assertions.assertEquals(200, code); + } + } +} diff --git a/rangiffler-e2e/src/test/java/org/rangiffler/config/Config.java b/rangiffler-e2e/src/test/java/org/rangiffler/config/Config.java index 2e1e030..4082744 100644 --- a/rangiffler-e2e/src/test/java/org/rangiffler/config/Config.java +++ b/rangiffler-e2e/src/test/java/org/rangiffler/config/Config.java @@ -3,7 +3,9 @@ public interface Config { static Config getInstance() { - return LocalConfig.INSTANCE; + return "docker".equals(System.getProperty("test.env")) + ? DockerConfig.INSTANCE + : LocalConfig.INSTANCE; } String authUrl(); @@ -14,4 +16,6 @@ static Config getInstance() { String apiJdbcUrl(); + String allureDockerUrl(); + } diff --git a/rangiffler-e2e/src/test/java/org/rangiffler/config/DockerConfig.java b/rangiffler-e2e/src/test/java/org/rangiffler/config/DockerConfig.java new file mode 100644 index 0000000..22666ac --- /dev/null +++ b/rangiffler-e2e/src/test/java/org/rangiffler/config/DockerConfig.java @@ -0,0 +1,47 @@ +package org.rangiffler.config; + +import com.codeborne.selenide.Configuration; +import org.openqa.selenium.chrome.ChromeOptions; + +public class DockerConfig implements Config { + + static final DockerConfig INSTANCE = new DockerConfig(); + + static { + Configuration.browserSize = "1920x1200"; + Configuration.remote = "http://selenoid:4444/wd/hub"; + Configuration.timeout = 10000; + Configuration.browser = "chrome"; + Configuration.browserVersion = "127.0"; + Configuration.pageLoadStrategy = "eager"; + Configuration.browserCapabilities = new ChromeOptions().addArguments("--no-sandbox"); + } + + private DockerConfig() { + } + + @Override + public String frontUrl() { + return "http://client.rangiffler.dc"; + } + + @Override + public String apiUrl() { + return "http://api.rangiffler.dc:8080"; + } + + @Override + public String authUrl() { + return "http://auth.rangiffler.dc:9000"; + } + + @Override + public String apiJdbcUrl() { + return "jdbc:mysql://rangiffler-mysql:3306/rangiffler-api?serverTimezone=UTC"; + } + + @Override + public String allureDockerUrl() { + return System.getenv("ALLURE_DOCKER_API"); + } +} diff --git a/rangiffler-e2e/src/test/java/org/rangiffler/config/LocalConfig.java b/rangiffler-e2e/src/test/java/org/rangiffler/config/LocalConfig.java index 1838fbc..4985c9d 100644 --- a/rangiffler-e2e/src/test/java/org/rangiffler/config/LocalConfig.java +++ b/rangiffler-e2e/src/test/java/org/rangiffler/config/LocalConfig.java @@ -26,4 +26,9 @@ public String authUrl() { public String apiJdbcUrl() { return "jdbc:mysql://127.0.0.1:3306/rangiffler-api?serverTimezone=UTC"; } + + @Override + public String allureDockerUrl() { + return null; + } } diff --git a/rangiffler-e2e/src/test/java/org/rangiffler/jupiter/AllureDockerExtension.java b/rangiffler-e2e/src/test/java/org/rangiffler/jupiter/AllureDockerExtension.java new file mode 100644 index 0000000..49d3240 --- /dev/null +++ b/rangiffler-e2e/src/test/java/org/rangiffler/jupiter/AllureDockerExtension.java @@ -0,0 +1,73 @@ +package org.rangiffler.jupiter; + +import lombok.SneakyThrows; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.rangiffler.api.AllureDockerApiClient; +import org.rangiffler.model.AllureResults; +import org.rangiffler.model.DecodedAllureFile; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Base64; +import java.util.List; +import java.util.stream.Stream; + +public class AllureDockerExtension implements SuiteExtension { + + private static final Logger LOG = LoggerFactory.getLogger(AllureDockerExtension.class); + + private static final Base64.Encoder encoder = Base64.getEncoder(); + private static final String allureResultsDirectory = "./rangiffler-e2e/build/allure-results"; + private static final String projectId = "rangiffler"; + + private static final AllureDockerApiClient allureDockerApiClient = new AllureDockerApiClient(); + + @Override + @SneakyThrows + public void beforeSuite(ExtensionContext context) { + if ("docker".equals(System.getProperty("test.env"))) { + allureDockerApiClient.createProjectIfNotExist(projectId); + allureDockerApiClient.clean(projectId); + } + } + + @Override + public void afterSuite() { + if ("docker".equals(System.getProperty("test.env"))) { + try (Stream paths = Files.walk(Path.of(allureResultsDirectory))) { + List allureResults = paths.filter(Files::isRegularFile).toList(); + List filesToSend = new ArrayList<>(); + for (Path allureResult : allureResults) { + try (InputStream is = Files.newInputStream(allureResult)) { + filesToSend.add( + new DecodedAllureFile( + allureResult.getFileName().toString(), + encoder.encodeToString(is.readAllBytes()) + ) + ); + } + } + allureDockerApiClient.createProjectIfNotExist(projectId); + allureDockerApiClient.sendResultsToAllure( + projectId, + new AllureResults( + filesToSend + ) + ); + allureDockerApiClient.generateReport( + projectId, + System.getenv("HEAD_COMMIT_MESSAGE"), + System.getenv("BUILD_URL"), + System.getenv("EXECUTION_TYPE") + ); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + } +} \ No newline at end of file diff --git a/rangiffler-e2e/src/test/java/org/rangiffler/jupiter/SuiteExtension.java b/rangiffler-e2e/src/test/java/org/rangiffler/jupiter/SuiteExtension.java new file mode 100644 index 0000000..3927dd9 --- /dev/null +++ b/rangiffler-e2e/src/test/java/org/rangiffler/jupiter/SuiteExtension.java @@ -0,0 +1,24 @@ +package org.rangiffler.jupiter; + +import org.junit.jupiter.api.extension.BeforeAllCallback; +import org.junit.jupiter.api.extension.ExtensionContext; + +public interface SuiteExtension extends BeforeAllCallback { + + default void beforeSuite(ExtensionContext context) { + } + + default void afterSuite() { + } + + @Override + default void beforeAll(ExtensionContext context) { + context.getRoot().getStore(ExtensionContext.Namespace.GLOBAL). + getOrComputeIfAbsent(this.getClass(), + k -> { + beforeSuite(context); + return (ExtensionContext.Store.CloseableResource) this::afterSuite; + } + ); + } +} diff --git a/rangiffler-e2e/src/test/java/org/rangiffler/model/AllureProject.java b/rangiffler-e2e/src/test/java/org/rangiffler/model/AllureProject.java new file mode 100644 index 0000000..e6f0e17 --- /dev/null +++ b/rangiffler-e2e/src/test/java/org/rangiffler/model/AllureProject.java @@ -0,0 +1,4 @@ +package org.rangiffler.model; + +public record AllureProject(String id) { +} \ No newline at end of file diff --git a/rangiffler-e2e/src/test/java/org/rangiffler/model/AllureResults.java b/rangiffler-e2e/src/test/java/org/rangiffler/model/AllureResults.java new file mode 100644 index 0000000..71d2839 --- /dev/null +++ b/rangiffler-e2e/src/test/java/org/rangiffler/model/AllureResults.java @@ -0,0 +1,8 @@ +package org.rangiffler.model; + +import com.fasterxml.jackson.annotation.JsonProperty; + +import java.util.List; + +public record AllureResults(@JsonProperty("results") List results) { +} diff --git a/rangiffler-e2e/src/test/java/org/rangiffler/model/DecodedAllureFile.java b/rangiffler-e2e/src/test/java/org/rangiffler/model/DecodedAllureFile.java new file mode 100644 index 0000000..fbd8184 --- /dev/null +++ b/rangiffler-e2e/src/test/java/org/rangiffler/model/DecodedAllureFile.java @@ -0,0 +1,7 @@ +package org.rangiffler.model; + +import com.fasterxml.jackson.annotation.JsonProperty; + +public record DecodedAllureFile(@JsonProperty("file_name") String fileName, + @JsonProperty("content_base64") String contentBase64) { +} diff --git a/rangiffler-e2e/src/test/java/org/rangiffler/page/LoginPage.java b/rangiffler-e2e/src/test/java/org/rangiffler/page/LoginPage.java new file mode 100644 index 0000000..13e60b0 --- /dev/null +++ b/rangiffler-e2e/src/test/java/org/rangiffler/page/LoginPage.java @@ -0,0 +1,22 @@ +package org.rangiffler.page; + +import com.codeborne.selenide.SelenideElement; +import io.qameta.allure.Step; + +import static com.codeborne.selenide.Condition.text; +import static com.codeborne.selenide.Selenide.$; + +public class LoginPage { + + private final SelenideElement usernameInput = $("input[name='username']"); + private final SelenideElement passwordInput = $("input[name='password']"); + private final SelenideElement submitButton = $("button[type='submit']"); + + @Step("Login with credentials: {username} / {password}") + public MainPage login(String username, String password) { + usernameInput.setValue(username); + passwordInput.setValue(password); + submitButton.click(); + return new MainPage(); + } +} diff --git a/rangiffler-e2e/src/test/java/org/rangiffler/page/MainPage.java b/rangiffler-e2e/src/test/java/org/rangiffler/page/MainPage.java new file mode 100644 index 0000000..ccf7bad --- /dev/null +++ b/rangiffler-e2e/src/test/java/org/rangiffler/page/MainPage.java @@ -0,0 +1,20 @@ +package org.rangiffler.page; + +import com.codeborne.selenide.SelenideElement; +import io.qameta.allure.Step; + +import static com.codeborne.selenide.Condition.visible; +import static com.codeborne.selenide.Selenide.$; + +public class MainPage { + + private final SelenideElement header = $("#root header"); + private final SelenideElement map = $(".worldmap__figure-container"); + + @Step("Check that main page loaded") + public MainPage checkThatPageLoaded() { + header.should(visible); + map.should(visible); + return this; + } +} diff --git a/rangiffler-e2e/src/test/java/org/rangiffler/page/RegisterPage.java b/rangiffler-e2e/src/test/java/org/rangiffler/page/RegisterPage.java new file mode 100644 index 0000000..6ad4f28 --- /dev/null +++ b/rangiffler-e2e/src/test/java/org/rangiffler/page/RegisterPage.java @@ -0,0 +1,31 @@ +package org.rangiffler.page; + +import com.codeborne.selenide.SelenideElement; +import io.qameta.allure.Step; + +import static com.codeborne.selenide.Condition.text; +import static com.codeborne.selenide.Selenide.$; + +public class RegisterPage { + + private final SelenideElement usernameInput = $("input[name='username']"); + private final SelenideElement passwordInput = $("input[name='password']"); + private final SelenideElement passwordSubmitInput = $("input[name='passwordSubmit']"); + private final SelenideElement submitButton = $("button[type='submit']"); + private final SelenideElement proceedLoginButton = $(".form_sign-in"); + + @Step("Fill registration page") + public RegisterPage fillRegisterPage(String login, String password, String passwordSubmit) { + usernameInput.setValue(login); + passwordInput.setValue(password); + passwordSubmitInput.setValue(passwordSubmit); + return this; + } + + @Step("Submit registration page") + public LoginPage submit() { + submitButton.click(); + proceedLoginButton.click(); + return new LoginPage(); + } +} diff --git a/rangiffler-e2e/src/test/java/org/rangiffler/page/WelcomePage.java b/rangiffler-e2e/src/test/java/org/rangiffler/page/WelcomePage.java new file mode 100644 index 0000000..0baed80 --- /dev/null +++ b/rangiffler-e2e/src/test/java/org/rangiffler/page/WelcomePage.java @@ -0,0 +1,21 @@ +package org.rangiffler.page; + +import com.codeborne.selenide.ElementsCollection; +import com.codeborne.selenide.SelenideElement; +import io.qameta.allure.Step; + +import static com.codeborne.selenide.Condition.text; +import static com.codeborne.selenide.Condition.visible; +import static com.codeborne.selenide.Selenide.$; + +public class WelcomePage { + + private final SelenideElement loginBtn = $("button[type='button']"); + private final SelenideElement registerBtn = $("a[href*='register']"); + + @Step("Open registration page") + public RegisterPage register() { + registerBtn.click(); + return new RegisterPage(); + } +} diff --git a/rangiffler-e2e/src/test/java/org/rangiffler/test/BaseGraphQLTest.java b/rangiffler-e2e/src/test/java/org/rangiffler/test/gql/BaseGraphQLTest.java similarity index 95% rename from rangiffler-e2e/src/test/java/org/rangiffler/test/BaseGraphQLTest.java rename to rangiffler-e2e/src/test/java/org/rangiffler/test/gql/BaseGraphQLTest.java index 377359a..d63a802 100644 --- a/rangiffler-e2e/src/test/java/org/rangiffler/test/BaseGraphQLTest.java +++ b/rangiffler-e2e/src/test/java/org/rangiffler/test/gql/BaseGraphQLTest.java @@ -1,4 +1,4 @@ -package org.rangiffler.test; +package org.rangiffler.test.gql; import com.apollographql.java.client.ApolloClient; import io.qameta.allure.okhttp3.AllureOkHttp3; diff --git a/rangiffler-e2e/src/test/java/org/rangiffler/test/CountriesTest.java b/rangiffler-e2e/src/test/java/org/rangiffler/test/gql/CountriesTest.java similarity index 96% rename from rangiffler-e2e/src/test/java/org/rangiffler/test/CountriesTest.java rename to rangiffler-e2e/src/test/java/org/rangiffler/test/gql/CountriesTest.java index ba74413..7df6e45 100644 --- a/rangiffler-e2e/src/test/java/org/rangiffler/test/CountriesTest.java +++ b/rangiffler-e2e/src/test/java/org/rangiffler/test/gql/CountriesTest.java @@ -1,10 +1,11 @@ -package org.rangiffler.test; +package org.rangiffler.test.gql; import com.apollographql.apollo.api.ApolloResponse; import com.apollographql.apollo.api.Error; import com.apollographql.apollo.exception.ApolloHttpException; import com.apollographql.java.client.ApolloCall; import com.apollographql.java.rx2.Rx2Apollo; +import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.rangiffler.GetCountriesQuery; import org.rangiffler.jupiter.ApiLogin; @@ -13,6 +14,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNull; +@Disabled public class CountriesTest extends BaseGraphQLTest { @Test diff --git a/rangiffler-e2e/src/test/java/org/rangiffler/test/web/RegistrationTest.java b/rangiffler-e2e/src/test/java/org/rangiffler/test/web/RegistrationTest.java new file mode 100644 index 0000000..4d71259 --- /dev/null +++ b/rangiffler-e2e/src/test/java/org/rangiffler/test/web/RegistrationTest.java @@ -0,0 +1,25 @@ +package org.rangiffler.test.web; + +import com.codeborne.selenide.Selenide; +import com.github.javafaker.Faker; +import org.junit.jupiter.api.Test; +import org.rangiffler.config.Config; +import org.rangiffler.page.WelcomePage; + +public class RegistrationTest { + + private static final Config CFG = Config.getInstance(); + private static final Faker faker = new Faker(); + + @Test + void shouldRegisterNewUser() { + String newUsername = faker.name().username(); + String password = "12345"; + Selenide.open(CFG.frontUrl(), WelcomePage.class) + .register() + .fillRegisterPage(newUsername, password, password) + .submit() + .login(newUsername, password) + .checkThatPageLoaded(); + } +} diff --git a/rangiffler-e2e/src/test/resources/META-INF/services/org.junit.jupiter.api.extension.Extension b/rangiffler-e2e/src/test/resources/META-INF/services/org.junit.jupiter.api.extension.Extension index 26adc42..29e4192 100644 --- a/rangiffler-e2e/src/test/resources/META-INF/services/org.junit.jupiter.api.extension.Extension +++ b/rangiffler-e2e/src/test/resources/META-INF/services/org.junit.jupiter.api.extension.Extension @@ -1 +1,2 @@ org.rangiffler.jupiter.ContextHolderExtension +org.rangiffler.jupiter.AllureDockerExtension diff --git a/rangiffler-gql-client/.dockerignore b/rangiffler-gql-client/.dockerignore new file mode 100644 index 0000000..b68a7a0 --- /dev/null +++ b/rangiffler-gql-client/.dockerignore @@ -0,0 +1,3 @@ +node_modules +.vite +dist diff --git a/rangiffler-gql-client/.env.docker b/rangiffler-gql-client/.env.docker new file mode 100644 index 0000000..a3a9747 --- /dev/null +++ b/rangiffler-gql-client/.env.docker @@ -0,0 +1,6 @@ +VITE_AUTH_URL=http://auth.rangiffler.dc:9000 +VITE_API_URL=http://api.rangiffler.dc:8080 +VITE_FRONT_HOST=client.rangiffler.dc +VITE_FRONT_URL=http://client.rangiffler.dc +VITE_RESPONSE_TYPE=code +VITE_CLIENT_ID=client \ No newline at end of file diff --git a/rangiffler-gql-client/Dockerfile b/rangiffler-gql-client/Dockerfile new file mode 100644 index 0000000..43c7b52 --- /dev/null +++ b/rangiffler-gql-client/Dockerfile @@ -0,0 +1,16 @@ +FROM node:22.6.0-alpine AS build +ARG VERSION +WORKDIR /app +COPY package.json ./ +RUN npm install +COPY . ./ +RUN npm run build:docker + +# release step +FROM nginx:1.27.1-alpine AS release +LABEL maintainer="Dmitrii Tuchs @dtuchs" +LABEL version=${VERSION} +COPY nginx.conf /etc/nginx/conf.d/default.conf +COPY --from=build /app/dist /usr/share/nginx/html/ +EXPOSE 80 +CMD ["nginx", "-g", "daemon off;"] diff --git a/rangiffler-gql-client/nginx.conf b/rangiffler-gql-client/nginx.conf new file mode 100644 index 0000000..4d14ea2 --- /dev/null +++ b/rangiffler-gql-client/nginx.conf @@ -0,0 +1,15 @@ +server { + listen 80; + server_name localhost; + + location / { + root /usr/share/nginx/html; + index index.html index.htm; + try_files $uri /index.html; + } + + error_page 500 502 503 504 /50x.html; + location = /50x.html { + root /usr/share/nginx/html; + } +} diff --git a/rangiffler-gql-client/package.json b/rangiffler-gql-client/package.json index 1b4a053..eae285c 100644 --- a/rangiffler-gql-client/package.json +++ b/rangiffler-gql-client/package.json @@ -5,7 +5,8 @@ "type": "module", "scripts": { "dev": "vite", - "build": "tsc && vite build", + "build": "vite build", + "build:docker": "vite build --mode docker", "lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0", "preview": "vite preview" }, diff --git a/selenoid/browsers.json b/selenoid/browsers.json new file mode 100644 index 0000000..a1f70f1 --- /dev/null +++ b/selenoid/browsers.json @@ -0,0 +1,15 @@ +{ + "chrome": { + "default": "127.0", + "versions": { + "125.0": { + "image": "selenoid/vnc_chrome:125.0", + "port": "4444" + }, + "127.0": { + "image": "selenoid/vnc_chrome:127.0", + "port": "4444" + } + } + } +} \ No newline at end of file From 91ff94bccd92b2c26d20c3a21c1c900ad03a64e3 Mon Sep 17 00:00:00 2001 From: dtuchs Date: Thu, 10 Oct 2024 23:27:36 +0500 Subject: [PATCH 2/2] fix --- docker-compose-test.yml | 6 ------ 1 file changed, 6 deletions(-) diff --git a/docker-compose-test.yml b/docker-compose-test.yml index 43710a8..e60d34a 100644 --- a/docker-compose-test.yml +++ b/docker-compose-test.yml @@ -4,8 +4,6 @@ services: image: mysql:8.3.0 environment: - MYSQL_ROOT_PASSWORD=secret - volumes: - - rangiffler:/var/lib/mysql healthcheck: test: ["CMD", "mysqladmin" ,"ping", "-h", "localhost"] interval: 3s @@ -91,10 +89,6 @@ services: networks: - rangiffler-network -volumes: - rangiffler: - external: true - networks: rangiffler-network: driver: bridge