mirror of
https://github.com/R0m1k3/LogiFlow.git
synced 2026-10-17 04:06:44 +02:00
feat(rust) : serveur réécrit en Rust, Node retiré ; Calendrier en tête du menu (#571)
- Serveur Rust (server-rs/, axum + sqlx) : les 148 adresses de l'API, l'interface, les migrations de démarrage et les tâches de fond ; même comportement que Node (banc : 2027 cas, 0 écart hors écarts déclarés USERS-02, RAPPRO-21, R4, R5). - Node retiré : server/, dépendances serveur, image Docker sans Node (51 Mo), docker-compose à un seul service applicatif. - R4 : écritures d'un autre site refusées (Sec-Fetch-Site / Origin). - R5 : « aujourd'hui » et sauvegarde automatique à l'heure de Paris. - R6 : mainteneur dans .github/CODEOWNERS. - Menu latéral : Accueil puis Calendrier en tête. - Plan et état : docs/PLAN-MIGRATION-RUST.md. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ke2qyS8rgacP3ap5iezDbM
This commit is contained in:
1 parent
6760f43e87
commit
600627c632
146 files changed
+31521
-20331
No files matched your search
@@ -0,0 +1,299 @@
|
||||
// Lot 5 : connexion et session, utilisateurs, magasins, sauvegardes, paramètres
|
||||
// généraux, diagnostic du schéma (docs/PLAN-MIGRATION-RUST.md). Les lectures
|
||||
// de /api/user, /api/default-credentials-check (socle.ts, « auth »),
|
||||
// /api/users et /api/groups (referentiel.ts) sont déjà au banc.
|
||||
import type { ReadCase, WriteScenario } from "../types";
|
||||
import { LOGGED_ACCOUNTS } from "../types";
|
||||
import { STUB_SMTP_PORT } from "../config";
|
||||
|
||||
// Le hash renvoyé par Node après POST / PUT /api/users (sel aléatoire) n'est pas
|
||||
// renvoyé par Rust : écart voulu, retiré de la comparaison (réponses et table users)
|
||||
const PASSWORD_HASH = {
|
||||
fields: ["password"],
|
||||
reason: "USERS-02 : l'empreinte du mot de passe n'est plus renvoyée après création ou modification d'un compte",
|
||||
};
|
||||
|
||||
// Session d'une connexion : identifiant et échéance propres à chaque passage
|
||||
// originalMaxAge : express-session le recalcule à la milliseconde près quand il
|
||||
// réenregistre une session (86399999 ou 86400000 selon l'instant)
|
||||
const SESSION_IGNORE = ["sid", "expire", "expires", "originalMaxAge"];
|
||||
|
||||
const BROWSER_ACCEPT = "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8";
|
||||
|
||||
// Magasin dont le serveur SMTP est le service simulé du banc
|
||||
const SMTP = {
|
||||
smtpEnabled: true,
|
||||
smtpHost: "127.0.0.1",
|
||||
smtpPort: STUB_SMTP_PORT,
|
||||
smtpSecure: false,
|
||||
smtpUser: "toul@banc.test",
|
||||
smtpSenderEmail: "toul@banc.test",
|
||||
smtpSenderName: "Magasin Toul",
|
||||
};
|
||||
|
||||
export const reads: ReadCase[] = [
|
||||
{ module: "backups", path: "/api/backups" },
|
||||
{ module: "backups", path: "/api/backups/backup_banc_1.sql/download" },
|
||||
{ module: "backups", path: "/api/backups/backup_banc_500.sql/download", accounts: ["admin", "manager"] },
|
||||
{ module: "backups", path: "/api/backups/absente.sql/download", accounts: ["admin", "manager"] },
|
||||
{ module: "utilities", path: "/api/utilities" },
|
||||
{ module: "admin", path: "/api/debug/log-schema" },
|
||||
{ module: "admin", path: "/api/debug/download-schema" },
|
||||
];
|
||||
|
||||
export const writes: WriteScenario[] = [
|
||||
// ---------- Session ----------
|
||||
{
|
||||
module: "session",
|
||||
name: "connexion réussie : réponse, cookie et session enregistrée",
|
||||
accounts: ["anonyme"],
|
||||
steps: [
|
||||
{ method: "POST", path: "/api/login", body: { username: "directeur", password: "banc-directeur" } },
|
||||
{ method: "GET", path: "/api/user" },
|
||||
],
|
||||
tables: ["sessions"],
|
||||
ignore: SESSION_IGNORE,
|
||||
},
|
||||
{
|
||||
module: "session",
|
||||
name: "connexion par formulaire ou par l'adresse",
|
||||
accounts: ["anonyme"],
|
||||
steps: [
|
||||
{ method: "POST", path: "/api/login", body: { username: "employe", password: "banc-employe" }, bodyType: "form" },
|
||||
{ method: "POST", path: "/api/login?username=manager&password=banc-manager" },
|
||||
{ method: "POST", path: "/api/login?username=sansmagasin", body: { password: "banc-sans-magasin" } },
|
||||
],
|
||||
tables: [],
|
||||
},
|
||||
{
|
||||
module: "session",
|
||||
name: "connexion refusée : identifiants mal formés",
|
||||
accounts: ["anonyme"],
|
||||
steps: [
|
||||
{ method: "POST", path: "/api/login", body: { username: "manager", password: 123 } },
|
||||
{ method: "POST", path: "/api/login", body: { username: "manager", password: true } },
|
||||
{ method: "POST", path: "/api/login", body: { username: "manager", password: ["banc-manager"] } },
|
||||
{ method: "POST", path: "/api/login", body: { username: ["manager"], password: "banc-manager" } },
|
||||
{ method: "POST", path: "/api/login", body: { username: "manager", password: "" } },
|
||||
{ method: "POST", path: "/api/login", body: { username: "MANAGER", password: "banc-manager" } },
|
||||
{ method: "POST", path: "/api/login", body: { username: 0, password: "x" } },
|
||||
{ method: "POST", path: "/api/login", body: { username: 12, password: "x" } },
|
||||
{ method: "POST", path: "/api/login", body: ["manager", "banc-manager"] },
|
||||
{ method: "POST", path: "/api/login?username=manager&username=directeur&password=banc-manager" },
|
||||
{ method: "POST", path: "/api/login", body: { username: "admin", password: "admin" } },
|
||||
],
|
||||
tables: ["sessions"],
|
||||
},
|
||||
{
|
||||
module: "session",
|
||||
name: "connexion par-dessus une session ouverte",
|
||||
steps: [
|
||||
{ method: "POST", path: "/api/login", body: { username: "sansmagasin", password: "banc-sans-magasin" } },
|
||||
// L'ancienne session est détruite : le cookie du compte ne vaut plus rien
|
||||
{ method: "GET", path: "/api/user" },
|
||||
],
|
||||
tables: ["sessions"],
|
||||
ignore: SESSION_IGNORE,
|
||||
},
|
||||
{
|
||||
module: "session",
|
||||
name: "déconnexion (GET et POST, selon Accept) puis lecture de l'utilisateur",
|
||||
accounts: [...LOGGED_ACCOUNTS, "anonyme"],
|
||||
steps: [
|
||||
{ method: "GET", path: "/api/logout", headers: { accept: BROWSER_ACCEPT } },
|
||||
{ method: "GET", path: "/api/user" },
|
||||
{ method: "POST", path: "/api/logout", headers: { accept: "application/json" } },
|
||||
{ method: "POST", path: "/api/logout" },
|
||||
],
|
||||
tables: ["sessions"],
|
||||
},
|
||||
{
|
||||
// Session ouverte par la connexion, lue par l'adresse de test SMTP
|
||||
module: "session",
|
||||
name: "session acceptée par le test SMTP d'un magasin",
|
||||
steps: [{ method: "POST", path: "/api/groups/1/test-smtp" }],
|
||||
tables: [],
|
||||
},
|
||||
|
||||
// ---------- Utilisateurs ----------
|
||||
{
|
||||
module: "users",
|
||||
name: "créer, modifier, affecter, retirer et supprimer un utilisateur",
|
||||
steps: [
|
||||
{
|
||||
method: "POST",
|
||||
path: "/api/users",
|
||||
body: { id: "bench_nouveau", username: "nouveau", email: "nouveau@banc.test", firstName: "Noé", lastName: "Nouveau", password: "banc-nouveau", role: "employee" },
|
||||
},
|
||||
{ method: "PUT", path: "/api/users/bench_nouveau", body: { firstName: "Noémie", lastName: "", email: "", role: "manager", password: "banc-nouveau-2" } },
|
||||
{ method: "POST", path: "/api/users/bench_nouveau/groups", body: { groupId: 2 } },
|
||||
{ method: "POST", path: "/api/users/bench_nouveau/groups", body: { groupId: 3 } },
|
||||
{ method: "DELETE", path: "/api/users/bench_nouveau/groups/2" },
|
||||
{ method: "GET", path: "/api/users" },
|
||||
{ method: "PUT", path: "/api/users/bench_employee", body: { password: "banc-employe-2" } },
|
||||
{ method: "DELETE", path: "/api/users/bench_nouveau" },
|
||||
{ method: "GET", path: "/api/users" },
|
||||
// Le mot de passe modifié est vérifiable (en dernier : la connexion
|
||||
// remplace la session du compte qui rejoue le scénario)
|
||||
{ method: "POST", path: "/api/login", body: { username: "employe", password: "banc-employe-2" } },
|
||||
],
|
||||
tables: ["users", "user_groups"],
|
||||
deviation: PASSWORD_HASH,
|
||||
},
|
||||
{
|
||||
module: "users",
|
||||
name: "créer un utilisateur sans identifiant ni adresse",
|
||||
accounts: ["admin", "manager"],
|
||||
steps: [
|
||||
{ method: "POST", path: "/api/users", body: { username: "sansid", password: "banc-sans-id", email: "" } },
|
||||
{ method: "POST", path: "/api/users", body: { username: "blanc", password: "x", email: " " } },
|
||||
{ method: "POST", path: "/api/users", body: { id: "", username: "sansid2", password: "x", email: null, firstName: "", role: "directeur" } },
|
||||
// Le mot de passe du compte créé est vérifiable
|
||||
{ method: "POST", path: "/api/login", body: { username: "sansid", password: "banc-sans-id" } },
|
||||
],
|
||||
tables: ["users"],
|
||||
// Identifiant généré : manual_<millisecondes>_<aléatoire>
|
||||
ignore: ["id"],
|
||||
deviation: PASSWORD_HASH,
|
||||
},
|
||||
{
|
||||
module: "users",
|
||||
name: "utilisateurs invalides, en double ou introuvables",
|
||||
steps: [
|
||||
{ method: "POST", path: "/api/users", body: {} },
|
||||
{ method: "POST", path: "/api/users", body: { username: "", password: "", email: 5, role: "chef" } },
|
||||
{ method: "POST", path: "/api/users", body: { username: "a", password: "b", email: "pas-un-courriel", firstName: null } },
|
||||
{ method: "POST", path: "/api/users", body: { username: "manager", password: "x" } },
|
||||
{ method: "POST", path: "/api/users", body: { username: "autre", password: "x", email: "manager@banc.test" } },
|
||||
{ method: "POST", path: "/api/users", body: { id: "bench_manager", username: "autre2", password: "x" } },
|
||||
{ method: "POST", path: "/api/users", body: ["x"] },
|
||||
{ method: "PUT", path: "/api/users/bench_employee", body: { role: "chef", firstName: 5 } },
|
||||
{ method: "PUT", path: "/api/users/bench_employee", body: { username: "manager" } },
|
||||
{ method: "PUT", path: "/api/users/bench_employee", body: { email: "directeur@banc.test" } },
|
||||
{ method: "PUT", path: "/api/users/inconnu", body: { firstName: "X" } },
|
||||
// Noms effacés ; adresse vide ignorée (non effacée, comme Node)
|
||||
{ method: "PUT", path: "/api/users/bench_employee", body: { firstName: null, lastName: "", email: "" } },
|
||||
{ method: "DELETE", path: "/api/users/admin_local" },
|
||||
{ method: "DELETE", path: "/api/users/bench_employee" },
|
||||
{ method: "POST", path: "/api/users/bench_manager/groups", body: { groupId: "2" } },
|
||||
{ method: "POST", path: "/api/users/bench_manager/groups", body: {} },
|
||||
{ method: "DELETE", path: "/api/users/bench_manager/groups/abc" },
|
||||
{ method: "DELETE", path: "/api/users/bench_manager/groups/1" },
|
||||
// Mot de passe vide enregistré tel quel (défaut de Node) : connexion
|
||||
// impossible (en dernier : une connexion réussie remplacerait la session)
|
||||
{ method: "PUT", path: "/api/users/bench_sans_magasin", body: { password: "" } },
|
||||
{ method: "POST", path: "/api/login", body: { username: "sansmagasin", password: "banc-sans-magasin" } },
|
||||
],
|
||||
tables: ["users", "user_groups"],
|
||||
deviation: PASSWORD_HASH,
|
||||
},
|
||||
|
||||
// ---------- Magasins ----------
|
||||
{
|
||||
module: "groups",
|
||||
name: "créer, modifier, tester et supprimer un magasin",
|
||||
steps: [
|
||||
{
|
||||
method: "POST",
|
||||
path: "/api/groups",
|
||||
body: { name: "Magasin Toul", color: "#7B1FA2", address: "", phone: "", logo: "", nocodbConfigId: null, ...SMTP, smtpPassword: "secret-toul" },
|
||||
},
|
||||
// Node relit le mot de passe chiffré par l'autre serveur
|
||||
{ method: "POST", path: "/api/groups/{{0.id}}/test-smtp" },
|
||||
{ method: "PUT", path: "/api/groups/{{0.id}}", body: { name: "Magasin de Toul", phone: "03 83 00 00 05", smtpPassword: "" } },
|
||||
{ method: "PUT", path: "/api/groups/{{0.id}}", body: { smtpPassword: "nouveau-secret", smtpPort: String(STUB_SMTP_PORT) } },
|
||||
{ method: "POST", path: "/api/groups/{{0.id}}/test-smtp" },
|
||||
{ method: "PUT", path: "/api/groups/{{0.id}}", body: { smtpSenderEmail: "", smtpPort: null } },
|
||||
{ method: "POST", path: "/api/groups/{{0.id}}/test-smtp" },
|
||||
{ method: "GET", path: "/api/groups" },
|
||||
{ method: "DELETE", path: "/api/groups/{{0.id}}" },
|
||||
],
|
||||
tables: ["groups"],
|
||||
// Chiffré avec un vecteur aléatoire
|
||||
ignore: ["smtp_password"],
|
||||
},
|
||||
{
|
||||
module: "groups",
|
||||
name: "tester le SMTP : configuration incomplète, serveur injoignable, magasin introuvable",
|
||||
steps: [
|
||||
{ method: "POST", path: "/api/groups/2/test-smtp" },
|
||||
{ method: "POST", path: "/api/groups/999/test-smtp" },
|
||||
{ method: "POST", path: "/api/groups/abc/test-smtp" },
|
||||
// Port fermé : connexion refusée (modification réservée à l'admin et au manager)
|
||||
{ method: "PUT", path: "/api/groups/2", body: { smtpHost: "127.0.0.1", smtpPort: 1, smtpSenderEmail: "metz@banc.test" } },
|
||||
{ method: "POST", path: "/api/groups/2/test-smtp" },
|
||||
],
|
||||
tables: [],
|
||||
},
|
||||
{
|
||||
module: "groups",
|
||||
name: "magasins invalides, introuvables ou encore utilisés",
|
||||
steps: [
|
||||
{ method: "POST", path: "/api/groups", body: {} },
|
||||
{ method: "POST", path: "/api/groups", body: { name: "X", smtpSenderEmail: 5 } },
|
||||
{ method: "POST", path: "/api/groups", body: { name: "X", smtpSenderEmail: "abc", smtpPort: "0" } },
|
||||
{ method: "POST", path: "/api/groups", body: { name: "X", smtpPort: "abc", phone: 12 } },
|
||||
{ method: "PUT", path: "/api/groups/1", body: { name: 5 } },
|
||||
{ method: "PUT", path: "/api/groups/999", body: { name: "Fantôme" } },
|
||||
{ method: "PUT", path: "/api/groups/abc", body: { name: "X" } },
|
||||
// Un manager modifie un magasin qui n'est pas le sien (comme Node)
|
||||
{ method: "PUT", path: "/api/groups/2", body: { color: "#000000", smtpPassword: null } },
|
||||
{ method: "DELETE", path: "/api/groups/1" },
|
||||
{ method: "DELETE", path: "/api/groups/999" },
|
||||
{ method: "DELETE", path: "/api/groups/abc" },
|
||||
],
|
||||
tables: ["groups"],
|
||||
ignore: ["smtp_password"],
|
||||
},
|
||||
|
||||
// ---------- Sauvegardes ----------
|
||||
{
|
||||
module: "backups",
|
||||
name: "créer une sauvegarde (pg_dump), ménage des anciennes",
|
||||
steps: [
|
||||
{ method: "POST", path: "/api/backups" },
|
||||
{ method: "GET", path: "/api/backups" },
|
||||
{ method: "DELETE", path: "/api/backups/{{0.filename}}" },
|
||||
{ method: "GET", path: "/api/backups" },
|
||||
],
|
||||
// Les deux plus anciennes (sans fichier) ont été supprimées par le ménage
|
||||
tables: ["database_backups"],
|
||||
// Identifiant aléatoire, nom horodaté, taille du fichier pg_dump
|
||||
ignore: ["id", "filename", "size"],
|
||||
},
|
||||
{
|
||||
module: "backups",
|
||||
name: "supprimer une sauvegarde et son fichier",
|
||||
steps: [
|
||||
{ method: "DELETE", path: "/api/backups/backup_banc_500.sql" },
|
||||
{ method: "GET", path: "/api/backups/backup_banc_500.sql/download" },
|
||||
{ method: "DELETE", path: "/api/backups/backup_banc_500.sql" },
|
||||
{ method: "DELETE", path: "/api/backups/absente.sql" },
|
||||
{ method: "GET", path: "/api/backups" },
|
||||
],
|
||||
tables: ["database_backups"],
|
||||
},
|
||||
|
||||
// ---------- Paramètres généraux ----------
|
||||
{
|
||||
module: "utilities",
|
||||
name: "modifier les paramètres généraux",
|
||||
steps: [
|
||||
{ method: "POST", path: "/api/utilities", body: { salesAnalysisUrl: "https://analyse.banc.test/nouveau" } },
|
||||
{ method: "POST", path: "/api/utilities", body: { automaticBackupsEnabled: true, autre: 1 } },
|
||||
{ method: "POST", path: "/api/utilities", body: { salesAnalysisUrl: null } },
|
||||
{ method: "GET", path: "/api/utilities" },
|
||||
],
|
||||
tables: ["utilities"],
|
||||
},
|
||||
{
|
||||
module: "utilities",
|
||||
name: "paramètres généraux invalides",
|
||||
steps: [
|
||||
{ method: "POST", path: "/api/utilities", body: { salesAnalysisUrl: 5 } },
|
||||
{ method: "POST", path: "/api/utilities", body: { salesAnalysisUrl: "x".repeat(501), automaticBackupsEnabled: "oui" } },
|
||||
{ method: "POST", path: "/api/utilities", body: [] },
|
||||
],
|
||||
tables: ["utilities"],
|
||||
},
|
||||
];
|
||||
@@ -0,0 +1,365 @@
|
||||
// Lot 4, commandes et livraisons : commandes fournisseurs, livraisons (validation,
|
||||
// contrôle, rapprochement automatique, échéance relue dans NocoDB), API externe
|
||||
// de rapprochement (/api/ext/v1, clé d'API) et relais ffnancy (sans réseau : seul
|
||||
// le refus sans session est rejoué). NocoDB est simulé par stub.ts.
|
||||
import { BENCH_API_KEY } from "../config";
|
||||
import type { AccountKey, ReadCase, WriteScenario } from "../types";
|
||||
|
||||
// Clé d'API (en-tête X-API-Key), et deux sessions qui n'y donnent pas accès
|
||||
const EXT_ACCOUNTS: AccountKey[] = ["api", "anonyme", "admin"];
|
||||
|
||||
// Durée de vie des entrées du cache des factures : relative à l'heure du passage
|
||||
const CACHE_IGNORE = ["expires_at"];
|
||||
|
||||
const ext = (path: string): ReadCase => ({ module: "external-api-v1", path: `/api/ext/v1${path}`, accounts: EXT_ACCOUNTS });
|
||||
|
||||
export const reads: ReadCase[] = [
|
||||
// ---------- Commandes (Orders.tsx, Dashboard.tsx, Calendar.tsx, modales, mobile) ----------
|
||||
{ module: "orders", path: "/api/orders" },
|
||||
{ module: "orders", path: "/api/orders?storeId=1" },
|
||||
{ module: "orders", path: "/api/orders?storeId=2" },
|
||||
{ module: "orders", path: "/api/orders?storeId=3" },
|
||||
{ module: "orders", path: "/api/orders?storeId=abc" },
|
||||
{ module: "orders", path: "/api/orders?" },
|
||||
{ module: "orders", path: "/api/orders?startDate={{today-30}}&endDate={{today+30}}" },
|
||||
{ module: "orders", path: "/api/orders?startDate={{today-30}}&endDate={{today+30}}&storeId=1" },
|
||||
{ module: "orders", path: "/api/orders?storeId=2&startDate={{today}}&endDate={{today}}" },
|
||||
{ module: "orders", path: "/api/orders?startDate={{today-400}}&endDate={{today+400}}&storeId=2" },
|
||||
{ module: "orders", path: "/api/orders?startDate={{today}}" },
|
||||
{ module: "orders", path: "/api/orders?startDate=abc&endDate=xyz" },
|
||||
{ module: "orders", path: "/api/orders/1" },
|
||||
{ module: "orders", path: "/api/orders/2" },
|
||||
{ module: "orders", path: "/api/orders/4" },
|
||||
{ module: "orders", path: "/api/orders/6" },
|
||||
{ module: "orders", path: "/api/orders/600" },
|
||||
{ module: "orders", path: "/api/orders/999" },
|
||||
{ module: "orders", path: "/api/orders/abc" },
|
||||
|
||||
// ---------- Livraisons (Deliveries.tsx, BLReconciliation.tsx, Calendar.tsx, mobile) ----------
|
||||
{ module: "deliveries", path: "/api/deliveries" },
|
||||
{ module: "deliveries", path: "/api/deliveries?storeId=1" },
|
||||
{ module: "deliveries", path: "/api/deliveries?storeId=2" },
|
||||
{ module: "deliveries", path: "/api/deliveries?storeId=3" },
|
||||
{ module: "deliveries", path: "/api/deliveries?storeId=abc" },
|
||||
{ module: "deliveries", path: "/api/deliveries?" },
|
||||
{ module: "deliveries", path: "/api/deliveries?status=delivered" },
|
||||
{ module: "deliveries", path: "/api/deliveries?status=delivered&storeId=2" },
|
||||
{ module: "deliveries", path: "/api/deliveries?status=inconnu" },
|
||||
{ module: "deliveries", path: "/api/deliveries?startDate={{today-30}}&endDate={{today+30}}" },
|
||||
{ module: "deliveries", path: "/api/deliveries?startDate={{today-30}}&endDate={{today+30}}&storeId=1" },
|
||||
{ module: "deliveries", path: "/api/deliveries?storeId=2&startDate={{today}}&endDate={{today}}" },
|
||||
{ module: "deliveries", path: "/api/deliveries?startDate={{today-30}}&endDate={{today+30}}&status=planned" },
|
||||
{ module: "deliveries", path: "/api/deliveries?startDate=abc&endDate=xyz" },
|
||||
{ module: "deliveries", path: "/api/deliveries?withBL=true" },
|
||||
{ module: "deliveries", path: "/api/deliveries?withBL=true&storeId=1" },
|
||||
{ module: "deliveries", path: "/api/deliveries/1" },
|
||||
{ module: "deliveries", path: "/api/deliveries/3" },
|
||||
{ module: "deliveries", path: "/api/deliveries/5" },
|
||||
{ module: "deliveries", path: "/api/deliveries/6" },
|
||||
{ module: "deliveries", path: "/api/deliveries/7" },
|
||||
{ module: "deliveries", path: "/api/deliveries/600" },
|
||||
{ module: "deliveries", path: "/api/deliveries/601" },
|
||||
{ module: "deliveries", path: "/api/deliveries/999" },
|
||||
{ module: "deliveries", path: "/api/deliveries/abc" },
|
||||
|
||||
// ---------- API externe (docs/API-RAPPROCHEMENT.md) ----------
|
||||
ext("/stores"),
|
||||
ext("/suppliers"),
|
||||
ext("/deliveries"),
|
||||
ext("/deliveries?storeId=1"),
|
||||
ext("/deliveries?storeId=2&reconciled=true"),
|
||||
ext("/deliveries?supplierId=2&hasInvoice=false"),
|
||||
ext("/deliveries?blNumber=%20bl-1002%20"),
|
||||
ext("/deliveries?blNumber=BL-1002&blNumber=BL-1004"),
|
||||
ext("/deliveries?hasBl=true&from={{today-30}}&to={{today-2}}"),
|
||||
ext("/deliveries?to={{today-6}}&reconciled=false"),
|
||||
ext("/deliveries?limit=2&offset=1"),
|
||||
ext("/deliveries?limit=1e3&offset=0x2"),
|
||||
ext("/deliveries?offset=50&limit="),
|
||||
ext("/deliveries?storeId=0&supplierId=1.5&reconciled=oui&hasBl=1&hasInvoice=&from=2026-1-1&to=x&limit=0&offset=-1"),
|
||||
ext("/deliveries?storeId=1&storeId=2"),
|
||||
ext("/deliveries?storeId=99999999999"),
|
||||
ext("/deliveries/1"),
|
||||
ext("/deliveries/2"),
|
||||
ext("/deliveries/3"),
|
||||
ext("/deliveries/602"),
|
||||
ext("/deliveries/999"),
|
||||
ext("/deliveries/abc"),
|
||||
ext("/deliveries/0"),
|
||||
ext("/deliveries/1.5"),
|
||||
ext("/deliveries/99999999999"),
|
||||
ext(""),
|
||||
ext("/"),
|
||||
ext("/inconnue"),
|
||||
ext("/stores/extra"),
|
||||
// Variantes qu'Express accepte (casse, barre finale) : relayées à Node
|
||||
ext("/stores/"),
|
||||
ext("/SUPPLIERS"),
|
||||
|
||||
// ---------- Relais ffnancy : sans session seulement (les autres appels partent sur Internet) ----------
|
||||
{ module: "ffnancy", path: "/api/ffnancy/articles?ean=3245678901234&limit=1", accounts: ["anonyme"] },
|
||||
{ module: "ffnancy", path: "/api/ffnancy/mouvements/entrees?artNoId=1&limit=1&dateDebut=2000-01-01", accounts: ["anonyme"] },
|
||||
];
|
||||
|
||||
const NEW_DELIVERY = { supplierId: 1, groupId: 1, scheduledDate: "{{today+4}}", quantity: 1, unit: "colis" };
|
||||
|
||||
export const writes: WriteScenario[] = [
|
||||
// ---------- Commandes ----------
|
||||
{
|
||||
module: "orders",
|
||||
name: "commandes : créer, modifier, supprimer",
|
||||
steps: [
|
||||
{ method: "POST", path: "/api/orders", body: { supplierId: 1, groupId: 1, plannedDate: "{{today+7}}", notes: "Commande du banc" } },
|
||||
{ method: "PUT", path: "/api/orders/{{0.id}}", body: { plannedDate: "{{today+8}}", notes: "Modifiée", quantity: 3, unit: "palettes" } },
|
||||
{ method: "GET", path: "/api/orders/{{0.id}}" },
|
||||
{ method: "DELETE", path: "/api/orders/{{0.id}}" },
|
||||
{ method: "POST", path: "/api/orders", body: { supplierId: 2, groupId: 2, plannedDate: "{{today+1}}" } },
|
||||
{ method: "PUT", path: "/api/orders/4", body: { status: "planned" } },
|
||||
{ method: "PUT", path: "/api/orders/6", body: { notes: "Épinal" } },
|
||||
{ method: "DELETE", path: "/api/orders/6" },
|
||||
{ method: "DELETE", path: "/api/orders/3" },
|
||||
// Commande qui a des livraisons : clé étrangère
|
||||
{ method: "DELETE", path: "/api/orders/2" },
|
||||
{ method: "GET", path: "/api/orders?storeId=1" },
|
||||
],
|
||||
tables: ["orders"],
|
||||
},
|
||||
{
|
||||
module: "orders",
|
||||
name: "commandes : corps invalides et identifiants inconnus",
|
||||
steps: [
|
||||
{ method: "POST", path: "/api/orders", body: {} },
|
||||
{ method: "POST", path: "/api/orders", body: { supplierId: "1", groupId: 1.5, plannedDate: 5, quantity: "2" } },
|
||||
{ method: "POST", path: "/api/orders", body: [] },
|
||||
{ method: "POST", path: "/api/orders", body: { supplierId: 1, groupId: 1, plannedDate: "pas une date" } },
|
||||
{ method: "POST", path: "/api/orders", body: { supplierId: 999, groupId: 1, plannedDate: "{{today}}" } },
|
||||
{ method: "POST", path: "/api/orders", body: { supplierId: 1, groupId: 1, plannedDate: "{{today}}", createdBy: "admin_local", status: "delivered", id: 999, createdAt: "{{today}}" } },
|
||||
{ method: "PUT", path: "/api/orders/1", body: { groupId: "x" } },
|
||||
{ method: "PUT", path: "/api/orders/1", body: [] },
|
||||
{ method: "PUT", path: "/api/orders/1", body: {} },
|
||||
// Commande déplacée dans un autre magasin, créateur changé
|
||||
{ method: "PUT", path: "/api/orders/3", body: { groupId: 3, createdBy: "bench_employee" } },
|
||||
{ method: "PUT", path: "/api/orders/999", body: {} },
|
||||
{ method: "PUT", path: "/api/orders/abc", body: {} },
|
||||
{ method: "DELETE", path: "/api/orders/999" },
|
||||
{ method: "DELETE", path: "/api/orders/abc" },
|
||||
{ method: "GET", path: "/api/orders/3" },
|
||||
],
|
||||
tables: ["orders"],
|
||||
},
|
||||
{
|
||||
module: "orders",
|
||||
name: "synchroniser les statuts commandes / livraisons",
|
||||
steps: [
|
||||
{ method: "POST", path: "/api/sync-order-delivery-status" },
|
||||
{ method: "POST", path: "/api/sync-order-delivery-status" },
|
||||
{ method: "GET", path: "/api/orders/600" },
|
||||
],
|
||||
tables: ["orders"],
|
||||
},
|
||||
|
||||
// ---------- Livraisons ----------
|
||||
{
|
||||
module: "deliveries",
|
||||
name: "livraisons : créer, modifier, supprimer",
|
||||
steps: [
|
||||
// CreateDeliveryModal : commande 3 en attente, passée à « planned »
|
||||
{ method: "POST", path: "/api/deliveries", body: { orderId: "3", supplierId: 3, groupId: 1, scheduledDate: "{{today+3}}", quantity: 2, unit: "palettes", status: "planned", notes: "Livraison du banc", createdBy: "admin_local" } },
|
||||
// EditDeliveryModal
|
||||
{ method: "PUT", path: "/api/deliveries/{{0.id}}", body: { supplierId: 3, groupId: 1, orderId: null, scheduledDate: "{{today+5}}", quantity: 4, unit: "palettes", notes: "Modifiée" } },
|
||||
{ method: "GET", path: "/api/deliveries/{{0.id}}" },
|
||||
{ method: "DELETE", path: "/api/deliveries/{{0.id}}" },
|
||||
{ method: "POST", path: "/api/deliveries", body: { ...NEW_DELIVERY, orderId: "none", groupId: 2 } },
|
||||
{ method: "POST", path: "/api/deliveries", body: { ...NEW_DELIVERY, orderId: 4 } },
|
||||
{ method: "POST", path: "/api/deliveries", body: { ...NEW_DELIVERY, orderId: 999 } },
|
||||
{ method: "PUT", path: "/api/deliveries/5", body: { orderId: 1 } },
|
||||
{ method: "PUT", path: "/api/deliveries/5", body: { orderId: 999 } },
|
||||
{ method: "PUT", path: "/api/deliveries/5", body: { notes: "Metz" } },
|
||||
{ method: "PUT", path: "/api/deliveries/601", body: { orderId: 3 } },
|
||||
{ method: "DELETE", path: "/api/deliveries/7" },
|
||||
// Commentaires supprimés avec la livraison (cascade)
|
||||
{ method: "DELETE", path: "/api/deliveries/2" },
|
||||
{ method: "GET", path: "/api/deliveries?storeId=1" },
|
||||
],
|
||||
tables: ["deliveries", "orders", "reconciliation_comments"],
|
||||
},
|
||||
{
|
||||
module: "deliveries",
|
||||
name: "livraisons : corps invalides et identifiants inconnus",
|
||||
steps: [
|
||||
{ method: "POST", path: "/api/deliveries", body: {} },
|
||||
{ method: "POST", path: "/api/deliveries", body: [] },
|
||||
{ method: "POST", path: "/api/deliveries", body: { ...NEW_DELIVERY, orderId: "abc" } },
|
||||
{ method: "POST", path: "/api/deliveries", body: { ...NEW_DELIVERY, orderId: "12abc" } },
|
||||
{ method: "POST", path: "/api/deliveries", body: { ...NEW_DELIVERY, orderId: 0 } },
|
||||
{ method: "POST", path: "/api/deliveries", body: { ...NEW_DELIVERY, orderId: "" } },
|
||||
{ method: "POST", path: "/api/deliveries", body: { ...NEW_DELIVERY, deliveredDate: "{{today}}" } },
|
||||
{ method: "POST", path: "/api/deliveries", body: { ...NEW_DELIVERY, status: "", blAmount: "12.5", blNumber: "BL-9000" } },
|
||||
{ method: "POST", path: "/api/deliveries", body: { ...NEW_DELIVERY, groupId: 3, orderId: 6 } },
|
||||
{ method: "PUT", path: "/api/deliveries/1", body: { invoiceAmountTTC: 5 } },
|
||||
{ method: "PUT", path: "/api/deliveries/1", body: { dueDate: "pas une date" } },
|
||||
{ method: "PUT", path: "/api/deliveries/4", body: { deliveredDate: "{{today}}" } },
|
||||
{ method: "PUT", path: "/api/deliveries/4", body: { validatedAt: "", blAmount: 99.5, invoiceAmount: true, notes: null } },
|
||||
{ method: "PUT", path: "/api/deliveries/4", body: { blAmount: 99.5, notes: null, blNumber: "" } },
|
||||
{ method: "PUT", path: "/api/deliveries/4", body: [] },
|
||||
{ method: "PUT", path: "/api/deliveries/999", body: {} },
|
||||
{ method: "PUT", path: "/api/deliveries/abc", body: {} },
|
||||
{ method: "DELETE", path: "/api/deliveries/999" },
|
||||
{ method: "DELETE", path: "/api/deliveries/abc" },
|
||||
{ method: "GET", path: "/api/deliveries/4" },
|
||||
],
|
||||
tables: ["deliveries", "orders"],
|
||||
},
|
||||
{
|
||||
module: "deliveries",
|
||||
name: "livraisons : référence de facture et échéance (NocoDB)",
|
||||
steps: [
|
||||
{ method: "PUT", path: "/api/deliveries/2", body: { invoiceReference: "FAC-2002", invoiceAmount: "310.00" } },
|
||||
{ method: "PUT", path: "/api/deliveries/2", body: { invoiceReference: "FAC-2002" } },
|
||||
{ method: "PUT", path: "/api/deliveries/2", body: { invoiceReference: "FAC-9999" } },
|
||||
{ method: "PUT", path: "/api/deliveries/2", body: { invoiceReference: "", dueDate: "{{today+60}}" } },
|
||||
{ method: "PUT", path: "/api/deliveries/4", body: { invoiceReference: "AV-3001" } },
|
||||
{ method: "PUT", path: "/api/deliveries/6", body: { invoiceReference: "FAC-2006" } },
|
||||
{ method: "PUT", path: "/api/deliveries/6", body: { invoiceReference: "FAC-2001", dueDate: null } },
|
||||
// Validation rapide et dévalidation (BLReconciliation.tsx)
|
||||
{ method: "PUT", path: "/api/deliveries/4", body: { reconciled: true, validatedAt: "{{today}}T08:00:00.000Z" } },
|
||||
{ method: "PUT", path: "/api/deliveries/1", body: { reconciled: false, validatedAt: null } },
|
||||
{ method: "GET", path: "/api/deliveries/2" },
|
||||
],
|
||||
tables: ["deliveries", "invoice_verification_cache"],
|
||||
ignore: CACHE_IGNORE,
|
||||
},
|
||||
{
|
||||
module: "deliveries",
|
||||
name: "livraisons : rapprochement automatique à la modification",
|
||||
steps: [
|
||||
// Bonduelle : livrée avec un BL, rapprochée d'office (la réponse reste la première mise à jour)
|
||||
{ method: "PUT", path: "/api/deliveries/601", body: { status: "delivered", blNumber: "BL-6010" } },
|
||||
{ method: "PUT", path: "/api/deliveries/3", body: { blNumber: "BL-3000" } },
|
||||
// Fournisseur changé : relu dans la table
|
||||
{ method: "PUT", path: "/api/deliveries/3", body: { supplierId: 1, status: "delivered" } },
|
||||
{ method: "PUT", path: "/api/deliveries/600", body: { supplierId: 2, blNumber: "BL-6000" } },
|
||||
{ method: "GET", path: "/api/deliveries/601" },
|
||||
],
|
||||
tables: ["deliveries"],
|
||||
},
|
||||
{
|
||||
module: "deliveries",
|
||||
name: "livraisons : valider",
|
||||
steps: [
|
||||
// Bonduelle, commande 2 : livraison rapprochée d'office, commande livrée
|
||||
{ method: "POST", path: "/api/deliveries/3/validate", body: { blNumber: " BL-3001 ", blAmount: 150.5 } },
|
||||
{ method: "POST", path: "/api/deliveries/5/validate", body: { blNumber: "BL-5001" } },
|
||||
{ method: "POST", path: "/api/deliveries/7/validate", body: { blNumber: "BL-7001", blAmount: "" } },
|
||||
// Cache : référence et BL de la livraison marqués « rapprochés »
|
||||
{ method: "POST", path: "/api/deliveries/4/validate", body: { blNumber: "BL-1004" } },
|
||||
{ method: "POST", path: "/api/deliveries/8/validate", body: { blNumber: "BL-0900", blAmount: null } },
|
||||
{ method: "POST", path: "/api/deliveries/2/validate", body: { blNumber: " " } },
|
||||
{ method: "POST", path: "/api/deliveries/2/validate", body: {} },
|
||||
{ method: "POST", path: "/api/deliveries/2/validate", body: [] },
|
||||
{ method: "POST", path: "/api/deliveries/2/validate", body: { blNumber: 5 } },
|
||||
{ method: "POST", path: "/api/deliveries/2/validate", body: { blNumber: "BL-1002", blAmount: "abc" } },
|
||||
{ method: "POST", path: "/api/deliveries/999/validate", body: { blNumber: "x" } },
|
||||
{ method: "POST", path: "/api/deliveries/abc/validate", body: { blNumber: "x" } },
|
||||
{ method: "GET", path: "/api/deliveries/3" },
|
||||
],
|
||||
tables: ["deliveries", "orders", "invoice_verification_cache"],
|
||||
ignore: CACHE_IGNORE,
|
||||
},
|
||||
{
|
||||
module: "deliveries",
|
||||
name: "livraisons : contrôle effectué",
|
||||
steps: [
|
||||
{ method: "PUT", path: "/api/deliveries/4/control" },
|
||||
{ method: "PUT", path: "/api/deliveries/6/control" },
|
||||
{ method: "PUT", path: "/api/deliveries/999/control" },
|
||||
{ method: "PUT", path: "/api/deliveries/abc/control" },
|
||||
{ method: "PUT", path: "/api/deliveries/99999999999/control" },
|
||||
{ method: "GET", path: "/api/deliveries?storeId=2" },
|
||||
],
|
||||
tables: ["deliveries"],
|
||||
},
|
||||
|
||||
// ---------- API externe ----------
|
||||
{
|
||||
module: "external-api-v1",
|
||||
name: "API externe : écrire la facture",
|
||||
accounts: ["api"],
|
||||
steps: [
|
||||
// Bonduelle (automatique), non rapprochée : échéance reprise de NocoDB
|
||||
{ method: "PATCH", path: "/api/ext/v1/deliveries/2", body: { invoiceReference: " FAC-2002 ", invoiceAmount: "1 234,56", invoiceAmountTTC: 1481.47 } },
|
||||
{ method: "PATCH", path: "/api/ext/v1/deliveries/2", body: { invoiceReference: "FAC-2002" } },
|
||||
{ method: "PATCH", path: "/api/ext/v1/deliveries/2", body: { dueDate: "{{today+45}}", reconciled: true } },
|
||||
{ method: "PATCH", path: "/api/ext/v1/deliveries/2", body: { invoiceAmount: "10" } },
|
||||
// Lactalis rapprochée : figée, sauf dévalidation dans la même requête
|
||||
{ method: "PATCH", path: "/api/ext/v1/deliveries/1", body: { invoiceAmount: 5 } },
|
||||
{ method: "PATCH", path: "/api/ext/v1/deliveries/1", body: { invoiceAmount: 5, reconciled: false } },
|
||||
{ method: "PATCH", path: "/api/ext/v1/deliveries/1", body: { invoiceReference: "" } },
|
||||
{ method: "PATCH", path: "/api/ext/v1/deliveries/1", body: { reconciled: true } },
|
||||
{ method: "PATCH", path: "/api/ext/v1/deliveries/602", body: { invoiceReference: "FAC-2006", invoiceAmountTTC: null } },
|
||||
{ method: "PATCH", path: "/api/ext/v1/deliveries/3", body: { invoiceReference: "X" } },
|
||||
{ method: "PATCH", path: "/api/ext/v1/deliveries/4", body: { invoiceReference: "FAC-9999" } },
|
||||
{ method: "PATCH", path: "/api/ext/v1/deliveries/4", body: { dueDate: "2026-02-30" } },
|
||||
{ method: "PATCH", path: "/api/ext/v1/deliveries/4", body: { dueDate: "2026-13-45" } },
|
||||
{ method: "PATCH", path: "/api/ext/v1/deliveries/999", body: { reconciled: true } },
|
||||
{ method: "PATCH", path: "/api/ext/v1/deliveries/abc", body: { reconciled: true } },
|
||||
{ method: "PATCH", path: "/api/ext/v1/deliveries/99999999999", body: { reconciled: true } },
|
||||
{ method: "GET", path: "/api/ext/v1/deliveries?storeId=1&hasInvoice=true" },
|
||||
],
|
||||
tables: ["deliveries", "invoice_verification_cache"],
|
||||
ignore: CACHE_IGNORE,
|
||||
},
|
||||
{
|
||||
module: "external-api-v1",
|
||||
name: "API externe : corps invalides et routes inconnues",
|
||||
accounts: ["api"],
|
||||
steps: [
|
||||
{ method: "PATCH", path: "/api/ext/v1/deliveries/2", body: {} },
|
||||
{ method: "PATCH", path: "/api/ext/v1/deliveries/2", body: [] },
|
||||
{ method: "PATCH", path: "/api/ext/v1/deliveries/2", body: { foo: 1, 2: 3 } },
|
||||
{ method: "PATCH", path: "/api/ext/v1/deliveries/2", body: { invoiceAmount: "abc", dueDate: "15/10/2026", reconciled: "oui", extra: 1 } },
|
||||
{ method: "PATCH", path: "/api/ext/v1/deliveries/2", body: { invoiceAmount: true, invoiceAmountTTC: "1e9" } },
|
||||
{ method: "PATCH", path: "/api/ext/v1/deliveries/2", body: { invoiceReference: "x".repeat(101) } },
|
||||
{ method: "PATCH", path: "/api/ext/v1/deliveries/2", body: { invoiceReference: 5, reconciled: null } },
|
||||
{ method: "PATCH", path: "/api/ext/v1/deliveries/abc", body: {} },
|
||||
{ method: "POST", path: "/api/ext/v1/deliveries", body: {} },
|
||||
{ method: "PUT", path: "/api/ext/v1/deliveries/2", body: {} },
|
||||
{ method: "DELETE", path: "/api/ext/v1/stores" },
|
||||
{ method: "POST", path: "/api/ext/v1/inconnue", body: { a: 1 } },
|
||||
{ method: "GET", path: "/api/ext/v1/deliveries/2" },
|
||||
],
|
||||
tables: ["deliveries"],
|
||||
},
|
||||
{
|
||||
module: "external-api-v1",
|
||||
name: "API externe : clés refusées ou passées autrement",
|
||||
accounts: ["api"],
|
||||
steps: [
|
||||
{ method: "GET", path: "/api/ext/v1/stores", headers: { "x-api-key": "lf_cle_revoquee" } },
|
||||
{ method: "GET", path: "/api/ext/v1/stores", headers: { "x-api-key": "mauvaise" } },
|
||||
{ method: "GET", path: "/api/ext/v1/stores", headers: { "x-api-key": " ", authorization: `Bearer ${BENCH_API_KEY}` } },
|
||||
{ method: "GET", path: "/api/ext/v1/suppliers", headers: { "x-api-key": "", authorization: `bearer ${BENCH_API_KEY} ` } },
|
||||
{ method: "GET", path: "/api/ext/v1/stores", headers: { "x-api-key": " ", authorization: "Bearer " } },
|
||||
{ method: "GET", path: "/api/ext/v1/stores", headers: { "x-api-key": " ", authorization: `Basic ${BENCH_API_KEY}` } },
|
||||
{ method: "PATCH", path: "/api/ext/v1/deliveries/2", body: { reconciled: true }, headers: { "x-api-key": "mauvaise" } },
|
||||
{ method: "POST", path: "/api/ext/v1/inconnue", headers: { "x-api-key": "mauvaise" } },
|
||||
],
|
||||
tables: ["deliveries"],
|
||||
},
|
||||
{
|
||||
module: "external-api-v1",
|
||||
name: "API externe : désactivée sans clé active",
|
||||
accounts: ["admin"],
|
||||
steps: [
|
||||
// La session ne donne pas accès à l'API externe
|
||||
{ method: "GET", path: "/api/ext/v1/stores" },
|
||||
{ method: "GET", path: "/api/ext/v1/stores", headers: { "x-api-key": BENCH_API_KEY } },
|
||||
{ method: "DELETE", path: "/api/external-api/keys/1" },
|
||||
{ method: "GET", path: "/api/ext/v1/stores" },
|
||||
{ method: "GET", path: "/api/ext/v1/stores", headers: { "x-api-key": BENCH_API_KEY } },
|
||||
{ method: "GET", path: "/api/ext/v1/inconnue" },
|
||||
{ method: "PATCH", path: "/api/ext/v1/deliveries/2", body: { reconciled: true }, headers: { "x-api-key": BENCH_API_KEY } },
|
||||
],
|
||||
tables: ["external_api_keys"],
|
||||
},
|
||||
];
|
||||
@@ -0,0 +1,410 @@
|
||||
// Lot 4, rapprochement : avoirs (et leur webhook), échéancier des paiements (lecture
|
||||
// complétée par NocoDB, export CSV), envoi d'une facture au webhook du magasin,
|
||||
// SQL et migration d'urgence de l'administration. NocoDB et les webhooks sont
|
||||
// simulés par stub.ts. Données complémentaires : seeds/lot4-rapprochement.ts.
|
||||
import { STUB_URL } from "../config";
|
||||
import type { ReadCase, WriteScenario } from "../types";
|
||||
|
||||
// Mois « AAAA-MM » relatif au mois courant (sélecteur de l'échéancier)
|
||||
function month(offset: number): string {
|
||||
const date = new Date();
|
||||
date.setUTCDate(1);
|
||||
date.setUTCMonth(date.getUTCMonth() + offset);
|
||||
return date.toISOString().slice(0, 7);
|
||||
}
|
||||
|
||||
// Durée de vie des entrées du cache : relative à l'heure du passage, différente
|
||||
// entre les deux serveurs
|
||||
const CACHE_IGNORE = ["expires_at"];
|
||||
|
||||
const ALL_METHODS = ["virement", "prelevement", "cheque", "Non défini"];
|
||||
|
||||
// Fichier envoyé par BLReconciliation.tsx avec les champs du formulaire
|
||||
const INVOICE_FILE = [{ field: "file", filename: "facture BL-1001.pdf", contentType: "application/pdf", size: 2048 }];
|
||||
|
||||
export const reads: ReadCase[] = [
|
||||
// ---------- Avoirs (Avoirs.tsx, mobile/AvoirsPage.tsx : storeId du magasin choisi) ----------
|
||||
{ module: "avoirs", path: "/api/avoirs" },
|
||||
{ module: "avoirs", path: "/api/avoirs?storeId=1" },
|
||||
{ module: "avoirs", path: "/api/avoirs?storeId=2" },
|
||||
{ module: "avoirs", path: "/api/avoirs?storeId=3" },
|
||||
{ module: "avoirs", path: "/api/avoirs?storeId=" },
|
||||
{ module: "avoirs", path: "/api/avoirs?storeId=abc" },
|
||||
{ module: "avoirs", path: "/api/avoirs?storeId=99999999999" },
|
||||
{ module: "avoirs", path: "/api/avoirs/1" },
|
||||
{ module: "avoirs", path: "/api/avoirs/3" },
|
||||
{ module: "avoirs", path: "/api/avoirs/701" },
|
||||
{ module: "avoirs", path: "/api/avoirs/999" },
|
||||
{ module: "avoirs", path: "/api/avoirs/abc" },
|
||||
|
||||
// ---------- Échéancier (PaymentSchedulePage.tsx : groupId du magasin choisi) ----------
|
||||
// La lecture complète en base les échéances et montants manquants (NocoDB) :
|
||||
// les comptes suivants voient les livraisons déjà complétées
|
||||
{ module: "payment-schedule", path: "/api/payment-schedule?groupId=1" },
|
||||
{ module: "payment-schedule", path: "/api/payment-schedule?groupId=2" },
|
||||
{ module: "payment-schedule", path: "/api/payment-schedule?groupId=3" },
|
||||
{ module: "payment-schedule", path: "/api/payment-schedule?groupId=999" },
|
||||
{ module: "payment-schedule", path: "/api/payment-schedule?groupId=abc", accounts: ["admin", "directeur", "manager"] },
|
||||
{ module: "payment-schedule", path: "/api/payment-schedule", accounts: ["admin", "directeur"] },
|
||||
{ module: "payment-schedule", path: "/api/payment-schedule?groupId=", accounts: ["admin", "directeur"] },
|
||||
{ module: "payment-schedule", path: "/api/payment-schedule?groupId=0", accounts: ["admin", "directeur"] },
|
||||
{ module: "payment-schedule", path: "/api/payment-schedule?groupId=1.5", accounts: ["admin", "directeur"] },
|
||||
{ module: "payment-schedule", path: "/api/payment-schedule?groupId=-1", accounts: ["admin", "directeur"] },
|
||||
{ module: "payment-schedule", path: "/api/payment-schedule?groupId=%201%20", accounts: ["admin", "directeur"] },
|
||||
{ module: "payment-schedule", path: "/api/payment-schedule?groupId=99999999999", accounts: ["admin", "directeur"] },
|
||||
];
|
||||
|
||||
export const writes: WriteScenario[] = [
|
||||
// ---------- Avoirs ----------
|
||||
{
|
||||
module: "avoirs",
|
||||
name: "créer un avoir (webhook du magasin à la création)",
|
||||
steps: [
|
||||
// Formulaire du bureau
|
||||
{ method: "POST", path: "/api/avoirs", body: { supplierId: 1, groupId: 1, invoiceReference: "AV-4001", amount: 12.5, comment: "Casse", commercialProcessed: false, status: "En attente de demande" } },
|
||||
{ method: "GET", path: "/api/avoirs/{{0.id}}" },
|
||||
// Formulaire mobile : montant forcé à 0, sans référence ni commentaire
|
||||
{ method: "POST", path: "/api/avoirs", body: { supplierId: 2, groupId: 1, amount: 0, status: "Demandé", createdBy: "pirate" } },
|
||||
// Metz : sans webhook
|
||||
{ method: "POST", path: "/api/avoirs", body: { supplierId: 3, groupId: 2, invoiceReference: "", amount: "-7.25", comment: "", commercialProcessed: true, status: "Reçu" } },
|
||||
// Épinal : aucun compte du banc n'y est affecté (sauf l'administrateur)
|
||||
{ method: "POST", path: "/api/avoirs", body: { supplierId: 4, groupId: 3, amount: null } },
|
||||
{ method: "GET", path: "/api/avoirs" },
|
||||
],
|
||||
tables: ["avoirs"],
|
||||
},
|
||||
{
|
||||
// Fiche du magasin modifiable par l'admin et le manager
|
||||
module: "avoirs",
|
||||
name: "webhook du magasin en erreur, injoignable ou invalide",
|
||||
accounts: ["admin", "manager"],
|
||||
steps: [
|
||||
{ method: "PUT", path: "/api/groups/1", body: { webhookUrl: `${STUB_URL}/inconnu` } },
|
||||
{ method: "POST", path: "/api/avoirs", body: { supplierId: 1, groupId: 1, invoiceReference: "AV-4101", amount: 1 } },
|
||||
{ method: "PUT", path: "/api/groups/1", body: { webhookUrl: "http://127.0.0.1:1/webhook" } },
|
||||
{ method: "POST", path: "/api/avoirs", body: { supplierId: 1, groupId: 1, invoiceReference: "AV-4102", amount: 2 } },
|
||||
{ method: "PUT", path: "/api/avoirs/1", body: { status: "Reçu" } },
|
||||
{ method: "PUT", path: "/api/groups/1", body: { webhookUrl: "pas une adresse" } },
|
||||
{ method: "POST", path: "/api/avoirs", body: { supplierId: 1, groupId: 1, invoiceReference: "AV-4103", amount: 3 } },
|
||||
],
|
||||
tables: ["avoirs"],
|
||||
},
|
||||
{
|
||||
module: "avoirs",
|
||||
name: "avoirs invalides à la création",
|
||||
steps: [
|
||||
{ method: "POST", path: "/api/avoirs", body: {} },
|
||||
{ method: "POST", path: "/api/avoirs", body: { supplierId: "1", groupId: 1.5 } },
|
||||
{ method: "POST", path: "/api/avoirs", body: { supplierId: 1, groupId: 1, amount: "abc", status: "Perdu", commercialProcessed: "oui" } },
|
||||
{ method: "POST", path: "/api/avoirs", body: { supplierId: 1, groupId: 1, invoiceReference: null, comment: null, commercialProcessed: null } },
|
||||
{ method: "POST", path: "/api/avoirs", body: ["x"] },
|
||||
// Erreurs de la base : fournisseur ou magasin inconnu, référence trop longue, montant trop grand
|
||||
{ method: "POST", path: "/api/avoirs", body: { supplierId: 999, groupId: 1 } },
|
||||
{ method: "POST", path: "/api/avoirs", body: { supplierId: 1, groupId: 999 } },
|
||||
{ method: "POST", path: "/api/avoirs", body: { supplierId: 1, groupId: 1, invoiceReference: "R".repeat(300) } },
|
||||
{ method: "POST", path: "/api/avoirs", body: { supplierId: 1, groupId: 1, amount: 1e9 } },
|
||||
// Montant vide : Number("") = 0
|
||||
{ method: "POST", path: "/api/avoirs", body: { supplierId: 1, groupId: 2, amount: "" } },
|
||||
],
|
||||
tables: ["avoirs"],
|
||||
},
|
||||
{
|
||||
module: "avoirs",
|
||||
name: "modifier un avoir (passage à « Reçu » et webhook)",
|
||||
steps: [
|
||||
// Formulaire complet : passage à « Reçu », webhook du magasin
|
||||
{ method: "PUT", path: "/api/avoirs/1", body: { supplierId: 1, groupId: 1, invoiceReference: "AV-3001", amount: 50, comment: "Reçu par courrier", commercialProcessed: true, status: "Reçu" } },
|
||||
// Déjà « Reçu » : pas de webhook ; montant, référence et commentaire effacés (AVOIRS-API-06)
|
||||
{ method: "PUT", path: "/api/avoirs/1", body: { status: "Reçu" } },
|
||||
// Metz (sans webhook)
|
||||
{ method: "PUT", path: "/api/avoirs/3", body: { status: "Demandé", amount: "15.5", invoiceReference: "AV-3003", comment: "" } },
|
||||
{ method: "PUT", path: "/api/avoirs/700", body: { status: "Reçu", invoiceReference: "FAC-2006", amount: 10.5 } },
|
||||
// Changement de magasin et de créateur acceptés (AVOIRS-API-07)
|
||||
{ method: "PUT", path: "/api/avoirs/2", body: { groupId: 2, createdBy: "pirate", invoiceReference: "AV-3002" } },
|
||||
// Épinal
|
||||
{ method: "PUT", path: "/api/avoirs/701", body: { status: "Reçu" } },
|
||||
{ method: "GET", path: "/api/avoirs/1" },
|
||||
],
|
||||
tables: ["avoirs"],
|
||||
},
|
||||
{
|
||||
module: "avoirs",
|
||||
name: "avoirs invalides ou introuvables à la modification",
|
||||
steps: [
|
||||
{ method: "PUT", path: "/api/avoirs/1", body: { status: "Perdu" } },
|
||||
{ method: "PUT", path: "/api/avoirs/1", body: { amount: "abc", supplierId: "2" } },
|
||||
{ method: "PUT", path: "/api/avoirs/1", body: { invoiceReference: null } },
|
||||
{ method: "PUT", path: "/api/avoirs/1", body: { supplierId: 999 } },
|
||||
{ method: "PUT", path: "/api/avoirs/999", body: { status: "Reçu" } },
|
||||
{ method: "PUT", path: "/api/avoirs/abc", body: { status: "Reçu" } },
|
||||
{ method: "PUT", path: "/api/avoirs/1", body: [] },
|
||||
],
|
||||
tables: ["avoirs"],
|
||||
},
|
||||
{
|
||||
module: "avoirs",
|
||||
name: "supprimer des avoirs",
|
||||
steps: [
|
||||
{ method: "DELETE", path: "/api/avoirs/2" },
|
||||
{ method: "DELETE", path: "/api/avoirs/700" },
|
||||
{ method: "DELETE", path: "/api/avoirs/701" },
|
||||
{ method: "DELETE", path: "/api/avoirs/999" },
|
||||
{ method: "DELETE", path: "/api/avoirs/abc" },
|
||||
{ method: "GET", path: "/api/avoirs" },
|
||||
],
|
||||
tables: ["avoirs"],
|
||||
},
|
||||
{
|
||||
module: "avoirs",
|
||||
name: "vérifier la facture d'un avoir dans NocoDB",
|
||||
steps: [
|
||||
{ method: "POST", path: "/api/avoirs/1/verify-invoice", body: { invoiceReference: "AV-3001", forceRefresh: false } },
|
||||
// Depuis le cache, puis en forçant
|
||||
{ method: "POST", path: "/api/avoirs/1/verify-invoice", body: { invoiceReference: "AV-3001" } },
|
||||
{ method: "POST", path: "/api/avoirs/1/verify-invoice", body: { invoiceReference: " av-3001 ", forceRefresh: "oui" } },
|
||||
{ method: "POST", path: "/api/avoirs/2/verify-invoice", body: { invoiceReference: "AV-INCONNU" } },
|
||||
{ method: "POST", path: "/api/avoirs/700/verify-invoice", body: { invoiceReference: "FAC-2006" } },
|
||||
// Épinal : sans configuration NocoDB
|
||||
{ method: "POST", path: "/api/avoirs/701/verify-invoice", body: { invoiceReference: "FAC-2004" } },
|
||||
{ method: "POST", path: "/api/avoirs/1/verify-invoice", body: {} },
|
||||
{ method: "POST", path: "/api/avoirs/1/verify-invoice", body: { invoiceReference: " " } },
|
||||
{ method: "POST", path: "/api/avoirs/1/verify-invoice", body: { invoiceReference: 2001 } },
|
||||
{ method: "POST", path: "/api/avoirs/999/verify-invoice", body: { invoiceReference: "AV-3001" } },
|
||||
{ method: "POST", path: "/api/avoirs/abc/verify-invoice", body: { invoiceReference: "AV-3001" } },
|
||||
],
|
||||
tables: ["invoice_verification_cache"],
|
||||
ignore: CACHE_IGNORE,
|
||||
},
|
||||
{
|
||||
module: "avoirs",
|
||||
name: "valider un avoir et l'état de son webhook",
|
||||
steps: [
|
||||
// Entrée du cache à marquer « rapprochée » à la validation
|
||||
{ method: "POST", path: "/api/avoirs/1/verify-invoice", body: { invoiceReference: "AV-3001" } },
|
||||
{ method: "PUT", path: "/api/avoirs/1/nocodb-verification", body: { verified: true } },
|
||||
{ method: "PUT", path: "/api/avoirs/2/nocodb-verification", body: { verified: false } },
|
||||
// Référence absente ou blanche : rien à marquer ; autre magasin accepté (AVOIRS-API-08)
|
||||
{ method: "PUT", path: "/api/avoirs/3/nocodb-verification", body: { verified: true } },
|
||||
{ method: "PUT", path: "/api/avoirs/702/nocodb-verification", body: { verified: "yes" } },
|
||||
{ method: "PUT", path: "/api/avoirs/700/nocodb-verification", body: {} },
|
||||
{ method: "PUT", path: "/api/avoirs/701/nocodb-verification", body: { verified: "oui" } },
|
||||
{ method: "PUT", path: "/api/avoirs/999/nocodb-verification", body: { verified: true } },
|
||||
{ method: "PUT", path: "/api/avoirs/abc/nocodb-verification", body: { verified: true } },
|
||||
{ method: "PUT", path: "/api/avoirs/1/webhook-status", body: { webhookSent: false } },
|
||||
{ method: "PUT", path: "/api/avoirs/2/webhook-status", body: { webhookSent: 1 } },
|
||||
{ method: "PUT", path: "/api/avoirs/700/webhook-status", body: {} },
|
||||
{ method: "PUT", path: "/api/avoirs/701/webhook-status", body: { webhookSent: null } },
|
||||
{ method: "PUT", path: "/api/avoirs/3/webhook-status", body: { webhookSent: "oui" } },
|
||||
{ method: "PUT", path: "/api/avoirs/999/webhook-status", body: { webhookSent: true } },
|
||||
{ method: "PUT", path: "/api/avoirs/abc/webhook-status", body: { webhookSent: true } },
|
||||
],
|
||||
tables: ["avoirs", "invoice_verification_cache"],
|
||||
ignore: CACHE_IGNORE,
|
||||
},
|
||||
|
||||
// ---------- Échéancier ----------
|
||||
{
|
||||
module: "payment-schedule",
|
||||
name: "échéancier : échéances et montants complétés par NocoDB",
|
||||
steps: [
|
||||
{ method: "GET", path: "/api/payment-schedule?groupId=1" },
|
||||
// Seconde lecture : le repli « TTC » recommence (montant TTC jamais écrit)
|
||||
{ method: "GET", path: "/api/payment-schedule?groupId=1" },
|
||||
{ method: "GET", path: "/api/payment-schedule?groupId=2" },
|
||||
{ method: "GET", path: "/api/payment-schedule?groupId=3" },
|
||||
],
|
||||
tables: ["deliveries", "invoice_verification_cache"],
|
||||
ignore: CACHE_IGNORE,
|
||||
},
|
||||
{
|
||||
module: "payment-schedule",
|
||||
name: "export CSV de l'échéancier",
|
||||
steps: [
|
||||
// Formulaire d'export : mois courant, tous les modes de paiement, HT et TTC
|
||||
{ method: "POST", path: "/api/payment-schedule/export", body: { groupId: "1", month: month(0), paymentMethods: ALL_METHODS, includeHT: true, includeTTC: true } },
|
||||
{ method: "POST", path: "/api/payment-schedule/export", body: { groupId: 1, month: month(0), paymentMethods: ["virement", "cheque"], includeHT: false, includeTTC: true } },
|
||||
{ method: "POST", path: "/api/payment-schedule/export", body: { groupId: 1, month: month(1), paymentMethods: ALL_METHODS, includeHT: "non", includeTTC: 0 } },
|
||||
{ method: "POST", path: "/api/payment-schedule/export", body: { groupId: 2, month: month(0), paymentMethods: ALL_METHODS, includeHT: true } },
|
||||
{ method: "POST", path: "/api/payment-schedule/export", body: { groupId: 3, month: month(0), paymentMethods: ALL_METHODS, includeTTC: true } },
|
||||
{ method: "POST", path: "/api/payment-schedule/export", body: { groupId: 1, month: "2020-13", paymentMethods: ALL_METHODS, includeHT: true } },
|
||||
{ method: "POST", path: "/api/payment-schedule/export", body: { groupId: 999, month: month(0), paymentMethods: ALL_METHODS } },
|
||||
],
|
||||
tables: [],
|
||||
},
|
||||
{
|
||||
module: "payment-schedule",
|
||||
name: "export CSV : paramètres invalides",
|
||||
accounts: ["admin", "directeur", "manager"],
|
||||
steps: [
|
||||
{ method: "POST", path: "/api/payment-schedule/export", body: {} },
|
||||
{ method: "POST", path: "/api/payment-schedule/export", body: { groupId: "abc", month: month(0), paymentMethods: ALL_METHODS } },
|
||||
{ method: "POST", path: "/api/payment-schedule/export", body: { groupId: 1.5, month: month(0), paymentMethods: ALL_METHODS } },
|
||||
{ method: "POST", path: "/api/payment-schedule/export", body: { groupId: 1, month: "2026-1", paymentMethods: ALL_METHODS } },
|
||||
{ method: "POST", path: "/api/payment-schedule/export", body: { groupId: 1, month: 202610, paymentMethods: ALL_METHODS } },
|
||||
{ method: "POST", path: "/api/payment-schedule/export", body: { groupId: 1, month: month(0), paymentMethods: [] } },
|
||||
{ method: "POST", path: "/api/payment-schedule/export", body: { groupId: 1, month: month(0), paymentMethods: "virement" } },
|
||||
{ method: "POST", path: "/api/payment-schedule/export", body: { groupId: 1, month: month(0), paymentMethods: ["virement", 5, null] } },
|
||||
{ method: "POST", path: "/api/payment-schedule/export", body: { groupId: 1, month: month(0) } },
|
||||
],
|
||||
tables: [],
|
||||
},
|
||||
|
||||
// ---------- Envoi d'une facture au webhook (BLReconciliation.tsx) ----------
|
||||
{
|
||||
module: "send-invoice",
|
||||
name: "envoyer une facture au webhook du magasin",
|
||||
steps: [
|
||||
{
|
||||
method: "POST",
|
||||
path: "/api/reconciliation/send-invoice",
|
||||
bodyType: "multipart",
|
||||
body: { webhookUrl: `${STUB_URL}/webhook/avoirs`, supplier: "Lactalis", blNumber: "BL-1001", type: "Facture" },
|
||||
files: INVOICE_FILE,
|
||||
},
|
||||
// Webhook BAP configuré ; champs facultatifs absents
|
||||
{
|
||||
method: "POST",
|
||||
path: "/api/reconciliation/send-invoice",
|
||||
bodyType: "multipart",
|
||||
body: { webhookUrl: `${STUB_URL}/webhook/bap` },
|
||||
files: [{ field: "file", filename: "scan échéance.png", contentType: "image/PNG", size: 10 }],
|
||||
},
|
||||
// Fichier ou adresse manquants
|
||||
{ method: "POST", path: "/api/reconciliation/send-invoice", bodyType: "multipart", body: { webhookUrl: `${STUB_URL}/webhook/avoirs`, supplier: "Lactalis" } },
|
||||
{ method: "POST", path: "/api/reconciliation/send-invoice", bodyType: "multipart", body: { supplier: "Lactalis" }, files: INVOICE_FILE },
|
||||
{ method: "POST", path: "/api/reconciliation/send-invoice", bodyType: "multipart", body: { webhookUrl: "" }, files: INVOICE_FILE },
|
||||
// Champ « file » en texte
|
||||
{ method: "POST", path: "/api/reconciliation/send-invoice", bodyType: "multipart", body: { file: "pas un fichier", webhookUrl: `${STUB_URL}/webhook/avoirs` } },
|
||||
{ method: "POST", path: "/api/reconciliation/send-invoice", bodyType: "multipart", body: { file: "", webhookUrl: `${STUB_URL}/webhook/avoirs` } },
|
||||
// Pas de multipart
|
||||
{ method: "POST", path: "/api/reconciliation/send-invoice", body: { webhookUrl: `${STUB_URL}/webhook/avoirs` } },
|
||||
{ method: "POST", path: "/api/reconciliation/send-invoice" },
|
||||
],
|
||||
tables: [],
|
||||
},
|
||||
{
|
||||
module: "send-invoice",
|
||||
name: "webhook de facture en erreur ou injoignable",
|
||||
accounts: ["admin"],
|
||||
steps: [
|
||||
// Adresses enregistrées sur la fiche du magasin d'Épinal
|
||||
{ method: "PUT", path: "/api/groups/3", body: { webhookUrl: `${STUB_URL}/inconnu` } },
|
||||
{
|
||||
method: "POST",
|
||||
path: "/api/reconciliation/send-invoice",
|
||||
bodyType: "multipart",
|
||||
body: { webhookUrl: `${STUB_URL}/inconnu`, supplier: "Jardin & Co", blNumber: "", type: "Avoir" },
|
||||
files: INVOICE_FILE,
|
||||
},
|
||||
{ method: "PUT", path: "/api/groups/3", body: { webhookUrl: "http://127.0.0.1:1/webhook" } },
|
||||
{
|
||||
method: "POST",
|
||||
path: "/api/reconciliation/send-invoice",
|
||||
bodyType: "multipart",
|
||||
body: { webhookUrl: "http://127.0.0.1:1/webhook", supplier: "Jardin & Co" },
|
||||
files: INVOICE_FILE,
|
||||
},
|
||||
],
|
||||
tables: [],
|
||||
},
|
||||
{
|
||||
// RAPPRO-21 : Node envoie le fichier à toute adresse fournie par le navigateur
|
||||
module: "send-invoice",
|
||||
name: "envoyer une facture à une adresse non configurée",
|
||||
// Les autres comptes sont refusés avant (403), comme dans le premier scénario
|
||||
accounts: ["admin", "directeur"],
|
||||
steps: [
|
||||
{
|
||||
method: "POST",
|
||||
path: "/api/reconciliation/send-invoice",
|
||||
bodyType: "multipart",
|
||||
body: { webhookUrl: `${STUB_URL}/webhook/non-configure`, supplier: "Lactalis", blNumber: "BL-1001" },
|
||||
files: INVOICE_FILE,
|
||||
},
|
||||
// Webhook BAP désactivé (par l'admin) : son adresse n'est plus acceptée
|
||||
{ method: "POST", path: "/api/webhook-bap-config", body: { webhookUrl: `${STUB_URL}/webhook/bap`, isActive: false } },
|
||||
{
|
||||
method: "POST",
|
||||
path: "/api/reconciliation/send-invoice",
|
||||
bodyType: "multipart",
|
||||
body: { webhookUrl: `${STUB_URL}/webhook/bap`, supplier: "Lactalis" },
|
||||
files: INVOICE_FILE,
|
||||
},
|
||||
],
|
||||
tables: [],
|
||||
deviation: {
|
||||
fields: ["status", "body", "appels"],
|
||||
reason:
|
||||
"RAPPRO-21 : l'adresse du webhook doit être celle d'un magasin ou du webhook BAP actif ; toute autre adresse est refusée (400) au lieu d'être appelée par le serveur",
|
||||
},
|
||||
},
|
||||
|
||||
// ---------- Administration : SQL et migration d'urgence (SQLExecutor.tsx) ----------
|
||||
{
|
||||
module: "admin-sql",
|
||||
name: "exécuter du SQL : lectures, types de colonnes et erreurs",
|
||||
steps: [
|
||||
{ method: "POST", path: "/api/admin/execute-sql", body: { sql: "SELECT id, name, color, nocodb_config_id FROM groups ORDER BY id" } },
|
||||
{
|
||||
method: "POST",
|
||||
path: "/api/admin/execute-sql",
|
||||
body: {
|
||||
sql: "SELECT count(*) AS total, sum(amount) AS somme, min(created_at) AS premier, min(created_at)::date AS jour, bool_and(true) AS vrai, " +
|
||||
"1.5::float8 AS flottant, 2::float8 AS entier, 7::int2 AS petit, 12345678901::int8 AS grand, NULL AS rien, '{\"a\": [1, \"b\"]}'::jsonb AS objet, " +
|
||||
"ARRAY[1, 2] AS tableau, ARRAY['a', NULL] AS textes, 'x'::bytea AS octets, '1 day 02:00'::interval AS duree, " +
|
||||
"'2026-01-01 10:00+02'::timestamptz AS horodate, 'f'::bool AS faux, 'é\"\\\\'::text AS texte FROM avoirs",
|
||||
},
|
||||
},
|
||||
{ method: "POST", path: "/api/admin/execute-sql", body: { sql: "SELECT 'a' AS x, 'b' AS x, 1" } },
|
||||
{ method: "POST", path: "/api/admin/execute-sql", body: { sql: "SELECT n, n * 1.5 AS demi FROM generate_series(1, 12) AS n" } },
|
||||
{ method: "POST", path: "/api/admin/execute-sql", body: { sql: "SELECT 1 WHERE false" } },
|
||||
{ method: "POST", path: "/api/admin/execute-sql", body: { sql: "SELECT 1 AS a; SELECT 2 AS b" } },
|
||||
{ method: "POST", path: "/api/admin/execute-sql", body: { sql: "SELECT 1 AS a; SELEC 2" } },
|
||||
{ method: "POST", path: "/api/admin/execute-sql", body: { sql: "ANALYZE groups" } },
|
||||
{ method: "POST", path: "/api/admin/execute-sql", body: { sql: " " } },
|
||||
{ method: "POST", path: "/api/admin/execute-sql", body: { sql: "SELEC 1" } },
|
||||
{ method: "POST", path: "/api/admin/execute-sql", body: { sql: "SELECT 1/0" } },
|
||||
{ method: "POST", path: "/api/admin/execute-sql", body: { sql: "" } },
|
||||
{ method: "POST", path: "/api/admin/execute-sql", body: { sql: 5 } },
|
||||
{ method: "POST", path: "/api/admin/execute-sql", body: {} },
|
||||
],
|
||||
tables: [],
|
||||
},
|
||||
{
|
||||
module: "admin-sql",
|
||||
name: "exécuter du SQL : modification d'une ligne",
|
||||
steps: [
|
||||
{ method: "POST", path: "/api/admin/execute-sql", body: { sql: "UPDATE groups SET color = '#123456' WHERE id = 3" } },
|
||||
{ method: "POST", path: "/api/admin/execute-sql", body: { sql: "UPDATE groups SET color = color WHERE id = 999" } },
|
||||
{ method: "POST", path: "/api/admin/execute-sql", body: { sql: "UPDATE avoirs SET comment = 'modifié' WHERE id = 1 RETURNING id, comment, amount" } },
|
||||
{ method: "POST", path: "/api/admin/execute-sql", body: { sql: "SELECT id, color FROM groups WHERE id = 3" } },
|
||||
],
|
||||
tables: ["groups", "avoirs"],
|
||||
},
|
||||
{
|
||||
module: "admin-sql",
|
||||
name: "migration d'urgence : secrets en clair chiffrés",
|
||||
steps: [
|
||||
{ method: "POST", path: "/api/admin/execute-sql", body: { sql: "UPDATE groups SET smtp_password = 'secret-en-clair' WHERE id = 2" } },
|
||||
{ method: "POST", path: "/api/admin/emergency-migration" },
|
||||
{ method: "POST", path: "/api/admin/execute-sql", body: { sql: "SELECT id, smtp_password LIKE 'enc:v1:%' AS chiffre FROM groups ORDER BY id" } },
|
||||
],
|
||||
tables: [],
|
||||
},
|
||||
{
|
||||
// N'existe que si EMERGENCY_SECRET est défini (sinon 404) : le banc rejoue le
|
||||
// secret de son propre environnement
|
||||
module: "admin-sql",
|
||||
name: "réinitialisation d'urgence du compte admin",
|
||||
accounts: ["anonyme", "manager"],
|
||||
steps: [
|
||||
{ method: "POST", path: "/api/emergency-admin-reset", body: { secret: "mauvais-secret" } },
|
||||
{ method: "POST", path: "/api/emergency-admin-reset", body: {} },
|
||||
{ method: "POST", path: "/api/emergency-admin-reset", body: { secret: process.env.EMERGENCY_SECRET || "secret-absent" } },
|
||||
{ method: "POST", path: "/api/login", body: { username: "admin", password: "admin" } },
|
||||
],
|
||||
tables: ["users"],
|
||||
// Empreinte au sel aléatoire
|
||||
ignore: ["password"],
|
||||
},
|
||||
];
|
||||
@@ -0,0 +1,313 @@
|
||||
// Lot 4, services partagés : rapprochement BL / factures (commentaires, courriels
|
||||
// fournisseurs, vérification des factures, cache), configuration NocoDB, BAP et
|
||||
// clés de l'API externe. NocoDB, les webhooks et le SMTP sont simulés par stub.ts.
|
||||
import { STUB_SMTP_PORT, STUB_URL } from "../config";
|
||||
import type { ReadCase, WriteScenario } from "../types";
|
||||
|
||||
export const reads: ReadCase[] = [
|
||||
// Commentaires de rapprochement (ReconciliationComments.tsx)
|
||||
{ module: "reconciliation", path: "/api/deliveries/2/reconciliation-comments" },
|
||||
{ module: "reconciliation", path: "/api/deliveries/4/reconciliation-comments" },
|
||||
{ module: "reconciliation", path: "/api/deliveries/6/reconciliation-comments" },
|
||||
{ module: "reconciliation", path: "/api/deliveries/999/reconciliation-comments" },
|
||||
{ module: "reconciliation", path: "/api/deliveries/abc/reconciliation-comments" },
|
||||
// Historique des relances (BLReconciliation.tsx : « ? » seul, ou storeId pour admin et directeur)
|
||||
{ module: "reconciliation", path: "/api/supplier-mail-logs?" },
|
||||
{ module: "reconciliation", path: "/api/supplier-mail-logs?storeId=1" },
|
||||
{ module: "reconciliation", path: "/api/supplier-mail-logs?storeId=2" },
|
||||
{ module: "reconciliation", path: "/api/supplier-mail-logs?deliveryId=2" },
|
||||
{ module: "reconciliation", path: "/api/supplier-mail-logs?deliveryId=abc" },
|
||||
{ module: "reconciliation", path: "/api/supplier-mail-logs?storeId=abc" },
|
||||
// Diagnostic du cache (sans appelant dans l'interface ; supprime au passage une entrée expirée)
|
||||
{ module: "reconciliation", path: "/api/cache/diagnosis" },
|
||||
// Configuration NocoDB (NocoDBConfig.tsx, Groups.tsx)
|
||||
{ module: "nocodb-config", path: "/api/nocodb-config" },
|
||||
{ module: "nocodb-config", path: "/api/nocodb-config/active" },
|
||||
// Webhook BAP (WebhookBAPConfig.tsx, BapUploadModal.tsx)
|
||||
{ module: "bap", path: "/api/webhook-bap-config" },
|
||||
// Clés de l'API externe (ExternalApiSettings.tsx)
|
||||
{ module: "external-api", path: "/api/external-api/keys" },
|
||||
];
|
||||
|
||||
// Durée de vie des entrées du cache : relative à l'heure du passage, différente
|
||||
// entre les deux serveurs (vérifiée par les tests unitaires de Rust)
|
||||
const CACHE_IGNORE = ["expires_at"];
|
||||
|
||||
export const writes: WriteScenario[] = [
|
||||
// ---------- Commentaires ----------
|
||||
{
|
||||
module: "reconciliation",
|
||||
name: "commentaires : ajouter, modifier, supprimer",
|
||||
steps: [
|
||||
{ method: "POST", path: "/api/deliveries/2/reconciliation-comments", body: { content: "Relance faite", type: "warning" } },
|
||||
{ method: "PUT", path: "/api/reconciliation-comments/{{0.id}}", body: { content: "Relance faite (modifié)", type: "success" } },
|
||||
{ method: "GET", path: "/api/deliveries/2/reconciliation-comments" },
|
||||
{ method: "DELETE", path: "/api/reconciliation-comments/{{0.id}}" },
|
||||
{ method: "POST", path: "/api/deliveries/6/reconciliation-comments", body: { content: "Commentaire à Metz" } },
|
||||
{ method: "PUT", path: "/api/reconciliation-comments/1", body: { content: "Modifié par un autre compte" } },
|
||||
{ method: "DELETE", path: "/api/reconciliation-comments/2" },
|
||||
{ method: "GET", path: "/api/deliveries/4/reconciliation-comments" },
|
||||
],
|
||||
tables: ["reconciliation_comments"],
|
||||
},
|
||||
{
|
||||
module: "reconciliation",
|
||||
name: "commentaires : corps invalides et identifiants inconnus",
|
||||
steps: [
|
||||
{ method: "POST", path: "/api/deliveries/2/reconciliation-comments", body: {} },
|
||||
{ method: "POST", path: "/api/deliveries/2/reconciliation-comments", body: { content: "x", type: "bizarre" } },
|
||||
{ method: "POST", path: "/api/deliveries/2/reconciliation-comments", body: { content: 5 } },
|
||||
{ method: "POST", path: "/api/deliveries/2/reconciliation-comments", body: { content: "Forcé", deliveryId: 6, groupId: 2, authorId: "admin_local" } },
|
||||
{ method: "POST", path: "/api/deliveries/999/reconciliation-comments", body: { content: "x" } },
|
||||
{ method: "POST", path: "/api/deliveries/abc/reconciliation-comments", body: { content: "x" } },
|
||||
{ method: "PUT", path: "/api/reconciliation-comments/1", body: { type: "inconnu" } },
|
||||
{ method: "PUT", path: "/api/reconciliation-comments/1", body: { deliveryId: 4, authorId: "admin_local" } },
|
||||
{ method: "PUT", path: "/api/reconciliation-comments/2", body: { deliveryId: 99999 } },
|
||||
{ method: "PUT", path: "/api/reconciliation-comments/999", body: {} },
|
||||
{ method: "PUT", path: "/api/reconciliation-comments/abc", body: {} },
|
||||
{ method: "DELETE", path: "/api/reconciliation-comments/999" },
|
||||
{ method: "DELETE", path: "/api/reconciliation-comments/abc" },
|
||||
],
|
||||
tables: ["reconciliation_comments"],
|
||||
},
|
||||
|
||||
// ---------- Courriel au fournisseur ----------
|
||||
{
|
||||
module: "reconciliation",
|
||||
name: "courriel au fournisseur : envoi et refus",
|
||||
steps: [
|
||||
{ method: "POST", path: "/api/deliveries/2/send-supplier-mail" },
|
||||
{ method: "POST", path: "/api/deliveries/4/send-supplier-mail" },
|
||||
{ method: "POST", path: "/api/deliveries/6/send-supplier-mail" },
|
||||
{ method: "POST", path: "/api/deliveries/7/send-supplier-mail" },
|
||||
{ method: "POST", path: "/api/deliveries/999/send-supplier-mail" },
|
||||
{ method: "POST", path: "/api/deliveries/abc/send-supplier-mail" },
|
||||
{ method: "GET", path: "/api/supplier-mail-logs?deliveryId=2" },
|
||||
],
|
||||
tables: ["supplier_mail_logs"],
|
||||
ignore: ["messageId", "message_id"],
|
||||
},
|
||||
{
|
||||
module: "reconciliation",
|
||||
name: "courriel au fournisseur : SMTP incomplet ou injoignable",
|
||||
steps: [
|
||||
// PUT /api/groups/:id reste servi par Node (lot 5) : admin et manager
|
||||
{ method: "PUT", path: "/api/groups/2", body: { smtpEnabled: true } },
|
||||
{ method: "POST", path: "/api/deliveries/6/send-supplier-mail" },
|
||||
// Metz : sans logo, sans nom d'expéditeur ni authentification
|
||||
{ method: "PUT", path: "/api/groups/2", body: { smtpHost: " 127.0.0.1 ", smtpPort: STUB_SMTP_PORT, smtpSenderEmail: "metz@banc.test", smtpUser: null } },
|
||||
{ method: "POST", path: "/api/deliveries/6/send-supplier-mail" },
|
||||
// Nancy : serveur injoignable
|
||||
{ method: "PUT", path: "/api/groups/1", body: { smtpPort: 1 } },
|
||||
{ method: "POST", path: "/api/deliveries/2/send-supplier-mail" },
|
||||
{ method: "GET", path: "/api/supplier-mail-logs?" },
|
||||
{ method: "GET", path: "/api/supplier-mail-logs?deliveryId=6&storeId=2" },
|
||||
],
|
||||
tables: ["supplier_mail_logs"],
|
||||
ignore: ["messageId", "message_id"],
|
||||
},
|
||||
|
||||
// ---------- Vérification des factures ----------
|
||||
{
|
||||
module: "reconciliation",
|
||||
name: "vérifier la facture d'une livraison",
|
||||
steps: [
|
||||
{ method: "POST", path: "/api/deliveries/4/verify-invoice", body: { invoiceReference: "FAC-2004", forceRefresh: false } },
|
||||
{ method: "POST", path: "/api/deliveries/4/verify-invoice", body: { invoiceReference: "FAC-2004", forceRefresh: true } },
|
||||
{ method: "POST", path: "/api/deliveries/2/verify-invoice", body: { blNumber: "BL-1002", forceRefresh: false } },
|
||||
{ method: "POST", path: "/api/deliveries/2/verify-invoice", body: { blNumber: "BL-1002" } },
|
||||
{ method: "POST", path: "/api/deliveries/2/verify-invoice", body: { blNumber: "BL-1004" } },
|
||||
{ method: "POST", path: "/api/deliveries/2/verify-invoice", body: { invoiceReference: "FAC-9999", blNumber: "BL-1002" } },
|
||||
{ method: "POST", path: "/api/deliveries/3/verify-invoice", body: { invoiceReference: " BL-1001 " } },
|
||||
{ method: "POST", path: "/api/deliveries/1/verify-invoice", body: { invoiceReference: "FAC-2001" } },
|
||||
{ method: "POST", path: "/api/deliveries/6/verify-invoice", body: { invoiceReference: "FAC-2006", forceRefresh: true } },
|
||||
{ method: "POST", path: "/api/deliveries/7/verify-invoice", body: { invoiceReference: "FAC-3000" } },
|
||||
{ method: "POST", path: "/api/deliveries/8/verify-invoice", body: { invoiceReference: "AV-3001", forceRefresh: true } },
|
||||
],
|
||||
tables: ["deliveries", "invoice_verification_cache"],
|
||||
ignore: CACHE_IGNORE,
|
||||
},
|
||||
{
|
||||
module: "reconciliation",
|
||||
name: "vérifier la facture d'une livraison : corps invalides",
|
||||
steps: [
|
||||
{ method: "POST", path: "/api/deliveries/4/verify-invoice", body: {} },
|
||||
{ method: "POST", path: "/api/deliveries/4/verify-invoice", body: { invoiceReference: " ", blNumber: "" } },
|
||||
{ method: "POST", path: "/api/deliveries/4/verify-invoice", body: { invoiceReference: 12 } },
|
||||
{ method: "POST", path: "/api/deliveries/4/verify-invoice", body: { invoiceReference: " ", blNumber: true } },
|
||||
{ method: "POST", path: "/api/deliveries/999/verify-invoice", body: { invoiceReference: "FAC-2004" } },
|
||||
{ method: "POST", path: "/api/deliveries/abc/verify-invoice", body: { invoiceReference: "FAC-2004" } },
|
||||
],
|
||||
tables: ["deliveries", "invoice_verification_cache"],
|
||||
ignore: CACHE_IGNORE,
|
||||
},
|
||||
{
|
||||
module: "reconciliation",
|
||||
name: "vérifier une facture : NocoDB injoignable, en erreur ou désactivé",
|
||||
steps: [
|
||||
{ method: "PUT", path: "/api/nocodb-config/1", body: { baseUrl: "http://127.0.0.1:1" } },
|
||||
{ method: "POST", path: "/api/deliveries/4/verify-invoice", body: { invoiceReference: "FAC-2004", forceRefresh: true } },
|
||||
{ method: "PUT", path: "/api/nocodb-config/1", body: { baseUrl: `${STUB_URL}/autre` } },
|
||||
{ method: "POST", path: "/api/deliveries/2/verify-invoice", body: { invoiceReference: "FAC-2002", forceRefresh: true } },
|
||||
{ method: "POST", path: "/api/deliveries/2/verify-invoice", body: { blNumber: "BL-1002", forceRefresh: true } },
|
||||
{ method: "PUT", path: "/api/nocodb-config/1", body: { baseUrl: "pas une adresse" } },
|
||||
{ method: "POST", path: "/api/deliveries/4/verify-invoice", body: { invoiceReference: "FAC-2004", forceRefresh: true } },
|
||||
{ method: "PUT", path: "/api/nocodb-config/1", body: { isActive: false } },
|
||||
{ method: "POST", path: "/api/deliveries/4/verify-invoice", body: { invoiceReference: "FAC-2004", forceRefresh: true } },
|
||||
],
|
||||
tables: ["invoice_verification_cache", "nocodb_config"],
|
||||
ignore: CACHE_IGNORE,
|
||||
},
|
||||
{
|
||||
module: "reconciliation",
|
||||
name: "vérifier des factures (adresses sans magasin contrôlé)",
|
||||
steps: [
|
||||
{ method: "POST", path: "/api/verify-invoice", body: { groupId: 1, invoiceReference: "FAC-2002" } },
|
||||
{ method: "POST", path: "/api/verify-invoice", body: { groupId: "1", invoiceReference: "fac-2002" } },
|
||||
{ method: "POST", path: "/api/verify-invoice", body: { groupId: 2, invoiceReference: "FAC-2001" } },
|
||||
{ method: "POST", path: "/api/verify-invoice", body: { groupId: 3, invoiceReference: "FAC-2001" } },
|
||||
{ method: "POST", path: "/api/verify-invoice", body: { groupId: 99, invoiceReference: "FAC-2001" } },
|
||||
{ method: "POST", path: "/api/verify-invoice", body: { groupId: "abc", invoiceReference: "FAC-2001" } },
|
||||
{ method: "POST", path: "/api/verify-invoice", body: { groupId: 1, invoiceReference: 5 } },
|
||||
{ method: "POST", path: "/api/verify-invoice", body: { groupId: 1, invoiceReference: " " } },
|
||||
{ method: "POST", path: "/api/verify-invoice", body: { groupId: 1 } },
|
||||
{ method: "POST", path: "/api/verify-invoices", body: { invoiceReferences: [{ groupId: 1, invoiceReference: "fac-2001", deliveryId: 10, supplierName: "lactalis" }, { groupId: 3, invoiceReference: "X", deliveryId: 2 }] } },
|
||||
{ method: "POST", path: "/api/verify-invoices", body: { invoiceReferences: [{ groupId: 1, invoiceReference: "FAC-2001", deliveryId: 1, supplierName: "Bonduelle" }] } },
|
||||
{ method: "POST", path: "/api/verify-invoices", body: { invoiceReferences: [{ groupId: 1, deliveryId: 3 }] } },
|
||||
// Une seule clé non numérique par appel : Node les range dans l'ordre où ses
|
||||
// vérifications parallèles se terminent
|
||||
{ method: "POST", path: "/api/verify-invoices", body: { invoiceReferences: [{ deliveryId: "a" }, { groupId: 99, deliveryId: 5 }] } },
|
||||
{ method: "POST", path: "/api/verify-invoices", body: { invoiceReferences: [7] } },
|
||||
{ method: "POST", path: "/api/verify-invoices", body: { invoiceReferences: [{ groupId: "abc", deliveryId: 4 }] } },
|
||||
{ method: "POST", path: "/api/verify-invoices", body: { invoiceReferences: [null] } },
|
||||
{ method: "POST", path: "/api/verify-invoices", body: { invoiceReferences: "FAC-2001" } },
|
||||
],
|
||||
tables: ["invoice_verification_cache"],
|
||||
ignore: CACHE_IGNORE,
|
||||
},
|
||||
|
||||
// ---------- Cache des factures ----------
|
||||
{
|
||||
module: "reconciliation",
|
||||
name: "cache : marquer rapproché, mise à jour en lot, diagnostic",
|
||||
steps: [
|
||||
{ method: "POST", path: "/api/cache/mark-reconciled", body: { invoiceReference: " FAC-2004 ", groupId: 1 } },
|
||||
{ method: "POST", path: "/api/cache/mark-reconciled", body: { invoiceReference: "FAC-1900", groupId: "1" } },
|
||||
{ method: "POST", path: "/api/cache/mark-reconciled", body: { invoiceReference: 5, groupId: 1 } },
|
||||
{ method: "POST", path: "/api/cache/mark-reconciled", body: { invoiceReference: "FAC-2004" } },
|
||||
{ method: "POST", path: "/api/cache/update-reconciled" },
|
||||
{ method: "GET", path: "/api/cache/diagnosis" },
|
||||
],
|
||||
tables: ["invoice_verification_cache"],
|
||||
},
|
||||
|
||||
// ---------- Configuration NocoDB ----------
|
||||
{
|
||||
module: "nocodb-config",
|
||||
name: "configurations NocoDB : créer, modifier, supprimer",
|
||||
steps: [
|
||||
{ method: "POST", path: "/api/nocodb-config", body: { name: "NocoDB secours", baseUrl: `${STUB_URL}/nocodb`, projectId: "psecours", apiToken: "jeton-neuf", description: "Seconde base", isActive: false, createdBy: "pirate" } },
|
||||
{ method: "PUT", path: "/api/nocodb-config/{{0.id}}", body: { name: "NocoDB renommée", apiToken: "jeton-modifié" } },
|
||||
{ method: "PUT", path: "/api/nocodb-config/{{0.id}}", body: {} },
|
||||
{ method: "GET", path: "/api/nocodb-config" },
|
||||
{ method: "GET", path: "/api/nocodb-config/active" },
|
||||
{ method: "DELETE", path: "/api/nocodb-config/{{0.id}}" },
|
||||
{ method: "DELETE", path: "/api/nocodb-config/999" },
|
||||
{ method: "PUT", path: "/api/nocodb-config/999", body: { name: "Absente" } },
|
||||
{ method: "POST", path: "/api/nocodb-config", body: { name: "Sans jeton", baseUrl: "http://x.test", projectId: "p", apiToken: "" } },
|
||||
],
|
||||
tables: ["nocodb_config"],
|
||||
ignore: ["api_token"],
|
||||
},
|
||||
{
|
||||
module: "nocodb-config",
|
||||
name: "configurations NocoDB : corps invalides",
|
||||
steps: [
|
||||
{ method: "POST", path: "/api/nocodb-config", body: {} },
|
||||
{ method: "POST", path: "/api/nocodb-config", body: { name: "x", baseUrl: 1, projectId: "p", apiToken: "t" } },
|
||||
{ method: "PUT", path: "/api/nocodb-config/1", body: { isActive: "oui" } },
|
||||
{ method: "PUT", path: "/api/nocodb-config/abc", body: {} },
|
||||
{ method: "DELETE", path: "/api/nocodb-config/abc" },
|
||||
],
|
||||
tables: ["nocodb_config"],
|
||||
ignore: ["api_token"],
|
||||
},
|
||||
|
||||
// ---------- BAP ----------
|
||||
{
|
||||
module: "bap",
|
||||
name: "webhook BAP : configuration",
|
||||
steps: [
|
||||
{ method: "POST", path: "/api/webhook-bap-config", body: { name: "BAP banc", webhookUrl: `${STUB_URL}/webhook/bap-nouveau`, description: "Modifiée", isActive: true, id: 7 } },
|
||||
{ method: "GET", path: "/api/webhook-bap-config" },
|
||||
{ method: "POST", path: "/api/webhook-bap-config", body: { webhookUrl: 12 } },
|
||||
{ method: "POST", path: "/api/webhook-bap-config", body: {} },
|
||||
{ method: "POST", path: "/api/webhook-bap-config", body: { name: "x".repeat(101), webhookUrl: "u", isActive: "oui" } },
|
||||
],
|
||||
tables: ["webhook_bap_config"],
|
||||
},
|
||||
{
|
||||
module: "bap",
|
||||
name: "webhook BAP : test de connexion",
|
||||
steps: [
|
||||
{ method: "POST", path: "/api/webhook-bap-config/test", body: { webhookUrl: `${STUB_URL}/webhook/bap` } },
|
||||
{ method: "POST", path: "/api/webhook-bap-config/test", body: { webhookUrl: `${STUB_URL}/autre` } },
|
||||
{ method: "POST", path: "/api/webhook-bap-config/test", body: {} },
|
||||
{ method: "POST", path: "/api/webhook-bap-config/test", body: { webhookUrl: "pas-une-url" } },
|
||||
{ method: "POST", path: "/api/webhook-bap-config/test", body: { webhookUrl: "http://127.0.0.1:1" } },
|
||||
{ method: "POST", path: "/api/webhook-bap-config/test", body: { webhookUrl: "ftp://x.test/a" } },
|
||||
],
|
||||
tables: [],
|
||||
},
|
||||
{
|
||||
module: "bap",
|
||||
name: "envoi d'un BAP au webhook",
|
||||
steps: [
|
||||
{ method: "POST", path: "/api/bap/send-webhook", body: { pdfBase64: "JVBERi0xLjQKJcOkw7zDtsOf", fileName: "Facture-123.PDF", recipient: "Laurie" } },
|
||||
{ method: "POST", path: "/api/bap/send-webhook", body: { pdfBase64: "JVBERi0xLjQK", fileName: "a.pdf", recipient: "Paul" } },
|
||||
{ method: "POST", path: "/api/bap/send-webhook", body: { pdfBase64: "JVBERi0xLjQK", fileName: "a.txt", recipient: "Jeremy" } },
|
||||
{ method: "POST", path: "/api/bap/send-webhook", body: { pdfBase64: 5, fileName: "a.pdf", recipient: "Jeremy" } },
|
||||
{ method: "POST", path: "/api/bap/send-webhook", body: { pdfBase64: [1, 2], fileName: "a.pdf", recipient: "Jeremy" } },
|
||||
{ method: "POST", path: "/api/bap/send-webhook", body: { pdfBase64: "x", fileName: 5, recipient: "Laurie" } },
|
||||
{ method: "POST", path: "/api/bap/send-webhook", body: { fileName: "a.pdf" } },
|
||||
{ method: "POST", path: "/api/webhook-bap-config", body: { webhookUrl: `${STUB_URL}/autre` } },
|
||||
{ method: "POST", path: "/api/bap/send-webhook", body: { pdfBase64: "JVBERi0xLjQK", fileName: "b.pdf", recipient: "Jeremy" } },
|
||||
{ method: "POST", path: "/api/webhook-bap-config", body: { webhookUrl: `${STUB_URL}/webhook/bap`, isActive: false } },
|
||||
{ method: "POST", path: "/api/bap/send-webhook", body: { pdfBase64: "JVBERi0xLjQK", fileName: "c.pdf", recipient: "Laurie" } },
|
||||
],
|
||||
tables: ["webhook_bap_config"],
|
||||
},
|
||||
|
||||
// ---------- Clés de l'API externe ----------
|
||||
{
|
||||
module: "external-api",
|
||||
name: "clés d'API : créer, révoquer",
|
||||
steps: [
|
||||
{ method: "POST", path: "/api/external-api/keys", body: { name: " Nouvel outil " } },
|
||||
{ method: "DELETE", path: "/api/external-api/keys/{{0.apiKey.id}}" },
|
||||
{ method: "DELETE", path: "/api/external-api/keys/{{0.apiKey.id}}" },
|
||||
{ method: "DELETE", path: "/api/external-api/keys/2" },
|
||||
{ method: "DELETE", path: "/api/external-api/keys/1" },
|
||||
{ method: "DELETE", path: "/api/external-api/keys/1e2" },
|
||||
{ method: "GET", path: "/api/external-api/keys" },
|
||||
],
|
||||
tables: ["external_api_keys"],
|
||||
ignore: ["key", "keyPrefix", "key_prefix", "key_hash"],
|
||||
},
|
||||
{
|
||||
module: "external-api",
|
||||
name: "clés d'API : nom ou identifiant invalide",
|
||||
steps: [
|
||||
{ method: "POST", path: "/api/external-api/keys", body: {} },
|
||||
{ method: "POST", path: "/api/external-api/keys", body: { name: " " } },
|
||||
{ method: "POST", path: "/api/external-api/keys", body: { name: 5 } },
|
||||
{ method: "POST", path: "/api/external-api/keys", body: { name: "x".repeat(101) } },
|
||||
{ method: "POST", path: "/api/external-api/keys", body: [] },
|
||||
{ method: "DELETE", path: "/api/external-api/keys/abc" },
|
||||
{ method: "DELETE", path: "/api/external-api/keys/0" },
|
||||
{ method: "DELETE", path: "/api/external-api/keys/1.5" },
|
||||
{ method: "DELETE", path: "/api/external-api/keys/99999999999" },
|
||||
],
|
||||
tables: ["external_api_keys"],
|
||||
},
|
||||
];
|
||||
@@ -0,0 +1,24 @@
|
||||
// Toutes les requêtes du banc, une liste par lot (docs/PLAN-MIGRATION-RUST.md)
|
||||
import type { ReadCase, WriteScenario } from "../types";
|
||||
import * as socle from "./socle";
|
||||
import * as referentiel from "./referentiel";
|
||||
import * as informations from "./informations";
|
||||
import * as quotidien from "./quotidien";
|
||||
import * as fournisseursServices from "./fournisseurs-services";
|
||||
import * as fournisseursCommandes from "./fournisseurs-commandes";
|
||||
import * as fournisseursRapprochement from "./fournisseurs-rapprochement";
|
||||
import * as administration from "./administration";
|
||||
|
||||
const modules = [
|
||||
socle,
|
||||
referentiel,
|
||||
informations,
|
||||
quotidien,
|
||||
fournisseursServices,
|
||||
fournisseursCommandes,
|
||||
fournisseursRapprochement,
|
||||
administration,
|
||||
];
|
||||
|
||||
export const READS: ReadCase[] = modules.flatMap((m) => m.reads);
|
||||
export const WRITES: WriteScenario[] = modules.flatMap((m) => m.writes);
|
||||
@@ -0,0 +1,251 @@
|
||||
// Lot 2 : Informations (annonces), Publicités, Météo, Statistiques et
|
||||
// Statistiques détaillées. Paramètres repris des appels de client/src
|
||||
// (AnnouncementCard, Calendar, Publicities, Dashboard, StatsPanel, Groups,
|
||||
// Suppliers, Analytics, WeatherWidget, WeatherSettings).
|
||||
// Les appels réels à Visual Crossing et Nominatim ne sont jamais déclenchés :
|
||||
// la météo du jour vient du cache (seeds/lot2.ts), et les cas de test de
|
||||
// connexion et de géolocalisation s'arrêtent avant l'appel externe.
|
||||
import type { ReadCase, WriteScenario } from "../types";
|
||||
|
||||
const YEAR = new Date().getUTCFullYear();
|
||||
const PERIOD = "startDate={{today-30}}T00:00:00.000Z&endDate={{today}}T23:59:59.999Z";
|
||||
const YEAR_PERIOD = "startDate={{year}}-01-01T00:00:00.000Z&endDate={{year}}-12-31T23:59:59.999Z";
|
||||
|
||||
const announcementReads: ReadCase[] = [
|
||||
{ module: "announcements", path: "/api/announcements" },
|
||||
{ module: "announcements", path: "/api/announcements?storeId=1" },
|
||||
{ module: "announcements", path: "/api/announcements?storeId=3" },
|
||||
// Magasin illisible : erreur SQL, repli de Node sur toutes les informations
|
||||
{ module: "announcements", path: "/api/announcements?storeId=abc" },
|
||||
];
|
||||
|
||||
const publicityReads: ReadCase[] = [
|
||||
{ module: "publicities", path: "/api/ad-campaigns" },
|
||||
{ module: "publicities", path: "/api/ad-campaigns?year={{year}}" },
|
||||
{ module: "publicities", path: "/api/ad-campaigns?year={{year}}&storeId=1" },
|
||||
{ module: "publicities", path: "/api/ad-campaigns?year={{year}}&storeId=2" },
|
||||
{ module: "publicities", path: "/api/ad-campaigns?year={{year-1}}" },
|
||||
{ module: "publicities", path: "/api/ad-campaigns?year={{year+1}}" },
|
||||
{ module: "publicities", path: "/api/ad-campaigns?storeId=3" },
|
||||
{ module: "publicities", path: "/api/ad-campaigns?year=abc" },
|
||||
{ module: "publicities", path: "/api/ad-campaigns?year=99999999999" },
|
||||
{ module: "publicities", path: "/api/ad-campaigns/1" },
|
||||
{ module: "publicities", path: "/api/ad-campaigns/3" },
|
||||
{ module: "publicities", path: "/api/ad-campaigns/200" },
|
||||
{ module: "publicities", path: "/api/ad-campaigns/202" },
|
||||
{ module: "publicities", path: "/api/ad-campaigns/999" },
|
||||
{ module: "publicities", path: "/api/ad-campaigns/abc" },
|
||||
{ module: "publicities", path: "/api/ad-campaigns/debug" },
|
||||
{ module: "publicities", path: "/api/ad-campaigns/debug?year={{year}}&storeId=1" },
|
||||
{ module: "publicities", path: "/api/ad-campaigns/debug?year=99999999999" },
|
||||
];
|
||||
|
||||
const weatherReads: ReadCase[] = [
|
||||
{ module: "weather", path: "/api/weather/settings" },
|
||||
{ module: "weather", path: "/api/weather/current" },
|
||||
];
|
||||
|
||||
const statsReads: ReadCase[] = [
|
||||
{ module: "stats", path: "/api/stats/by-group" },
|
||||
{ module: "stats", path: "/api/stats/by-group?storeId=1" },
|
||||
{ module: "stats", path: "/api/stats/by-group?storeId=2" },
|
||||
{ module: "stats", path: "/api/stats/by-group?storeId=abc" },
|
||||
{ module: "stats", path: "/api/stats/by-supplier" },
|
||||
{ module: "stats", path: "/api/stats/by-supplier?storeId=1" },
|
||||
{ module: "stats", path: "/api/stats/by-supplier?storeId=3" },
|
||||
{ module: "stats", path: "/api/stats/by-supplier?storeId=abc" },
|
||||
{ module: "stats", path: "/api/stats/monthly" },
|
||||
{ module: "stats", path: "/api/stats/monthly?year={{year}}&month={{month}}" },
|
||||
{ module: "stats", path: "/api/stats/monthly?year={{year}}&month={{month}}&storeId=1" },
|
||||
{ module: "stats", path: "/api/stats/monthly?year={{year}}&month={{month}}&storeId=2" },
|
||||
{ module: "stats", path: "/api/stats/monthly?year={{year}}&month={{month}}&storeId=abc" },
|
||||
{ module: "stats", path: "/api/stats/monthly?year={{year-1}}&month={{month}}" },
|
||||
{ module: "stats", path: "/api/stats/monthly?year={{year}}&month=12" },
|
||||
{ module: "stats", path: "/api/stats/monthly?year={{year}}&month=13" },
|
||||
{ module: "stats", path: "/api/stats/monthly?year=abc&month=3" },
|
||||
{ module: "stats", path: "/api/stats/yearly" },
|
||||
{ module: "stats", path: "/api/stats/yearly?year={{year}}" },
|
||||
{ module: "stats", path: "/api/stats/yearly?year={{year}}&storeId=1" },
|
||||
{ module: "stats", path: "/api/stats/yearly?year={{year}}&storeId=3" },
|
||||
{ module: "stats", path: "/api/stats/yearly?year={{year-1}}" },
|
||||
{ module: "stats", path: "/api/stats/yearly?year=abc" },
|
||||
];
|
||||
|
||||
const analyticsReads: ReadCase[] = [
|
||||
{ module: "analytics", path: "/api/analytics/summary" },
|
||||
{ module: "analytics", path: `/api/analytics/summary?${PERIOD}` },
|
||||
{ module: "analytics", path: `/api/analytics/summary?${YEAR_PERIOD}&supplierIds=1,2&groupIds=1` },
|
||||
{ module: "analytics", path: `/api/analytics/summary?${PERIOD}&groupIds=2` },
|
||||
{ module: "analytics", path: `/api/analytics/summary?${PERIOD}&status=delivered` },
|
||||
{ module: "analytics", path: "/api/analytics/summary?startDate={{today+500}}T00:00:00.000Z" },
|
||||
{ module: "analytics", path: "/api/analytics/summary?supplierIds=abc" },
|
||||
{ module: "analytics", path: "/api/analytics/summary?groupIds=1.5" },
|
||||
{ module: "analytics", path: "/api/analytics/summary?startDate=pas-une-date" },
|
||||
{ module: "analytics", path: `/api/analytics/timeseries?${PERIOD}&granularity=day` },
|
||||
{ module: "analytics", path: `/api/analytics/timeseries?${YEAR_PERIOD}&granularity=week` },
|
||||
{ module: "analytics", path: `/api/analytics/timeseries?${YEAR_PERIOD}&granularity=month&groupIds=1,2` },
|
||||
{ module: "analytics", path: "/api/analytics/timeseries" },
|
||||
{ module: "analytics", path: "/api/analytics/timeseries?granularity=autre&supplierIds=1,4" },
|
||||
{ module: "analytics", path: "/api/analytics/timeseries?supplierIds=1.5,2" },
|
||||
{ module: "analytics", path: "/api/analytics/timeseries?supplierIds=abc" },
|
||||
{ module: "analytics", path: "/api/analytics/timeseries?endDate=pas-une-date" },
|
||||
{ module: "analytics", path: "/api/analytics/by-supplier" },
|
||||
{ module: "analytics", path: `/api/analytics/by-supplier?${PERIOD}` },
|
||||
{ module: "analytics", path: `/api/analytics/by-supplier?${YEAR_PERIOD}&groupIds=1,3` },
|
||||
{ module: "analytics", path: "/api/analytics/by-supplier?groupIds=abc" },
|
||||
{ module: "analytics", path: "/api/analytics/by-store" },
|
||||
{ module: "analytics", path: `/api/analytics/by-store?${PERIOD}&supplierIds=1,2` },
|
||||
{ module: "analytics", path: "/api/analytics/by-store?supplierIds=abc" },
|
||||
{ module: "analytics", path: "/api/analytics/by-store?startDate=pas-une-date" },
|
||||
{ module: "analytics", path: `/api/analytics/export?${PERIOD}&type=summary` },
|
||||
{ module: "analytics", path: "/api/analytics/export" },
|
||||
{ module: "analytics", path: `/api/analytics/export?${YEAR_PERIOD}&type=timeseries` },
|
||||
{ module: "analytics", path: `/api/analytics/export?${PERIOD}&groupIds=1&type=suppliers` },
|
||||
{ module: "analytics", path: "/api/analytics/export?type=stores&supplierIds=2" },
|
||||
{ module: "analytics", path: "/api/analytics/export?type=inconnu" },
|
||||
{ module: "analytics", path: "/api/analytics/export?type=timeseries&startDate={{today+500}}T00:00:00.000Z" },
|
||||
{ module: "analytics", path: "/api/analytics/export?type=stores&supplierIds=abc" },
|
||||
{ module: "analytics", path: "/api/analytics/export?type=suppliers&groupIds=abc" },
|
||||
{ module: "analytics", path: "/api/analytics/export?type=r%C3%A9sum%C3%A9" },
|
||||
{ module: "analytics", path: "/api/analytics/export?type=%E2%82%AC" },
|
||||
];
|
||||
|
||||
export const reads: ReadCase[] = [
|
||||
...announcementReads,
|
||||
...publicityReads,
|
||||
...weatherReads,
|
||||
...statsReads,
|
||||
...analyticsReads,
|
||||
];
|
||||
|
||||
const announcementWrites: WriteScenario[] = [
|
||||
{
|
||||
module: "announcements",
|
||||
name: "créer, modifier, supprimer une information",
|
||||
steps: [
|
||||
{ method: "POST", path: "/api/announcements", body: { title: "Inventaire tournant", content: "Rayon frais lundi", type: "warning" } },
|
||||
{ method: "POST", path: "/api/announcements", body: { title: "Livraison décalée", content: "Camion attendu à 14 h", storeId: 1 } },
|
||||
{ method: "POST", path: "/api/announcements", body: { title: "Autre magasin", content: "Pour Metz", type: "", storeId: 2, createdBy: "pirate" } },
|
||||
{ method: "PUT", path: "/api/announcements/{{0.id}}", body: { title: "Inventaire reporté", type: "error", storeId: null } },
|
||||
{ method: "PUT", path: "/api/announcements/202", body: { content: "Réunion déplacée", storeId: 3 } },
|
||||
{ method: "DELETE", path: "/api/announcements/{{1.id}}" },
|
||||
{ method: "DELETE", path: "/api/announcements/3" },
|
||||
{ method: "GET", path: "/api/announcements" },
|
||||
],
|
||||
tables: ["dashboard_messages"],
|
||||
},
|
||||
{
|
||||
module: "announcements",
|
||||
name: "information invalide ou introuvable",
|
||||
steps: [
|
||||
{ method: "POST", path: "/api/announcements", body: {} },
|
||||
{ method: "POST", path: "/api/announcements", body: { title: "x".repeat(300), content: 5 } },
|
||||
{ method: "POST", path: "/api/announcements", body: { title: "Type inconnu", content: "Refusé par la base", type: "urgent" } },
|
||||
{ method: "POST", path: "/api/announcements", body: { title: "Magasin inconnu", content: "Clé étrangère", storeId: 999 } },
|
||||
{ method: "POST", path: "/api/announcements", body: { title: "Magasin en texte", content: "Refusé", storeId: "1" } },
|
||||
{ method: "PUT", path: "/api/announcements/999", body: { title: "Introuvable" } },
|
||||
{ method: "PUT", path: "/api/announcements/abc", body: { title: "Illisible" } },
|
||||
{ method: "PUT", path: "/api/announcements/1", body: {} },
|
||||
{ method: "PUT", path: "/api/announcements/1", body: { type: 5, storeId: "2" } },
|
||||
{ method: "PUT", path: "/api/announcements/1", body: { type: "urgent" } },
|
||||
{ method: "DELETE", path: "/api/announcements/999" },
|
||||
{ method: "DELETE", path: "/api/announcements/abc" },
|
||||
],
|
||||
tables: ["dashboard_messages"],
|
||||
},
|
||||
];
|
||||
|
||||
const publicityWrites: WriteScenario[] = [
|
||||
{
|
||||
module: "publicities",
|
||||
name: "créer, modifier, supprimer une publicité",
|
||||
steps: [
|
||||
{
|
||||
method: "POST", path: "/api/ad-campaigns",
|
||||
body: { pubNumber: "PUB-50", designation: "Fête des jardins", startDate: "{{today+40}}", endDate: "{{today+50}}", year: YEAR, participatingGroups: [1, 2] },
|
||||
},
|
||||
{ method: "PUT", path: "/api/ad-campaigns/{{0.id}}", body: { designation: "Fête des jardins (prolongée)", endDate: "{{today+55}}", participatingGroups: [3] } },
|
||||
{ method: "PUT", path: "/api/ad-campaigns/1", body: { pubNumber: "PUB-01", year: String(YEAR), participatingGroups: [] } },
|
||||
{ method: "PUT", path: "/api/ad-campaigns/2", body: { designation: ["Spécial", "jardin"], createdBy: "bench_manager", inconnu: 1 } },
|
||||
{ method: "PUT", path: "/api/ad-campaigns/202", body: { participatingGroups: 5 } },
|
||||
{ method: "GET", path: "/api/ad-campaigns/{{0.id}}" },
|
||||
{ method: "POST", path: "/api/ad-campaigns/{{0.id}}/delete", body: {} },
|
||||
{ method: "DELETE", path: "/api/ad-campaigns/3" },
|
||||
{ method: "GET", path: "/api/ad-campaigns?year={{year}}" },
|
||||
],
|
||||
tables: ["publicities", "publicity_participations"],
|
||||
},
|
||||
{
|
||||
module: "publicities",
|
||||
name: "publicité invalide, en double ou introuvable",
|
||||
steps: [
|
||||
{ method: "POST", path: "/api/ad-campaigns", body: {} },
|
||||
{ method: "POST", path: "/api/ad-campaigns", body: { pubNumber: "PUB-01", designation: "Doublon", startDate: "{{today}}", endDate: "{{today+7}}", year: YEAR } },
|
||||
{ method: "POST", path: "/api/ad-campaigns", body: { pubNumber: "PUB-60", designation: "Magasin inconnu", startDate: "{{today}}", endDate: "{{today+7}}", year: YEAR, participatingGroups: [1, 999] } },
|
||||
{ method: "POST", path: "/api/ad-campaigns", body: { pubNumber: "PUB-61", designation: "Groupes en texte", startDate: "{{today}}", endDate: "{{today+7}}", year: YEAR, participatingGroups: "12" } },
|
||||
{ method: "POST", path: "/api/ad-campaigns", body: { pubNumber: "PUB-62", designation: "Date illisible", startDate: "bientôt", endDate: "{{today+7}}", year: YEAR } },
|
||||
{ method: "PUT", path: "/api/ad-campaigns/1", body: { startDate: "pas une date" } },
|
||||
{ method: "PUT", path: "/api/ad-campaigns/1", body: { createdAt: "2020-01-01" } },
|
||||
{ method: "PUT", path: "/api/ad-campaigns/1", body: { pubNumber: "PUB-02" } },
|
||||
{ method: "PUT", path: "/api/ad-campaigns/1", body: { year: 2030.5 } },
|
||||
{ method: "PUT", path: "/api/ad-campaigns/1", body: { designation: null } },
|
||||
{ method: "PUT", path: "/api/ad-campaigns/2", body: { participatingGroups: null } },
|
||||
{ method: "PUT", path: "/api/ad-campaigns/200", body: { participatingGroups: [2, 2] } },
|
||||
{ method: "PUT", path: "/api/ad-campaigns/999", body: { designation: "Introuvable" } },
|
||||
{ method: "PUT", path: "/api/ad-campaigns/998", body: { participatingGroups: [1] } },
|
||||
{ method: "PUT", path: "/api/ad-campaigns/abc", body: { designation: "Illisible" } },
|
||||
{ method: "PUT", path: "/api/ad-campaigns/201", body: {} },
|
||||
{ method: "DELETE", path: "/api/ad-campaigns/999" },
|
||||
{ method: "DELETE", path: "/api/ad-campaigns/abc" },
|
||||
{ method: "POST", path: "/api/ad-campaigns/99999999999/delete", body: {} },
|
||||
],
|
||||
tables: ["publicities", "publicity_participations"],
|
||||
},
|
||||
];
|
||||
|
||||
const weatherWrites: WriteScenario[] = [
|
||||
{
|
||||
module: "weather",
|
||||
name: "configurer la météo",
|
||||
steps: [
|
||||
{ method: "PUT", path: "/api/weather/settings/1", body: { location: "Metz, France", isActive: true, apiKey: "cle-meteo-modifiee" } },
|
||||
{ method: "GET", path: "/api/weather/current" },
|
||||
{ method: "POST", path: "/api/weather/settings", body: { apiKey: "seconde-cle-meteo", location: "Épinal, France" } },
|
||||
{ method: "GET", path: "/api/weather/settings" },
|
||||
{ method: "PUT", path: "/api/weather/settings/999", body: { location: "Introuvable" } },
|
||||
{ method: "PUT", path: "/api/weather/settings/1", body: {} },
|
||||
{ method: "PUT", path: "/api/weather/settings/1", body: { isActive: false } },
|
||||
{ method: "GET", path: "/api/weather/current" },
|
||||
],
|
||||
tables: ["weather_settings", "weather_data"],
|
||||
},
|
||||
{
|
||||
module: "weather",
|
||||
name: "configuration météo invalide",
|
||||
steps: [
|
||||
{ method: "POST", path: "/api/weather/settings", body: {} },
|
||||
{ method: "POST", path: "/api/weather/settings", body: { apiKey: 5, isActive: "oui" } },
|
||||
{ method: "PUT", path: "/api/weather/settings/1", body: { isActive: "oui" } },
|
||||
{ method: "PUT", path: "/api/weather/settings/abc", body: { location: "Illisible" } },
|
||||
{ method: "PUT", path: "/api/weather/settings/abc", body: { location: 12 } },
|
||||
],
|
||||
tables: ["weather_settings"],
|
||||
},
|
||||
{
|
||||
// Corps incomplets : refusés avant tout appel à Visual Crossing ou Nominatim
|
||||
module: "weather",
|
||||
name: "test de connexion et géolocalisation incomplets",
|
||||
steps: [
|
||||
{ method: "POST", path: "/api/weather/test-connection", body: {} },
|
||||
{ method: "POST", path: "/api/weather/test-connection", body: { apiKey: "cle-de-test-meteo" } },
|
||||
{ method: "POST", path: "/api/weather/test-connection", body: { apiKey: "", location: "Nancy, France" } },
|
||||
{ method: "POST", path: "/api/weather/geolocation", body: {} },
|
||||
{ method: "POST", path: "/api/weather/geolocation", body: { latitude: 0, longitude: 6.18 } },
|
||||
{ method: "POST", path: "/api/weather/geolocation", body: { latitude: 48.69 } },
|
||||
{ method: "POST", path: "/api/weather/geolocation", body: { latitude: "", longitude: "6.18" } },
|
||||
],
|
||||
tables: ["weather_settings", "weather_data"],
|
||||
},
|
||||
];
|
||||
|
||||
export const writes: WriteScenario[] = [...announcementWrites, ...publicityWrites, ...weatherWrites];
|
||||
@@ -0,0 +1,359 @@
|
||||
// Lot 3 « Au quotidien » : tâches, DLC, commandes clients et SAV.
|
||||
// Lignes du banc par magasin (seed.ts et seeds/lot3.ts) :
|
||||
// Nancy (1) : tâches 1, 2, 3, 301, 302 ; DLC 1 à 5, 301, 302, 303, 305 ;
|
||||
// commandes 1, 2, 3, 301 ; SAV 1, 2, 301
|
||||
// Metz (2) : tâches 4, 5, 303 ; DLC 6, 304 ; commandes 4, 5, 302, 303 ; SAV 3, 302
|
||||
// Épinal (3) : tâche 300 ; DLC 300 ; commande 300 ; SAV 300 (aucun compte du banc sauf l'admin)
|
||||
import type { ReadCase, WriteScenario } from "../types";
|
||||
|
||||
export const reads: ReadCase[] = [
|
||||
// Tâches : sans magasin choisi, un non-admin reçoit une liste vide
|
||||
{ module: "tasks", path: "/api/tasks" },
|
||||
{ module: "tasks", path: "/api/tasks?storeId=1" },
|
||||
{ module: "tasks", path: "/api/tasks?storeId=2" },
|
||||
{ module: "tasks", path: "/api/tasks?storeId=3" },
|
||||
{ module: "tasks", path: "/api/tasks?storeId=abc" },
|
||||
{ module: "tasks", path: "/api/tasks?storeId=99999999999" },
|
||||
{ module: "tasks", path: "/api/tasks?storeId=1.9" },
|
||||
|
||||
// DLC : filtres de DlcPage, de la fenêtre d'alerte et du tableau de bord
|
||||
{ module: "dlc", path: "/api/dlc-products" },
|
||||
{ module: "dlc", path: "/api/dlc-products?storeId=1" },
|
||||
{ module: "dlc", path: "/api/dlc-products?storeId=2" },
|
||||
{ module: "dlc", path: "/api/dlc-products?storeId=3" },
|
||||
{ module: "dlc", path: "/api/dlc-products?storeId=abc" },
|
||||
{ module: "dlc", path: "/api/dlc-products?status=expires" },
|
||||
{ module: "dlc", path: "/api/dlc-products?status=expires&storeId=1" },
|
||||
{ module: "dlc", path: "/api/dlc-products?status=expires_soon" },
|
||||
{ module: "dlc", path: "/api/dlc-products?status=expires_soon&storeId=2" },
|
||||
{ module: "dlc", path: "/api/dlc-products?status=en_cours" },
|
||||
{ module: "dlc", path: "/api/dlc-products?status=valides" },
|
||||
{ module: "dlc", path: "/api/dlc-products?status=all" },
|
||||
{ module: "dlc", path: "/api/dlc-products?status=inconnu" },
|
||||
{ module: "dlc", path: "/api/dlc-products?supplierId=2" },
|
||||
{ module: "dlc", path: "/api/dlc-products?supplierId=all" },
|
||||
{ module: "dlc", path: "/api/dlc-products?supplierId=abc" },
|
||||
{ module: "dlc", path: "/api/dlc-products?supplierId=0" },
|
||||
{ module: "dlc", path: "/api/dlc-products?supplierId=99999999999" },
|
||||
{ module: "dlc", path: "/api/dlc-products?storeId=99999999999" },
|
||||
{ module: "dlc", path: "/api/dlc-products?search=camem" },
|
||||
{ module: "dlc", path: "/api/dlc-products?search=BONDU" },
|
||||
{ module: "dlc", path: "/api/dlc-products?search=%25" },
|
||||
{ module: "dlc", path: "/api/dlc-products?storeId=1&status=expires_soon&supplierId=1&search=e" },
|
||||
{ module: "dlc", path: "/api/dlc-products/stats" },
|
||||
{ module: "dlc", path: "/api/dlc-products/stats?storeId=1" },
|
||||
{ module: "dlc", path: "/api/dlc-products/stats?storeId=2" },
|
||||
{ module: "dlc", path: "/api/dlc-products/stats?storeId=3" },
|
||||
{ module: "dlc", path: "/api/dlc-products/stats?storeId=abc" },
|
||||
{ module: "dlc", path: "/api/dlc-products/1" },
|
||||
{ module: "dlc", path: "/api/dlc-products/6" },
|
||||
{ module: "dlc", path: "/api/dlc-products/300" },
|
||||
{ module: "dlc", path: "/api/dlc-products/303" },
|
||||
{ module: "dlc", path: "/api/dlc-products/9999" },
|
||||
{ module: "dlc", path: "/api/dlc-products/abc" },
|
||||
|
||||
// Commandes clients : la page n'envoie storeId que pour l'admin, les appels à passer pour tous
|
||||
{ module: "customer-orders", path: "/api/customer-orders" },
|
||||
{ module: "customer-orders", path: "/api/customer-orders?storeId=1" },
|
||||
{ module: "customer-orders", path: "/api/customer-orders?storeId=2" },
|
||||
{ module: "customer-orders", path: "/api/customer-orders?storeId=3" },
|
||||
{ module: "customer-orders", path: "/api/customer-orders?storeId=abc" },
|
||||
{ module: "customer-orders", path: "/api/customer-orders?storeId=2.5" },
|
||||
{ module: "customer-orders", path: "/api/customer-orders/pending-calls" },
|
||||
{ module: "customer-orders", path: "/api/customer-orders/pending-calls?storeId=1" },
|
||||
{ module: "customer-orders", path: "/api/customer-orders/pending-calls?storeId=3" },
|
||||
{ module: "customer-orders", path: "/api/customer-orders/pending-calls?storeId=abc" },
|
||||
|
||||
// SAV : filtres de SavTickets (groupId pour l'admin seulement côté interface)
|
||||
{ module: "sav", path: "/api/sav/tickets" },
|
||||
{ module: "sav", path: "/api/sav/tickets?groupId=1" },
|
||||
{ module: "sav", path: "/api/sav/tickets?groupId=3" },
|
||||
{ module: "sav", path: "/api/sav/tickets?groupId=abc" },
|
||||
{ module: "sav", path: "/api/sav/tickets?groupId=0" },
|
||||
{ module: "sav", path: "/api/sav/tickets?groupId=99999999999" },
|
||||
{ module: "sav", path: "/api/sav/tickets?supplierId=99999999999" },
|
||||
{ module: "sav", path: "/api/sav/tickets?endDate={{today}}T23:59:59%2B02:00" },
|
||||
{ module: "sav", path: "/api/sav/tickets?status=nouveau" },
|
||||
{ module: "sav", path: "/api/sav/tickets?priority=critique&groupId=3" },
|
||||
{ module: "sav", path: "/api/sav/tickets?supplierId=4" },
|
||||
{ module: "sav", path: "/api/sav/tickets?supplierId=abc" },
|
||||
{ module: "sav", path: "/api/sav/tickets?status=attente_pieces&priority=normale&supplierId=4&groupId=1" },
|
||||
{ module: "sav", path: "/api/sav/tickets?startDate={{today-10}}&endDate={{today}}" },
|
||||
{ module: "sav", path: "/api/sav/tickets?startDate=pas-une-date" },
|
||||
{ module: "sav", path: "/api/sav/tickets/1" },
|
||||
{ module: "sav", path: "/api/sav/tickets/3" },
|
||||
{ module: "sav", path: "/api/sav/tickets/300" },
|
||||
{ module: "sav", path: "/api/sav/tickets/301" },
|
||||
{ module: "sav", path: "/api/sav/tickets/9999" },
|
||||
{ module: "sav", path: "/api/sav/tickets/abc" },
|
||||
{ module: "sav", path: "/api/sav/stats" },
|
||||
{ module: "sav", path: "/api/sav/stats?groupId=1" },
|
||||
{ module: "sav", path: "/api/sav/stats?groupId=3" },
|
||||
{ module: "sav", path: "/api/sav/stats?groupId=abc" },
|
||||
];
|
||||
|
||||
export const writes: WriteScenario[] = [
|
||||
// ---------- Tâches ----------
|
||||
{
|
||||
module: "tasks",
|
||||
name: "créer une tâche, la modifier, la terminer, la supprimer",
|
||||
steps: [
|
||||
{ method: "POST", path: "/api/tasks", body: { title: "Nouvelle tâche", description: "", priority: "high", status: "pending", assignedTo: "Sophie", startDate: "{{today+1}}", dueDate: "{{today+3}}", groupId: 1, createdBy: "manager" } },
|
||||
{ method: "PUT", path: "/api/tasks/{{0.id}}", body: { title: "Tâche modifiée", priority: "low", assignedTo: "Marc", startDate: "", dueDate: "{{today+5}}" } },
|
||||
{ method: "POST", path: "/api/tasks/{{0.id}}/complete" },
|
||||
{ method: "GET", path: "/api/tasks?storeId=1" },
|
||||
{ method: "DELETE", path: "/api/tasks/{{0.id}}" },
|
||||
],
|
||||
tables: ["tasks"],
|
||||
},
|
||||
{
|
||||
module: "tasks",
|
||||
name: "tâches de son magasin et d'un autre",
|
||||
steps: [
|
||||
{ method: "PUT", path: "/api/tasks/2", body: { status: "completed", description: null, startDate: "{{today}}T14:30", dueDate: null } },
|
||||
{ method: "PUT", path: "/api/tasks/4", body: { title: "Autre magasin" } },
|
||||
{ method: "PUT", path: "/api/tasks/300", body: { title: "Épinal" } },
|
||||
{ method: "POST", path: "/api/tasks/1/complete" },
|
||||
{ method: "POST", path: "/api/tasks/5/complete" },
|
||||
{ method: "POST", path: "/api/tasks/300/complete" },
|
||||
{ method: "DELETE", path: "/api/tasks/3" },
|
||||
{ method: "DELETE", path: "/api/tasks/303" },
|
||||
{ method: "DELETE", path: "/api/tasks/300" },
|
||||
{ method: "PUT", path: "/api/tasks/9999", body: { title: "Absente" } },
|
||||
{ method: "POST", path: "/api/tasks/9999/complete" },
|
||||
{ method: "DELETE", path: "/api/tasks/9999" },
|
||||
{ method: "DELETE", path: "/api/tasks/abc" },
|
||||
{ method: "POST", path: "/api/tasks/abc/complete" },
|
||||
],
|
||||
tables: ["tasks"],
|
||||
},
|
||||
{
|
||||
module: "tasks",
|
||||
name: "créer des tâches : magasin par défaut, autre magasin, corps invalides",
|
||||
steps: [
|
||||
{ method: "POST", path: "/api/tasks", body: { title: "Sans magasin", assignedTo: "Léa" } },
|
||||
{ method: "POST", path: "/api/tasks", body: { title: "Magasin d'Épinal", assignedTo: "Léa", groupId: 3, dueDate: "{{today+2}}T18:00:00.000Z" } },
|
||||
{ method: "POST", path: "/api/tasks", body: { title: "Magasin en texte", assignedTo: "Léa", groupId: "1", startDate: 0 } },
|
||||
{ method: "POST", path: "/api/tasks", body: { title: "Metz", assignedTo: "Léa", groupId: 2, status: "completed", completedBy: "x" } },
|
||||
{ method: "POST", path: "/api/tasks", body: {} },
|
||||
{ method: "POST", path: "/api/tasks", body: { title: "Priorité inconnue", assignedTo: "Léa", groupId: 1, priority: "urgent" } },
|
||||
{ method: "POST", path: "/api/tasks", body: { title: "Échéance invalide", assignedTo: "Léa", groupId: 1, dueDate: "pas une date" } },
|
||||
{ method: "POST", path: "/api/tasks", body: { title: "Départ invalide", assignedTo: "Léa", groupId: 1, startDate: "2026-13-45" } },
|
||||
{ method: "POST", path: "/api/tasks", body: { title: "Date de création", assignedTo: "Léa", groupId: 1, createdAt: "{{today}}" } },
|
||||
{ method: "POST", path: "/api/tasks", body: { id: 900, title: "Identifiant imposé", assignedTo: "Léa", groupId: 1, inconnu: true } },
|
||||
],
|
||||
tables: ["tasks"],
|
||||
},
|
||||
{
|
||||
module: "tasks",
|
||||
name: "modifier une tâche avec des valeurs invalides",
|
||||
steps: [
|
||||
{ method: "PUT", path: "/api/tasks/1", body: { priority: "urgent" } },
|
||||
{ method: "PUT", path: "/api/tasks/1", body: { startDate: "n'importe quoi" } },
|
||||
{ method: "PUT", path: "/api/tasks/1", body: { dueDate: "n'importe quoi" } },
|
||||
{ method: "PUT", path: "/api/tasks/1", body: { title: null } },
|
||||
{ method: "PUT", path: "/api/tasks/1", body: { assignedTo: 42, groupId: 2, createdBy: "pirate" } },
|
||||
{ method: "PUT", path: "/api/tasks/1", body: {} },
|
||||
{ method: "PUT", path: "/api/tasks/abc", body: {} },
|
||||
{ method: "PUT", path: "/api/tasks/99999999999", body: {} },
|
||||
],
|
||||
tables: ["tasks"],
|
||||
},
|
||||
|
||||
// ---------- DLC ----------
|
||||
{
|
||||
module: "dlc",
|
||||
name: "créer un produit DLC, le modifier, le valider, le supprimer",
|
||||
steps: [
|
||||
{
|
||||
method: "POST", path: "/api/dlc-products", body: {
|
||||
productName: "Jambon blanc", name: "Jambon blanc", gencode: "3000000000999", supplierId: 1, groupId: 1,
|
||||
dlcDate: "{{today+4}}", expiryDate: "{{today+4}}T00:00:00.000Z", dateType: "dlc", quantity: 1, unit: "unité",
|
||||
location: "Magasin", alertThreshold: 15, status: "en_cours", notes: "", createdBy: "pirate",
|
||||
},
|
||||
},
|
||||
{ method: "PUT", path: "/api/dlc-products/{{0.id}}", body: { productName: "Jambon supérieur", name: "Jambon supérieur", expiryDate: "{{today+6}}", dlcDate: "{{today+6}}", notes: "Modifié", groupId: 1 } },
|
||||
{ method: "POST", path: "/api/dlc-products/{{0.id}}/validate" },
|
||||
{ method: "GET", path: "/api/dlc-products/{{0.id}}" },
|
||||
{ method: "DELETE", path: "/api/dlc-products/{{0.id}}" },
|
||||
{ method: "GET", path: "/api/dlc-products/{{0.id}}" },
|
||||
],
|
||||
tables: ["dlc_products"],
|
||||
},
|
||||
{
|
||||
module: "dlc",
|
||||
name: "actions sur les produits de son magasin",
|
||||
steps: [
|
||||
{ method: "PUT", path: "/api/dlc-products/2/stock-epuise" },
|
||||
{ method: "PUT", path: "/api/dlc-products/4/restore-stock" },
|
||||
{ method: "PUT", path: "/api/dlc-products/2/mark-processed" },
|
||||
{ method: "PUT", path: "/api/dlc-products/5/unmark-processed" },
|
||||
{ method: "POST", path: "/api/dlc-products/1/validate" },
|
||||
{ method: "GET", path: "/api/dlc-products/stats?storeId=1" },
|
||||
{ method: "DELETE", path: "/api/dlc-products/302" },
|
||||
],
|
||||
tables: ["dlc_products"],
|
||||
},
|
||||
{
|
||||
module: "dlc",
|
||||
name: "actions sur les produits d'un autre magasin",
|
||||
steps: [
|
||||
{ method: "PUT", path: "/api/dlc-products/6/stock-epuise" },
|
||||
{ method: "PUT", path: "/api/dlc-products/6/mark-processed" },
|
||||
{ method: "POST", path: "/api/dlc-products/304/validate" },
|
||||
{ method: "PUT", path: "/api/dlc-products/300/stock-epuise" },
|
||||
{ method: "PUT", path: "/api/dlc-products/300/restore-stock" },
|
||||
{ method: "PUT", path: "/api/dlc-products/300/mark-processed" },
|
||||
{ method: "PUT", path: "/api/dlc-products/300/unmark-processed" },
|
||||
{ method: "POST", path: "/api/dlc-products/300/validate" },
|
||||
{ method: "PUT", path: "/api/dlc-products/300", body: { notes: "Épinal" } },
|
||||
{ method: "DELETE", path: "/api/dlc-products/300" },
|
||||
{ method: "PUT", path: "/api/dlc-products/6", body: { quantity: 2 } },
|
||||
{ method: "DELETE", path: "/api/dlc-products/6" },
|
||||
{ method: "PUT", path: "/api/dlc-products/9999/stock-epuise" },
|
||||
{ method: "PUT", path: "/api/dlc-products/9999/restore-stock" },
|
||||
{ method: "POST", path: "/api/dlc-products/9999/validate" },
|
||||
{ method: "PUT", path: "/api/dlc-products/9999", body: { notes: "x" } },
|
||||
{ method: "DELETE", path: "/api/dlc-products/abc" },
|
||||
{ method: "PUT", path: "/api/dlc-products/abc/mark-processed" },
|
||||
{ method: "PUT", path: "/api/dlc-products/abc/unmark-processed" },
|
||||
],
|
||||
tables: ["dlc_products"],
|
||||
},
|
||||
{
|
||||
module: "dlc",
|
||||
name: "créer et modifier des produits DLC avec des corps invalides",
|
||||
steps: [
|
||||
{ method: "POST", path: "/api/dlc-products", body: { groupId: 1 } },
|
||||
{ method: "POST", path: "/api/dlc-products", body: { name: "Texte", supplierId: 1, groupId: "1", expiryDate: "{{today+2}}" } },
|
||||
{ method: "POST", path: "/api/dlc-products", body: { name: "Épinal", supplierId: 1, groupId: 3, expiryDate: "{{today+2}}" } },
|
||||
{ method: "POST", path: "/api/dlc-products", body: { name: "Type inconnu", supplierId: 1, groupId: 1, expiryDate: "{{today+2}}", dateType: "xyz" } },
|
||||
{ method: "POST", path: "/api/dlc-products", body: { name: "Horodatage", supplierId: 1, groupId: 1, expiryDate: "{{today+2}}", validatedAt: "{{today}}" } },
|
||||
{ method: "POST", path: "/api/dlc-products", body: { name: "Minimal", supplierId: 2, groupId: 1, expiryDate: "{{today+9}}", stockEpuise: null, quantity: "7" } },
|
||||
{ method: "PUT", path: "/api/dlc-products/1", body: { status: "inconnu" } },
|
||||
{ method: "PUT", path: "/api/dlc-products/1", body: { expiryDate: null } },
|
||||
{ method: "PUT", path: "/api/dlc-products/1", body: { processedAt: "{{today}}" } },
|
||||
{ method: "PUT", path: "/api/dlc-products/1", body: { updatedAt: "{{today}}", inconnu: 1 } },
|
||||
{ method: "PUT", path: "/api/dlc-products/2", body: { groupId: 3, createdBy: "pirate" } },
|
||||
],
|
||||
tables: ["dlc_products"],
|
||||
},
|
||||
|
||||
// ---------- Commandes clients ----------
|
||||
{
|
||||
module: "customer-orders",
|
||||
name: "créer une commande client, la modifier, prévenir le client, la supprimer",
|
||||
steps: [
|
||||
{
|
||||
method: "POST", path: "/api/customer-orders", body: {
|
||||
orderTaker: "Sophie", customerName: "M. Nouveau", customerPhone: "06 12 34 56 78", customerEmail: "nouveau@client.test",
|
||||
productDesignation: "Barbecue", productReference: "BBQ-1", gencode: "3245678909999", quantity: 1, supplierId: 4,
|
||||
status: "En attente de Commande", deposit: 30, isPromotionalPrice: false, groupId: 1, notes: "Livraison",
|
||||
},
|
||||
},
|
||||
{ method: "PUT", path: "/api/customer-orders/{{0.id}}", body: { status: "Disponible", deposit: "45.5", quantity: 2 } },
|
||||
{ method: "GET", path: "/api/customer-orders/pending-calls" },
|
||||
{ method: "PATCH", path: "/api/customer-orders/{{0.id}}/mark-called", body: { comment: "Message laissé" } },
|
||||
{ method: "PUT", path: "/api/customer-orders/{{0.id}}", body: { customerNotified: false, notifiedAt: null, notifiedComment: null } },
|
||||
{ method: "DELETE", path: "/api/customer-orders/{{0.id}}" },
|
||||
],
|
||||
tables: ["customer_orders"],
|
||||
},
|
||||
{
|
||||
module: "customer-orders",
|
||||
name: "commandes clients de son magasin et d'un autre",
|
||||
steps: [
|
||||
{ method: "PATCH", path: "/api/customer-orders/3/mark-called", body: {} },
|
||||
{ method: "PATCH", path: "/api/customer-orders/301/mark-called", body: { comment: "" } },
|
||||
{ method: "PATCH", path: "/api/customer-orders/303/mark-called", body: { comment: "Metz" } },
|
||||
{ method: "PATCH", path: "/api/customer-orders/300/mark-called", body: { comment: "Épinal" } },
|
||||
{ method: "PUT", path: "/api/customer-orders/4", body: { status: "Retiré" } },
|
||||
{ method: "PUT", path: "/api/customer-orders/300", body: { status: "Retiré" } },
|
||||
{ method: "DELETE", path: "/api/customer-orders/5" },
|
||||
{ method: "DELETE", path: "/api/customer-orders/300" },
|
||||
{ method: "DELETE", path: "/api/customer-orders/2" },
|
||||
{ method: "PUT", path: "/api/customer-orders/9999", body: { status: "x" } },
|
||||
{ method: "PATCH", path: "/api/customer-orders/9999/mark-called", body: {} },
|
||||
{ method: "DELETE", path: "/api/customer-orders/9999" },
|
||||
{ method: "PATCH", path: "/api/customer-orders/abc/mark-called", body: {} },
|
||||
{ method: "DELETE", path: "/api/customer-orders/abc" },
|
||||
],
|
||||
tables: ["customer_orders"],
|
||||
},
|
||||
{
|
||||
module: "customer-orders",
|
||||
name: "créer et modifier des commandes clients avec des corps invalides",
|
||||
steps: [
|
||||
{ method: "POST", path: "/api/customer-orders", body: {} },
|
||||
{ method: "POST", path: "/api/customer-orders", body: { orderTaker: "Léa", customerName: "Texte", customerPhone: "06", productDesignation: "Pelle", gencode: "1", supplierId: 4, groupId: "1" } },
|
||||
{ method: "POST", path: "/api/customer-orders", body: { orderTaker: "Léa", customerName: "Épinal", customerPhone: "06", productDesignation: "Pelle", gencode: "1", supplierId: 4, groupId: 3 } },
|
||||
{ method: "POST", path: "/api/customer-orders", body: { orderTaker: "Léa", customerName: "Horodatage", customerPhone: "06", productDesignation: "Pelle", gencode: "1", supplierId: 4, groupId: 1, notifiedAt: "{{today}}" } },
|
||||
{ method: "POST", path: "/api/customer-orders", body: { orderTaker: "Léa", customerName: "Fournisseur absent", customerPhone: "06", productDesignation: "Pelle", gencode: "1", supplierId: 999, groupId: 1 } },
|
||||
{ method: "PUT", path: "/api/customer-orders/1", body: { quantity: "abc" } },
|
||||
{ method: "PUT", path: "/api/customer-orders/1", body: { createdAt: "{{today}}" } },
|
||||
{ method: "PUT", path: "/api/customer-orders/1", body: { deposit: "12,5" } },
|
||||
{ method: "PUT", path: "/api/customer-orders/1", body: { updatedAt: "2020-01-01", inconnu: 1, notes: ["a", "b"] } },
|
||||
{ method: "PUT", path: "/api/customer-orders/2", body: {} },
|
||||
],
|
||||
tables: ["customer_orders"],
|
||||
},
|
||||
|
||||
// ---------- SAV ----------
|
||||
{
|
||||
module: "sav",
|
||||
name: "créer un ticket SAV, le modifier, le commenter, le supprimer",
|
||||
steps: [
|
||||
{
|
||||
method: "POST", path: "/api/sav/tickets", body: {
|
||||
supplierId: 3, groupId: 1, productGencode: "3245678901241", productReference: "PS-18", productDesignation: "Perceuse sans fil",
|
||||
problemType: "defectueux", problemDescription: "Batterie HS", priority: "haute", status: "nouveau", createdBy: "1",
|
||||
clientName: "M. Laurent", clientPhone: "06 22 22 22 22",
|
||||
},
|
||||
},
|
||||
{ method: "PATCH", path: "/api/sav/tickets/{{0.id}}", body: { status: "en_cours", priority: "critique", resolutionDescription: "Pièce commandée" } },
|
||||
{ method: "POST", path: "/api/sav/tickets/{{0.id}}/history", body: { description: "Appel au fournisseur" } },
|
||||
{ method: "GET", path: "/api/sav/tickets/{{0.id}}" },
|
||||
{ method: "POST", path: "/api/sav/tickets", body: { supplierId: 4, productGencode: "1", productDesignation: "Second ticket", problemDescription: "Sans magasin choisi" } },
|
||||
{ method: "GET", path: "/api/sav/stats" },
|
||||
{ method: "DELETE", path: "/api/sav/tickets/{{0.id}}" },
|
||||
{ method: "DELETE", path: "/api/sav/tickets/{{4.id}}" },
|
||||
],
|
||||
tables: ["sav_tickets"],
|
||||
},
|
||||
{
|
||||
module: "sav",
|
||||
name: "tickets SAV de son magasin et d'un autre",
|
||||
steps: [
|
||||
{ method: "PATCH", path: "/api/sav/tickets/1", body: { status: "resolu" } },
|
||||
{ method: "PATCH", path: "/api/sav/tickets/3", body: { priority: "haute" } },
|
||||
{ method: "PATCH", path: "/api/sav/tickets/300", body: { status: "ferme" } },
|
||||
{ method: "POST", path: "/api/sav/tickets/2/history", body: { description: "Relance" } },
|
||||
{ method: "POST", path: "/api/sav/tickets/3/history", body: { description: "Metz" } },
|
||||
{ method: "POST", path: "/api/sav/tickets/300/history", body: {} },
|
||||
{ method: "DELETE", path: "/api/sav/tickets/302" },
|
||||
{ method: "DELETE", path: "/api/sav/tickets/300" },
|
||||
{ method: "PATCH", path: "/api/sav/tickets/9999", body: {} },
|
||||
{ method: "POST", path: "/api/sav/tickets/9999/history", body: {} },
|
||||
{ method: "DELETE", path: "/api/sav/tickets/9999" },
|
||||
{ method: "PATCH", path: "/api/sav/tickets/abc", body: {} },
|
||||
{ method: "DELETE", path: "/api/sav/tickets/abc" },
|
||||
],
|
||||
tables: ["sav_tickets"],
|
||||
},
|
||||
{
|
||||
module: "sav",
|
||||
name: "créer et modifier des tickets SAV avec des corps invalides",
|
||||
steps: [
|
||||
{ method: "POST", path: "/api/sav/tickets", body: {} },
|
||||
{ method: "POST", path: "/api/sav/tickets", body: { supplierId: null, groupId: "2", productGencode: 12, productDesignation: "x".repeat(501), problemType: "casse", problemDescription: "x", priority: "urgente", status: "ouvert" } },
|
||||
{ method: "POST", path: "/api/sav/tickets", body: { supplierId: 1, groupId: 3, productGencode: "1", productDesignation: "Épinal", problemDescription: "Autre magasin" } },
|
||||
{ method: "POST", path: "/api/sav/tickets", body: { supplierId: 1, groupId: 99, productGencode: "1", productDesignation: "Magasin inconnu", problemDescription: "x" } },
|
||||
{ method: "POST", path: "/api/sav/tickets", body: { supplierId: 999, groupId: 1, productGencode: "1", productDesignation: "Fournisseur inconnu", problemDescription: "x", ticketNumber: "SAV-PIRATE", createdAt: "2020-01-01" } },
|
||||
{ method: "PATCH", path: "/api/sav/tickets/1", body: { status: "inconnu", priority: 3 } },
|
||||
{ method: "PATCH", path: "/api/sav/tickets/1", body: { supplierId: 999 } },
|
||||
{ method: "PATCH", path: "/api/sav/tickets/2", body: { groupId: 3, createdBy: "pirate", clientName: null, ticketNumber: "SAV-PIRATE" } },
|
||||
{ method: "PATCH", path: "/api/sav/tickets/2", body: {} },
|
||||
],
|
||||
tables: ["sav_tickets"],
|
||||
},
|
||||
];
|
||||
@@ -0,0 +1,49 @@
|
||||
import type { ReadCase, WriteScenario } from "../types";
|
||||
|
||||
export const reads: ReadCase[] = [
|
||||
{ module: "contacts", path: "/api/contacts" },
|
||||
{ module: "contacts", path: "/api/contacts?groupId=1" },
|
||||
{ module: "contacts", path: "/api/contacts?groupId=2" },
|
||||
{ module: "suppliers", path: "/api/suppliers" },
|
||||
{ module: "suppliers", path: "/api/suppliers?dlc=true" },
|
||||
{ module: "groups", path: "/api/groups" },
|
||||
{ module: "users", path: "/api/users" },
|
||||
// Express ignore la casse et une « / » finale
|
||||
{ module: "contacts", path: "/api/Contacts?groupId=1" },
|
||||
{ module: "suppliers", path: "/api/suppliers/" },
|
||||
];
|
||||
|
||||
export const writes: WriteScenario[] = [
|
||||
{
|
||||
module: "contacts",
|
||||
name: "créer, modifier, supprimer un contact",
|
||||
steps: [
|
||||
{ method: "POST", path: "/api/contacts", body: { groupId: 1, name: "Nouveau contact", company: "Société", phone: "06 99 99 99 99", email: "nouveau@banc.test", notes: "Note" } },
|
||||
{ method: "PUT", path: "/api/contacts/{{0.id}}", body: { name: "Contact modifié", phone: "06 88 88 88 88" } },
|
||||
{ method: "PUT", path: "/api/contacts/3", body: { name: "Autre magasin" } },
|
||||
{ method: "DELETE", path: "/api/contacts/{{0.id}}" },
|
||||
{ method: "DELETE", path: "/api/contacts/3" },
|
||||
],
|
||||
tables: ["contacts"],
|
||||
},
|
||||
{
|
||||
module: "contacts",
|
||||
name: "créer un contact invalide",
|
||||
steps: [
|
||||
{ method: "POST", path: "/api/contacts", body: { groupId: 1 } },
|
||||
{ method: "POST", path: "/api/contacts", body: { groupId: 2, name: "Metz" } },
|
||||
],
|
||||
tables: ["contacts"],
|
||||
},
|
||||
{
|
||||
module: "suppliers",
|
||||
name: "créer, modifier, supprimer un fournisseur",
|
||||
steps: [
|
||||
{ method: "POST", path: "/api/suppliers", body: { name: "Nouveau fournisseur", contact: "M. Neuf", phone: "01 23 45 67 89", hasDlc: true, email: "neuf@banc.test", codefou: "F099" } },
|
||||
{ method: "PUT", path: "/api/suppliers/{{0.id}}", body: { name: "Fournisseur renommé", automaticReconciliation: true } },
|
||||
{ method: "DELETE", path: "/api/suppliers/{{0.id}}" },
|
||||
{ method: "POST", path: "/api/suppliers", body: { contact: "sans nom" } },
|
||||
],
|
||||
tables: ["suppliers"],
|
||||
},
|
||||
];
|
||||
@@ -0,0 +1,66 @@
|
||||
import type { ReadCase, WriteScenario } from "../types";
|
||||
|
||||
export const reads: ReadCase[] = [
|
||||
{ module: "socle", path: "/", accounts: ["anonyme"] },
|
||||
{ module: "socle", path: "/deliveries", accounts: ["anonyme", "manager"] },
|
||||
{ module: "socle", path: "/favicon.ico", accounts: ["anonyme"] },
|
||||
{ module: "socle", path: "/api/health", accounts: ["anonyme"] },
|
||||
{ module: "socle", path: "/api/adresse-inconnue", accounts: ["anonyme", "admin"] },
|
||||
{ module: "auth", path: "/api/user" },
|
||||
{ module: "auth", path: "/api/default-credentials-check" },
|
||||
];
|
||||
|
||||
export const writes: WriteScenario[] = [
|
||||
{
|
||||
module: "auth",
|
||||
name: "déconnexion puis lecture de l'utilisateur",
|
||||
steps: [
|
||||
{ method: "POST", path: "/api/logout" },
|
||||
{ method: "GET", path: "/api/user" },
|
||||
],
|
||||
tables: [],
|
||||
},
|
||||
{
|
||||
module: "auth",
|
||||
name: "connexion refusée",
|
||||
accounts: ["anonyme"],
|
||||
steps: [
|
||||
{ method: "POST", path: "/api/login", body: { username: "manager", password: "mauvais" } },
|
||||
{ method: "POST", path: "/api/login", body: { username: "inconnu", password: "x" } },
|
||||
{ method: "POST", path: "/api/login", body: {} },
|
||||
],
|
||||
tables: ["users"],
|
||||
},
|
||||
// ---------- Requêtes d'écriture d'un autre site (décision R4) ----------
|
||||
{
|
||||
module: "socle",
|
||||
name: "écriture d'un autre site refusée",
|
||||
accounts: ["manager"],
|
||||
steps: [
|
||||
{
|
||||
method: "POST",
|
||||
path: "/api/logout",
|
||||
headers: { origin: "https://site-tiers.example", "sec-fetch-site": "cross-site" },
|
||||
},
|
||||
{ method: "POST", path: "/api/logout", headers: { origin: "null" } },
|
||||
// La session est toujours ouverte côté Rust
|
||||
{ method: "GET", path: "/api/user" },
|
||||
],
|
||||
tables: [],
|
||||
deviation: {
|
||||
fields: ["status", "headers", "body"],
|
||||
reason:
|
||||
"R4 : une requête /api qui n'est pas une lecture est refusée (403) quand le navigateur indique qu'elle vient d'un autre site (Sec-Fetch-Site, sinon Origin)",
|
||||
},
|
||||
},
|
||||
{
|
||||
module: "socle",
|
||||
name: "écriture du même site acceptée",
|
||||
accounts: ["manager"],
|
||||
steps: [
|
||||
{ method: "POST", path: "/api/logout", headers: { "sec-fetch-site": "same-origin" } },
|
||||
{ method: "GET", path: "/api/user" },
|
||||
],
|
||||
tables: [],
|
||||
},
|
||||
];
|
||||
Reference in new issue
Block a user