Adds the referential models, the first database-backed settings screens, and the register the compliance matrix requires before any parameter is enforceable. The register is the point of the lot. The matrix is explicit that copying another product's configuration is not enough — each parameter must carry its value, source, effective date, population and an approver. Approval records the session's actor, never a form field: a signature you can type yourself is worth nothing. The screen names the domains that have no approved parameter yet, so the gap is visible rather than assumed closed. Two bugs of the same family, both now structurally impossible: - The Prisma scoping extension read a hand-written list of models carrying accountId. The four models added here were missing from it, so writes failed with an opaque Prisma error — and a read would have silently returned every account's rows. The list is now derived from the schema itself. - The RLS policies were likewise per-table. A new integration test fails if any table with an accountId column lacks forced RLS and both policies, which is the failure mode that hides best: nobody writes a wrong rule, someone forgets to write one. An end-to-end test signs in as a manager and confirms the settings screens refuse to render — the sidebar hiding them is a convenience, the server check is the control. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Cr9dkEHwbDgkWPnyGj1Rjv
61 lines
2.4 KiB
TypeScript
61 lines
2.4 KiB
TypeScript
import { expect, test } from '@playwright/test';
|
|
|
|
/**
|
|
* Ce test se connecte en manager : il ne peut donc pas réutiliser la session
|
|
* partagée de la direction, d'où le projet « anonyme ».
|
|
*/
|
|
async function signIn(page: import('@playwright/test').Page, email: string) {
|
|
await page.goto('/connexion');
|
|
await page.getByLabel('Adresse électronique').fill(email);
|
|
await page.getByLabel('Mot de passe').fill('planflow-demo-2026');
|
|
await page.getByRole('button', { name: 'Se connecter' }).click();
|
|
await expect(page.getByRole('heading', { name: 'Aperçu RH' })).toBeVisible();
|
|
}
|
|
|
|
test('la direction lit et alimente le registre de paramétrage', async ({
|
|
page,
|
|
}) => {
|
|
await signIn(page, 'direction@example.test');
|
|
await page.goto('/reglages/registre');
|
|
|
|
await expect(
|
|
page.getByRole('heading', { name: 'Registre de paramétrage juridique' }),
|
|
).toBeVisible();
|
|
|
|
const parameter = `Durée quotidienne maximale ${Date.now()}`;
|
|
// Ciblage par attribut `name` : les libellés portent un texte d'aide, et
|
|
// celui de « Source » contient lui-même le mot « valeur », ce qui rend la
|
|
// correspondance par libellé ambiguë.
|
|
const form = page.locator('form').filter({ hasText: 'Consigner' });
|
|
await form.locator('input[name="key"]').fill(parameter);
|
|
await form.locator('input[name="value"]').fill('10 h');
|
|
await form
|
|
.locator('input[name="source"]')
|
|
.fill('IDCC 1517 — texte consolidé Legifrance');
|
|
await form.locator('input[name="population"]').fill('Tous les salariés');
|
|
await page.getByRole('button', { name: 'Consigner' }).click();
|
|
|
|
const row = page.getByRole('row', { name: new RegExp(parameter) });
|
|
await expect(row).toBeVisible();
|
|
|
|
// Consigné n'est pas approuvé : la matrice exige un approbateur nommé.
|
|
await row.getByRole('button', { name: 'Approuver' }).click();
|
|
await expect(row.getByText(/Approuvé le/)).toBeVisible();
|
|
});
|
|
|
|
test('un manager ne peut ni voir ni modifier les établissements', async ({
|
|
page,
|
|
}) => {
|
|
await signIn(page, 'manager.nantes@example.test');
|
|
|
|
// La barre latérale ne propose pas la section, mais c'est un confort :
|
|
// le contrôle qui compte est celui du serveur, testé en accédant à l'URL.
|
|
await page.goto('/reglages/etablissements');
|
|
|
|
// Le refus se manifeste par une erreur serveur, pas par une page qui
|
|
// s'affiche à moitié : la lecture elle-même est refusée.
|
|
await expect(
|
|
page.getByRole('heading', { name: 'Établissements' }),
|
|
).toBeHidden();
|
|
});
|