ujusum:3-codage:2-sprints:1-sprint-1
Différences
Ci-dessous, les différences entre deux révisions de la page.
| ujusum:3-codage:2-sprints:1-sprint-1 [2026/06/08 01:58] – créée admin | ujusum:3-codage:2-sprints:1-sprint-1 [2026/06/08 02:01] (Version actuelle) – admin | ||
|---|---|---|---|
| Ligne 8222: | Ligne 8222: | ||
| ---- | ---- | ||
| - | ====== | + | ====== |
| - | ===== Sprint 1-C.2 ===== | + | ===== Objectif |
| - | Tests Unitaires | + | Sécuriser l' |
| - | Objectif | + | À l' |
| - | Valider automatiquement | + | < |
| + | ✓ PasswordService testé | ||
| + | |||
| + | ✓ TokenService testé | ||
| + | |||
| + | ✓ AuthService testé | ||
| + | |||
| + | ✓ JwtStrategy testée | ||
| + | |||
| + | ✓ RolesGuard testé | ||
| + | |||
| + | ✓ PermissionsGuard testé | ||
| + | |||
| + | ✓ Couverture > 80% | ||
| + | |||
| + | ✓ CI compatible | ||
| + | </ | ||
| + | |||
| + | ---- | ||
| + | |||
| + | ====== Architecture des tests ====== | ||
| + | |||
| + | ===== Répertoire ===== | ||
| + | |||
| + | Créer | ||
| < | < | ||
| - | PasswordService | + | apps/ |
| - | AuthService | + | ├── domain |
| + | │ | ||
| + | │ | ||
| + | │ | ||
| + | │ | ||
| + | │ | ||
| + | │ | ||
| + | │ | ||
| + | ├── infrastructure | ||
| + | │ | ||
| + | │ | ||
| + | │ | ||
| + | │ | ||
| + | │ | ||
| + | └── presentation | ||
| + | │ | ||
| + | └── guards | ||
| - | JwtStrategy | + | ├── roles.guard.spec.ts |
| + | └── permissions.guard.spec.ts | ||
| + | </ | ||
| - | RolesGuard | + | ---- |
| - | PermissionsGuard | + | ====== Étape 1 — Configuration Jest ====== |
| + | |||
| + | ===== Vérifier ===== | ||
| + | |||
| + | <code bash> | ||
| + | npm install -D | ||
| + | |||
| + | jest | ||
| + | |||
| + | ts-jest | ||
| + | |||
| + | @types/ | ||
| + | </ | ||
| + | |||
| + | ---- | ||
| + | |||
| + | ===== Vérifier ===== | ||
| + | |||
| + | < | ||
| + | apps/ | ||
| + | </ | ||
| + | |||
| + | ---- | ||
| + | |||
| + | ===== Coverage ===== | ||
| + | |||
| + | <code ts> | ||
| + | coverageThreshold: | ||
| + | |||
| + | global: { | ||
| + | |||
| + | branches: 80, | ||
| + | |||
| + | functions: 80, | ||
| + | |||
| + | lines: 80, | ||
| + | |||
| + | statements: 80 | ||
| + | } | ||
| + | } | ||
| + | </ | ||
| + | |||
| + | ---- | ||
| + | |||
| + | ====== Étape 2 — PasswordService ====== | ||
| + | |||
| + | ===== Fichier ===== | ||
| + | |||
| + | < | ||
| + | password.service.spec.ts | ||
| + | </ | ||
| + | |||
| + | ---- | ||
| + | |||
| + | ===== Test hash ===== | ||
| + | |||
| + | <code ts> | ||
| + | describe( | ||
| + | ' | ||
| + | () => { | ||
| + | |||
| + | it( | ||
| + | ' | ||
| + | async () => { | ||
| + | |||
| + | const hash = | ||
| + | |||
| + | await service.hash( | ||
| + | ' | ||
| + | ); | ||
| + | |||
| + | expect(hash) | ||
| + | .toBeDefined(); | ||
| + | |||
| + | expect(hash) | ||
| + | .not | ||
| + | .toEqual( | ||
| + | ' | ||
| + | ); | ||
| + | } | ||
| + | ); | ||
| + | } | ||
| + | ); | ||
| + | </ | ||
| + | |||
| + | ---- | ||
| + | |||
| + | ===== Test compare ===== | ||
| + | |||
| + | <code ts> | ||
| + | it( | ||
| + | ' | ||
| + | async () => { | ||
| + | |||
| + | const hash = | ||
| + | |||
| + | await service.hash( | ||
| + | ' | ||
| + | ); | ||
| + | |||
| + | const result = | ||
| + | |||
| + | await service.compare( | ||
| + | |||
| + | ' | ||
| + | |||
| + | hash | ||
| + | ); | ||
| + | |||
| + | expect(result) | ||
| + | .toBe(true); | ||
| + | } | ||
| + | ); | ||
| + | </ | ||
| + | |||
| + | ---- | ||
| + | |||
| + | ====== Étape 3 — TokenService ====== | ||
| + | |||
| + | ===== Fichier ===== | ||
| + | |||
| + | < | ||
| + | token.service.spec.ts | ||
| + | </ | ||
| + | |||
| + | ---- | ||
| + | |||
| + | ===== Test Access Token ===== | ||
| + | |||
| + | <code ts> | ||
| + | it( | ||
| + | ' | ||
| + | async () => { | ||
| + | |||
| + | const token = | ||
| + | |||
| + | await service | ||
| + | .generateAccessToken({ | ||
| + | |||
| + | sub: ' | ||
| + | |||
| + | tenantId: ' | ||
| + | |||
| + | email: | ||
| + | ' | ||
| + | }); | ||
| + | |||
| + | expect(token) | ||
| + | .toBeDefined(); | ||
| + | } | ||
| + | ); | ||
| + | </ | ||
| + | |||
| + | ---- | ||
| + | |||
| + | ===== Test Refresh Token ===== | ||
| + | |||
| + | <code ts> | ||
| + | it( | ||
| + | ' | ||
| + | async () => { | ||
| + | |||
| + | const token = | ||
| + | |||
| + | await service | ||
| + | .generateRefreshToken(); | ||
| + | |||
| + | expect(token) | ||
| + | .toBeDefined(); | ||
| + | } | ||
| + | ); | ||
| + | </ | ||
| + | |||
| + | ---- | ||
| + | |||
| + | ====== Étape 4 — Mock Prisma ====== | ||
| + | |||
| + | ===== Créer ===== | ||
| + | |||
| + | < | ||
| + | test/ | ||
| + | </ | ||
| + | |||
| + | ---- | ||
| + | |||
| + | ===== Contenu ===== | ||
| + | |||
| + | <code ts> | ||
| + | export const prismaMock = { | ||
| + | |||
| + | user: { | ||
| + | |||
| + | findUnique: jest.fn(), | ||
| + | |||
| + | create: jest.fn(), | ||
| + | |||
| + | update: jest.fn() | ||
| + | }, | ||
| + | |||
| + | role: { | ||
| + | |||
| + | findFirst: jest.fn() | ||
| + | }, | ||
| + | |||
| + | userRole: { | ||
| + | |||
| + | create: jest.fn() | ||
| + | }, | ||
| + | |||
| + | refreshToken: | ||
| + | |||
| + | create: jest.fn(), | ||
| + | |||
| + | findMany: jest.fn(), | ||
| + | |||
| + | update: jest.fn(), | ||
| + | |||
| + | updateMany: jest.fn() | ||
| + | }, | ||
| + | |||
| + | session: { | ||
| + | |||
| + | create: jest.fn(), | ||
| + | |||
| + | updateMany: jest.fn() | ||
| + | }, | ||
| + | |||
| + | tenant: { | ||
| + | |||
| + | findUnique: jest.fn() | ||
| + | } | ||
| + | }; | ||
| + | </ | ||
| + | |||
| + | ---- | ||
| + | |||
| + | ====== Étape 5 — AuthService ====== | ||
| + | |||
| + | ===== Fichier ===== | ||
| + | |||
| + | < | ||
| + | auth.service.spec.ts | ||
| + | </ | ||
| + | |||
| + | ---- | ||
| + | |||
| + | ===== Setup ===== | ||
| + | |||
| + | <code ts> | ||
| + | beforeEach( | ||
| + | async () => { | ||
| + | |||
| + | const module = | ||
| + | |||
| + | await Test | ||
| + | .createTestingModule({ | ||
| + | |||
| + | providers: [ | ||
| + | |||
| + | AuthService, | ||
| + | |||
| + | { | ||
| + | provide: | ||
| + | PrismaService, | ||
| + | |||
| + | useValue: | ||
| + | prismaMock | ||
| + | } | ||
| + | ] | ||
| + | }) | ||
| + | .compile(); | ||
| + | } | ||
| + | ); | ||
| + | </ | ||
| + | |||
| + | ---- | ||
| + | |||
| + | ====== Étape 6 — Test Register ====== | ||
| + | |||
| + | ===== Cas nominal ===== | ||
| + | |||
| + | <code ts> | ||
| + | it( | ||
| + | ' | ||
| + | async () => { | ||
| + | |||
| + | prismaMock.user | ||
| + | .findUnique | ||
| + | .mockResolvedValue( | ||
| + | null | ||
| + | ); | ||
| + | |||
| + | const result = | ||
| + | |||
| + | await service.register({ | ||
| + | |||
| + | email: | ||
| + | ' | ||
| + | |||
| + | password: | ||
| + | ' | ||
| + | |||
| + | firstName: | ||
| + | ' | ||
| + | |||
| + | lastName: | ||
| + | ' | ||
| + | }); | ||
| + | |||
| + | expect(result) | ||
| + | .toBeDefined(); | ||
| + | } | ||
| + | ); | ||
| + | </ | ||
| + | |||
| + | ---- | ||
| + | |||
| + | ====== Étape 7 — Email déjà utilisé ====== | ||
| + | |||
| + | ===== Test ===== | ||
| + | |||
| + | <code ts> | ||
| + | it( | ||
| + | ' | ||
| + | async () => { | ||
| + | |||
| + | prismaMock.user | ||
| + | .findUnique | ||
| + | .mockResolvedValue({}); | ||
| + | |||
| + | await expect( | ||
| + | |||
| + | service.register( | ||
| + | dto | ||
| + | ) | ||
| + | |||
| + | ).rejects.toThrow( | ||
| + | ConflictException | ||
| + | ); | ||
| + | } | ||
| + | ); | ||
| + | </ | ||
| + | |||
| + | ---- | ||
| + | |||
| + | ====== Étape 8 — Test Login ====== | ||
| + | |||
| + | ===== Succès ===== | ||
| + | |||
| + | <code ts> | ||
| + | it( | ||
| + | ' | ||
| + | async () => { | ||
| + | |||
| + | const result = | ||
| + | |||
| + | await service.login( | ||
| + | dto, | ||
| + | session | ||
| + | ); | ||
| + | |||
| + | expect(result) | ||
| + | .toBeDefined(); | ||
| + | } | ||
| + | ); | ||
| + | </ | ||
| + | |||
| + | ---- | ||
| + | |||
| + | ===== Mot de passe invalide ===== | ||
| + | |||
| + | <code ts> | ||
| + | it( | ||
| + | ' | ||
| + | async () => { | ||
| + | |||
| + | await expect( | ||
| + | |||
| + | service.login( | ||
| + | dto, | ||
| + | session | ||
| + | ) | ||
| + | |||
| + | ).rejects.toThrow( | ||
| + | UnauthorizedException | ||
| + | ); | ||
| + | } | ||
| + | ); | ||
| + | </ | ||
| + | |||
| + | ---- | ||
| + | |||
| + | ====== Étape 9 — Test Refresh ====== | ||
| + | |||
| + | ===== Succès ===== | ||
| + | |||
| + | <code ts> | ||
| + | it( | ||
| + | ' | ||
| + | async () => { | ||
| + | |||
| + | const result = | ||
| + | |||
| + | await service.refresh({ | ||
| + | |||
| + | refreshToken: | ||
| + | ' | ||
| + | }); | ||
| + | |||
| + | expect(result) | ||
| + | .toBeDefined(); | ||
| + | } | ||
| + | ); | ||
| + | </ | ||
| + | |||
| + | ---- | ||
| + | |||
| + | ===== Échec ===== | ||
| + | |||
| + | <code ts> | ||
| + | it( | ||
| + | ' | ||
| + | async () => { | ||
| + | |||
| + | await expect( | ||
| + | |||
| + | service.refresh({ | ||
| + | |||
| + | refreshToken: | ||
| + | ' | ||
| + | }) | ||
| + | |||
| + | ).rejects.toThrow( | ||
| + | UnauthorizedException | ||
| + | ); | ||
| + | } | ||
| + | ); | ||
| + | </ | ||
| + | |||
| + | ---- | ||
| + | |||
| + | ====== Étape 10 — Test Logout ====== | ||
| + | |||
| + | ===== Succès ===== | ||
| + | |||
| + | <code ts> | ||
| + | it( | ||
| + | ' | ||
| + | async () => { | ||
| + | |||
| + | const result = | ||
| + | |||
| + | await service.logout( | ||
| + | ' | ||
| + | ); | ||
| + | |||
| + | expect( | ||
| + | result.success | ||
| + | ).toBe(true); | ||
| + | } | ||
| + | ); | ||
| + | </ | ||
| + | |||
| + | ---- | ||
| + | |||
| + | ====== Étape 11 — Test Current User ====== | ||
| + | |||
| + | ===== Succès ===== | ||
| + | |||
| + | <code ts> | ||
| + | it( | ||
| + | ' | ||
| + | async () => { | ||
| + | |||
| + | const result = | ||
| + | |||
| + | await service.me( | ||
| + | ' | ||
| + | ); | ||
| + | |||
| + | expect(result) | ||
| + | .toBeDefined(); | ||
| + | |||
| + | expect( | ||
| + | result.email | ||
| + | ).toEqual( | ||
| + | ' | ||
| + | ); | ||
| + | } | ||
| + | ); | ||
| + | </ | ||
| + | |||
| + | ---- | ||
| + | |||
| + | ====== Étape 12 — JwtStrategy ====== | ||
| + | |||
| + | ===== Fichier ===== | ||
| + | |||
| + | < | ||
| + | jwt.strategy.spec.ts | ||
| + | </ | ||
| + | |||
| + | ---- | ||
| + | |||
| + | ===== Test ===== | ||
| + | |||
| + | <code ts> | ||
| + | it( | ||
| + | ' | ||
| + | async () => { | ||
| + | |||
| + | const result = | ||
| + | |||
| + | await strategy.validate({ | ||
| + | |||
| + | sub: ' | ||
| + | |||
| + | email: | ||
| + | ' | ||
| + | |||
| + | tenantId: | ||
| + | ' | ||
| + | }); | ||
| + | |||
| + | expect( | ||
| + | result.id | ||
| + | ).toEqual(' | ||
| + | } | ||
| + | ); | ||
| + | </ | ||
| + | |||
| + | ---- | ||
| + | |||
| + | ====== Étape 13 — RolesGuard ====== | ||
| + | |||
| + | ===== Fichier ===== | ||
| + | |||
| + | < | ||
| + | roles.guard.spec.ts | ||
| + | </ | ||
| + | |||
| + | ---- | ||
| + | |||
| + | ===== Autorisé ===== | ||
| + | |||
| + | <code ts> | ||
| + | it( | ||
| + | ' | ||
| + | () => { | ||
| + | |||
| + | expect( | ||
| + | guard.canActivate( | ||
| + | context | ||
| + | ) | ||
| + | ).toBe(true); | ||
| + | } | ||
| + | ); | ||
| + | </ | ||
| + | |||
| + | ---- | ||
| + | |||
| + | ===== Refusé ===== | ||
| + | |||
| + | <code ts> | ||
| + | it( | ||
| + | ' | ||
| + | () => { | ||
| + | |||
| + | expect( | ||
| + | guard.canActivate( | ||
| + | context | ||
| + | ) | ||
| + | ).toBe(false); | ||
| + | } | ||
| + | ); | ||
| + | </ | ||
| + | |||
| + | ---- | ||
| + | |||
| + | ====== Étape 14 — PermissionsGuard | ||
| + | |||
| + | ===== Fichier ===== | ||
| + | |||
| + | < | ||
| + | permissions.guard.spec.ts | ||
| + | </ | ||
| + | |||
| + | ---- | ||
| + | |||
| + | ===== Autorisé ===== | ||
| + | |||
| + | <code ts> | ||
| + | it( | ||
| + | ' | ||
| + | () => { | ||
| + | |||
| + | expect( | ||
| + | guard.canActivate( | ||
| + | context | ||
| + | ) | ||
| + | ).toBe(true); | ||
| + | } | ||
| + | ); | ||
| + | </ | ||
| + | |||
| + | ---- | ||
| + | |||
| + | ===== Refusé ===== | ||
| + | |||
| + | <code ts> | ||
| + | it( | ||
| + | ' | ||
| + | () => { | ||
| + | |||
| + | expect( | ||
| + | guard.canActivate( | ||
| + | context | ||
| + | ) | ||
| + | ).toBe(false); | ||
| + | } | ||
| + | ); | ||
| + | </ | ||
| + | |||
| + | ---- | ||
| + | |||
| + | ====== Étape 15 — Exécution ====== | ||
| + | |||
| + | ===== Lancer ===== | ||
| + | |||
| + | <code bash> | ||
| + | nx test api | ||
| + | </ | ||
| + | |||
| + | ---- | ||
| + | |||
| + | ===== Avec coverage ===== | ||
| + | |||
| + | <code bash> | ||
| + | nx test api \ | ||
| + | --coverage | ||
| + | </ | ||
| + | |||
| + | ---- | ||
| + | |||
| + | ====== Étape 16 — Rapport ====== | ||
| + | |||
| + | ===== Généré dans ===== | ||
| + | |||
| + | < | ||
| + | coverage/ | ||
| + | </ | ||
| + | |||
| + | ---- | ||
| + | |||
| + | ===== Ouvrir ===== | ||
| + | |||
| + | < | ||
| + | coverage/ | ||
| + | </ | ||
| + | |||
| + | ---- | ||
| + | |||
| + | ====== Étape 17 — Objectifs ====== | ||
| + | |||
| + | ===== PasswordService ===== | ||
| + | |||
| + | < | ||
| + | 100 % | ||
| + | </ | ||
| + | |||
| + | ---- | ||
| + | |||
| + | ===== TokenService ===== | ||
| + | |||
| + | < | ||
| + | 100 % | ||
| + | </ | ||
| + | |||
| + | ---- | ||
| + | |||
| + | ===== RolesGuard ===== | ||
| + | |||
| + | < | ||
| + | 100 % | ||
| + | </ | ||
| + | |||
| + | ---- | ||
| + | |||
| + | ===== PermissionsGuard ===== | ||
| + | |||
| + | < | ||
| + | 100 % | ||
| + | </ | ||
| + | |||
| + | ---- | ||
| + | |||
| + | ===== AuthService ===== | ||
| + | |||
| + | < | ||
| + | > 80 % | ||
| + | </ | ||
| + | |||
| + | ---- | ||
| + | |||
| + | ====== Étape 18 — Ajout CI ====== | ||
| + | |||
| + | ===== Workflow ===== | ||
| + | |||
| + | < | ||
| + | .github/ | ||
| + | </ | ||
| + | |||
| + | ---- | ||
| + | |||
| + | ===== Ajouter ===== | ||
| + | |||
| + | <code yaml> | ||
| + | - name: Run Tests | ||
| + | |||
| + | run: nx test api | ||
| + | </ | ||
| + | |||
| + | ---- | ||
| + | |||
| + | ===== Coverage ===== | ||
| + | |||
| + | <code yaml> | ||
| + | - name: Coverage | ||
| + | |||
| + | run: nx test api --coverage | ||
| + | </ | ||
| + | |||
| + | ---- | ||
| + | |||
| + | ====== Étape 19 — Badge Coverage ====== | ||
| + | |||
| + | ===== README ===== | ||
| + | |||
| + | <code markdown> | ||
| + | ![Coverage] | ||
| + | </ | ||
| + | |||
| + | ---- | ||
| + | |||
| + | ====== Définition de terminé ====== | ||
| + | |||
| + | Le Sprint 1-C.2 est terminé lorsque : | ||
| + | |||
| + | < | ||
| + | ✓ PasswordService testé | ||
| + | |||
| + | ✓ TokenService testé | ||
| + | |||
| + | ✓ AuthService testé | ||
| + | |||
| + | ✓ JwtStrategy testée | ||
| + | |||
| + | ✓ RolesGuard testé | ||
| + | |||
| + | ✓ PermissionsGuard testé | ||
| + | |||
| + | ✓ Coverage > 80 % | ||
| + | |||
| + | ✓ CI verte | ||
| + | </ | ||
| + | |||
| + | ---- | ||
| + | |||
| + | ====== Livrables ====== | ||
| + | |||
| + | < | ||
| + | auth.service.spec.ts | ||
| + | |||
| + | password.service.spec.ts | ||
| + | |||
| + | token.service.spec.ts | ||
| + | |||
| + | jwt.strategy.spec.ts | ||
| + | |||
| + | roles.guard.spec.ts | ||
| + | |||
| + | permissions.guard.spec.ts | ||
| + | |||
| + | coverage report | ||
| + | </ | ||
| + | |||
| + | ---- | ||
| + | |||
| + | ====== Sprint 1-C.3 — Tests E2E Authentification ====== | ||
| + | |||
| + | ===== Objectif ===== | ||
| + | |||
| + | Valider le fonctionnement réel de toute la chaîne d' | ||
| + | |||
| + | À l' | ||
| + | |||
| + | < | ||
| + | ✓ Register testé | ||
| + | |||
| + | ✓ Login testé | ||
| + | |||
| + | ✓ Current User testé | ||
| + | |||
| + | ✓ Refresh testé | ||
| + | |||
| + | ✓ Logout testé | ||
| + | |||
| + | ✓ RBAC testé | ||
| + | |||
| + | ✓ PostgreSQL réel | ||
| + | |||
| + | ✓ JWT réel | ||
| + | |||
| + | ✓ Prisma réel | ||
| + | </ | ||
| + | |||
| + | Ces tests constituent la première validation métier complète de la plateforme. | ||
| + | |||
| + | ---- | ||
| + | |||
| + | ====== Architecture E2E ====== | ||
| + | |||
| + | ===== Environnement ===== | ||
| + | |||
| + | < | ||
| + | NestJS | ||
| + | |||
| + | ↓ | ||
| + | |||
| + | Prisma | ||
| + | |||
| + | ↓ | ||
| + | |||
| + | PostgreSQL | ||
| + | |||
| + | ↓ | ||
| + | |||
| + | JWT | ||
| + | |||
| + | ↓ | ||
| + | |||
| + | API réelle | ||
| + | </ | ||
| + | |||
| + | ---- | ||
| + | |||
| + | ===== Pas de Mock ===== | ||
| + | |||
| + | Contrairement aux tests unitaires : | ||
| + | |||
| + | < | ||
| + | Aucun Mock | ||
| + | |||
| + | Aucun Fake Service | ||
| + | |||
| + | Base PostgreSQL réelle | ||
| + | </ | ||
| + | |||
| + | ---- | ||
| + | |||
| + | ====== Étape 1 — Création du projet E2E ====== | ||
| + | |||
| + | ===== Vérifier ===== | ||
| + | |||
| + | < | ||
| + | apps/ | ||
| + | </ | ||
| + | |||
| + | ---- | ||
| + | |||
| + | ===== Structure ===== | ||
| + | |||
| + | < | ||
| + | apps | ||
| + | |||
| + | ├── api | ||
| + | |||
| + | └── api-e2e | ||
| + | |||
| + | ├── src | ||
| + | |||
| + | │ | ||
| + | |||
| + | │ | ||
| + | |||
| + | │ | ||
| + | ├── jest.config.ts | ||
| + | |||
| + | └── project.json | ||
| + | </ | ||
| + | |||
| + | ---- | ||
| + | |||
| + | ====== Étape 2 — Installation ====== | ||
| + | |||
| + | ===== Vérifier ===== | ||
| + | |||
| + | <code bash> | ||
| + | npm install -D supertest | ||
| + | |||
| + | npm install -D @types/ | ||
| + | </ | ||
| + | |||
| + | ---- | ||
| + | |||
| + | ====== Étape 3 — Base de test ====== | ||
| + | |||
| + | ===== Créer ===== | ||
| + | |||
| + | < | ||
| + | .env.test | ||
| + | </ | ||
| + | |||
| + | ---- | ||
| + | |||
| + | ===== Contenu ===== | ||
| + | |||
| + | < | ||
| + | DATABASE_URL= | ||
| + | |||
| + | postgresql:// | ||
| + | postgres@localhost: | ||
| + | rental_platform_test | ||
| + | |||
| + | JWT_SECRET=test-secret | ||
| + | |||
| + | JWT_EXPIRES_IN=15m | ||
| + | |||
| + | REFRESH_SECRET=test-refresh | ||
| + | |||
| + | REFRESH_EXPIRES_IN=30d | ||
| + | </ | ||
| + | |||
| + | ---- | ||
| + | |||
| + | ====== Étape 4 — Création DB Test ====== | ||
| + | |||
| + | ===== PostgreSQL ===== | ||
| + | |||
| + | <code sql> | ||
| + | CREATE DATABASE | ||
| + | rental_platform_test; | ||
| + | </ | ||
| + | |||
| + | ---- | ||
| + | |||
| + | ====== Étape 5 — Migration ====== | ||
| + | |||
| + | ===== Exécuter ===== | ||
| + | |||
| + | <code bash> | ||
| + | DATABASE_URL=... | ||
| + | |||
| + | npx prisma migrate deploy | ||
| + | </ | ||
| + | |||
| + | ---- | ||
| + | |||
| + | ===== Seed ===== | ||
| + | |||
| + | <code bash> | ||
| + | DATABASE_URL=... | ||
| + | |||
| + | npx prisma db seed | ||
| + | </ | ||
| + | |||
| + | ---- | ||
| + | |||
| + | ====== Étape 6 — Bootstrap E2E ====== | ||
| + | |||
| + | ===== Créer ===== | ||
| + | |||
| + | < | ||
| + | apps/ | ||
| + | </ | ||
| + | |||
| + | ---- | ||
| + | |||
| + | ===== Base ===== | ||
| + | |||
| + | <code ts> | ||
| + | describe( | ||
| + | 'Auth E2E', | ||
| + | () => { | ||
| + | |||
| + | let app: | ||
| + | INestApplication; | ||
| + | |||
| + | beforeAll( | ||
| + | async () => { | ||
| + | |||
| + | const moduleRef = | ||
| + | |||
| + | await Test | ||
| + | .createTestingModule({ | ||
| + | |||
| + | imports: [ | ||
| + | |||
| + | AppModule | ||
| + | ] | ||
| + | }) | ||
| + | .compile(); | ||
| + | |||
| + | app = | ||
| + | moduleRef | ||
| + | .createNestApplication(); | ||
| + | |||
| + | await app.init(); | ||
| + | } | ||
| + | ); | ||
| + | |||
| + | afterAll( | ||
| + | async () => { | ||
| + | |||
| + | await app.close(); | ||
| + | } | ||
| + | ); | ||
| + | } | ||
| + | ); | ||
| + | </ | ||
| + | |||
| + | ---- | ||
| + | |||
| + | ====== Étape 7 — Variables de scénario ====== | ||
| + | |||
| + | ===== Ajouter ===== | ||
| + | |||
| + | <code ts> | ||
| + | let accessToken: | ||
| + | string; | ||
| + | |||
| + | let refreshToken: | ||
| + | string; | ||
| + | |||
| + | let userId: | ||
| + | string; | ||
| + | </ | ||
| + | |||
| + | ---- | ||
| + | |||
| + | ====== Étape 8 — Test Register ====== | ||
| + | |||
| + | ===== Cas nominal ===== | ||
| + | |||
| + | <code ts> | ||
| + | it( | ||
| + | ' | ||
| + | async () => { | ||
| + | |||
| + | const response = | ||
| + | |||
| + | await request( | ||
| + | |||
| + | app.getHttpServer() | ||
| + | ) | ||
| + | |||
| + | .post( | ||
| + | '/ | ||
| + | ) | ||
| + | |||
| + | .send({ | ||
| + | |||
| + | email: | ||
| + | ' | ||
| + | |||
| + | password: | ||
| + | ' | ||
| + | |||
| + | firstName: | ||
| + | ' | ||
| + | |||
| + | lastName: | ||
| + | ' | ||
| + | }) | ||
| + | |||
| + | .expect(201); | ||
| + | |||
| + | expect( | ||
| + | |||
| + | response.body | ||
| + | .accessToken | ||
| + | |||
| + | ).toBeDefined(); | ||
| + | |||
| + | userId = | ||
| + | |||
| + | response.body | ||
| + | .user.id; | ||
| + | } | ||
| + | ); | ||
| + | </ | ||
| + | |||
| + | ---- | ||
| + | |||
| + | ====== Étape 9 — Test Login ====== | ||
| + | |||
| + | ===== Cas nominal ===== | ||
| + | |||
| + | <code ts> | ||
| + | it( | ||
| + | ' | ||
| + | async () => { | ||
| + | |||
| + | const response = | ||
| + | |||
| + | await request( | ||
| + | |||
| + | app.getHttpServer() | ||
| + | ) | ||
| + | |||
| + | .post( | ||
| + | '/ | ||
| + | ) | ||
| + | |||
| + | .send({ | ||
| + | |||
| + | email: | ||
| + | ' | ||
| + | |||
| + | password: | ||
| + | ' | ||
| + | }) | ||
| + | |||
| + | .expect(201); | ||
| + | |||
| + | accessToken = | ||
| + | |||
| + | response.body | ||
| + | .accessToken; | ||
| + | |||
| + | refreshToken = | ||
| + | |||
| + | response.body | ||
| + | .refreshToken; | ||
| + | |||
| + | expect( | ||
| + | accessToken | ||
| + | ).toBeDefined(); | ||
| + | |||
| + | expect( | ||
| + | refreshToken | ||
| + | ).toBeDefined(); | ||
| + | } | ||
| + | ); | ||
| + | </ | ||
| + | |||
| + | ---- | ||
| + | |||
| + | ====== Étape 10 — Test Current User ====== | ||
| + | |||
| + | ===== Endpoint ===== | ||
| + | |||
| + | < | ||
| + | GET /auth/me | ||
| + | </ | ||
| + | |||
| + | ---- | ||
| + | |||
| + | ===== Test ===== | ||
| + | |||
| + | <code ts> | ||
| + | it( | ||
| + | ' | ||
| + | async () => { | ||
| + | |||
| + | const response = | ||
| + | |||
| + | await request( | ||
| + | |||
| + | app.getHttpServer() | ||
| + | ) | ||
| + | |||
| + | .get( | ||
| + | '/ | ||
| + | ) | ||
| + | |||
| + | .set( | ||
| + | |||
| + | ' | ||
| + | |||
| + | `Bearer ${accessToken}` | ||
| + | ) | ||
| + | |||
| + | .expect(200); | ||
| + | |||
| + | expect( | ||
| + | |||
| + | response.body.email | ||
| + | |||
| + | ).toEqual( | ||
| + | ' | ||
| + | ); | ||
| + | } | ||
| + | ); | ||
| + | </ | ||
| + | |||
| + | ---- | ||
| + | |||
| + | ====== Étape 11 — Test RBAC ====== | ||
| + | |||
| + | ===== Endpoint ===== | ||
| + | |||
| + | < | ||
| + | GET / | ||
| + | </ | ||
| + | |||
| + | ---- | ||
| + | |||
| + | ===== CUSTOMER ===== | ||
| + | |||
| + | <code ts> | ||
| + | it( | ||
| + | ' | ||
| + | async () => { | ||
| + | |||
| + | await request( | ||
| + | |||
| + | app.getHttpServer() | ||
| + | ) | ||
| + | |||
| + | .get( | ||
| + | '/ | ||
| + | ) | ||
| + | |||
| + | .set( | ||
| + | |||
| + | ' | ||
| + | |||
| + | `Bearer ${accessToken}` | ||
| + | ) | ||
| + | |||
| + | .expect(403); | ||
| + | } | ||
| + | ); | ||
| + | </ | ||
| + | |||
| + | ---- | ||
| + | |||
| + | ====== Étape 12 — Test Refresh ====== | ||
| + | |||
| + | ===== Endpoint ===== | ||
| + | |||
| + | < | ||
| + | POST / | ||
| + | </ | ||
| + | |||
| + | ---- | ||
| + | |||
| + | ===== Test ===== | ||
| + | |||
| + | <code ts> | ||
| + | it( | ||
| + | ' | ||
| + | async () => { | ||
| + | |||
| + | const response = | ||
| + | |||
| + | await request( | ||
| + | |||
| + | app.getHttpServer() | ||
| + | ) | ||
| + | |||
| + | .post( | ||
| + | '/ | ||
| + | ) | ||
| + | |||
| + | .send({ | ||
| + | |||
| + | refreshToken | ||
| + | }) | ||
| + | |||
| + | .expect(201); | ||
| + | |||
| + | accessToken = | ||
| + | |||
| + | response.body | ||
| + | .accessToken; | ||
| + | |||
| + | refreshToken = | ||
| + | |||
| + | response.body | ||
| + | .refreshToken; | ||
| + | |||
| + | expect( | ||
| + | accessToken | ||
| + | ).toBeDefined(); | ||
| + | } | ||
| + | ); | ||
| + | </ | ||
| + | |||
| + | ---- | ||
| + | |||
| + | ====== Étape 13 — Test Logout ====== | ||
| + | |||
| + | ===== Endpoint ===== | ||
| + | |||
| + | < | ||
| + | POST / | ||
| + | </ | ||
| + | |||
| + | ---- | ||
| + | |||
| + | ===== Test ===== | ||
| + | |||
| + | <code ts> | ||
| + | it( | ||
| + | ' | ||
| + | async () => { | ||
| + | |||
| + | await request( | ||
| + | |||
| + | app.getHttpServer() | ||
| + | ) | ||
| + | |||
| + | .post( | ||
| + | '/ | ||
| + | ) | ||
| + | |||
| + | .set( | ||
| + | |||
| + | ' | ||
| + | |||
| + | `Bearer ${accessToken}` | ||
| + | ) | ||
| + | |||
| + | .expect(201); | ||
| + | } | ||
| + | ); | ||
| + | </ | ||
| + | |||
| + | ---- | ||
| + | |||
| + | ====== Étape 14 — Vérification sécurité ====== | ||
| + | |||
| + | ===== Ancien Refresh Token ===== | ||
| + | |||
| + | Ne doit plus fonctionner. | ||
| + | |||
| + | ---- | ||
| + | |||
| + | ===== Test ===== | ||
| + | |||
| + | <code ts> | ||
| + | it( | ||
| + | ' | ||
| + | async () => { | ||
| + | |||
| + | await request( | ||
| + | |||
| + | app.getHttpServer() | ||
| + | ) | ||
| + | |||
| + | .post( | ||
| + | '/ | ||
| + | ) | ||
| + | |||
| + | .send({ | ||
| + | |||
| + | refreshToken | ||
| + | }) | ||
| + | |||
| + | .expect(401); | ||
| + | } | ||
| + | ); | ||
| + | </ | ||
| + | |||
| + | ---- | ||
| + | |||
| + | ====== Étape 15 — Cas erreur Login ====== | ||
| + | |||
| + | ===== Mauvais mot de passe ===== | ||
| + | |||
| + | <code ts> | ||
| + | it( | ||
| + | ' | ||
| + | async () => { | ||
| + | |||
| + | await request( | ||
| + | |||
| + | app.getHttpServer() | ||
| + | ) | ||
| + | |||
| + | .post( | ||
| + | '/ | ||
| + | ) | ||
| + | |||
| + | .send({ | ||
| + | |||
| + | email: | ||
| + | ' | ||
| + | |||
| + | password: | ||
| + | ' | ||
| + | }) | ||
| + | |||
| + | .expect(401); | ||
| + | } | ||
| + | ); | ||
| + | </ | ||
| + | |||
| + | ---- | ||
| + | |||
| + | ====== Étape 16 — Cas erreur Register ====== | ||
| + | |||
| + | ===== Email existant ===== | ||
| + | |||
| + | <code ts> | ||
| + | it( | ||
| + | ' | ||
| + | async () => { | ||
| + | |||
| + | await request( | ||
| + | |||
| + | app.getHttpServer() | ||
| + | ) | ||
| + | |||
| + | .post( | ||
| + | '/ | ||
| + | ) | ||
| + | |||
| + | .send({ | ||
| + | |||
| + | email: | ||
| + | ' | ||
| + | |||
| + | password: | ||
| + | ' | ||
| + | |||
| + | firstName: | ||
| + | ' | ||
| + | |||
| + | lastName: | ||
| + | ' | ||
| + | }) | ||
| + | |||
| + | .expect(409); | ||
| + | } | ||
| + | ); | ||
| + | </ | ||
| + | |||
| + | ---- | ||
| + | |||
| + | ====== Étape 17 — Nettoyage ====== | ||
| + | |||
| + | ===== Supprimer utilisateur ===== | ||
| + | |||
| + | Dans : | ||
| + | |||
| + | < | ||
| + | afterAll() | ||
| + | </ | ||
| + | |||
| + | ---- | ||
| + | |||
| + | ===== Exemple ===== | ||
| + | |||
| + | <code ts> | ||
| + | await prisma.user.deleteMany({ | ||
| + | |||
| + | where: { | ||
| + | |||
| + | email: | ||
| + | ' | ||
| + | } | ||
| + | }); | ||
| + | </ | ||
| + | |||
| + | ---- | ||
| + | |||
| + | ====== Étape 18 — Configuration NX ====== | ||
| + | |||
| + | ===== project.json ===== | ||
| + | |||
| + | Ajouter : | ||
| + | |||
| + | <code json> | ||
| + | { | ||
| + | " | ||
| + | |||
| + | " | ||
| + | |||
| + | " | ||
| + | |||
| + | " | ||
| + | } | ||
| + | } | ||
| + | } | ||
| + | </ | ||
| + | |||
| + | ---- | ||
| + | |||
| + | ====== Étape 19 — Exécution ====== | ||
| + | |||
| + | ===== Lancer ===== | ||
| + | |||
| + | <code bash> | ||
| + | nx e2e api-e2e | ||
| + | </ | ||
| + | |||
| + | ---- | ||
| + | |||
| + | ===== Résultat attendu ===== | ||
| + | |||
| + | < | ||
| + | PASS | ||
| Register | Register | ||
| + | |||
| + | PASS | ||
| Login | Login | ||
| + | |||
| + | PASS | ||
| + | |||
| + | Current User | ||
| + | |||
| + | PASS | ||
| Refresh | Refresh | ||
| + | |||
| + | PASS | ||
| Logout | Logout | ||
| - | Current User | + | PASS |
| + | |||
| + | RBAC | ||
| </ | </ | ||
| - | avec une couverture minimale de : | + | ---- |
| + | |||
| + | ====== Étape 20 — Intégration CI ====== | ||
| + | |||
| + | ===== Workflow ===== | ||
| < | < | ||
| - | 80 % | + | .github/ |
| </ | </ | ||
| + | ---- | ||
| + | |||
| + | ===== Ajouter ===== | ||
| + | |||
| + | <code yaml> | ||
| + | - name: E2E Tests | ||
| + | |||
| + | run: | ||
| + | |||
| + | nx e2e api-e2e | ||
| + | </ | ||
| + | |||
| + | ---- | ||
| + | |||
| + | ====== Étape 21 — Pipeline qualité ====== | ||
| + | |||
| + | ===== Ordre ===== | ||
| + | |||
| + | < | ||
| + | Lint | ||
| + | |||
| + | ↓ | ||
| + | |||
| + | Build | ||
| + | |||
| + | ↓ | ||
| + | |||
| + | Unit Tests | ||
| + | |||
| + | ↓ | ||
| + | |||
| + | E2E Tests | ||
| + | |||
| + | ↓ | ||
| + | |||
| + | Docker Build | ||
| + | </ | ||
| + | |||
| + | ---- | ||
| + | |||
| + | ====== Définition de terminé ====== | ||
| + | |||
| + | Le Sprint 1-C.3 est terminé lorsque : | ||
| + | |||
| + | < | ||
| + | ✓ Register testé | ||
| + | |||
| + | ✓ Login testé | ||
| + | |||
| + | ✓ Current User testé | ||
| + | |||
| + | ✓ Refresh testé | ||
| + | |||
| + | ✓ Logout testé | ||
| + | |||
| + | ✓ RBAC testé | ||
| + | |||
| + | ✓ PostgreSQL réel | ||
| + | |||
| + | ✓ JWT réel | ||
| + | |||
| + | ✓ CI verte | ||
| + | </ | ||
| + | |||
| + | ---- | ||
| + | |||
| + | ====== Livrables ====== | ||
| + | |||
| + | < | ||
| + | auth.e2e-spec.ts | ||
| + | |||
| + | .env.test | ||
| + | |||
| + | Configuration E2E | ||
| + | |||
| + | Pipeline CI | ||
| + | </ | ||
| + | |||
| + | ---- | ||
| + | |||
| + | ====== Bilan Sprint 1 ====== | ||
| + | |||
| + | Le Sprint 1 est considéré terminé lorsque : | ||
| + | |||
| + | < | ||
| + | ✓ AuthModule opérationnel | ||
| + | |||
| + | ✓ Register opérationnel | ||
| + | |||
| + | ✓ Login opérationnel | ||
| + | |||
| + | ✓ Refresh opérationnel | ||
| + | |||
| + | ✓ Logout opérationnel | ||
| + | |||
| + | ✓ Current User opérationnel | ||
| + | |||
| + | ✓ RBAC opérationnel | ||
| + | |||
| + | ✓ Tests unitaires verts | ||
| + | |||
| + | ✓ Tests E2E verts | ||
| + | |||
| + | ✓ Swagger documenté | ||
| + | |||
| + | ✓ CI verte | ||
| + | </ | ||
| + | |||
| + | ---- | ||
| + | |||
| + | ====== Étape suivante ====== | ||
| + | |||
| + | ===== Sprint 2 — Gestion des Utilisateurs ===== | ||
| + | |||
| + | Objectif : | ||
| + | |||
| + | Construire le premier domaine métier protégé par le RBAC : | ||
| + | |||
| + | < | ||
| + | User | ||
| + | |||
| + | Profile | ||
| + | |||
| + | Address | ||
| + | |||
| + | Preferences | ||
| + | |||
| + | NotificationSettings | ||
| + | |||
| + | UserSession | ||
| + | |||
| + | ConnectionHistory | ||
| + | </ | ||
| + | |||
| + | avec : | ||
| + | |||
| + | < | ||
| + | CRUD Utilisateurs | ||
| + | |||
| + | Gestion Profil | ||
| + | |||
| + | Gestion Préférences | ||
| + | |||
| + | Historique Connexions | ||
| + | |||
| + | Administration Utilisateurs | ||
| + | </ | ||
ujusum/3-codage/2-sprints/1-sprint-1.1780876719.txt.gz · Dernière modification : 2026/06/08 01:58 de admin