Files
siop2/apps/web/e2e/parcours-portail.spec.ts
pr-daaif 50254daea4 feat(r2.4): portail public QR — signalement sans compte, suivi par jeton
- 3 routes publiques /portal (les seules @Public métier), throttlées
  (@nestjs/throttler : 10 signalements/min, 60 lectures/min) : résolution
  du QR, signalement (retourne un jeton de suivi opaque), suivi par
  référence + jeton — pas de jeton, pas de lecture, jamais de liste
- migration r2_portail : Request.publicToken (unique)
- page /q/{réf} fidèle à l'écran 6 validé R0 (mobile d'abord) : équipement
  prérempli « détecté par le QR », interrupteur personne bloquée,
  suivi sans jargon Reçu → Intervention → Résolu (dérivé de l'OT lié),
  rejet affiché « Sans suite : {motif} », photo annoncée (upload R3) ;
  signalements gardés sur le téléphone (localStorage, 10 max)
- e2e : LA boucle produit — gardien sans compte → traitement → ✓ Résolu ;
  11 tests Playwright verts, 50 tests API, 52 opérations au contrat
- générateur OpenAPI : paramètres non-« id » plus typés uuid

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-16 17:33:10 +01:00

78 lines
3.3 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { expect, test } from '@playwright/test';
/**
* R2.4 — LA boucle produit : le gardien scanne le QR (aucun compte), signale,
* l'équipe traite (via API), le gardien voit « Résolu » sur son téléphone.
*/
const suffix = Date.now().toString(36);
const API = 'http://localhost:3000';
test('portail QR : signalement sans compte → traitement → suivi Résolu', async ({
page,
request,
}) => {
// 1 · Le gardien scanne le QR de l'ascenseur A1 → formulaire prérempli
await page.goto('/q/A1');
await expect(page.getByText(/Ascenseur A1/)).toBeVisible();
await expect(page.getByText('détecté par le QR')).toBeVisible();
await page.getByLabel('Décrivez le problème').fill(`E2E ${suffix} — voyant cabine éteint`);
await page.getByRole('button', { name: 'Envoyer le signalement' }).click();
await expect(page.getByText('l\'équipe SPELEV est prévenue')).toBeVisible();
// « Mes signalements » : Reçu, sans jargon
const ligne = page.locator('.item-liste', { hasText: suffix });
await expect(ligne).toBeVisible();
await expect(ligne.locator('.etape.fait')).toHaveText('✓ Reçu');
// 2 · Côté SPELEV (via API — le parcours UI est couvert par parcours-r2) :
// approbation → démarrage → bilan → clôture
const comptes = await (await request.get(`${API}/auth/demo-accounts`)).json();
const salmaId = comptes.accounts.find((a: { roleName: string }) => a.roleName === 'Dispatcher').id;
const jeton = (await (await request.post(`${API}/auth/demo-login`, { data: { userId: salmaId } })).json()).accessToken;
const auth = { Authorization: `Bearer ${jeton}` };
const demandes = await (await request.get(`${API}/requests`, { headers: auth })).json();
const demande = demandes.requests.find((r: { description: string }) => r.description.includes(suffix));
expect(demande).toBeTruthy();
const ot = await (
await request.post(`${API}/requests/${demande.id}/approve`, { headers: auth, data: {} })
).json();
await request.post(`${API}/work-orders/${ot.id}/transition`, {
headers: auth,
data: { to: 'IN_PROGRESS' },
});
// 3 · Le gardien recharge : « Intervention » franchie
await page.reload();
await expect(page.locator('.item-liste', { hasText: suffix }).getByText('✓ Intervention')).toBeVisible();
// 4 · Clôture avec bilan → « Résolu »
const refs = await (await request.get(`${API}/reference-values`, { headers: auth })).json();
const valeur = (field: string) =>
refs.referenceValues.find((v: { field: string; isActive: boolean }) => v.field === field && v.isActive).id;
await request.put(`${API}/work-orders/${ot.id}/report`, {
headers: auth,
data: {
doorStateId: valeur('DOOR_STATE'),
actionTakenId: valeur('ACTION_TAKEN'),
componentConcernedId: valeur('COMPONENT_CONCERNED'),
},
});
const cloture = await request.post(`${API}/work-orders/${ot.id}/transition`, {
headers: auth,
data: { to: 'DONE' },
});
expect(cloture.ok()).toBe(true);
await page.reload();
await expect(page.locator('.item-liste', { hasText: suffix }).getByText('✓ Résolu')).toBeVisible();
});
test('QR inconnu : message clair, pas décran technique', async ({ page }) => {
await page.goto('/q/INTROUVABLE-99');
await expect(page.getByText(/aucun appareil connu/)).toBeVisible();
});