From 9435124f24b3ac08fd478f6aefdef5bea42df6f6 Mon Sep 17 00:00:00 2001 From: Quentin Bellanger Date: Fri, 13 Dec 2024 17:38:16 +0100 Subject: [PATCH] Ajoute des tests end-to-end pour les audits et rapports (#894) * add basic test * test audit edition * test audit deletion * improve seeds generation and document it * add notes test * add statement test * move tests documentation in cypress folder * add copy audit, criteria search and hide references tests * add tests commands * make getbylabel accept regex * test filters, download, transverse status, finish audit, na on topic and reset filters features * add some report tests * seed an audit before each test * seed audits for report tests * use object options in create test audit and fix wait tests * document tests script and command * add todo tests * fix regex in test to catch finish date on audit overview page * add account creation tests * handle error where user login with wrong token format * test user login and settings actions * temp: to reset * remove unecessary create test account function * use debug api endpoints to generate test audits * test audits list page * update tests doc * lint * only import debug controller on certain condition * remove faker * remove cypress boilerplate files --------- Co-authored-by: Adrien Boutigny --- confiture-rest-api/src/app.module.ts | 9 +- confiture-rest-api/src/auth/auth.module.ts | 3 +- confiture-rest-api/src/debug.controller.ts | 199 +++++++ confiture-web-app/README.md | 18 - .../components/account/dashboard/AuditRow.vue | 8 +- .../src/components/audit/AraTabs.vue | 3 +- .../src/components/audit/PagesSample.vue | 1 - .../src/pages/report/ReportPage.vue | 4 +- confiture-web-app/src/store/account.ts | 11 +- cypress/.gitignore | 1 + cypress/README.md | 38 ++ cypress/e2e/account.cy.ts | 423 +++++++++++++- cypress/e2e/audit.cy.ts | 520 +++++++++++++++++- cypress/e2e/report.cy.ts | 173 +++++- cypress/examples/1-getting-started/todo.cy.js | 143 ----- .../2-advanced-examples/actions.cy.js | 299 ---------- .../2-advanced-examples/aliasing.cy.js | 39 -- .../2-advanced-examples/assertions.cy.js | 176 ------ .../2-advanced-examples/connectors.cy.js | 98 ---- .../2-advanced-examples/cookies.cy.js | 118 ---- .../2-advanced-examples/cypress_api.cy.js | 185 ------- .../examples/2-advanced-examples/files.cy.js | 85 --- .../2-advanced-examples/location.cy.js | 32 -- .../examples/2-advanced-examples/misc.cy.js | 104 ---- .../2-advanced-examples/navigation.cy.js | 56 -- .../network_requests.cy.js | 163 ------ .../2-advanced-examples/querying.cy.js | 114 ---- .../spies_stubs_clocks.cy.js | 201 ------- .../2-advanced-examples/storage.cy.js | 110 ---- .../2-advanced-examples/traversal.cy.js | 121 ---- .../2-advanced-examples/utilities.cy.js | 108 ---- .../2-advanced-examples/viewport.cy.js | 58 -- .../2-advanced-examples/waiting.cy.js | 30 - .../examples/2-advanced-examples/window.cy.js | 22 - cypress/fixtures/audit.json | 40 +- cypress/fixtures/example.json | 5 - cypress/fixtures/statement.json | 13 + cypress/support/commands.ts | 106 +++- cypress/support/e2e.ts | 4 +- package.json | 2 + 40 files changed, 1502 insertions(+), 2341 deletions(-) create mode 100644 confiture-rest-api/src/debug.controller.ts create mode 100644 cypress/.gitignore create mode 100644 cypress/README.md delete mode 100644 cypress/examples/1-getting-started/todo.cy.js delete mode 100644 cypress/examples/2-advanced-examples/actions.cy.js delete mode 100644 cypress/examples/2-advanced-examples/aliasing.cy.js delete mode 100644 cypress/examples/2-advanced-examples/assertions.cy.js delete mode 100644 cypress/examples/2-advanced-examples/connectors.cy.js delete mode 100644 cypress/examples/2-advanced-examples/cookies.cy.js delete mode 100644 cypress/examples/2-advanced-examples/cypress_api.cy.js delete mode 100644 cypress/examples/2-advanced-examples/files.cy.js delete mode 100644 cypress/examples/2-advanced-examples/location.cy.js delete mode 100644 cypress/examples/2-advanced-examples/misc.cy.js delete mode 100644 cypress/examples/2-advanced-examples/navigation.cy.js delete mode 100644 cypress/examples/2-advanced-examples/network_requests.cy.js delete mode 100644 cypress/examples/2-advanced-examples/querying.cy.js delete mode 100644 cypress/examples/2-advanced-examples/spies_stubs_clocks.cy.js delete mode 100644 cypress/examples/2-advanced-examples/storage.cy.js delete mode 100644 cypress/examples/2-advanced-examples/traversal.cy.js delete mode 100644 cypress/examples/2-advanced-examples/utilities.cy.js delete mode 100644 cypress/examples/2-advanced-examples/viewport.cy.js delete mode 100644 cypress/examples/2-advanced-examples/waiting.cy.js delete mode 100644 cypress/examples/2-advanced-examples/window.cy.js delete mode 100644 cypress/fixtures/example.json create mode 100644 cypress/fixtures/statement.json diff --git a/confiture-rest-api/src/app.module.ts b/confiture-rest-api/src/app.module.ts index 149f65d6..dd870543 100644 --- a/confiture-rest-api/src/app.module.ts +++ b/confiture-rest-api/src/app.module.ts @@ -8,6 +8,8 @@ import { MailModule } from "./mail/mail.module"; import { AuthModule } from "./auth/auth.module"; import { ProfileModule } from "./profile/profile.module"; import { UserMiddleware } from "./auth/user.middleware"; +import { DebugController } from "./debug.controller"; +import { PrismaService } from "./prisma.service"; @Module({ imports: [ @@ -21,7 +23,12 @@ import { UserMiddleware } from "./auth/user.middleware"; AuthModule, ProfileModule ], - controllers: [HealthCheckController] + providers: process.env.DEBUG_ENDPOINTS ? [PrismaService] : [], + controllers: [ + HealthCheckController, + // enable debug enpoints only when the DEBUG_ENDPOINTS variable is set + ...(process.env.DEBUG_ENDPOINTS ? [DebugController] : []) + ] }) export class AppModule implements NestModule { configure(consumer: MiddlewareConsumer) { diff --git a/confiture-rest-api/src/auth/auth.module.ts b/confiture-rest-api/src/auth/auth.module.ts index 8bf21ee1..7d65e862 100644 --- a/confiture-rest-api/src/auth/auth.module.ts +++ b/confiture-rest-api/src/auth/auth.module.ts @@ -30,6 +30,7 @@ import { CreateAccountController } from "./create-account.controller"; }), FeedbackModule, AuditsModule - ] + ], + exports: [AuthService] }) export class AuthModule {} diff --git a/confiture-rest-api/src/debug.controller.ts b/confiture-rest-api/src/debug.controller.ts new file mode 100644 index 00000000..dcf0d4e8 --- /dev/null +++ b/confiture-rest-api/src/debug.controller.ts @@ -0,0 +1,199 @@ +import { Body, Controller, Post } from "@nestjs/common"; +import { PrismaService } from "./prisma.service"; +import { AuthService } from "./auth/auth.service"; +import { + CriterionResultStatus, + CriterionResultUserImpact +} from "@prisma/client"; +import { nanoid } from "nanoid"; +import { CRITERIA } from "./audits/criteria"; + +@Controller("debug") +export class DebugController { + constructor( + private readonly prisma: PrismaService, + private readonly auth: AuthService + ) {} + + @Post("verification-token") + async getAccountVerificationToken(@Body() body: { username: string }) { + const token = this.auth.regenerateVerificationToken(body.username); + return token; + } + + @Post("password-reset-verification-token") + async getPasswordResetVerificationToken(@Body() body: { username: string }) { + const token = this.auth.generatePasswordResetVerificationToken( + body.username + ); + + return token; + } + + @Post("email-update-verification-token") + async getEmailUpdateVerificationToken(@Body() body: { uid: string }) { + const token = this.auth.regenerateEmailUpdateVerificationToken(body.uid); + + return token; + } + + @Post("create-verified-user") + async createVerifiedUser() { + const email = `john-doe${Math.random()}@example.com`; + const password = "pouetpouetpouet"; + + await this.auth.createUnverifiedUser(email, password); + const user = await this.prisma.user.update({ + data: { + isVerified: true, + verificationJti: null + }, + where: { + username: email + } + }); + + const authToken = await this.auth.signin(email, password); + + return { + username: email, + password, + authToken, + uid: user.uid + }; + } + + @Post("create-audit") + async createAudit( + @Body() + body: { + isComplete: boolean; + isPristine: boolean; + noImprovements: boolean; + auditorEmail?: string; + } + ) { + const editUniqueId = `edit-${nanoid()}`; + const reportUniqueId = `report-${nanoid()}`; + + const completedAudit = await this.prisma.audit.create({ + data: { + editUniqueId: editUniqueId, + consultUniqueId: reportUniqueId, + creationDate: new Date(), + publicationDate: body.isComplete ? new Date() : null, + auditTrace: { + create: { + auditConsultUniqueId: editUniqueId, + auditEditUniqueId: reportUniqueId + } + }, + auditType: "FULL", + procedureName: "Audit de mon petit site", + auditorEmail: body.auditorEmail || "etienne.durand@example.com", + auditorName: "Étienne Durand", + transverseElementsPage: { + create: { + name: "Éléments transverses", + url: "" + } + }, + pages: { + createMany: { + data: [ + { + name: "Accueil", + url: "https://example.com" + }, + { + name: "Contact", + url: "https://example.com/contact" + }, + { + name: "À propos", + url: "https://example.com/a-propos" + }, + { + name: "Blog", + url: "https://example.com/blog" + }, + { + name: "Article", + url: "https://example.com/blog/article" + }, + { + name: "Connexion", + url: "https://example.com/connexion" + }, + { + name: "Documentation", + url: "https://example.com/documentation" + }, + { + name: "FAQ", + url: "https://example.com/faq" + } + ] + } + } + }, + include: { + transverseElementsPage: true + } + }); + + const auditPages = await this.prisma.auditedPage.findMany({ + where: { + auditUniqueId: editUniqueId + } + }); + + if (!body.isPristine) { + await Promise.all( + [completedAudit.transverseElementsPage, ...auditPages].map(async (p) => + this.prisma.criterionResult.createMany({ + data: CRITERIA.map((c, i) => ({ + status: [ + CriterionResultStatus.COMPLIANT, + CriterionResultStatus.NOT_APPLICABLE, + CriterionResultStatus.NOT_COMPLIANT + ][i % 3], + notCompliantComment: "Une erreur ici", + notApplicableComment: body.noImprovements + ? null + : "Attention quand même si ça devient applicable", + compliantComment: body.noImprovements ? null : "Peut mieux faire", + quickWin: i % 7 === 0, + userImpact: [ + CriterionResultUserImpact.MINOR, + CriterionResultUserImpact.MAJOR, + CriterionResultUserImpact.BLOCKING, + null + ][i % 4], + topic: c.topic, + criterium: c.criterium, + pageId: p.id + })) + }) + ) + ); + } + + if (!body.isComplete && !body.isPristine) { + await this.prisma.criterionResult.delete({ + where: { + pageId_topic_criterium: { + topic: 1, + criterium: 1, + pageId: auditPages[0].id + } + } + }); + } + + return { + editId: editUniqueId, + reportId: reportUniqueId + }; + } +} diff --git a/confiture-web-app/README.md b/confiture-web-app/README.md index ed751b1e..5c28995a 100644 --- a/confiture-web-app/README.md +++ b/confiture-web-app/README.md @@ -31,24 +31,6 @@ Lancer le [serveur local sur le port 3000](http://localhost:3000) : yarn dev ``` -## Tests - -[Cypress](https://www.cypress.io/) est utilisé pour lancer des tests end-to-end (e2e) dans un navigateur pour reproduire le comportement des utilisateurs. - -Les tests peuvent être lancés de 2 manières : - -- Via l’application Cypress avec : - - ```sh - yarn cypress open - ``` - -- Via le terminal avec : - - ```sh - yarn cypress run - ``` - ## Guidelines - Utiliser les media queries en "desktop first" et avec la notation suivante avec les [valeurs de points de rupture du DSFR](https://www.systeme-de-design.gouv.fr/elements-d-interface/fondamentaux-techniques/grille-et-points-de-rupture) : diff --git a/confiture-web-app/src/components/account/dashboard/AuditRow.vue b/confiture-web-app/src/components/account/dashboard/AuditRow.vue index f6360a28..cccd75b6 100644 --- a/confiture-web-app/src/components/account/dashboard/AuditRow.vue +++ b/confiture-web-app/src/components/account/dashboard/AuditRow.vue @@ -133,7 +133,7 @@ function copyAuditLink(uniqueId: string) { } function copyReportLink(uniqueId: string) { - const url = `${window.location.origin}/rapports/${uniqueId}`; + const url = `${window.location.origin}/rapport/${uniqueId}`; navigator.clipboard.writeText(url).then(() => { notify( @@ -174,8 +174,8 @@ function copyStatementLink(uniqueId: string) { 'fr-badge--purple-glycine': isInProgress || isNotStarted }" > - Statut - {{ isInProgress || isNotStarted ? "En cours" : "Terminé" }} + Statut {{ isInProgress || isNotStarted ? "En cours" : "Terminé" }}