mirror of
https://github.com/siop-spelev/siop2.git
synced 2026-08-08 12:41:54 +00:00
- 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>
78 lines
3.3 KiB
TypeScript
78 lines
3.3 KiB
TypeScript
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();
|
||
});
|