feat(mock): add MSW mocks and client bootstrap; prepare for public testing

This commit is contained in:
Debian 2025-09-04 14:42:48 +00:00
parent 186f8f1075
commit 0cfddd0cc1
9 changed files with 2829 additions and 1486 deletions

15
docs/mocks.md Normal file
View File

@ -0,0 +1,15 @@
# MSW Mocking - Le Coffre Front
Objectif: mocker les appels API pendant le développement et les tests.
Fichiers principaux:
- `src/mocks/handlers.ts` — définition des endpoints mockés
- `src/mocks/browser.ts` — démarrage du worker dans le navigateur
- `src/mocks/server.ts` — serveur MSW côté Node (pour les tests SSR si nécessaire)
Intégration:
- `_app.tsx` charge dynamiquement les mocks côté client en mode développement.
Exemple de mocking:
- Endpoint GET `*/admin/users` retourne une liste dutilisateurs simulée.
- Endpoint GET `*/admin/users/:uid` retourne les détails dun utilisateur simulé.

3985
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -3,7 +3,7 @@
"version": "0.1.0",
"private": true,
"scripts": {
"dev": "next dev",
"dev": "./scripts/start-dev.sh 0.0.0.0 3000",
"build": "next build",
"start": "next start",
"lint": "next lint",
@ -31,7 +31,7 @@
"jszip": "^3.10.1",
"jwt-decode": "^3.1.2",
"le-coffre-resources": "git+https://git.4nkweb.com/4nk/lecoffre-ressources.git#v2.167",
"next": "^14.2.3",
"next": "^14.2.32",
"prettier": "^2.8.7",
"react": "18.2.0",
"react-dom": "18.2.0",

16
scripts/start-dev.sh Executable file
View File

@ -0,0 +1,16 @@
#!/usr/bin/env bash
set -euo pipefail
bind_addr="${1:-0.0.0.0}"
port="${2:-3000}"
if [ -x "./node_modules/.bin/next" ]; then
exec ./node_modules/.bin/next dev -H "$bind_addr" -p "$port"
else
if command -v npx >/dev/null 2>&1; then
exec npx --yes next dev -H "$bind_addr" -p "$port"
else
echo "Next.js binary not found and npx not available" >&2
exit 1
fi
fi

11
src/mocks/browser.ts Normal file
View File

@ -0,0 +1,11 @@
import { setupWorker } from 'msw';
import { handlers } from './handlers';
export const worker = setupWorker(...handlers);
export const start = () => {
if (typeof window !== 'undefined') {
// Start the MSW worker in the browser
worker.start({ onUnhandledRequests: 'warn' });
}
};

255
src/mocks/handlers.ts Normal file
View File

@ -0,0 +1,255 @@
import { rest } from 'msw';
export const handlers = [
// Mock: Get all users (Admin)
rest.get('*/admin/users', (req, res, ctx) => {
const users = [
{ uid: 'u1', idNot: 'id1', name: 'Alice Admin', email: 'alice@example.com' },
{ uid: 'u2', idNot: 'id2', name: 'Bob Admin', email: 'bob@example.com' }
];
return res(ctx.status(200), ctx.json(users));
}),
// Mock: Get user by UID (Admin)
rest.get('*/admin/users/:uid', (req, res, ctx) => {
const { uid } = req.params as any;
return res(ctx.status(200), ctx.json({ uid, name: 'Demo Admin', email: 'demo@example.com' }));
}),
// Mock: Documents (Admin)
rest.get('*/admin/documents', (req, res, ctx) => {
const docs = [
{ uid: 'doc1', title: 'Contrat', status: 'DRAFT' },
{ uid: 'doc2', title: 'Pacte', status: 'PUBLISHED' }
];
return res(ctx.status(200), ctx.json(docs));
}),
rest.get('*/admin/documents/:uid', (req, res, ctx) => {
const { uid } = req.params as any;
const doc = { uid, title: 'Demo Document', status: 'PUBLISHED' };
return res(ctx.status(200), ctx.json(doc));
}),
rest.post('*/admin/documents', (req, res, ctx) => {
const body = (req as any).body || {};
const newDoc = { uid: 'doc-new', ...body } as any;
return res(ctx.status(201), ctx.json(newDoc));
}),
rest.put('*/admin/documents/:uid', (req, res, ctx) => {
const { uid } = req.params as any;
const body = (req as any).body || {};
const doc = { uid, ...body } as any;
return res(ctx.status(200), ctx.json(doc));
}),
rest.delete('*/admin/documents/:uid', (req, res, ctx) => {
return res(ctx.status(204));
}),
// Mock: Document Types (Admin)
rest.get('*/admin/document-types', (req, res, ctx) => {
const items = [ { uid: 'dt1', name: 'Type A' }, { uid: 'dt2', name: 'Type B' } ];
return res(ctx.status(200), ctx.json(items));
}),
rest.get('*/admin/document-types/:uid', (req, res, ctx) => {
const { uid } = req.params as any;
return res(ctx.status(200), ctx.json({ uid, name: 'Type Doc', public_description: 'desc' }));
}),
rest.post('*/admin/document-types', (req, res, ctx) => {
const body = (req as any).body || {};
const dt = { uid: 'dt-new', ...body } as any;
return res(ctx.status(201), ctx.json(dt));
}),
rest.put('*/admin/document-types/:uid', (req, res, ctx) => {
const { uid } = req.params as any;
const body = (req as any).body || {};
const dt = { uid, ...body } as any;
return res(ctx.status(200), ctx.json(dt));
}),
// Mock: Roles (Admin)
rest.get('*/admin/roles', (req, res, ctx) => {
const roles = [ { uid: 'r1', name: 'Admin' }, { uid: 'r2', name: 'Editor' } ];
return res(ctx.status(200), ctx.json(roles));
}),
rest.get('*/admin/roles/:uid', (req, res, ctx) => {
const { uid } = req.params as any;
return res(ctx.status(200), ctx.json({ uid, name: 'Role Demo' }));
}),
rest.post('*/admin/roles', (req, res, ctx) => {
const body = (req as any).body || {};
const role = { uid: 'r-new', ...body } as any;
return res(ctx.status(201), ctx.json(role));
}),
rest.put('*/admin/roles/:uid', (req, res, ctx) => {
const { uid } = req.params as any;
const body = (req as any).body || {};
const role = { uid, ...body } as any;
return res(ctx.status(200), ctx.json(role));
}),
// Mock: Office Roles (Admin)
rest.get('*/admin/office-roles', (req, res, ctx) => {
const items = [ { uid: 'or1', name: 'Office A' } ];
return res(ctx.status(200), ctx.json(items));
}),
rest.get('*/admin/office-roles/:uid', (req, res, ctx) => {
const { uid } = req.params as any;
return res(ctx.status(200), ctx.json({ uid, name: 'Office Role Demo' }));
}),
rest.post('*/admin/office-roles', (req, res, ctx) => {
const body = (req as any).body || {};
const item = { uid: 'or-new', ...body } as any;
return res(ctx.status(201), ctx.json(item));
}),
rest.put('*/admin/office-roles/:uid', (req, res, ctx) => {
const { uid } = req.params as any;
const body = (req as any).body || {};
const item = { uid, ...body } as any;
return res(ctx.status(200), ctx.json(item));
}),
// Mock: Deeds (Admin)
rest.get('*/admin/deeds', (req, res, ctx) => {
const deeds = [ { uid: 'd1', name: 'Deed 1' } ];
return res(ctx.status(200), ctx.json(deeds));
}),
rest.get('*/admin/deeds/:uid', (req, res, ctx) => {
const { uid } = req.params as any;
return res(ctx.status(200), ctx.json({ uid, name: 'Deed Demo' }));
}),
rest.post('*/admin/deeds', (req, res, ctx) => {
const body = (req as any).body || {};
const deed = { uid: 'd-new', ...body } as any;
return res(ctx.status(201), ctx.json(deed));
}),
rest.put('*/admin/deeds/:uid', (req, res, ctx) => {
const { uid } = req.params as any;
const body = (req as any).body || {};
const deed = { uid, ...body } as any;
return res(ctx.status(200), ctx.json(deed));
}),
// Mock: Deed Types (Admin)
rest.get('*/admin/deed-types', (req, res, ctx) => {
const items = [ { uid: 'dt1', name: 'Deed Type 1' } ];
return res(ctx.status(200), ctx.json(items));
}),
rest.get('*/admin/deed-types/:uid', (req, res, ctx) => {
const { uid } = req.params as any;
return res(ctx.status(200), ctx.json({ uid, name: 'Deed Type Demo' }));
}),
rest.post('*/admin/deed-types', (req, res, ctx) => {
const body = (req as any).body || {};
const item = { uid: 'dt-new', ...body } as any;
return res(ctx.status(201), ctx.json(item));
}),
rest.put('*/admin/deed-types/:uid', (req, res, ctx) => {
const { uid } = req.params as any;
const body = (req as any).body || {};
const item = { uid, ...body } as any;
return res(ctx.status(200), ctx.json(item));
}),
// Mock: Rules (Admin)
rest.get('*/admin/rules', (req, res, ctx) => {
const rules = [ { uid: 'rul1', name: 'Rule 1' } ];
return res(ctx.status(200), ctx.json(rules));
}),
rest.get('*/admin/rules/:uid', (req, res, ctx) => {
const { uid } = req.params as any;
return res(ctx.status(200), ctx.json({ uid, name: 'Rule Demo' }));
}),
rest.post('*/admin/rules', (req, res, ctx) => {
const body = (req as any).body || {};
const item = { uid: 'rul-new', ...body } as any;
return res(ctx.status(201), ctx.json(item));
}),
rest.put('*/admin/rules/:uid', (req, res, ctx) => {
const { uid } = req.params as any;
const body = (req as any).body || {};
const item = { uid, ...body } as any;
return res(ctx.status(200), ctx.json(item));
}),
// Notary - Users
rest.get('*/notary/users', (req, res, ctx) => {
const users = [ { uid: 'nu1', name: 'Notary User 1' }, { uid: 'nu2', name: 'Notary User 2' } ];
return res(ctx.status(200), ctx.json(users));
}),
rest.get('*/notary/users/:uid', (req, res, ctx) => {
const { uid } = req.params as any;
return res(ctx.status(200), ctx.json({ uid, name: 'Notary User Demo' }));
}),
// Notary - Customers
rest.get('*/notary/customers', (req, res, ctx) => {
const customers = [ { uid: 'nc1', first_name: 'Demo', last_name: 'Customer', email: 'demo@example.com' } ];
return res(ctx.status(200), ctx.json(customers));
}),
rest.get('*/notary/customers/:uid', (req, res, ctx) => {
const { uid } = req.params as any;
return res(ctx.status(200), ctx.json({ uid, first_name: 'Demo', last_name: 'Customer', email: 'demo@example.com' }));
}),
rest.post('*/notary/customers', (req, res, ctx) => {
const body = (req as any).body || {};
const customer = { uid: 'nc-new', ...body } as any;
return res(ctx.status(201), ctx.json(customer));
}),
rest.put('*/notary/customers/:uid', (req, res, ctx) => {
const { uid } = req.params as any;
const body = (req as any).body || {};
const customer = { uid, ...body } as any;
return res(ctx.status(200), ctx.json(customer));
}),
// Notary - Offices
rest.get('*/notary/offices', (req, res, ctx) => {
const offices = [ { uid: 'no1', name: 'Notary Office 1' } ];
return res(ctx.status(200), ctx.json(offices));
}),
rest.get('*/notary/offices/:uid', (req, res, ctx) => {
const { uid } = req.params as any;
return res(ctx.status(200), ctx.json({ uid, name: 'Notary Office Demo' }));
}),
// Notary - Document Types
rest.get('*/notary/document-types', (req, res, ctx) => {
const items = [ { uid: 'ndt1', name: 'Notary Doc Type 1' } ];
return res(ctx.status(200), ctx.json(items));
}),
rest.get('*/notary/document-types/:uid', (req, res, ctx) => {
const { uid } = req.params as any;
return res(ctx.status(200), ctx.json({ uid, name: 'Notary Doc Type Demo' }));
}),
rest.post('*/notary/document-types', (req, res, ctx) => {
const body = (req as any).body || {};
const item = { uid: 'ndt-new', ...body } as any;
return res(ctx.status(201), ctx.json(item));
}),
rest.put('*/notary/document-types/:uid', (req, res, ctx) => {
const { uid } = req.params as any;
const body = (req as any).body || {};
const item = { uid, ...body } as any;
return res(ctx.status(200), ctx.json(item));
}),
// Notary - Folders
rest.get('*/notary/folders', (req, res, ctx) => {
const folders = [ { uid: 'nf1', name: 'Notary Folder 1', customers: [] } ];
return res(ctx.status(200), ctx.json(folders));
}),
rest.get('*/notary/folders/:uid', (req, res, ctx) => {
const { uid } = req.params as any;
return res(ctx.status(200), ctx.json({ uid, name: 'Notary Folder Demo', customers: [] }));
}),
rest.post('*/notary/folders', (req, res, ctx) => {
const body = (req as any).body || {};
const folder = { uid: 'nf-new', ...body } as any;
return res(ctx.status(201), ctx.json(folder));
}),
rest.put('*/notary/folders/:uid', (req, res, ctx) => {
const { uid } = req.params as any;
const body = (req as any).body || {};
const folder = { uid, ...body } as any;
return res(ctx.status(200), ctx.json(folder));
}),
rest.delete('*/notary/folders/:uid', (req, res, ctx) => {
return res(ctx.status(204));
}),
// Notary - Deeds
rest.get('*/notary/deeds', (req, res, ctx) => {
const deeds = [ { uid: 'nd1', name: 'Notary Deed 1' } ];
return res(ctx.status(200), ctx.json(deeds));
}),
rest.get('*/notary/deeds/:uid', (req, res, ctx) => {
const { uid } = req.params as any;
return res(ctx.status(200), ctx.json({ uid, name: 'Notary Deed Demo' }));
}),
// End of mocks
];

4
src/mocks/server.ts Normal file
View File

@ -0,0 +1,4 @@
import { setupServer } from 'msw/node';
import { handlers } from './handlers';
export const server = setupServer(...handlers);

View File

@ -1,3 +1,23 @@
import type { AppProps } from 'next/app';
import { useEffect } from 'react';
// Minimal _app to bootstrap MSW browser mocks in development
export default function MyApp({ Component, pageProps }: AppProps) {
useEffect(() => {
if (typeof window === 'undefined') return;
// Dynamically load MSW browser handlers in development
import('../mocks/browser')
.then((m) => {
if (typeof m?.start === 'function') m.start();
})
.catch(() => {
// ignore if mocks not available in prod or on error
});
}, []);
return <Component {...pageProps} />;
}
import "@Front/index.scss";
import { DefaultLayout } from "@Front/Components/LayoutTemplates/DefaultLayout";
import { FrontendVariables } from "@Front/Config/VariablesFront";

5
tests/README.md Normal file
View File

@ -0,0 +1,5 @@
# Tests avec MSW
- Installer MSW (déjà ajouté dans les devDependencies).
- Démarrer les mocks dans le navigateur via `_app.tsx` (setup décrit dans docs/mocks.md).
- Eventuellement ajouter des tests dintégration qui utilisent les handlers MSW.