From 30117e26aa7650a143923fcf3042331450af48a9 Mon Sep 17 00:00:00 2001 From: michelzzw Date: Fri, 30 Jan 2026 08:17:59 -0500 Subject: [PATCH 01/21] Update yarn.lock --- testapi-Service/frontend/yarn.lock | 33 +++++------------------------- 1 file changed, 5 insertions(+), 28 deletions(-) diff --git a/testapi-Service/frontend/yarn.lock b/testapi-Service/frontend/yarn.lock index d7ee57ff..d067c4b0 100644 --- a/testapi-Service/frontend/yarn.lock +++ b/testapi-Service/frontend/yarn.lock @@ -2883,16 +2883,16 @@ "resolved" "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-0.9.3.tgz" "version" "0.9.3" -"esbuild-darwin-64@0.14.22": - "integrity" "sha512-d8Ceuo6Vw6HM3fW218FB6jTY6O3r2WNcTAU0SGsBkXZ3k8SDoRLd3Nrc//EqzdgYnzDNMNtrWegK2Qsss4THhw==" - "resolved" "https://registry.npmjs.org/esbuild-darwin-64/-/esbuild-darwin-64-0.14.22.tgz" - "version" "0.14.22" - "esbuild-wasm@0.14.22": "integrity" "sha512-FOSAM29GN1fWusw0oLMv6JYhoheDIh5+atC72TkJKfIUMID6yISlicoQSd9gsNSFsNBvABvtE2jR4JB1j4FkFw==" "resolved" "https://registry.npmjs.org/esbuild-wasm/-/esbuild-wasm-0.14.22.tgz" "version" "0.14.22" +"esbuild-windows-64@0.14.22": + "integrity" "sha512-AxjIDcOmx17vr31C5hp20HIwz1MymtMjKqX4qL6whPj0dT9lwxPexmLj6G1CpR3vFhui6m75EnBEe4QL82SYqw==" + "resolved" "https://registry.npmjs.org/esbuild-windows-64/-/esbuild-windows-64-0.14.22.tgz" + "version" "0.14.22" + "esbuild@0.14.22": "integrity" "sha512-CjFCFGgYtbFOPrwZNJf7wsuzesx8kqwAffOlbYcFDLFuUtP8xloK1GH+Ai13Qr0RZQf9tE7LMTHJ2iVGJ1SKZA==" "resolved" "https://registry.npmjs.org/esbuild/-/esbuild-0.14.22.tgz" @@ -3197,11 +3197,6 @@ "resolved" "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz" "version" "1.0.0" -"fsevents@~2.3.2": - "integrity" "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==" - "resolved" "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz" - "version" "2.3.2" - "function-bind@^1.1.1": "integrity" "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" "resolved" "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz" @@ -4426,29 +4421,11 @@ "resolved" "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz" "version" "2.6.2" -"nice-napi@^1.0.2": - "integrity" "sha512-px/KnJAJZf5RuBGcfD+Sp2pAKq0ytz8j+1NehvgIGFkvtvFrDM3T8E4x/JJODXK9WZow8RRGrbA9QQ3hs+pDhA==" - "resolved" "https://registry.npmjs.org/nice-napi/-/nice-napi-1.0.2.tgz" - "version" "1.0.2" - dependencies: - "node-addon-api" "^3.0.0" - "node-gyp-build" "^4.2.2" - -"node-addon-api@^3.0.0": - "integrity" "sha512-mmcei9JghVNDYydghQmeDX8KoAm0FAiYyIcUt/N4nhyAipB17pllZQDOJD2fotxABnt4Mdz+dKTO7eftLg4d0A==" - "resolved" "https://registry.npmjs.org/node-addon-api/-/node-addon-api-3.2.1.tgz" - "version" "3.2.1" - "node-forge@^1": "integrity" "sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==" "resolved" "https://registry.npmjs.org/node-forge/-/node-forge-1.3.1.tgz" "version" "1.3.1" -"node-gyp-build@^4.2.2": - "integrity" "sha512-amJnQCcgtRVw9SvoebO3BKGESClrfXGCUTX9hSn1OuGQTQBOZmVd0Z0OlecpuRksKvbsUqALE8jls/ErClAPuQ==" - "resolved" "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.4.0.tgz" - "version" "4.4.0" - "node-gyp@^8.2.0": "integrity" "sha512-olTJRgUtAb/hOXG0E93wZDs5YiJlgbXxTwQAFHyNlRsXQnYzUaF2aGgujZbw+hR8aF4ZG/rST57bWMWD16jr9w==" "resolved" "https://registry.npmjs.org/node-gyp/-/node-gyp-8.4.1.tgz" From 23daacdf8e18cdbd5f5215b1f7bc32b443e13b51 Mon Sep 17 00:00:00 2001 From: michelzzw Date: Wed, 11 Feb 2026 04:38:10 -0500 Subject: [PATCH 02/21] declaration fix declaration fix --- .../java/ca/estmtl/taf/registry/RegistryApplicationTests.java | 2 +- .../java/ca/etsmtl/selenium/config/DevCorsConfiguration.java | 2 +- .../java/ca/etsmtl/taf/user/AuthGatewayApplicationTests.java | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/registry/src/test/java/ca/estmtl/taf/registry/RegistryApplicationTests.java b/registry/src/test/java/ca/estmtl/taf/registry/RegistryApplicationTests.java index 3491507c..6e317e1a 100644 --- a/registry/src/test/java/ca/estmtl/taf/registry/RegistryApplicationTests.java +++ b/registry/src/test/java/ca/estmtl/taf/registry/RegistryApplicationTests.java @@ -1,4 +1,4 @@ -package ca.etsmtl.taf.registry; +package ca.estmtl.taf.registry; import org.junit.jupiter.api.Test; import org.springframework.boot.test.context.SpringBootTest; diff --git a/testapi-Service/selenium/src/main/java/ca/etsmtl/selenium/config/DevCorsConfiguration.java b/testapi-Service/selenium/src/main/java/ca/etsmtl/selenium/config/DevCorsConfiguration.java index 7b8fdca5..25212fc7 100644 --- a/testapi-Service/selenium/src/main/java/ca/etsmtl/selenium/config/DevCorsConfiguration.java +++ b/testapi-Service/selenium/src/main/java/ca/etsmtl/selenium/config/DevCorsConfiguration.java @@ -1,4 +1,4 @@ -package org.config; +package ca.etsmtl.selenium.config; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Profile; diff --git a/user/src/test/java/ca/etsmtl/taf/user/AuthGatewayApplicationTests.java b/user/src/test/java/ca/etsmtl/taf/user/AuthGatewayApplicationTests.java index eac025e5..4637dda5 100644 --- a/user/src/test/java/ca/etsmtl/taf/user/AuthGatewayApplicationTests.java +++ b/user/src/test/java/ca/etsmtl/taf/user/AuthGatewayApplicationTests.java @@ -1,4 +1,4 @@ -package ca.etsmtl.taf.auth; +package ca.etsmtl.taf.user; import org.junit.jupiter.api.Test; import org.springframework.boot.test.context.SpringBootTest; From 46f30ac4c83e7d859d9c05e0ab17047dfa29ec40 Mon Sep 17 00:00:00 2001 From: michelzzw Date: Wed, 11 Feb 2026 05:49:03 -0500 Subject: [PATCH 03/21] Update docker-compose-local-test.yml The previous teams created two independent services (backend and testapi) to run the TestAPI functionality, but only backend-team2 was included in the local deployment configuration. This caused the backend to fail when trying to forward API test execution requests to the testapi service. Changes: - Add testapi-team2 service definition (build from testapi-Service/testapi, port 8082, mapped to host 8086) - Fix TEST_API_SERVICE_PORT for backend-team2 (8080 -> 8082) - Add testapi-team2 to backend-team2 depends_on --- docker-compose-local-test.yml | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/docker-compose-local-test.yml b/docker-compose-local-test.yml index 65acb975..4ac68e69 100644 --- a/docker-compose-local-test.yml +++ b/docker-compose-local-test.yml @@ -222,6 +222,27 @@ services: retries: 3 start_period: 90s + # ============================================ + # TESTAPI - TEAM 2 (REST-Assured Test Executor) + # ============================================ + + testapi-team2: + build: + context: ./testapi-Service/testapi + dockerfile: Dockerfile + container_name: taf-testapi-team2 + restart: unless-stopped + ports: + - "8086:8082" + networks: + - taf-local + healthcheck: + test: ["CMD", "curl", "-f", "http://localhost:8082/actuator/health"] + interval: 30s + timeout: 10s + retries: 3 + start_period: 90s + # ============================================ # TESTING BACKEND - TEAM 2 (TESTAPI-SERVICE) # ============================================ @@ -249,12 +270,14 @@ services: SELENIUM_URL: http://selenium-team2 SELENIUM_PORT: 4444 TEST_API_SERVICE_URL: http://testapi-team2 - TEST_API_SERVICE_PORT: 8080 + TEST_API_SERVICE_PORT: 8082 depends_on: mongodb: condition: service_healthy registry: condition: service_healthy + testapi-team2: + condition: service_started networks: - taf-local healthcheck: From e9110834705b794d4a891a69e96da9b3bd096549 Mon Sep 17 00:00:00 2001 From: michelzzw Date: Wed, 11 Feb 2026 10:41:28 -0500 Subject: [PATCH 04/21] Fix NPE crashes in testapi RequestController The testapi microservice crashed with NullPointerException or unhandled ConnectException when: 1. The target API was unreachable (e.g. DNS resolution failure inside Docker container) 2. The request body was missing optional fields like expectedHeaders Changes: - Add try-catch in execute() to handle ConnectException when the target API is unreachable - Add null check for response in getAnswer() to return a structured error message instead of crashing - Add null check in checkResponseHeaders() for missing expectedHeaders field Before: 500 Internal Server Error with raw stack trace After: Structured JSON response with clear error message --- .../java/org/requests/RequestController.java | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/testapi-Service/testapi/src/main/java/org/requests/RequestController.java b/testapi-Service/testapi/src/main/java/org/requests/RequestController.java index e0d3d01b..5bb825b4 100644 --- a/testapi-Service/testapi/src/main/java/org/requests/RequestController.java +++ b/testapi-Service/testapi/src/main/java/org/requests/RequestController.java @@ -39,6 +39,15 @@ public Answer getAnswer() { this.execute(); Answer answer = new Answer(); + + // If request failed (connection refused, timeout, etc.) + if (this.response == null) { + answer.answer = false; + answer.statusCode = -1; + answer.output = ""; + answer.messages.add("❌ Erreur: Impossible de joindre l'API cible à " + this.request.getApiUrl()); + return answer; + } answer.statusCode = this.response.getStatusCode(); answer.output = this.response.getBody().asPrettyString(); @@ -83,9 +92,14 @@ public Answer getAnswer() { - private void execute() { +private void execute() { + try { this.response = this.request.getMethod().execute(this.httpRequest, this.request.getApiUrl()); + } catch (Exception e) { + System.out.println("ERROR: Failed to execute request to " + this.request.getApiUrl() + " : " + e.getMessage()); + this.response = null; } +} private boolean checkStatusCode() { System.out.println("Expected Status Code: " + this.request.getStatusCode()); @@ -170,6 +184,9 @@ private boolean checkResponseTime() { private boolean checkResponseHeaders() { boolean ok = true; + if (request.getExpectedHeaders() == null || request.getExpectedHeaders().isEmpty()) { + return true; + } for (Map.Entry expected : request.getExpectedHeaders().entrySet()) { String foundValue = response.header(expected.getKey()); From 728cee0e75d4f2cb52ad1d94438cc4a2fbf52af3 Mon Sep 17 00:00:00 2001 From: michelzzw Date: Wed, 11 Feb 2026 10:41:59 -0500 Subject: [PATCH 05/21] Add DNS configuration for testapi-team2 container The testapi container could not resolve external domain names due to Docker's internal DNS limitations, causing Rest-Assured API calls to fail with ConnectException. Added Google public DNS (8.8.8.8, 8.8.4.4) to testapi-team2 service to enable external API access from within the container. --- docker-compose-local-test.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docker-compose-local-test.yml b/docker-compose-local-test.yml index 4ac68e69..ce362a66 100644 --- a/docker-compose-local-test.yml +++ b/docker-compose-local-test.yml @@ -234,6 +234,9 @@ services: restart: unless-stopped ports: - "8086:8082" + dns: + - 8.8.8.8 + - 8.8.4.4 networks: - taf-local healthcheck: From 3f655a28ba7ad7d26802be447b7d70ae3ab91fc8 Mon Sep 17 00:00:00 2001 From: michelzzw Date: Wed, 11 Feb 2026 11:22:43 -0500 Subject: [PATCH 06/21] Add Swagger @Schema examples to backend TestApiRequest Updated field annotations to provide meaningful default values in Swagger UI "Try it out", replacing generic "string" placeholders with realistic examples (method: GET, apiUrl: jsonplaceholder endpoint, statusCode: 200). This makes the Swagger interface more user-friendly for testing and demo purposes. --- .../taf/payload/request/TestApiRequest.java | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/testapi-Service/backend/src/main/java/ca/etsmtl/taf/payload/request/TestApiRequest.java b/testapi-Service/backend/src/main/java/ca/etsmtl/taf/payload/request/TestApiRequest.java index 3fd34b57..fe5e9755 100644 --- a/testapi-Service/backend/src/main/java/ca/etsmtl/taf/payload/request/TestApiRequest.java +++ b/testapi-Service/backend/src/main/java/ca/etsmtl/taf/payload/request/TestApiRequest.java @@ -1,21 +1,29 @@ package ca.etsmtl.taf.payload.request; + +import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotBlank; import java.util.Map; public class TestApiRequest { @NotBlank + @Schema(example = "GET") private String method; @NotBlank + @Schema(example = "https://jsonplaceholder.typicode.com/posts/1") private String apiUrl; + @Schema(example = "200") private int statusCode; - + + @Schema(example = "") private String input; - + + @Schema(example = "") private String expectedOutput; - private Map headers; // Ajout du champ manquant + @Schema(example = "{}") + private Map headers; // Getters et setters public String getMethod() { return this.method; } @@ -35,4 +43,4 @@ public class TestApiRequest { public Map getHeaders() { return headers; } public void setHeaders(Map headers) { this.headers = headers; } -} +} \ No newline at end of file From d3b8a3ce395b69bac6c3d5191ee5e565a12a190d Mon Sep 17 00:00:00 2001 From: michelzzw Date: Wed, 11 Feb 2026 12:06:43 -0500 Subject: [PATCH 07/21] solve version conflict Java and spring boot version conflict solved --- .vscode/settings.json | 4 ++++ testapi-Service/gatling/pom.xml | 4 ++-- testapi-Service/pom.xml | 2 +- testapi-Service/selenium/pom.xml | 18 +++++++--------- testapi-Service/testapi/Dockerfile | 2 +- testapi-Service/testapi/pom.xml | 33 ++++++++++++++++++------------ 6 files changed, 35 insertions(+), 28 deletions(-) create mode 100644 .vscode/settings.json diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 00000000..b84f89c3 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,4 @@ +{ + "java.configuration.updateBuildConfiguration": "interactive", + "java.compile.nullAnalysis.mode": "automatic" +} \ No newline at end of file diff --git a/testapi-Service/gatling/pom.xml b/testapi-Service/gatling/pom.xml index f4df009b..24808e8e 100644 --- a/testapi-Service/gatling/pom.xml +++ b/testapi-Service/gatling/pom.xml @@ -101,8 +101,8 @@ maven-compiler-plugin 3.10.1 - 11 - 11 + 17 + 17 diff --git a/testapi-Service/pom.xml b/testapi-Service/pom.xml index fb81e5b5..63578ec4 100644 --- a/testapi-Service/pom.xml +++ b/testapi-Service/pom.xml @@ -37,7 +37,7 @@ 17 1.18.24 - 3.3.5 + 3.5.3 pom diff --git a/testapi-Service/selenium/pom.xml b/testapi-Service/selenium/pom.xml index 358427a1..6dda0869 100644 --- a/testapi-Service/selenium/pom.xml +++ b/testapi-Service/selenium/pom.xml @@ -12,8 +12,8 @@ selenium - 8 - 8 + 17 + 17 UTF-8 ca.etsmtl.selenium.requests.SeleniumApplication @@ -23,30 +23,26 @@ org.springframework.boot spring-boot-starter-web - 2.7.0 org.springframework.boot spring-boot-starter-validation - 2.7.3 org.springframework.boot spring-boot-devtools - 2.7.0 runtime true org.springframework.boot spring-boot-starter-test - 2.7.0 test org.seleniumhq.selenium selenium-java - 3.141.59 + 4.27.0 org.projectlombok @@ -61,15 +57,15 @@ org.springframework.boot spring-boot-maven-plugin - 2.7.0 + ${spring-boot.version} org.apache.maven.plugins maven-compiler-plugin - 3.7.0 + 3.11.0 - 1.8 - 1.8 + 17 + 17 UTF-8 diff --git a/testapi-Service/testapi/Dockerfile b/testapi-Service/testapi/Dockerfile index 387f759e..d2e98dbb 100644 --- a/testapi-Service/testapi/Dockerfile +++ b/testapi-Service/testapi/Dockerfile @@ -1,4 +1,4 @@ -FROM maven:3.8.6-eclipse-temurin-11 as builder +FROM maven:3.9.5-eclipse-temurin-17 as builder WORKDIR /testapi COPY . . diff --git a/testapi-Service/testapi/pom.xml b/testapi-Service/testapi/pom.xml index 6988bc87..c149cd77 100644 --- a/testapi-Service/testapi/pom.xml +++ b/testapi-Service/testapi/pom.xml @@ -8,9 +8,21 @@ testapi 1.0-SNAPSHOT + + + + org.springframework.boot + spring-boot-starter-parent + 3.5.3 + import + pom + + + + - 8 - 8 + 17 + 17 UTF-8 org.requests.TestManager @@ -21,7 +33,7 @@ io.rest-assured rest-assured - 5.2.0 + 5.5.0 compile @@ -34,37 +46,32 @@ org.springframework.boot spring-boot-starter-web - 2.7.0 org.springframework.boot spring-boot-starter-validation - 2.7.3 org.springframework.boot spring-boot-devtools - 2.7.0 runtime true org.springframework.boot spring-boot-starter-test - 2.7.0 test com.google.code.gson gson - 2.8.9 + 2.11.0 @@ -75,15 +82,15 @@ org.springframework.boot spring-boot-maven-plugin - 2.7.0 + 3.5.3 org.apache.maven.plugins maven-compiler-plugin - 3.7.0 + 3.11.0 - 1.8 - 1.8 + 17 + 17 UTF-8 From f9f8de91eb37d37c586edb8a72211750a10522e2 Mon Sep 17 00:00:00 2001 From: michelzzw Date: Wed, 11 Feb 2026 12:13:16 -0500 Subject: [PATCH 08/21] Disable MongoDB auto-index-creation to prevent startup crash Backend crashes with E11000 duplicate key error on taf.roles collection every time it restarts against an existing MongoDB instance. This happens because Spring Data MongoDB attempts to recreate the unique index on the Role.name field at every startup, conflicting with already existing data. Changed auto-index-creation from true to false in application.yml. The index already exists from the first successful startup, so there is no need to recreate it on every restart. --- testapi-Service/backend/src/main/resources/application.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/testapi-Service/backend/src/main/resources/application.yml b/testapi-Service/backend/src/main/resources/application.yml index 7fedbbfd..690b50b9 100644 --- a/testapi-Service/backend/src/main/resources/application.yml +++ b/testapi-Service/backend/src/main/resources/application.yml @@ -11,7 +11,7 @@ spring: uri: ${DB_URI} authentication-database: ${DB_AUTH} database: ${DB_NAME} - auto-index-creation: true + auto-index-creation: false jpa: hibernate: From ef64347d762fc8397dd66ba5dcb9175352d39921 Mon Sep 17 00:00:00 2001 From: michelzzw Date: Wed, 11 Feb 2026 12:38:44 -0500 Subject: [PATCH 09/21] Clean up hardcoded IPs, typos, and credentials across config files MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Multiple .env and config files contained hardcoded external IPs (185.133.251.89, 15.133.251.89, 198.7.119.0) from previous semesters' AWS deployments that no longer exist. These have been replaced with localhost for local development. Changes: - testapi-Service/.env: Fix typos (DOKCER→DOCKER, PASWORD→PASSWORD, PERFORMACE→PERFORMANCE), replace external IPs with localhost, update MongoDB credentials to local defaults - testapi-Service/backend/.env: Replace DB_URI and EUREKA_HOST external IPs with localhost - frontend environment.ts: Update apiUrl from external IP to localhost - build.sh: Sync variable references with corrected spelling - Delete .docker_config.env.old (contained stale plaintext credentials) --- testapi-Service/.docker_config.env.old | 8 -------- testapi-Service/.env | 14 +++++++------- testapi-Service/backend/.env | 4 ++-- testapi-Service/build.sh | 6 +++--- .../frontend/src/environments/environment.ts | 4 ++-- 5 files changed, 14 insertions(+), 22 deletions(-) delete mode 100644 testapi-Service/.docker_config.env.old diff --git a/testapi-Service/.docker_config.env.old b/testapi-Service/.docker_config.env.old deleted file mode 100644 index e664f46d..00000000 --- a/testapi-Service/.docker_config.env.old +++ /dev/null @@ -1,8 +0,0 @@ -TEST_API_SERVICE_URL="http://127.0.0.1" -TEST_API_SERVICE_PORT="8082" -SPRING_DATASOURCE_URL="jdbc:mysql://nostrasoft.com:3306/nostr321_taf-db" -SPRING_DATASOURCE_USERNAME="nostr321_taf" -SPRING_DATASOURCE_PASSWORD="JKaVLX75iHwtrHc" - -NAME=test-lab -DB_URI=mongodb+srv://admin:VRuAd2Nvmp4ELHh5@db/?retryWrites=true \ No newline at end of file diff --git a/testapi-Service/.env b/testapi-Service/.env index 147e54f2..ac9b6879 100644 --- a/testapi-Service/.env +++ b/testapi-Service/.env @@ -1,8 +1,8 @@ -DOKCER_URL=http://198.7.119.0:5000 -REGISTRY_BASE=198.7.119.0:5000 +DOCKER_URL=http://localhost:5000 +REGISTRY_BASE=localhost:5000 -DOKCER_USER=admin -DOKCER_PASWORD=registryPass +DOCKER_USER=admin +DOCKER_PASSWORD=registryPass # Version BACKEND_VERSION=0.0.1 FRONTEND_VERSION=0.0.1 @@ -19,9 +19,9 @@ EUREKA_PORT=8761 EUREKA_USERNAME=eureka EUREKA_PASSWORD=eureka -PERFORMACE_DB_NAME=taf_database -PERFORMACE_DB_URI=mongodb://admin:secret@15.133.251.89:47017/mydb?authSource=admin +PERFORMANCE_DB_NAME=taf_database +PERFORMANCE_DB_URI=mongodb://admin:admin123@localhost:27017/taf?authSource=admin -DB_URI=mongodb://admin:secret@15.133.251.89:47017/mydb?authSource=admin +DB_URI=mongodb://admin:admin123@localhost:27017/taf?authSource=admin DB_NAME=taf_database DB_AUTH=admin \ No newline at end of file diff --git a/testapi-Service/backend/.env b/testapi-Service/backend/.env index 736130fe..03c6cd7b 100644 --- a/testapi-Service/backend/.env +++ b/testapi-Service/backend/.env @@ -9,7 +9,7 @@ SELENIUM_PORT=8090 JWT_SECRET=bezKoderSecretKey JWT_EXPIRES=86400000 -DB_URI=mongodb://admin:secret@185.133.251.89:47017/mydb?authSource=admin +DB_URI=mongodb://admin:admin123@localhost:27017/taf?authSource=admin DB_NAME=taf_database DB_AUTH=admin #SPRING_DATASOURCE_URL=jdbc:mysql://localhost:4306/test @@ -19,7 +19,7 @@ DB_AUTH=admin #SPRING_JPA_PROPERTIES_HIBERNETE_DIALECT=org.hibernate.dialect.MySQL8Dialect # Eureka -EUREKA_HOST=185.133.251.89 +EUREKA_HOST=localhost GATEWAY_PORT=8762 EUREKA_PORT=8761 EUREKA_USERNAME=eureka diff --git a/testapi-Service/build.sh b/testapi-Service/build.sh index 533a8b90..ffbe5ceb 100644 --- a/testapi-Service/build.sh +++ b/testapi-Service/build.sh @@ -1,8 +1,8 @@ #!/bin/bash source .env -REGISTRY_URL=$DOKCER_URL -USERNAME=$DOKCER_USER -PASSWORD=$DOKCER_PASWORD +REGISTRY_URL=$DOCKER_URL +USERNAME=$DOCKER_USER +PASSWORD=$DOCKER_PASSWORD REGISTRY=$REGISTRY_BASE docker compose build backend diff --git a/testapi-Service/frontend/src/environments/environment.ts b/testapi-Service/frontend/src/environments/environment.ts index 6bd153e3..c1524bac 100644 --- a/testapi-Service/frontend/src/environments/environment.ts +++ b/testapi-Service/frontend/src/environments/environment.ts @@ -4,8 +4,8 @@ export const environment = { production: false, - apiUrl: 'http://185.133.251.89:8762', - //apiUrl: 'http://198.7.119.0:4200/api' + apiUrl: 'http://localhost:8762', + //apiUrl: 'http://localhost:4200/api' }; /* From 5cce93de6dfa316640281372017c2dd997885d54 Mon Sep 17 00:00:00 2001 From: michelzzw Date: Wed, 11 Feb 2026 12:51:25 -0500 Subject: [PATCH 10/21] Clean up .env variable duplication and stale config - Remove 8 duplicate variables from root .env that already exist in backend/.env (DB_URI, DB_NAME, DB_AUTH, EUREKA_HOST/PORT/USERNAME/PASSWORD, GATEWAY_PORT) - Remove 3 dead variables from root .env never referenced by any config (GATEWAY_HOST, PERFORMANCE_DB_NAME, PERFORMANCE_DB_URI) - Remove 5 lines of commented-out MySQL config from backend/.env containing hardcoded credentials, typos (DATABSE, HIBERNETE) - Root .env now only holds Docker build variables; backend/.env holds all Spring Boot runtime variables --- testapi-Service/.env | 16 +--------------- testapi-Service/backend/.env | 5 ----- 2 files changed, 1 insertion(+), 20 deletions(-) diff --git a/testapi-Service/.env b/testapi-Service/.env index ac9b6879..d5052a36 100644 --- a/testapi-Service/.env +++ b/testapi-Service/.env @@ -10,18 +10,4 @@ SELENIUM_VERSION=0.0.1 TEST_API_VERSION=0.0.1 TEST_UI_VERSION=0.0.1 TEST_TEAM4_SERVER_VERSION=0.0.1 -TEST_TEAM4_CLIENT_VERSION=0.0.1 - -EUREKA_HOST=localhost -GATEWAY_HOST=localhost -GATEWAY_PORT=8762 -EUREKA_PORT=8761 -EUREKA_USERNAME=eureka -EUREKA_PASSWORD=eureka - -PERFORMANCE_DB_NAME=taf_database -PERFORMANCE_DB_URI=mongodb://admin:admin123@localhost:27017/taf?authSource=admin - -DB_URI=mongodb://admin:admin123@localhost:27017/taf?authSource=admin -DB_NAME=taf_database -DB_AUTH=admin \ No newline at end of file +TEST_TEAM4_CLIENT_VERSION=0.0.1 \ No newline at end of file diff --git a/testapi-Service/backend/.env b/testapi-Service/backend/.env index 03c6cd7b..ed6c51ab 100644 --- a/testapi-Service/backend/.env +++ b/testapi-Service/backend/.env @@ -12,11 +12,6 @@ JWT_EXPIRES=86400000 DB_URI=mongodb://admin:admin123@localhost:27017/taf?authSource=admin DB_NAME=taf_database DB_AUTH=admin -#SPRING_DATASOURCE_URL=jdbc:mysql://localhost:4306/test -#SPRING_DATASOURCE_DATABSE=test -#SPRING_DATASOURCE_USERNAME=nostr321_taf -#SPRING_DATASOURCE_PASSWORD=JKaVLX75iHwtrHc -#SPRING_JPA_PROPERTIES_HIBERNETE_DIALECT=org.hibernate.dialect.MySQL8Dialect # Eureka EUREKA_HOST=localhost From e95eaff692565900fdc9807e86e4032b6f818446 Mon Sep 17 00:00:00 2001 From: michelzzw Date: Wed, 11 Feb 2026 22:55:19 -0500 Subject: [PATCH 11/21] Activate JWT authentication with Swagger UI integration MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Activate the dormant JWT authentication system and integrate it with Swagger UI for the TAF backend. Changes: Rename AuthController.java_ → AuthController.java to enable /api/auth/signup and /api/auth/signin endpoints Configure WebSecurityConfig to enforce authentication: only auth, swagger, actuator, test, and error paths are public; all other requests require a valid JWT Create OpenApiConfig.java with @SecurityScheme (Bearer JWT) and server URLs (localhost:8084, gateway, relative) Add @SecurityRequirement("bearerAuth") to TestApiController, GatlingApiController, TestSeleniumController, TestController Add @Schema examples to SignupRequest and LoginRequest for better Swagger UI defaults Add jaxb-api:2.3.1 dependency to fix javax.xml.bind.DatatypeConverter ClassNotFoundException with jjwt:0.9.1 on Java 17 Add okhttp:4.12.0 version, upgrade maven-compiler-plugin to 3.13.0 Upgrade spring-boot to 3.5.10, add xstream:1.4.21 to fix CVE-2024-47072 Update settings.json for Java Language Server Maven project recognition --- .vscode/settings.json | 12 ++++++- testapi-Service/backend/pom.docker.xml | 5 +++ testapi-Service/backend/pom.xml | 8 ++++- .../ca/etsmtl/taf/config/OpenApiConfig.java | 33 +++++++++++++++++++ ...thController.java_ => AuthController.java} | 0 .../taf/controller/GatlingApiController.java | 3 ++ .../taf/controller/TestApiController.java | 3 +- .../etsmtl/taf/controller/TestController.java | 3 ++ .../controller/TestSeleniumController.java | 3 ++ .../taf/payload/request/LoginRequest.java | 3 ++ .../taf/payload/request/SignupRequest.java | 6 ++++ .../taf/security/WebSecurityConfig.java | 7 +++- testapi-Service/pom.xml | 8 ++++- 13 files changed, 89 insertions(+), 5 deletions(-) create mode 100644 testapi-Service/backend/src/main/java/ca/etsmtl/taf/config/OpenApiConfig.java rename testapi-Service/backend/src/main/java/ca/etsmtl/taf/controller/{AuthController.java_ => AuthController.java} (100%) diff --git a/.vscode/settings.json b/.vscode/settings.json index b84f89c3..dd96067f 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,4 +1,14 @@ { "java.configuration.updateBuildConfiguration": "interactive", - "java.compile.nullAnalysis.mode": "automatic" + "java.compile.nullAnalysis.mode": "automatic", + "java.project.sourcePaths": [], + "java.import.exclusions": [ + "**/node_modules/**", + "**/.metadata/**", + "**/archetype-resources/**", + "**/META-INF/maven/**", + "**/frontend/**" + ], + "java.project.referencedLibraries": [], + "java.import.maven.enabled": true } \ No newline at end of file diff --git a/testapi-Service/backend/pom.docker.xml b/testapi-Service/backend/pom.docker.xml index 20fec4b3..e947aae6 100644 --- a/testapi-Service/backend/pom.docker.xml +++ b/testapi-Service/backend/pom.docker.xml @@ -69,6 +69,11 @@ jjwt 0.9.1 + + javax.xml.bind + jaxb-api + 2.3.1 + org.projectlombok diff --git a/testapi-Service/backend/pom.xml b/testapi-Service/backend/pom.xml index 8c6b77ad..e55f862e 100644 --- a/testapi-Service/backend/pom.xml +++ b/testapi-Service/backend/pom.xml @@ -97,6 +97,11 @@ jjwt 0.9.1 + + javax.xml.bind + jaxb-api + 2.3.1 + org.projectlombok @@ -136,6 +141,7 @@ com.squareup.okhttp3 okhttp + 4.12.0 @@ -184,7 +190,7 @@ org.apache.maven.plugins maven-compiler-plugin - 3.11.0 + 3.13.0 17 17 diff --git a/testapi-Service/backend/src/main/java/ca/etsmtl/taf/config/OpenApiConfig.java b/testapi-Service/backend/src/main/java/ca/etsmtl/taf/config/OpenApiConfig.java new file mode 100644 index 00000000..b062d3c7 --- /dev/null +++ b/testapi-Service/backend/src/main/java/ca/etsmtl/taf/config/OpenApiConfig.java @@ -0,0 +1,33 @@ +package ca.etsmtl.taf.config; + +import io.swagger.v3.oas.annotations.OpenAPIDefinition; +import io.swagger.v3.oas.annotations.enums.SecuritySchemeType; +import io.swagger.v3.oas.annotations.info.Info; +import io.swagger.v3.oas.annotations.security.SecurityScheme; +import io.swagger.v3.oas.annotations.servers.Server; +import org.springframework.context.annotation.Configuration; + +@Configuration +@OpenAPIDefinition( + info = @Info( + title = "TAF - Test Automation Framework API", + version = "1.0", + description = "API pour le framework d'automatisation de tests (TAF). " + + "Utilisez /api/auth/signin pour obtenir un token JWT, " + + "puis cliquez sur 'Authorize' et entrez: Bearer " + ), + servers = { + @Server(url = "http://localhost:8084", description = "Local Docker (team2)"), + @Server(url = "http://localhost:8080", description = "Via Gateway"), + @Server(url = "/", description = "Relative (current host)") + } +) +@SecurityScheme( + name = "bearerAuth", + type = SecuritySchemeType.HTTP, + scheme = "bearer", + bearerFormat = "JWT", + description = "Entrez le token JWT obtenu via /api/auth/signin" +) +public class OpenApiConfig { +} diff --git a/testapi-Service/backend/src/main/java/ca/etsmtl/taf/controller/AuthController.java_ b/testapi-Service/backend/src/main/java/ca/etsmtl/taf/controller/AuthController.java similarity index 100% rename from testapi-Service/backend/src/main/java/ca/etsmtl/taf/controller/AuthController.java_ rename to testapi-Service/backend/src/main/java/ca/etsmtl/taf/controller/AuthController.java diff --git a/testapi-Service/backend/src/main/java/ca/etsmtl/taf/controller/GatlingApiController.java b/testapi-Service/backend/src/main/java/ca/etsmtl/taf/controller/GatlingApiController.java index e47258e2..03332977 100644 --- a/testapi-Service/backend/src/main/java/ca/etsmtl/taf/controller/GatlingApiController.java +++ b/testapi-Service/backend/src/main/java/ca/etsmtl/taf/controller/GatlingApiController.java @@ -11,9 +11,12 @@ import java.util.ArrayList; import java.util.List; +import io.swagger.v3.oas.annotations.security.SecurityRequirement; + @CrossOrigin(origins = "*", maxAge = 3600) @RestController @RequestMapping("/api/gatling") +@SecurityRequirement(name = "bearerAuth") public class GatlingApiController { @PostMapping("/runSimulation") diff --git a/testapi-Service/backend/src/main/java/ca/etsmtl/taf/controller/TestApiController.java b/testapi-Service/backend/src/main/java/ca/etsmtl/taf/controller/TestApiController.java index 7c0dbc7d..fe4cb6d1 100644 --- a/testapi-Service/backend/src/main/java/ca/etsmtl/taf/controller/TestApiController.java +++ b/testapi-Service/backend/src/main/java/ca/etsmtl/taf/controller/TestApiController.java @@ -16,11 +16,12 @@ import org.springframework.beans.factory.annotation.Value; - +import io.swagger.v3.oas.annotations.security.SecurityRequirement; @CrossOrigin(origins = "*", maxAge = 3600) @RestController @RequestMapping("/api/testapi") +@SecurityRequirement(name = "bearerAuth") public class TestApiController { @Value("${taf.app.testAPI_url}") String Test_API_microservice_url; diff --git a/testapi-Service/backend/src/main/java/ca/etsmtl/taf/controller/TestController.java b/testapi-Service/backend/src/main/java/ca/etsmtl/taf/controller/TestController.java index e39670d4..cb1983f4 100644 --- a/testapi-Service/backend/src/main/java/ca/etsmtl/taf/controller/TestController.java +++ b/testapi-Service/backend/src/main/java/ca/etsmtl/taf/controller/TestController.java @@ -5,9 +5,12 @@ import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.web.bind.annotation.*; +import io.swagger.v3.oas.annotations.security.SecurityRequirement; + @CrossOrigin(origins = "*", maxAge = 3600) @RestController @RequestMapping("/api/test") +@SecurityRequirement(name = "bearerAuth") public class TestController { @GetMapping("/all") public String allAccess() { diff --git a/testapi-Service/backend/src/main/java/ca/etsmtl/taf/controller/TestSeleniumController.java b/testapi-Service/backend/src/main/java/ca/etsmtl/taf/controller/TestSeleniumController.java index d54af36b..d48f03f1 100644 --- a/testapi-Service/backend/src/main/java/ca/etsmtl/taf/controller/TestSeleniumController.java +++ b/testapi-Service/backend/src/main/java/ca/etsmtl/taf/controller/TestSeleniumController.java @@ -10,9 +10,12 @@ import java.net.URISyntaxException; import java.util.List; +import io.swagger.v3.oas.annotations.security.SecurityRequirement; + @CrossOrigin(origins = "*", maxAge = 3600) @RestController @RequestMapping("/api") +@SecurityRequirement(name = "bearerAuth") public class TestSeleniumController { private final SeleniumService seleniumService; diff --git a/testapi-Service/backend/src/main/java/ca/etsmtl/taf/payload/request/LoginRequest.java b/testapi-Service/backend/src/main/java/ca/etsmtl/taf/payload/request/LoginRequest.java index cd452f1d..cebc9f6b 100644 --- a/testapi-Service/backend/src/main/java/ca/etsmtl/taf/payload/request/LoginRequest.java +++ b/testapi-Service/backend/src/main/java/ca/etsmtl/taf/payload/request/LoginRequest.java @@ -1,12 +1,15 @@ package ca.etsmtl.taf.payload.request; +import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotBlank; public class LoginRequest { @NotBlank + @Schema(example = "equipe3") private String username; @NotBlank + @Schema(example = "equipe3") private String password; public String getUsername() { diff --git a/testapi-Service/backend/src/main/java/ca/etsmtl/taf/payload/request/SignupRequest.java b/testapi-Service/backend/src/main/java/ca/etsmtl/taf/payload/request/SignupRequest.java index 7f160f2c..f7dd8e20 100644 --- a/testapi-Service/backend/src/main/java/ca/etsmtl/taf/payload/request/SignupRequest.java +++ b/testapi-Service/backend/src/main/java/ca/etsmtl/taf/payload/request/SignupRequest.java @@ -2,6 +2,7 @@ import java.util.Set; +import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.Email; import jakarta.validation.constraints.Size; @@ -9,21 +10,26 @@ public class SignupRequest { @NotBlank @Size(min = 3, max = 50) + @Schema(example = "equipe3") private String fullName; @NotBlank @Size(min = 3, max = 20) + @Schema(example = "equipe3") private String username; @NotBlank @Size(max = 50) @Email + @Schema(example = "equipe3@etsmtl.ca") private String email; + @Schema(example = "[\"user\"]") private Set role; @NotBlank @Size(min = 6, max = 40) + @Schema(example = "equipe3") private String password; public String getFullName() { diff --git a/testapi-Service/backend/src/main/java/ca/etsmtl/taf/security/WebSecurityConfig.java b/testapi-Service/backend/src/main/java/ca/etsmtl/taf/security/WebSecurityConfig.java index 2a348bb4..01c616ea 100644 --- a/testapi-Service/backend/src/main/java/ca/etsmtl/taf/security/WebSecurityConfig.java +++ b/testapi-Service/backend/src/main/java/ca/etsmtl/taf/security/WebSecurityConfig.java @@ -53,7 +53,12 @@ public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Excepti .exceptionHandling().authenticationEntryPoint(unauthorizedHandler).and() .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and() .authorizeHttpRequests((authz) -> authz - .requestMatchers("/**").permitAll() + .requestMatchers("/api/auth/**").permitAll() + .requestMatchers("/swagger-ui/**", "/swagger-ui.html", "/api-docs/**", "/v3/api-docs/**").permitAll() + .requestMatchers("/actuator/**").permitAll() + .requestMatchers("/api/test/**").permitAll() + .requestMatchers("/error").permitAll() + .anyRequest().authenticated() ); http.addFilterBefore(authenticationJwtTokenFilter(), UsernamePasswordAuthenticationFilter.class); diff --git a/testapi-Service/pom.xml b/testapi-Service/pom.xml index 63578ec4..91b1ce71 100644 --- a/testapi-Service/pom.xml +++ b/testapi-Service/pom.xml @@ -10,6 +10,12 @@ + + + com.thoughtworks.xstream + xstream + 1.4.21 + org.springframework.boot spring-boot-starter-parent @@ -37,7 +43,7 @@ 17 1.18.24 - 3.5.3 + 3.5.10 pom From 98368bcb1db744834abc7e8695e9aee503a6baf3 Mon Sep 17 00:00:00 2001 From: michelzzw Date: Thu, 12 Feb 2026 17:35:49 -0500 Subject: [PATCH 12/21] Add Google OAuth2 backend authentication --- testapi-Service/backend/pom.docker.xml | 4 + testapi-Service/backend/pom.xml | 4 + .../ca/etsmtl/taf/config/OpenApiConfig.java | 7 +- .../taf/controller/OAuth2Controller.java | 35 +++++ .../main/java/ca/etsmtl/taf/entity/User.java | 19 ++- .../etsmtl/taf/repository/UserRepository.java | 4 + .../taf/security/WebSecurityConfig.java | 12 +- .../ca/etsmtl/taf/security/jwt/JwtUtils.java | 9 ++ .../oauth2/OAuth2LoginSuccessHandler.java | 130 ++++++++++++++++++ .../src/main/resources/application.yml | 16 +++ 10 files changed, 233 insertions(+), 7 deletions(-) create mode 100644 testapi-Service/backend/src/main/java/ca/etsmtl/taf/controller/OAuth2Controller.java create mode 100644 testapi-Service/backend/src/main/java/ca/etsmtl/taf/security/oauth2/OAuth2LoginSuccessHandler.java diff --git a/testapi-Service/backend/pom.docker.xml b/testapi-Service/backend/pom.docker.xml index e947aae6..a8afd1f5 100644 --- a/testapi-Service/backend/pom.docker.xml +++ b/testapi-Service/backend/pom.docker.xml @@ -64,6 +64,10 @@ org.springframework.boot spring-boot-starter-data-mongodb + + org.springframework.boot + spring-boot-starter-oauth2-client + io.jsonwebtoken jjwt diff --git a/testapi-Service/backend/pom.xml b/testapi-Service/backend/pom.xml index e55f862e..e17dbf4f 100644 --- a/testapi-Service/backend/pom.xml +++ b/testapi-Service/backend/pom.xml @@ -92,6 +92,10 @@ + + org.springframework.boot + spring-boot-starter-oauth2-client + io.jsonwebtoken jjwt diff --git a/testapi-Service/backend/src/main/java/ca/etsmtl/taf/config/OpenApiConfig.java b/testapi-Service/backend/src/main/java/ca/etsmtl/taf/config/OpenApiConfig.java index b062d3c7..8805bf2b 100644 --- a/testapi-Service/backend/src/main/java/ca/etsmtl/taf/config/OpenApiConfig.java +++ b/testapi-Service/backend/src/main/java/ca/etsmtl/taf/config/OpenApiConfig.java @@ -13,8 +13,9 @@ title = "TAF - Test Automation Framework API", version = "1.0", description = "API pour le framework d'automatisation de tests (TAF). " - + "Utilisez /api/auth/signin pour obtenir un token JWT, " - + "puis cliquez sur 'Authorize' et entrez: Bearer " + + "**Authentification JWT** : POST /api/auth/signin -> token JWT, puis Authorize -> Bearer . " + + "**Connexion Google OAuth2** : Ouvrez /oauth2/authorization/google dans le navigateur -> " + + "connexion Google -> redirection avec token JWT." ), servers = { @Server(url = "http://localhost:8084", description = "Local Docker (team2)"), @@ -27,7 +28,7 @@ type = SecuritySchemeType.HTTP, scheme = "bearer", bearerFormat = "JWT", - description = "Entrez le token JWT obtenu via /api/auth/signin" + description = "Entrez le token JWT obtenu via /api/auth/signin ou via Google OAuth2" ) public class OpenApiConfig { } diff --git a/testapi-Service/backend/src/main/java/ca/etsmtl/taf/controller/OAuth2Controller.java b/testapi-Service/backend/src/main/java/ca/etsmtl/taf/controller/OAuth2Controller.java new file mode 100644 index 00000000..da014ff2 --- /dev/null +++ b/testapi-Service/backend/src/main/java/ca/etsmtl/taf/controller/OAuth2Controller.java @@ -0,0 +1,35 @@ +package ca.etsmtl.taf.controller; + +import java.util.Map; + +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; + +/** + * OAuth2 related endpoints. + * The actual Google login flow is handled by Spring Security at /oauth2/authorization/google. + * This controller provides supplementary API endpoints. + */ +@CrossOrigin(origins = "*", maxAge = 3600) +@RestController +@RequestMapping("/api/oauth2") +@Tag(name = "OAuth2 Authentication", description = "Google OAuth2 login endpoints") +public class OAuth2Controller { + + @GetMapping("/login-url") + @Operation( + summary = "Get Google OAuth2 login URL", + description = "Returns the URL to initiate Google OAuth2 login. " + + "Redirect the user's browser to this URL to start the Google login flow." + ) + public ResponseEntity getGoogleLoginUrl() { + return ResponseEntity.ok(Map.of( + "provider", "google", + "loginUrl", "/oauth2/authorization/google", + "description", "Redirect user's browser to this URL to initiate Google login" + )); + } +} diff --git a/testapi-Service/backend/src/main/java/ca/etsmtl/taf/entity/User.java b/testapi-Service/backend/src/main/java/ca/etsmtl/taf/entity/User.java index 1dfdb816..38af63d9 100644 --- a/testapi-Service/backend/src/main/java/ca/etsmtl/taf/entity/User.java +++ b/testapi-Service/backend/src/main/java/ca/etsmtl/taf/entity/User.java @@ -12,6 +12,8 @@ import org.springframework.data.mongodb.core.mapping.Document; import org.springframework.data.mongodb.core.mapping.DBRef; +import java.util.UUID; + import lombok.Getter; import lombok.Setter; @@ -29,7 +31,7 @@ public class User { @Indexed(unique = true) @NotBlank - @Size(max = 20) + @Size(max = 50) private String username; @Indexed(unique = true) @@ -38,13 +40,16 @@ public class User { @Email private String email; - @NotBlank @Size(max = 120) private String password; @DBRef private Set roles = new HashSet<>(); + private String provider; // "local" or "google" + + private String googleId; + public User() {} public User(String fullName, String username, String email, String password) { @@ -52,6 +57,16 @@ public User(String fullName, String username, String email, String password) { this.username = username; this.email = email; this.password = password; + this.provider = "local"; + } + + public User(String fullName, String username, String email, String provider, String googleId) { + this.fullName = fullName; + this.username = username; + this.email = email; + this.provider = provider; + this.googleId = googleId; + this.password = UUID.randomUUID().toString(); // OAuth2 users get a random unusable password } public String getId() { diff --git a/testapi-Service/backend/src/main/java/ca/etsmtl/taf/repository/UserRepository.java b/testapi-Service/backend/src/main/java/ca/etsmtl/taf/repository/UserRepository.java index 674e9c62..2c7e3ea2 100644 --- a/testapi-Service/backend/src/main/java/ca/etsmtl/taf/repository/UserRepository.java +++ b/testapi-Service/backend/src/main/java/ca/etsmtl/taf/repository/UserRepository.java @@ -11,6 +11,10 @@ public interface UserRepository extends MongoRepository { Optional findByUsername(String username); + Optional findByEmail(String email); + + Optional findByGoogleId(String googleId); + Boolean existsByUsername(String username); Boolean existsByEmail(String email); diff --git a/testapi-Service/backend/src/main/java/ca/etsmtl/taf/security/WebSecurityConfig.java b/testapi-Service/backend/src/main/java/ca/etsmtl/taf/security/WebSecurityConfig.java index 01c616ea..3b9e5adc 100644 --- a/testapi-Service/backend/src/main/java/ca/etsmtl/taf/security/WebSecurityConfig.java +++ b/testapi-Service/backend/src/main/java/ca/etsmtl/taf/security/WebSecurityConfig.java @@ -5,7 +5,6 @@ import org.springframework.context.annotation.Configuration; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration; -import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; @@ -17,6 +16,7 @@ import ca.etsmtl.taf.security.jwt.AuthEntryPointJwt; import ca.etsmtl.taf.security.jwt.AuthTokenFilter; +import ca.etsmtl.taf.security.oauth2.OAuth2LoginSuccessHandler; import ca.etsmtl.taf.security.services.UserDetailsServiceImpl; @Configuration @@ -32,6 +32,9 @@ public class WebSecurityConfig { @Autowired private AuthEntryPointJwt unauthorizedHandler; + @Autowired + private OAuth2LoginSuccessHandler oAuth2LoginSuccessHandler; + @Bean public AuthTokenFilter authenticationJwtTokenFilter() { return new AuthTokenFilter(); @@ -51,14 +54,19 @@ public PasswordEncoder passwordEncoder() { public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { http.cors().and().csrf().disable() .exceptionHandling().authenticationEntryPoint(unauthorizedHandler).and() - .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and() + .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED).and() .authorizeHttpRequests((authz) -> authz .requestMatchers("/api/auth/**").permitAll() + .requestMatchers("/api/oauth2/login-url").permitAll() + .requestMatchers("/oauth2/**", "/login/oauth2/**").permitAll() .requestMatchers("/swagger-ui/**", "/swagger-ui.html", "/api-docs/**", "/v3/api-docs/**").permitAll() .requestMatchers("/actuator/**").permitAll() .requestMatchers("/api/test/**").permitAll() .requestMatchers("/error").permitAll() .anyRequest().authenticated() + ) + .oauth2Login(oauth2 -> oauth2 + .successHandler(oAuth2LoginSuccessHandler) ); http.addFilterBefore(authenticationJwtTokenFilter(), UsernamePasswordAuthenticationFilter.class); diff --git a/testapi-Service/backend/src/main/java/ca/etsmtl/taf/security/jwt/JwtUtils.java b/testapi-Service/backend/src/main/java/ca/etsmtl/taf/security/jwt/JwtUtils.java index d9577041..ff368024 100644 --- a/testapi-Service/backend/src/main/java/ca/etsmtl/taf/security/jwt/JwtUtils.java +++ b/testapi-Service/backend/src/main/java/ca/etsmtl/taf/security/jwt/JwtUtils.java @@ -33,6 +33,15 @@ public String generateJwtToken(Authentication authentication) { .compact(); } + public String generateJwtTokenForUsername(String username) { + return Jwts.builder() + .setSubject(username) + .setIssuedAt(new Date()) + .setExpiration(new Date((new Date()).getTime() + jwtExpirationMs)) + .signWith(SignatureAlgorithm.HS512, jwtSecret) + .compact(); + } + public String getUserNameFromJwtToken(String token) { return Jwts.parser().setSigningKey(jwtSecret).parseClaimsJws(token).getBody().getSubject(); } diff --git a/testapi-Service/backend/src/main/java/ca/etsmtl/taf/security/oauth2/OAuth2LoginSuccessHandler.java b/testapi-Service/backend/src/main/java/ca/etsmtl/taf/security/oauth2/OAuth2LoginSuccessHandler.java new file mode 100644 index 00000000..bac54a6a --- /dev/null +++ b/testapi-Service/backend/src/main/java/ca/etsmtl/taf/security/oauth2/OAuth2LoginSuccessHandler.java @@ -0,0 +1,130 @@ +package ca.etsmtl.taf.security.oauth2; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.Base64; +import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.security.core.Authentication; +import org.springframework.security.oauth2.core.user.OAuth2User; +import org.springframework.security.web.authentication.SimpleUrlAuthenticationSuccessHandler; +import org.springframework.stereotype.Component; + +import ca.etsmtl.taf.entity.ERole; +import ca.etsmtl.taf.entity.Role; +import ca.etsmtl.taf.entity.User; +import ca.etsmtl.taf.repository.RoleRepository; +import ca.etsmtl.taf.repository.UserRepository; +import ca.etsmtl.taf.security.jwt.JwtUtils; + +import com.fasterxml.jackson.databind.ObjectMapper; + +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; + +/** + * Handles successful Google OAuth2 login. + * Creates or finds the user in MongoDB, generates a JWT token, + * and redirects to the frontend with the token as a query parameter. + */ +@Component +public class OAuth2LoginSuccessHandler extends SimpleUrlAuthenticationSuccessHandler { + + private static final Logger log = LoggerFactory.getLogger(OAuth2LoginSuccessHandler.class); + + @Autowired + private JwtUtils jwtUtils; + + @Autowired + private UserRepository userRepository; + + @Autowired + private RoleRepository roleRepository; + + @Autowired + private ObjectMapper objectMapper; + + @Value("${taf.app.oauth2FrontendRedirectUrl:http://localhost:4200}") + private String frontendRedirectUrl; + + @Override + public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, + Authentication authentication) throws IOException, ServletException { + + OAuth2User oAuth2User = (OAuth2User) authentication.getPrincipal(); + + String googleId = oAuth2User.getAttribute("sub"); + String email = oAuth2User.getAttribute("email"); + String name = oAuth2User.getAttribute("name"); + + log.info("OAuth2 login success for Google user: {} ({})", name, email); + + // Find existing user by googleId, or by email, or create a new one + User user = userRepository.findByGoogleId(googleId) + .orElseGet(() -> userRepository.findByEmail(email) + .map(existingUser -> { + // Link Google account to existing local user + existingUser.setGoogleId(googleId); + existingUser.setProvider("google"); + return userRepository.save(existingUser); + }) + .orElseGet(() -> { + // Create a brand new user from Google info + String username = email.split("@")[0]; // Use email prefix as username + // Ensure username uniqueness + if (userRepository.existsByUsername(username)) { + username = username + "_g" + googleId.substring(0, 6); + } + User newUser = new User(name, username, email, "google", googleId); + + // Assign default ROLE_USER + Set roles = new HashSet<>(); + roleRepository.findByName(ERole.ROLE_USER).ifPresent(roles::add); + newUser.setRoles(roles); + + return userRepository.save(newUser); + }) + ); + + // Generate JWT token for this user + String jwtToken = jwtUtils.generateJwtTokenForUsername(user.getUsername()); + + // Build user info for frontend + List roles = user.getRoles().stream() + .map(role -> role.getName().name()) + .collect(Collectors.toList()); + + Map userInfo = new LinkedHashMap<>(); + userInfo.put("accessToken", jwtToken); + userInfo.put("id", user.getId()); + userInfo.put("fullName", user.getFullName()); + userInfo.put("username", user.getUsername()); + userInfo.put("email", user.getEmail()); + userInfo.put("roles", roles); + + String userInfoBase64; + try { + String json = objectMapper.writeValueAsString(userInfo); + userInfoBase64 = Base64.getUrlEncoder().withoutPadding().encodeToString(json.getBytes(StandardCharsets.UTF_8)); + } catch (Exception e) { + log.error("Failed to encode user info", e); + userInfoBase64 = ""; + } + + // Redirect to frontend with JWT token and user info + String redirectUrl = frontendRedirectUrl + "/oauth2/callback?token=" + jwtToken + "&userInfo=" + userInfoBase64; + log.info("Redirecting OAuth2 user to: {}", frontendRedirectUrl + "/oauth2/callback?token=***"); + + getRedirectStrategy().sendRedirect(request, response, redirectUrl); + } +} diff --git a/testapi-Service/backend/src/main/resources/application.yml b/testapi-Service/backend/src/main/resources/application.yml index 690b50b9..89c72d6f 100644 --- a/testapi-Service/backend/src/main/resources/application.yml +++ b/testapi-Service/backend/src/main/resources/application.yml @@ -13,6 +13,21 @@ spring: database: ${DB_NAME} auto-index-creation: false + security: + oauth2: + client: + registration: + google: + client-id: ${GOOGLE_CLIENT_ID:placeholder} + client-secret: ${GOOGLE_CLIENT_SECRET:placeholder} + scope: openid, profile, email + provider: + google: + authorization-uri: https://accounts.google.com/o/oauth2/v2/auth + token-uri: https://oauth2.googleapis.com/token + user-info-uri: https://openidconnect.googleapis.com/v1/userinfo + user-name-attribute: sub + jpa: hibernate: ddl-auto: update @@ -30,6 +45,7 @@ taf: testAPI_port: ${TEST_API_SERVICE_PORT} selenium_container_url: ${SELENIUM_URL} selenium_container_port: ${SELENIUM_PORT} + oauth2FrontendRedirectUrl: ${OAUTH2_FRONTEND_REDIRECT_URL:http://localhost:4200} server: port: 8080 From 17d1c2dd1767e2a9dc763f59375b8288eb1e9ba0 Mon Sep 17 00:00:00 2001 From: michelzzw Date: Thu, 12 Feb 2026 17:36:11 -0500 Subject: [PATCH 13/21] Add Google OAuth2 frontend integration --- .../frontend/src/app/app-routing.module.ts | 2 + .../frontend/src/app/app.module.ts | 2 + .../src/app/login/login.component.css | 20 ++++++- .../src/app/login/login.component.html | 17 ++++++ .../frontend/src/app/login/login.component.ts | 7 +++ .../oauth2-callback.component.css | 26 ++++++++ .../oauth2-callback.component.html | 19 ++++++ .../oauth2-callback.component.ts | 60 +++++++++++++++++++ .../src/environments/environment.prod.ts | 3 +- .../frontend/src/environments/environment.ts | 1 + 10 files changed, 155 insertions(+), 2 deletions(-) create mode 100644 testapi-Service/frontend/src/app/oauth2-callback/oauth2-callback.component.css create mode 100644 testapi-Service/frontend/src/app/oauth2-callback/oauth2-callback.component.html create mode 100644 testapi-Service/frontend/src/app/oauth2-callback/oauth2-callback.component.ts diff --git a/testapi-Service/frontend/src/app/app-routing.module.ts b/testapi-Service/frontend/src/app/app-routing.module.ts index 0642c0d1..58ac5a34 100644 --- a/testapi-Service/frontend/src/app/app-routing.module.ts +++ b/testapi-Service/frontend/src/app/app-routing.module.ts @@ -12,6 +12,7 @@ import { GatlingComponent } from './gatling/gatling.component'; import { TestApiComponent } from './interface-test-api/test-api/test-api.component'; import { TestSeleniumComponent } from './selenium/test-selenium.component'; import { PerformanceTestApiComponent } from './performance-test-api/performance-test-api.component'; +import { OAuth2CallbackComponent } from './oauth2-callback/oauth2-callback.component'; const routes: Routes = [ { path: 'home', component: HomeComponent }, @@ -26,6 +27,7 @@ const routes: Routes = [ { path: 'test-api', component: TestApiComponent }, { path: 'test-selenium', component: TestSeleniumComponent }, { path: 'performance-test-api', component: PerformanceTestApiComponent }, + { path: 'oauth2/callback', component: OAuth2CallbackComponent }, { path: '', redirectTo: 'home', pathMatch: 'full' } ]; diff --git a/testapi-Service/frontend/src/app/app.module.ts b/testapi-Service/frontend/src/app/app.module.ts index 03a7f8ad..7db91cf6 100644 --- a/testapi-Service/frontend/src/app/app.module.ts +++ b/testapi-Service/frontend/src/app/app.module.ts @@ -12,6 +12,7 @@ import { HomeComponent } from './home/home.component'; import { ProfileComponent } from './profile/profile.component'; import { BoardAdminComponent } from './board-admin/board-admin.component'; import { BoardUserComponent } from './board-user/board-user.component'; +import { OAuth2CallbackComponent } from './oauth2-callback/oauth2-callback.component'; import { authInterceptorProviders } from './_helpers/auth.interceptor'; import { ProjectComponent } from './project/project.component'; @@ -49,6 +50,7 @@ import { ErrorDialogComponent } from './interface-test-api/test-api/error-dialog ProfileComponent, BoardAdminComponent, BoardUserComponent, + OAuth2CallbackComponent, ProjectComponent, TestApiComponent, AddTestDialogComponent, diff --git a/testapi-Service/frontend/src/app/login/login.component.css b/testapi-Service/frontend/src/app/login/login.component.css index 924cf006..8b9f1e69 100644 --- a/testapi-Service/frontend/src/app/login/login.component.css +++ b/testapi-Service/frontend/src/app/login/login.component.css @@ -28,5 +28,23 @@ label { display: block; -moz-border-radius: 50%; -webkit-border-radius: 50%; - border-radius: 50%; +} + +.btn-google { + background-color: #fff; + color: #757575; + border: 1px solid #ddd; + padding: 10px 16px; + font-size: 14px; + font-weight: 500; + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; + transition: background-color 0.2s, box-shadow 0.2s; +} + +.btn-google:hover { + background-color: #eee; + border-color: #ccc; } \ No newline at end of file diff --git a/testapi-Service/frontend/src/app/login/login.component.html b/testapi-Service/frontend/src/app/login/login.component.html index 52e0730a..c29f817a 100644 --- a/testapi-Service/frontend/src/app/login/login.component.html +++ b/testapi-Service/frontend/src/app/login/login.component.html @@ -66,6 +66,23 @@ Échec de connexion: {{ errorMessage }} + +
+
+

Ou connectez-vous avec

+ +
diff --git a/testapi-Service/frontend/src/app/login/login.component.ts b/testapi-Service/frontend/src/app/login/login.component.ts index 6c2a9536..d6e33f39 100644 --- a/testapi-Service/frontend/src/app/login/login.component.ts +++ b/testapi-Service/frontend/src/app/login/login.component.ts @@ -1,6 +1,7 @@ import { Component, OnInit } from '@angular/core'; import { AuthService } from '../_services/auth.service'; import { TokenStorageService } from '../_services/token-storage.service'; +import { environment } from '../../environments/environment'; @Component({ selector: 'app-login', @@ -51,4 +52,10 @@ export class LoginComponent implements OnInit { reloadPage(): void { window.location.reload(); } + + loginWithGoogle(): void { + // Redirect browser directly to backend's OAuth2 endpoint + // Backend handles the full OAuth2 flow and redirects back to frontend with JWT + window.location.href = environment.oauth2BackendUrl + '/oauth2/authorization/google'; + } } diff --git a/testapi-Service/frontend/src/app/oauth2-callback/oauth2-callback.component.css b/testapi-Service/frontend/src/app/oauth2-callback/oauth2-callback.component.css new file mode 100644 index 00000000..0836a151 --- /dev/null +++ b/testapi-Service/frontend/src/app/oauth2-callback/oauth2-callback.component.css @@ -0,0 +1,26 @@ +.card-container.card { + max-width: 400px !important; + padding: 40px 40px; +} + +.card { + background-color: #f7f7f7; + padding: 20px 25px 30px; + margin: 0 auto 25px; + margin-top: 50px; + border-radius: 2px; + box-shadow: 0px 2px 2px rgba(0, 0, 0, 0.3); +} + +.profile-img-card { + width: 96px; + height: 96px; + margin: 0 auto 10px; + display: block; + border-radius: 50%; +} + +h4 { + margin-top: 15px; + color: #333; +} diff --git a/testapi-Service/frontend/src/app/oauth2-callback/oauth2-callback.component.html b/testapi-Service/frontend/src/app/oauth2-callback/oauth2-callback.component.html new file mode 100644 index 00000000..bea34298 --- /dev/null +++ b/testapi-Service/frontend/src/app/oauth2-callback/oauth2-callback.component.html @@ -0,0 +1,19 @@ +
+
+
+ +

Connexion avec Google...

+

Veuillez patienter pendant que nous vous connectons.

+
+ +
+ + Retour à la connexion +
+
+
diff --git a/testapi-Service/frontend/src/app/oauth2-callback/oauth2-callback.component.ts b/testapi-Service/frontend/src/app/oauth2-callback/oauth2-callback.component.ts new file mode 100644 index 00000000..20df4be3 --- /dev/null +++ b/testapi-Service/frontend/src/app/oauth2-callback/oauth2-callback.component.ts @@ -0,0 +1,60 @@ +import { Component, OnInit } from '@angular/core'; +import { ActivatedRoute } from '@angular/router'; +import { TokenStorageService } from '../_services/token-storage.service'; + +@Component({ + selector: 'app-oauth2-callback', + templateUrl: './oauth2-callback.component.html', + styleUrls: ['./oauth2-callback.component.css'] +}) +export class OAuth2CallbackComponent implements OnInit { + isLoading = true; + isError = false; + errorMessage = ''; + + constructor( + private route: ActivatedRoute, + private tokenStorage: TokenStorageService + ) {} + + ngOnInit(): void { + this.route.queryParams.subscribe((params: { [key: string]: string }) => { + const token: string | undefined = params['token']; + const userInfoBase64: string | undefined = params['userInfo']; + + if (!token) { + this.isLoading = false; + this.isError = true; + this.errorMessage = 'Aucun token reçu de Google. Veuillez réessayer.'; + return; + } + + try { + // Save JWT token + this.tokenStorage.saveToken(token); + + if (userInfoBase64) { + // Decode base64url user info + const json: string = atob(userInfoBase64.replace(/-/g, '+').replace(/_/g, '/')); + const userInfo: Record = JSON.parse(json); + this.tokenStorage.saveUser(userInfo); + } else { + // Minimal user object if no userInfo provided + this.tokenStorage.saveUser({ + accessToken: token, + fullName: 'Google User', + roles: ['ROLE_USER'] + }); + } + + // Redirect to home and reload to update navbar + window.location.href = '/home'; + } catch (e) { + this.isLoading = false; + this.isError = true; + this.errorMessage = 'Erreur lors du traitement de la connexion Google.'; + console.error('OAuth2 callback error:', e); + } + }); + } +} diff --git a/testapi-Service/frontend/src/environments/environment.prod.ts b/testapi-Service/frontend/src/environments/environment.prod.ts index 43a51f41..4d7a965e 100644 --- a/testapi-Service/frontend/src/environments/environment.prod.ts +++ b/testapi-Service/frontend/src/environments/environment.prod.ts @@ -1,4 +1,5 @@ export const environment = { production: true, - apiUrl: '/my-backend' + apiUrl: '/my-backend', + oauth2BackendUrl: 'http://localhost:8084' }; diff --git a/testapi-Service/frontend/src/environments/environment.ts b/testapi-Service/frontend/src/environments/environment.ts index c1524bac..2e4ffe65 100644 --- a/testapi-Service/frontend/src/environments/environment.ts +++ b/testapi-Service/frontend/src/environments/environment.ts @@ -5,6 +5,7 @@ export const environment = { production: false, apiUrl: 'http://localhost:8762', + oauth2BackendUrl: 'http://localhost:8084', //apiUrl: 'http://localhost:4200/api' }; From 7ebec16c4f482d532e13fc462029265dbc0d8cce Mon Sep 17 00:00:00 2001 From: michelzzw Date: Thu, 12 Feb 2026 17:36:32 -0500 Subject: [PATCH 14/21] Update Docker configuration for OAuth2 support --- docker-compose-local-test.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docker-compose-local-test.yml b/docker-compose-local-test.yml index ce362a66..509fd7f8 100644 --- a/docker-compose-local-test.yml +++ b/docker-compose-local-test.yml @@ -266,6 +266,9 @@ services: DB_NAME: taf JWT_SECRET: tafSecretKeyForJWTTokenGeneration2024 JWT_EXPIRES: 86400000 + GOOGLE_CLIENT_ID: ${GOOGLE_CLIENT_ID:-placeholder} + GOOGLE_CLIENT_SECRET: ${GOOGLE_CLIENT_SECRET:-placeholder} + OAUTH2_FRONTEND_REDIRECT_URL: http://localhost:4300 EUREKA_HOST: registry EUREKA_PORT: 8761 EUREKA_USERNAME: eureka From e8b3b82d738ac2826b56e79059ce554d6cda9916 Mon Sep 17 00:00:00 2001 From: michelzzw Date: Thu, 12 Feb 2026 17:36:49 -0500 Subject: [PATCH 15/21] Remove unused imports and dead code across modules --- .../services/CustomUserDetailsService.java | 1 - .../etsmtl/taf/auth/services/JwtService.java | 3 --- .../etsmtl/taf/gateway/GatewayApplication.java | 1 - .../etsmtl/selenium/requests/UseSelenium.java | 6 ++---- .../selenium/SeleniumApplicationTests.java | 1 - .../taf/user/AuthGatewayApplication.java | 3 --- .../taf/user/controller/UserController.java | 10 +--------- .../java/ca/etsmtl/taf/user/jwt/JwtUtil.java | 18 ------------------ .../user/payload/request/PasswordRequest.java | 1 - .../etsmtl/taf/user/services/JwtService.java | 5 ----- 10 files changed, 3 insertions(+), 46 deletions(-) diff --git a/auth/src/main/java/ca/etsmtl/taf/auth/services/CustomUserDetailsService.java b/auth/src/main/java/ca/etsmtl/taf/auth/services/CustomUserDetailsService.java index 00e952de..0747892b 100644 --- a/auth/src/main/java/ca/etsmtl/taf/auth/services/CustomUserDetailsService.java +++ b/auth/src/main/java/ca/etsmtl/taf/auth/services/CustomUserDetailsService.java @@ -9,7 +9,6 @@ import org.springframework.stereotype.Service; import ca.etsmtl.taf.auth.model.CustomUserDetails; -import java.util.ArrayList; import java.util.Optional; import lombok.extern.log4j.Log4j2; diff --git a/auth/src/main/java/ca/etsmtl/taf/auth/services/JwtService.java b/auth/src/main/java/ca/etsmtl/taf/auth/services/JwtService.java index c1d53984..a78b1084 100644 --- a/auth/src/main/java/ca/etsmtl/taf/auth/services/JwtService.java +++ b/auth/src/main/java/ca/etsmtl/taf/auth/services/JwtService.java @@ -1,6 +1,5 @@ package ca.etsmtl.taf.auth.services; -import ca.etsmtl.taf.auth.entity.User; import ca.etsmtl.taf.auth.jwt.JwtUtil; import ca.etsmtl.taf.auth.model.CustomUserDetails; import ca.etsmtl.taf.auth.payload.request.LoginRequest; @@ -10,12 +9,10 @@ import io.jsonwebtoken.JwtException; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContextHolder; -import org.springframework.security.core.userdetails.UserDetails; import org.springframework.stereotype.Service; import org.springframework.web.client.HttpClientErrorException; import org.springframework.security.crypto.password.PasswordEncoder; diff --git a/gateway/src/main/java/ca/etsmtl/taf/gateway/GatewayApplication.java b/gateway/src/main/java/ca/etsmtl/taf/gateway/GatewayApplication.java index 1b58d681..fe07410c 100644 --- a/gateway/src/main/java/ca/etsmtl/taf/gateway/GatewayApplication.java +++ b/gateway/src/main/java/ca/etsmtl/taf/gateway/GatewayApplication.java @@ -9,7 +9,6 @@ import org.springframework.cloud.client.discovery.EnableDiscoveryClient; import java.util.List; -import java.util.Map; @EnableDiscoveryClient @SpringBootApplication diff --git a/testapi-Service/selenium/src/main/java/ca/etsmtl/selenium/requests/UseSelenium.java b/testapi-Service/selenium/src/main/java/ca/etsmtl/selenium/requests/UseSelenium.java index fbfadf45..23d8828b 100644 --- a/testapi-Service/selenium/src/main/java/ca/etsmtl/selenium/requests/UseSelenium.java +++ b/testapi-Service/selenium/src/main/java/ca/etsmtl/selenium/requests/UseSelenium.java @@ -13,8 +13,6 @@ import org.openqa.selenium.support.ui.ExpectedConditions; import java.time.Duration; -import java.util.concurrent.TimeUnit; -import java.util.Arrays; import java.util.List; import java.util.NoSuchElementException; import java.io.File; @@ -68,7 +66,7 @@ public SeleniumResponse testWithSelenium(@RequestBody SeleniumCase seleniumCase) System.out.println("go to : " + seleniumAction.getInput()); driver.get(seleniumAction.getInput()); // Utilisez Duration.ofSeconds si vous êtes en Selenium 4+ - driver.manage().timeouts().implicitlyWait(1, TimeUnit.SECONDS); + driver.manage().timeouts().implicitlyWait(Duration.ofSeconds(1)); break; case 2: // FillField System.out.println("fill : " + seleniumAction.getObject() + " with " + seleniumAction.getInput()); @@ -77,7 +75,7 @@ public SeleniumResponse testWithSelenium(@RequestBody SeleniumCase seleniumCase) break; case 3: // GetAttribute WebElement webElement = driver.findElement(By.name(seleniumAction.getTarget())); - String pageAttribute = webElement.getAttribute(seleniumAction.getObject()); + String pageAttribute = webElement.getDomAttribute(seleniumAction.getObject()); if (!pageAttribute.equals(seleniumAction.getInput())) { String outputMessage = "Attribute " + seleniumAction.getObject() + " of " + seleniumAction.getTarget() diff --git a/testapi-Service/selenium/src/test/java/ca/etsmtl/selenium/SeleniumApplicationTests.java b/testapi-Service/selenium/src/test/java/ca/etsmtl/selenium/SeleniumApplicationTests.java index 3c40bd94..9c83a718 100644 --- a/testapi-Service/selenium/src/test/java/ca/etsmtl/selenium/SeleniumApplicationTests.java +++ b/testapi-Service/selenium/src/test/java/ca/etsmtl/selenium/SeleniumApplicationTests.java @@ -1,6 +1,5 @@ package ca.etsmtl.selenium; -import org.junit.jupiter.api.Test; import org.springframework.boot.test.context.SpringBootTest; @SpringBootTest diff --git a/user/src/main/java/ca/etsmtl/taf/user/AuthGatewayApplication.java b/user/src/main/java/ca/etsmtl/taf/user/AuthGatewayApplication.java index d59d9091..5f0e62c8 100644 --- a/user/src/main/java/ca/etsmtl/taf/user/AuthGatewayApplication.java +++ b/user/src/main/java/ca/etsmtl/taf/user/AuthGatewayApplication.java @@ -4,15 +4,12 @@ import org.springframework.boot.CommandLineRunner; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; -import org.springframework.cloud.client.ServiceInstance; import org.springframework.cloud.client.discovery.EnableDiscoveryClient; import ca.etsmtl.taf.user.repository.RoleRepository; import ca.etsmtl.taf.user.entity.ERole; import ca.etsmtl.taf.user.entity.Role; import java.util.Optional; -import java.util.List; - @EnableDiscoveryClient @SpringBootApplication @Log4j2 diff --git a/user/src/main/java/ca/etsmtl/taf/user/controller/UserController.java b/user/src/main/java/ca/etsmtl/taf/user/controller/UserController.java index c807d4b7..973298aa 100644 --- a/user/src/main/java/ca/etsmtl/taf/user/controller/UserController.java +++ b/user/src/main/java/ca/etsmtl/taf/user/controller/UserController.java @@ -2,8 +2,6 @@ import ca.etsmtl.taf.user.entity.User; import ca.etsmtl.taf.user.payload.request.*; -import org.springframework.security.authentication.AuthenticationManager; -import ca.etsmtl.taf.user.services.JwtService; import ca.etsmtl.taf.user.services.UserService; import org.springframework.beans.factory.annotation.Autowired; @@ -20,17 +18,11 @@ @RequestMapping("/api") public class UserController { - @Autowired - private AuthenticationManager authenticationManager; - - @GetMapping("/home") + @GetMapping("/home") public String greeting() { return "Hello from User Microservice!"; } - @Autowired - private JwtService jwtService; - @Autowired private UserService userService; diff --git a/user/src/main/java/ca/etsmtl/taf/user/jwt/JwtUtil.java b/user/src/main/java/ca/etsmtl/taf/user/jwt/JwtUtil.java index 81d2312c..c0fa8327 100644 --- a/user/src/main/java/ca/etsmtl/taf/user/jwt/JwtUtil.java +++ b/user/src/main/java/ca/etsmtl/taf/user/jwt/JwtUtil.java @@ -1,6 +1,5 @@ package ca.etsmtl.taf.user.jwt; -import ca.etsmtl.taf.user.model.enums.TokenClaims; import io.jsonwebtoken.Claims; import io.jsonwebtoken.Jwts; import io.jsonwebtoken.SignatureAlgorithm; @@ -58,21 +57,4 @@ private String createToken(Map claims, String subject) { .setExpiration(new Date(System.currentTimeMillis() + jwtExpirationMs)) .signWith(SignatureAlgorithm.HS512, secret).compact(); } - - private String createToken(Map claims, String subject, String userId) { - return Jwts.builder().setClaims(claims).setSubject(subject).setIssuedAt(new Date(System.currentTimeMillis())) - .setExpiration(new Date(System.currentTimeMillis() + jwtExpirationMs)) - .signWith(SignatureAlgorithm.HS512, secret).claim(TokenClaims.USER_ID.getValue(), userId).compact(); - } - - private String refreshToken(Map claims, final String token) { - String jwt = token; - if (token.startsWith("Bearer ")) { - jwt = jwt.substring(7); - } - String username = this.extractUsername(jwt); - return Jwts.builder().setClaims(claims).setSubject(username).setIssuedAt(new Date(System.currentTimeMillis())) - .setExpiration(new Date(System.currentTimeMillis() + jwtExpirationMs)) - .signWith(SignatureAlgorithm.HS512, secret).compact(); - } } \ No newline at end of file diff --git a/user/src/main/java/ca/etsmtl/taf/user/payload/request/PasswordRequest.java b/user/src/main/java/ca/etsmtl/taf/user/payload/request/PasswordRequest.java index 942d8e8e..180d9ed6 100644 --- a/user/src/main/java/ca/etsmtl/taf/user/payload/request/PasswordRequest.java +++ b/user/src/main/java/ca/etsmtl/taf/user/payload/request/PasswordRequest.java @@ -1,6 +1,5 @@ package ca.etsmtl.taf.user.payload.request; -import java.util.Set; import jakarta.validation.constraints.*; import lombok.Data; diff --git a/user/src/main/java/ca/etsmtl/taf/user/services/JwtService.java b/user/src/main/java/ca/etsmtl/taf/user/services/JwtService.java index d41effd4..8b4198f9 100644 --- a/user/src/main/java/ca/etsmtl/taf/user/services/JwtService.java +++ b/user/src/main/java/ca/etsmtl/taf/user/services/JwtService.java @@ -3,14 +3,12 @@ import ca.etsmtl.taf.user.jwt.JwtUtil; import ca.etsmtl.taf.user.model.CustomUserDetails; import ca.etsmtl.taf.user.payload.request.LoginRequest; -import ca.etsmtl.taf.user.payload.request.RefreshTokenRequest; import ca.etsmtl.taf.user.payload.response.JwtResponse; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContextHolder; -import org.springframework.security.core.userdetails.UserDetails; import org.springframework.stereotype.Service; import java.util.stream.Collectors; @@ -22,9 +20,6 @@ public class JwtService { @Autowired private AuthenticationManager authenticationManager; - @Autowired - private CustomUserDetailsService userDetailsService; - @Autowired private JwtUtil jwtUtil; From cebe096f6887650a57e2e68cc4e4c21e1bc04746 Mon Sep 17 00:00:00 2001 From: michelzzw Date: Thu, 12 Feb 2026 17:37:23 -0500 Subject: [PATCH 16/21] Fix update logic bugs in UserService --- .../ca/etsmtl/taf/user/services/UserService.java | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/user/src/main/java/ca/etsmtl/taf/user/services/UserService.java b/user/src/main/java/ca/etsmtl/taf/user/services/UserService.java index 8b0f2de2..5feb9acb 100644 --- a/user/src/main/java/ca/etsmtl/taf/user/services/UserService.java +++ b/user/src/main/java/ca/etsmtl/taf/user/services/UserService.java @@ -12,7 +12,6 @@ import org.springframework.stereotype.Service; import java.util.HashSet; -import java.util.NoSuchElementException; import java.util.Set; @Service @@ -36,12 +35,15 @@ public class UserService { public User update(String username, UpdateRequest signUpRequest) { // Update user's account User user = userRepository.findByUsername(username).orElseThrow(); - if(signUpRequest.getFullName().isEmpty()) + if(!signUpRequest.getFullName().isEmpty()) user.setFullName(signUpRequest.getFullName()); - if(signUpRequest.getEmail().isEmpty()) + if(!signUpRequest.getEmail().isEmpty()) user.setEmail(signUpRequest.getEmail()); - return userRepository.save(user); + if (user != null) { + return userRepository.save(user); + } + return user; } @@ -110,6 +112,9 @@ public User save(SignupRequest signUpRequest) { * @return */ public User findById(String id) { + if (id == null) { + throw new IllegalArgumentException("id cannot be null"); + } return userRepository.findById(id).orElseThrow(); } From 8c9ce0723c8e4d132fa3ffb6b3888b0b07ed691d Mon Sep 17 00:00:00 2001 From: michelzzw Date: Thu, 12 Feb 2026 23:10:16 -0500 Subject: [PATCH 17/21] =?UTF-8?q?docs(testapi):=20mettre=20=C3=A0=20jour?= =?UTF-8?q?=20README=20avec=20documentation=20compl=C3=A8te=20des=20tests?= =?UTF-8?q?=20d'API?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Ajout table des matières avec ancres - Ajout diagramme de flux du test d'API (Frontend → Backend → TestAPI → API cible) - Ajout section complète Tests d'API : utilisation UI, format requête/réponse JSON, 3 assertions (status code, corps JSON, en-têtes), exemple curl - Compression de la section Authentification (JWT + OAuth2 en résumé) - Réorganisation générale : fonctionnalité principale avant authentification --- testapi-Service/README.md | 307 +++++++++++++++++++++++++++++++++----- 1 file changed, 270 insertions(+), 37 deletions(-) diff --git a/testapi-Service/README.md b/testapi-Service/README.md index 28fc6036..d8c69321 100644 --- a/testapi-Service/README.md +++ b/testapi-Service/README.md @@ -1,76 +1,309 @@ ![Logo taf](./logo_taf.png) -# Test Automation Framework +# Test Automation Framework — Module TestAPI (Team 2) -TAF est un projet de R&D de cadriciel d’automatisation de test +TAF est un cadriciel d'automatisation de tests développé à l'ÉTS. Il permet d'exécuter des **tests d'API** (Rest-Assured), des **tests de performance** (Gatling) et des **tests d'interface** (Selenium) à travers une interface web Angular unique, sans écrire de code. +L'utilisateur construit ses cas de test depuis l'interface, les lance en un clic, et visualise les résultats (pass/fail) directement dans le tableau. -Il permet l'utilisation de plusieurs outils de tests (Selenium, Gatling, ...) à travers une interface web unique. +## Table des matières -L'application côté serveur est une application Java utilisant Springboot. -L'interface est une application web utilisant le framework Angular. +- [Architecture](#architecture) +- [Lancer le projet](#lancer-le-projet) +- [Tests d'API (Rest-Assured)](#tests-dapi-rest-assured) ← fonctionnalité principale +- [Autres modules](#autres-modules-gatling-selenium) +- [Authentification](#authentification) +- [Documentation API (Swagger UI)](#documentation-api-swagger-ui) +- [Variables d'environnement](#variables-denvironnement) +- [Technologies](#technologies) +- [Contribuer](#contribuer) -## Builder et lancer le projet +## Architecture -Pour lancer le projet, la méthode la plus simple est d'utiliser +Le module se compose de 4 microservices orchestrés par Docker Compose : -### Avec docker : +| Service | Rôle | Port hôte → interne | +|---------|------|---------------------| +| **frontend** | Interface Angular — construction et visualisation des tests | 4300 → 4200 | +| **backend** | API Spring Boot — authentification, routage vers les microservices | 8084 → 8080 | +| **testapi** | Exécution des tests d'API via **Rest-Assured** | 8086 → 8082 | +| **selenium** | Exécution des tests d'interface via **Selenium WebDriver** | 4445 → 4444 | -- (Prérequis) Installez Docker ainsi que Docker Compose sur votre système -- Exécutez la commande suivante : -```bash -docker compose --env-file .docker_config.env up +Les services s'enregistrent auprès d'**Eureka** (registry, port 8761) pour la découverte de services. + +### Flux d'un test d'API + +``` +Frontend Angular Backend (8084) TestAPI (8086) API cible +┌──────────────┐ ┌────────────────────┐ ┌──────────────────┐ ┌──────────┐ +│ Tableau de │─POST→│ TestApiController │─POST→│ RequestController│─HTTP→│ ex: │ +│ tests │ │ /api/testapi/ │ │ (Rest-Assured) │ │ jsonplace│ +│ │←JSON─│ checkApi │←JSON─│ │←─────│ holder │ +└──────────────┘ └────────────────────┘ └──────────────────┘ └──────────┘ ``` -### Sans docker : +Le backend agit comme un proxy transparent : il sérialise la requête JSON et la transmet au microservice testapi. C'est le microservice testapi qui effectue l'appel HTTP réel via Rest-Assured et exécute les assertions. -Il est aussi possible de lancer les applications côté serveur et côté client séparément. Cela n'est cependant pas recommandé, car cela est plus complexe, et le deviendra de plus en plus à mesure que de nouveaux services seront ajouté. +## Lancer le projet -**Backend :** -- Installez maven sur votre système avec `mvn clean install`. -- Configurez l'application Springboot dans votre ide (Eclipse, IntelliJ). Des outils sont disponibles dans ces IDEs pour lancer l'application. +### Avec Docker (recommandé) -**TestAPI** +**Prérequis :** Docker et Docker Compose installés. -- Dans le répertoire `testapi`, exécutez : +```powershell +# Démarrage rapide — Team 2 uniquement +.\start-taf-local.ps1 -Mode team2 -Build -```bash -cd testapi -mvn clean install +# Ou manuellement +docker compose -f docker-compose-local-test.yml up -d --build ``` -- Puis lancez l’API de test avec : +### Sans Docker (développement local) ```bash -mvn spring-boot:run +# Backend (port 8080) +cd backend && mvn clean install && mvn spring-boot:run + +# TestAPI (port 8082) +cd testapi && mvn clean install && mvn spring-boot:run + +# Frontend (port 4200) +cd frontend && npm install && ng serve --open ``` -- L’API sera accessible sur le port configuré (par défaut, [http://localhost:8080](http://localhost:8080)). +> Les variables d'environnement dans `backend/.env` doivent être configurées (MongoDB, JWT, Eureka, etc.). + +### Vérification après démarrage + +| Élément | URL | Résultat attendu | +|---------|-----|-------------------| +| Frontend | http://localhost:4300 | Page de connexion | +| Swagger UI | http://localhost:8084/swagger-ui.html | Interface API | +| Eureka | http://localhost:8761 | TEAM2 enregistré | +| Health check | http://localhost:8084/actuator/health | `{"status":"UP"}` | --- -**Frontend** +## Tests d'API (Rest-Assured) + +C'est la **fonctionnalité principale** du module TestAPI. Elle permet de tester n'importe quelle API REST en spécifiant la requête attendue et les critères de validation. + +### Utilisation via l'interface Angular + +1. **Se connecter** (JWT ou Google OAuth2) +2. Naviguer vers la page **Tests API** +3. Cliquer sur le bouton **+** pour ajouter un test +4. Remplir le formulaire : + - **Méthode** — `GET`, `POST`, `PUT`, `DELETE` (dropdown) + - **URL de l'API** — l'URL complète de l'API à tester + - **Statut attendu** — le code HTTP attendu (ex: `200`, `404`) + - **Headers** — paires clé/valeur à envoyer avec la requête (optionnel) + - **Sortie attendue** — le corps JSON attendu dans la réponse (optionnel) + - **En-têtes attendus** — les en-têtes attendus dans la réponse (optionnel) + - **Temps de réponse** — temps maximum acceptable en ms (optionnel) +5. Cliquer **« Lancer les tests »** — tous les tests du tableau s'exécutent en parallèle +6. La colonne **Résultat** affiche **vert** (pass) ou **rouge** (fail) avec un tooltip détaillant les erreurs + +**Fonctions supplémentaires :** + +- **Export CSV** — télécharge les définitions de tests au format CSV +- **Import CSV** — charge des tests depuis un fichier CSV +- **Supprimer** — supprime un test individuel du tableau + +> **Note :** Les résultats sont conservés en mémoire côté frontend uniquement. Un rafraîchissement de la page réinitialise les résultats (mais pas les définitions de tests). + +### Requête API — format JSON + +Endpoint : `POST /api/testapi/checkApi` (backend, port 8084) + +```json +{ + "method": "GET", + "apiUrl": "https://jsonplaceholder.typicode.com/posts/1", + "statusCode": 200, + "input": "", + "expectedOutput": "{\"userId\": 1, \"id\": 1}", + "headers": { + "Accept": "application/json" + } +} +``` + +| Champ | Type | Requis | Description | +|-------|------|--------|-------------| +| `method` | `String` | **Oui** | Méthode HTTP : `GET`, `POST`, `PUT`, `DELETE`, `PATCH`, `HEAD`, `OPTIONS` | +| `apiUrl` | `String` | **Oui** | URL complète de l'API cible à tester | +| `statusCode` | `int` | Non | Code HTTP attendu (ex: `200`, `201`, `404`) | +| `input` | `String` | Non | Corps de la requête à envoyer (pour POST/PUT) | +| `expectedOutput` | `String` | Non | Corps JSON attendu dans la réponse | +| `headers` | `Map` | Non | En-têtes HTTP à inclure dans la requête | + +> Le microservice testapi accepte aussi `expectedHeaders` (en-têtes de réponse attendus) et `responseTime` (temps max en ms), mais ces champs ne sont pas exposés par le backend proxy actuellement. + +### Validations exécutées (assertions) + +Le microservice testapi exécute **3 assertions actives** via Rest-Assured sur chaque test : + +| Assertion | Logique | Message d'erreur si échec | +|-----------|---------|---------------------------| +| **Code de statut** | `code attendu == code reçu` | `❌ Code de statut attendu: 200, reçu: 404` | +| **Corps de la réponse** | Comparaison JSON récursive champ par champ via `JsonComparator` | Retourne `fieldAnswer` avec `true`/`false` par champ | +| **En-têtes de réponse** | Chaque paire clé/valeur de `expectedHeaders` doit exister dans la réponse | `❌ En-tête attendu manquant: Content-Type` | + +La validation du **temps de réponse** est implémentée dans le code mais actuellement désactivée (commentée). + +**Comparaison JSON (JsonComparator) :** La comparaison du corps de réponse est récursive — elle parcourt l'arbre JSON attendu vs reçu et retourne un objet indiquant `true`/`false` pour chaque champ, y compris les objets et tableaux imbriqués. + +### Réponse API — format JSON + +```json +{ + "answer": true, + "statusCode": 200, + "output": "{\n \"userId\": 1,\n \"id\": 1,\n \"title\": \"...\"\n}", + "fieldAnswer": { + "userId": true, + "id": true + }, + "messages": [] +} +``` + +| Champ | Type | Description | +|-------|------|-------------| +| `answer` | `boolean` | Résultat global : `true` si toutes les assertions passent | +| `statusCode` | `int` | Code HTTP réellement reçu de l'API cible | +| `output` | `String` | Corps de la réponse reçue (formaté en JSON lisible) | +| `fieldAnswer` | `JsonNode` | Résultat champ par champ : `{"field1": true, "field2": false}` | +| `messages` | `List` | Messages d'erreur détaillés avec préfixe `❌` | + +**En cas d'erreur de connexion** (API cible injoignable) : -- Assurez-vous d’avoir **Node.js** et **npm** installés sur votre système. -- Dans le répertoire `frontend`, installez les dépendances avec : +```json +{ + "answer": false, + "statusCode": -1, + "output": null, + "fieldAnswer": null, + "messages": ["❌ Impossible de joindre l'API cible à https://..."] +} +``` + +### Exemple complet avec curl ```bash -cd frontend -npm install +# 1. Se connecter pour obtenir un token JWT +TOKEN=$(curl -s -X POST http://localhost:8084/api/auth/signin \ + -H "Content-Type: application/json" \ + -d '{"username":"user1","password":"password123"}' | jq -r '.accessToken') + +# 2. Lancer un test d'API +curl -X POST http://localhost:8084/api/testapi/checkApi \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer $TOKEN" \ + -d '{ + "method": "GET", + "apiUrl": "https://jsonplaceholder.typicode.com/posts/1", + "statusCode": 200, + "expectedOutput": "{\"userId\": 1, \"id\": 1}" + }' ``` -- Lancez l’application avec la ligne de commande : +--- + +## Autres modules (Gatling, Selenium) + +Le backend intègre également des endpoints pour les **tests de performance** (Gatling, `POST /api/gatling/runSimulation`) et les **tests d'interface** (Selenium, `POST /api/testselenium`). Ces modules sont développés et maintenus par d'autres équipes (Projet #4 et Projet #2 respectivement). Consultez leur documentation pour plus de détails. + +--- + +## Authentification + +Tous les endpoints de test (`/api/testapi/**`, etc.) nécessitent un token JWT. + +### JWT (classique) ```bash -ng serve --open +# Inscription +curl -X POST http://localhost:8084/api/auth/signup \ + -H "Content-Type: application/json" \ + -d '{"fullName":"user1","username":"user1","email":"user1@example.com","role":["user"],"password":"password123"}' + +# Connexion → retourne un accessToken +curl -X POST http://localhost:8084/api/auth/signin \ + -H "Content-Type: application/json" \ + -d '{"username":"user1","password":"password123"}' ``` -- L’interface s’ouvrira automatiquement dans votre navigateur à l’adresse [http://localhost:4200] -## Contribuer au projet +Utilisez le token dans l'en-tête : `Authorization: Bearer ` + +### Google OAuth2 + +Connexion via compte Google disponible sur la page de login du frontend. Nécessite les variables d'environnement `GOOGLE_CLIENT_ID` et `GOOGLE_CLIENT_SECRET` configurées avant le démarrage Docker : + +```powershell +$env:GOOGLE_CLIENT_ID = "votre-client-id.apps.googleusercontent.com" +$env:GOOGLE_CLIENT_SECRET = "votre-client-secret" +docker compose -f docker-compose-local-test.yml up -d --build backend-team2 +``` + +> Sans ces variables, le login Google ne fonctionne pas, mais le reste de l'application (JWT, tests d'API, Gatling, Selenium) fonctionne normalement. + +### Endpoints publics + +`POST /api/auth/signup`, `POST /api/auth/signin`, `GET /api/oauth2/login-url`, `/swagger-ui/**`, `/api-docs/**`, `/actuator/**` + +--- + +## Documentation API (Swagger UI) + +Accessible à : [http://localhost:8084/swagger-ui.html](http://localhost:8084/swagger-ui.html) + +1. Connectez-vous via `POST /api/auth/signin` — copiez le `accessToken` +2. Cliquez sur **Authorize** (🔒) → entrez `Bearer ` → validez +3. Tous les endpoints sont accessibles avec des exemples pré-remplis + +--- + +## Variables d'environnement + +| Variable | Description | Valeur par défaut | +|----------|-------------|-------------------| +| `DB_URI` | URI de connexion MongoDB | — | +| `DB_NAME` | Nom de la base de données | — | +| `DB_AUTH` | Base d'authentification MongoDB | `admin` | +| `JWT_SECRET` | Clé secrète pour la signature JWT (HS512) | — | +| `JWT_EXPIRES` | Durée de validité du token JWT (ms) | `86400000` (24h) | +| `GOOGLE_CLIENT_ID` | ID client Google OAuth2 | `placeholder` | +| `GOOGLE_CLIENT_SECRET` | Secret client Google OAuth2 | `placeholder` | +| `OAUTH2_FRONTEND_REDIRECT_URL` | URL de redirection frontend après OAuth2 | `http://localhost:4200` | +| `EUREKA_HOST` | Hôte du serveur Eureka | — | +| `EUREKA_PORT` | Port du serveur Eureka | `8761` | +| `TEST_API_SERVICE_URL` | URL du microservice TestAPI | — | +| `TEST_API_SERVICE_PORT` | Port du microservice TestAPI | `8082` | +| `SELENIUM_URL` | URL du microservice Selenium | — | +| `SELENIUM_PORT` | Port du microservice Selenium | `4444` | + +## Technologies + +| Composant | Version | Rôle | +|-----------|---------|------| +| Java | 17 | Langage backend | +| Spring Boot | 3.5.10 | Framework backend | +| **Rest-Assured** | **5.5.0** | **Exécution des tests d'API** | +| Angular | 14 | Framework frontend | +| MongoDB | latest | Stockage utilisateurs/rôles | +| Gatling | — | Tests de performance | +| Selenium Java | 4.27.0 | Tests d'interface | +| Maven | 3.9+ | Build backend | +| jjwt | 0.9.1 | Tokens JWT | +| springdoc-openapi | 2.6.0 | Swagger UI | + +## Contribuer -La démarche pour contribuer au projet est disponible dans le document [CONTRIBUTING.md](./CONTRIBUTING.md). -La programmation en JS/TS et Java font usage de conventions précises disponibles dans le fichier [CONVENTIONS.md](./documentation/CONVENTIONS.md). La non-utilisation de ces normes pourrait mener à un refus de vos contributions. +La démarche pour contribuer est disponible dans [CONTRIBUTING.md](./CONTRIBUTING.md). +Les conventions de code (JS/TS et Java) sont dans [CONVENTIONS.md](./documentation/CONVENTIONS.md). ## Contact -En cas de questions, vous pouvez rejoindre [le discord TAF](https://discord.gg/TYrqTdHEqk). Veilliez à ce qu'uniquement 1 ou 2 personnes de votre équipe le rejoigne afin de limiter le nombres de personnes et de faciliter la communication. \ No newline at end of file +En cas de questions : [Discord TAF](https://discord.gg/TYrqTdHEqk) (1-2 personnes par équipe max). From 5839077d8e3eb14d6eb83062ee9184d96800ae86 Mon Sep 17 00:00:00 2001 From: michelzzw Date: Thu, 12 Feb 2026 23:10:33 -0500 Subject: [PATCH 18/21] =?UTF-8?q?refactor(backend):=20ajouter=20annotation?= =?UTF-8?q?s=20@NonNull=20pour=20la=20robustesse=20des=20contr=C3=B4leurs?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Ajout @NonNull sur les paramètres de méthodes pour supprimer les avertissements - Fichiers modifiés : DevCorsConfiguration, JwtAuthenticationFilter, UserRepository --- .../java/ca/etsmtl/selenium/config/DevCorsConfiguration.java | 3 ++- .../java/ca/etsmtl/taf/user/jwt/JwtAuthenticationFilter.java | 3 ++- .../java/ca/etsmtl/taf/user/repository/UserRepository.java | 3 ++- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/testapi-Service/selenium/src/main/java/ca/etsmtl/selenium/config/DevCorsConfiguration.java b/testapi-Service/selenium/src/main/java/ca/etsmtl/selenium/config/DevCorsConfiguration.java index 25212fc7..b1c5ea34 100644 --- a/testapi-Service/selenium/src/main/java/ca/etsmtl/selenium/config/DevCorsConfiguration.java +++ b/testapi-Service/selenium/src/main/java/ca/etsmtl/selenium/config/DevCorsConfiguration.java @@ -2,6 +2,7 @@ import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Profile; +import org.springframework.lang.NonNull; import org.springframework.web.servlet.config.annotation.CorsRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; @@ -9,7 +10,7 @@ @Profile("local") public class DevCorsConfiguration implements WebMvcConfigurer { @Override - public void addCorsMappings(CorsRegistry registry) { + public void addCorsMappings(@NonNull CorsRegistry registry) { registry.addMapping("/microservice/**").allowedMethods("GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS") .exposedHeaders("Authorization"); } diff --git a/user/src/main/java/ca/etsmtl/taf/user/jwt/JwtAuthenticationFilter.java b/user/src/main/java/ca/etsmtl/taf/user/jwt/JwtAuthenticationFilter.java index 080e1f7d..3b14d3f1 100644 --- a/user/src/main/java/ca/etsmtl/taf/user/jwt/JwtAuthenticationFilter.java +++ b/user/src/main/java/ca/etsmtl/taf/user/jwt/JwtAuthenticationFilter.java @@ -12,6 +12,7 @@ import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.web.authentication.WebAuthenticationDetailsSource; import org.springframework.web.filter.OncePerRequestFilter; +import org.springframework.lang.NonNull; import java.io.IOException; @@ -28,7 +29,7 @@ public JwtAuthenticationFilter(JwtUtil jwtUtil) { } @Override - protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) + protected void doFilterInternal(@NonNull HttpServletRequest request, @NonNull HttpServletResponse response, @NonNull FilterChain chain) throws ServletException, IOException { final String authorizationHeader = request.getHeader(HttpHeaders.AUTHORIZATION); String username = null; diff --git a/user/src/main/java/ca/etsmtl/taf/user/repository/UserRepository.java b/user/src/main/java/ca/etsmtl/taf/user/repository/UserRepository.java index 796c2309..5d7401b7 100644 --- a/user/src/main/java/ca/etsmtl/taf/user/repository/UserRepository.java +++ b/user/src/main/java/ca/etsmtl/taf/user/repository/UserRepository.java @@ -2,6 +2,7 @@ import java.util.Optional; import org.springframework.data.mongodb.repository.MongoRepository; +import org.springframework.lang.NonNull; import ca.etsmtl.taf.user.entity.User; @@ -10,7 +11,7 @@ public interface UserRepository extends MongoRepository { boolean existsByUsername(String username); - boolean existsById(String id); + boolean existsById(@NonNull String id); boolean existsByEmail(String email); From 11ad7e3a8013b523207b146f99c8b50f842ec442 Mon Sep 17 00:00:00 2001 From: michelzzw Date: Fri, 13 Feb 2026 00:57:51 -0500 Subject: [PATCH 19/21] =?UTF-8?q?fix(frontend):=20corriger=20l'authentific?= =?UTF-8?q?ation=20JWT=20pour=20les=20requ=C3=AAtes=20API?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Utiliser le header 'Authorization' au lieu de 'x-access-token' dans l'intercepteur HTTP (compatibilité Spring Boot) - Lire le champ 'token' du service auth en plus de 'accessToken' lors du login (compatibilité avec le microservice auth séparé) --- testapi-Service/frontend/src/app/_helpers/auth.interceptor.ts | 4 ++-- testapi-Service/frontend/src/app/login/login.component.ts | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/testapi-Service/frontend/src/app/_helpers/auth.interceptor.ts b/testapi-Service/frontend/src/app/_helpers/auth.interceptor.ts index eaa176c8..47eb5bcc 100644 --- a/testapi-Service/frontend/src/app/_helpers/auth.interceptor.ts +++ b/testapi-Service/frontend/src/app/_helpers/auth.interceptor.ts @@ -5,8 +5,8 @@ import { HttpInterceptor, HttpHandler, HttpRequest } from '@angular/common/http' import { TokenStorageService } from '../_services/token-storage.service'; import { Observable } from 'rxjs'; -// const TOKEN_HEADER_KEY = 'Authorization'; // for Spring Boot back-end -const TOKEN_HEADER_KEY = 'x-access-token'; // for Node.js Express back-end +// const TOKEN_HEADER_KEY = 'x-access-token'; // for Node.js Express back-end +const TOKEN_HEADER_KEY = 'Authorization'; // for Spring Boot back-end @Injectable() export class AuthInterceptor implements HttpInterceptor { diff --git a/testapi-Service/frontend/src/app/login/login.component.ts b/testapi-Service/frontend/src/app/login/login.component.ts index d6e33f39..05f81ed6 100644 --- a/testapi-Service/frontend/src/app/login/login.component.ts +++ b/testapi-Service/frontend/src/app/login/login.component.ts @@ -34,7 +34,7 @@ export class LoginComponent implements OnInit { this.authService.login(username, password).subscribe({ next: data => { - this.tokenStorage.saveToken(data.accessToken); + this.tokenStorage.saveToken(data.token || data.accessToken); this.tokenStorage.saveUser(data); this.isLoginFailed = false; From 6e1f2c2bb6fe0b6a159dc317dd19bcc2740be4ee Mon Sep 17 00:00:00 2001 From: michelzzw Date: Fri, 13 Feb 2026 00:58:05 -0500 Subject: [PATCH 20/21] =?UTF-8?q?fix(testapi):=20corriger=20les=20bugs=20d?= =?UTF-8?q?e=20validation=20et=20compatibilit=C3=A9=20Jakarta=20EE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Migrer javax.validation vers jakarta.validation (Spring Boot 3.x) - Gérer le cas NullNode dans checkOutput() pour éviter les faux négatifs - Ignorer la vérification du temps de réponse quand non défini (responseTime <= 0) - Optimiser le Dockerfile avec -DskipTests -B pour accélérer le build --- testapi-Service/testapi/Dockerfile | 2 +- .../src/main/java/org/requests/RequestController.java | 5 ++++- .../src/main/java/org/requests/TestApiController.java | 2 +- .../java/org/requests/payload/request/TestApiRequest.java | 2 +- 4 files changed, 7 insertions(+), 4 deletions(-) diff --git a/testapi-Service/testapi/Dockerfile b/testapi-Service/testapi/Dockerfile index d2e98dbb..f615b07c 100644 --- a/testapi-Service/testapi/Dockerfile +++ b/testapi-Service/testapi/Dockerfile @@ -3,7 +3,7 @@ FROM maven:3.9.5-eclipse-temurin-17 as builder WORKDIR /testapi COPY . . -RUN mvn clean install +RUN mvn clean install -DskipTests -B EXPOSE 8090 ENTRYPOINT ["mvn", "spring-boot:run" ] \ No newline at end of file diff --git a/testapi-Service/testapi/src/main/java/org/requests/RequestController.java b/testapi-Service/testapi/src/main/java/org/requests/RequestController.java index 5bb825b4..98011208 100644 --- a/testapi-Service/testapi/src/main/java/org/requests/RequestController.java +++ b/testapi-Service/testapi/src/main/java/org/requests/RequestController.java @@ -122,7 +122,7 @@ private boolean checkOutput() { System.out.println("DEBUG : expectedOutput brut = " + expectedOutput); // Si aucun expectedOutput ou texte vide, ignorer la comparaison - if (expectedOutput == null || + if (expectedOutput == null || expectedOutput.isNull() || (expectedOutput.isTextual() && expectedOutput.asText().trim().isEmpty())) { System.out.println("DEBUG : Aucun expectedOutput défini."); return true; @@ -179,6 +179,9 @@ private boolean checkOutput() { private boolean checkResponseTime() { + if (request.getResponseTime() <= 0) { + return true; + } return response.getTime() < request.getResponseTime(); } diff --git a/testapi-Service/testapi/src/main/java/org/requests/TestApiController.java b/testapi-Service/testapi/src/main/java/org/requests/TestApiController.java index c56765b6..cc15fbb4 100644 --- a/testapi-Service/testapi/src/main/java/org/requests/TestApiController.java +++ b/testapi-Service/testapi/src/main/java/org/requests/TestApiController.java @@ -3,7 +3,7 @@ import org.requests.payload.request.TestApiRequest; import org.springframework.web.bind.annotation.*; -import javax.validation.Valid; +import jakarta.validation.Valid; import java.io.Serializable; @CrossOrigin(origins = "*", maxAge = 3600) diff --git a/testapi-Service/testapi/src/main/java/org/requests/payload/request/TestApiRequest.java b/testapi-Service/testapi/src/main/java/org/requests/payload/request/TestApiRequest.java index c93a0707..491a550a 100644 --- a/testapi-Service/testapi/src/main/java/org/requests/payload/request/TestApiRequest.java +++ b/testapi-Service/testapi/src/main/java/org/requests/payload/request/TestApiRequest.java @@ -3,7 +3,7 @@ import com.fasterxml.jackson.databind.JsonNode; import org.requests.Method; -import javax.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotBlank; import java.util.Map; public class TestApiRequest { From 16bc5cac7081c6eb10a09abc0d309b2dc5db4343 Mon Sep 17 00:00:00 2001 From: michelzzw Date: Sat, 14 Feb 2026 09:52:15 -0500 Subject: [PATCH 21/21] Remove contact section from README Removed contact information section from README. --- testapi-Service/README.md | 3 --- 1 file changed, 3 deletions(-) diff --git a/testapi-Service/README.md b/testapi-Service/README.md index d8c69321..06f15160 100644 --- a/testapi-Service/README.md +++ b/testapi-Service/README.md @@ -304,6 +304,3 @@ Accessible à : [http://localhost:8084/swagger-ui.html](http://localhost:8084/sw La démarche pour contribuer est disponible dans [CONTRIBUTING.md](./CONTRIBUTING.md). Les conventions de code (JS/TS et Java) sont dans [CONVENTIONS.md](./documentation/CONVENTIONS.md). -## Contact - -En cas de questions : [Discord TAF](https://discord.gg/TYrqTdHEqk) (1-2 personnes par équipe max).