docs: enrichir ARCHITECTURE, DEVELOPMENT_PRINCIPLES, GOLDEN_DATASET, TESTS_AUTOMATISES, TEST_ENVIRONMENT

This commit is contained in:
Hermann_Kitio 2026-04-17 18:35:50 +03:00
parent 52b8e9d011
commit f343fb4696
5 changed files with 1649 additions and 1476 deletions

File diff suppressed because it is too large Load diff

View file

@ -1,10 +1,8 @@
# DEVELOPMENT_PRINCIPLES.md — Expria / Coach TCF Canada # DEVELOPMENT_PRINCIPLES.md — Expria Frontend
> **Document de référence — Version 1.0** > **Document de référence — Version 1.0**
> Ce document définit les règles que Claude Code doit lire et respecter > Ce document définit les règles que toute session Claude Code sur le frontend doit lire et respecter, sans exception.
> à chaque session de développement, sans exception. > Adapté de `DEVELOPMENT_PRINCIPLES.md` du backend — les principes sont les mêmes, les règles spécifiques sont ajustées.
> Il est conçu pour un projet maintenu par un fondateur non-technique
> assisté par l'IA.
--- ---
@ -13,10 +11,12 @@
**Avant d'écrire la moindre ligne de code, tu dois :** **Avant d'écrire la moindre ligne de code, tu dois :**
1. Lire ce fichier en entier 1. Lire ce fichier en entier
2. Lire ARCHITECTURE.md 2. Lire `ARCHITECTURE.md` frontend
3. Lire PLANS_TARIFAIRES.md 3. Lire `PLANS_TARIFAIRES.md` (copie du backend)
4. Annoncer : "Documents lus. Voici mon plan pour cette session." 4. Lire `PARCOURS_UTILISATEURS.md` (section du plan concerné)
5. Produire un plan détaillé et attendre la validation 5. Consulter les ADRs pertinents dans `docs/adr/`
6. Annoncer : "Documents lus. Voici mon plan pour cette session."
7. Produire un plan détaillé et attendre la validation
**Tu ne passes à l'implémentation que quand Hermann dit "GO".** **Tu ne passes à l'implémentation que quand Hermann dit "GO".**
@ -24,7 +24,7 @@
## 1. Le cycle de travail obligatoire ## 1. Le cycle de travail obligatoire
Chaque modification, petite ou grande, suit ce cycle sans exception : Identique au backend :
``` ```
ÉTAPE 1 — ANALYSE ÉTAPE 1 — ANALYSE
@ -47,13 +47,15 @@ Chaque modification, petite ou grande, suit ce cycle sans exception :
Maximum 3 fichiers touchés par étape Maximum 3 fichiers touchés par étape
ÉTAPE 4 — VÉRIFICATION ÉTAPE 4 — VÉRIFICATION
Lancer npm run test (backend) Lancer npm run typecheck
Annoncer le résultat : "X tests passés, 0 échecs" Lancer npm run test
Annoncer le résultat : "Typecheck OK. X tests passés, 0 échecs."
Si un test échoue : STOP — corriger avant de continuer Si un test échoue : STOP — corriger avant de continuer
ÉTAPE 5 — RÉSUMÉ ÉTAPE 5 — RÉSUMÉ
Lister exactement ce qui a été modifié Lister exactement ce qui a été modifié
Indiquer les tests manuels à rejouer (référence au Golden Dataset) Indiquer les tests manuels à rejouer (Golden Dataset)
Proposer un message de commit
``` ```
--- ---
@ -63,141 +65,184 @@ Chaque modification, petite ou grande, suit ce cycle sans exception :
### Règle A — Plan avant code ### Règle A — Plan avant code
Claude Code ne commence jamais à coder sans plan validé. Claude Code ne commence jamais à coder sans plan validé.
Même pour une modification "simple" d'une ligne. Même pour une modification "simple" d'une ligne.
La simplicité apparente est trompeuse dans un projet avec des permissions par plan.
### Règle B — Maximum 3 fichiers par étape ### Règle B — Maximum 3 fichiers par étape
Si une tâche nécessite de modifier plus de 3 fichiers, Si une tâche nécessite de modifier plus de 3 fichiers, elle est découpée en plusieurs étapes avec validation intermédiaire.
elle est découpée en plusieurs étapes avec validation intermédiaire.
Chaque étape est testée avant de passer à la suivante.
### Règle C — Tests verts avant de continuer ### Règle C — Tests verts avant de continuer
Après chaque étape d'implémentation : Après chaque étape d'implémentation :
`npm run typecheck` doit retourner 0 erreur.
`npm run test` doit retourner 0 échec. `npm run test` doit retourner 0 échec.
Si un test échoue, on corrige avant de passer à l'étape suivante.
On ne livre jamais une étape avec des tests rouges.
### Règle D — Jamais de logique de plan en dur dans le code ### Règle D — Jamais de logique de plan en dur dans le code
Toutes les vérifications de permissions lisent depuis `lib/access.ts`.
Exemple interdit :
```typescript ```typescript
// ❌ JAMAIS // ❌ JAMAIS
if (user.plan === 'premium') { ... } if (user.plan === 'premium') { ... }
if (user.plan !== 'free') { ... } if (user.plan !== 'free') { ... }
```
Exemple correct :
```typescript
// ✅ TOUJOURS // ✅ TOUJOURS
const perms = getPlanPermissions(user.plan) import { hasAccess } from '@/entities/user/lib'
if (perms.exam_mode) { ... } if (hasAccess(user.plan, 'exam_mode')) { ... }
``` ```
Voir ADR 005 pour le détail.
### Règle E — Jamais de clé privée dans le frontend ### Règle E — Jamais de clé privée dans le frontend
Les variables suivantes n'existent que dans le backend : Seules ces variables sont autorisées dans le frontend :
- `VITE_API_URL`
- `VITE_SUPABASE_URL`
- `VITE_SUPABASE_ANON_KEY` (clé publique)
- `VITE_ENABLE_T2_LIVE` (flag)
- `VITE_SENTRY_DSN` (si monitoring actif)
Les variables suivantes **n'existent jamais** dans le frontend :
- `SUPABASE_SERVICE_ROLE_KEY` - `SUPABASE_SERVICE_ROLE_KEY`
- `GEMINI_API_KEY` - `GEMINI_API_KEY`
- `DEEPSEEK_API_KEY` - `DEEPSEEK_API_KEY`
- `STRIPE_SECRET_KEY` - `STRIPE_SECRET_KEY`
- `STRIPE_WEBHOOK_SECRET` - `STRIPE_WEBHOOK_SECRET`
Le frontend n'a accès qu'à : ### Règle F — Jamais d'appel direct à Supabase pour des données métier
- `VITE_SUPABASE_ANON_KEY` (clé publique Supabase) Supabase côté frontend est **exclusivement** utilisé pour l'auth :
- `VITE_API_URL` (URL du backend) ```typescript
// ✅ Autorisé
supabase.auth.signInWithPassword(...)
supabase.auth.signOut()
supabase.auth.getSession()
### Règle F — Jamais de modification de la base de données sans migration SQL // ❌ Interdit
Toute modification du schéma Supabase passe par un fichier de migration supabase.from('productions').select()
dans `supabase/migrations/`. supabase.from('profiles').update(...)
Jamais de modification directe dans le dashboard Supabase sans fichier de migration correspondant. ```
### Règle G — Jamais de suppression de données utilisateur Toute lecture/écriture de données métier passe par le backend Hono.
Aucune opération `DELETE` sur les tables `productions` ou `profiles`
sauf dans les scripts de test explicitement balisés.
En cas de doute : archiver, ne pas supprimer.
### Règle H — Signaler tout écart par rapport au plan ### Règle G — access.ts doit rester identique au backend
Si pendant l'implémentation Claude Code réalise que le plan doit être modifié, `src/entities/user/access.ts` est une **copie bit-à-bit** de `expria-backend/src/lib/access.ts`.
il STOP, signale le changement, explique pourquoi, et attend une nouvelle validation. Toute modification se fait dans les deux dépôts, dans le même commit logique.
Voir ADR 004.
### Règle H — Jamais de logique métier dans `features/`
La logique métier (permissions, quotas, floutage, règles de validation) vit exclusivement dans `src/entities/<domaine>/lib.ts`.
Les composants React de `features/` appellent ces fonctions, sans les réimplémenter.
### Règle I — Signaler tout écart par rapport au plan
Si pendant l'implémentation Claude Code réalise que le plan doit être modifié, il STOP, signale le changement, explique pourquoi, et attend une nouvelle validation.
Il ne prend jamais de décision architecturale de sa propre initiative. Il ne prend jamais de décision architecturale de sa propre initiative.
### Règle J — Sécurité
- Jamais `dangerouslySetInnerHTML` sans `DOMPurify`
- Jamais `eval`, `new Function`, `setTimeout(string, ...)`
- Jamais de secret dans `localStorage` ou `sessionStorage`
- Jamais de `console.log` de données utilisateur (email, JWT, payload API)
- Toujours passer par `apiFetch` (jamais `fetch` nu)
Voir `SECURITY.md` pour le détail.
### Règle K — Pas de worktree Git
Claude Code ne crée jamais de worktree Git.
Toutes les modifications se font directement dans le dossier du projet principal.
--- ---
## 3. Structure du code — conventions ## 3. Structure du code — conventions
### Nommage des fichiers ### Nommage des fichiers
```
frontend/src/
pages/ → PascalCase.tsx (Dashboard.tsx, Simulation.tsx)
components/ → PascalCase.tsx (RapportCard.tsx, PaywallModal.tsx)
hooks/ → camelCase.ts (useAuth.ts, usePlan.ts)
api/ → camelCase.ts (simulations.ts, corrections.ts)
lib/ → camelCase.ts (access.ts, supabase.ts)
types/ → camelCase.ts (plans.ts, simulation.ts)
backend/src/ ```
routes/ → camelCase.ts (simulations.ts, stripe.ts) src/
controllers/ → camelCase.ts (simulationController.ts) app/ → camelCase.tsx (providers.tsx, router.tsx)
middleware/ → camelCase.ts (auth.ts, plan.ts) entities/
lib/ → camelCase.ts (access.ts, deepseek.ts) <domaine>/
__tests__/ → camelCase.test.ts (canUserSimulate.test.ts) types.ts → interfaces et types
lib.ts → fonctions pures métier
api.ts → fonctions d'appel API
access.ts → cas spécial : identique au backend
features/
<feature>/
components/ → PascalCase.tsx (LoginForm.tsx, PaywallModal.tsx)
pages/ → PascalCase.tsx (LoginPage.tsx, DashboardPage.tsx)
hooks/ → camelCase.ts (useAuth.ts, usePlan.ts)
lib/ → camelCase.ts (ws-client.ts, audio.ts)
state/ → kebab-case.ts (t2-machine.ts)
shared/
components/ → PascalCase.tsx
hooks/ → camelCase.ts
lib/ → kebab-case.ts (api-client.ts, auth-client.ts)
types/ → camelCase.ts
config/ → camelCase.ts
__tests__/ → <nom-du-fichier-testé>.test.ts
``` ```
### Nommage des variables et fonctions ### Nommage des variables et fonctions
```typescript ```typescript
// Variables : camelCase // Variables : camelCase
const userPlan = 'premium' const userPlan = 'premium'
const simulationsUsed = 5 const simulationsUsed = 5
// Fonctions : camelCase, verbe en premier // Fonctions : camelCase, verbe en premier
function getUserPlan(userId: string) { } function hasAccess(plan: Plan, feature: BooleanPermission): boolean { }
function canUserSimulate(profile: Profile) { } function canSimulate(plan: Plan, used: number): SimulationCheck { }
function updateUserPlan(userId: string, plan: Plan) { }
// Composants React : PascalCase
function DashboardPage() { }
function PaywallModal() { }
// Hooks : use + PascalCase (convention React)
function useAuth() { }
function usePlan() { }
// Types et interfaces : PascalCase // Types et interfaces : PascalCase
type Plan = 'free' | 'standard' | 'premium' type Plan = 'free' | 'standard' | 'premium'
interface UserProfile { } interface UserProfile { }
interface SimulationResult { }
// Constantes globales : SCREAMING_SNAKE_CASE // Constantes globales : SCREAMING_SNAKE_CASE
const MAX_FREE_SIMULATIONS = 5 const MAX_FREE_SIMULATIONS = 5
const PLANS = { free: {...}, standard: {...}, premium: {...} } const PLANS = { ... }
``` ```
### Structure d'une route Hono (backend) ### Structure d'un composant de feature
```typescript ```typescript
// Pattern obligatoire pour toutes les routes // features/dashboard/pages/DashboardPage.tsx
app.post('/simulations', authMiddleware, planMiddleware('simulation'), async (c) => {
// 1. Récupérer et valider les données entrantes
const body = await c.req.json()
// 2. Logique métier (déléguer au controller) import { usePlan } from '../hooks/usePlan'
const result = await simulationController.create(body, c.get('user')) import { hasAccess } from '@/entities/user/lib'
// 3. Retourner la réponse export function DashboardPage() {
return c.json(result, 201)
})
```
### Structure d'un composant React (frontend)
```typescript
// Pattern obligatoire pour tous les composants
interface Props {
// Toujours typer les props
}
export function NomDuComposant({ prop1, prop2 }: Props) {
// 1. Hooks en premier // 1. Hooks en premier
const { user } = useAuth() const { plan, isLoading, error } = usePlan()
const perms = usePlan()
// 2. Handlers // 2. Gestion des états de chargement et d'erreur
const handleClick = () => { } if (isLoading) return <Spinner />
if (error) return <ErrorView error={error} />
// 3. Rendu conditionnel selon le plan // 3. Logique de permission via hasAccess
if (!perms.dashboard) return <PaywallModal feature="dashboard" /> if (!hasAccess(plan.plan, 'dashboard')) {
return <DashboardFreeAperçu />
}
// 4. JSX // 4. Rendu principal
return ( return <DashboardComplete plan={plan} />
<div>...</div> }
) ```
### Structure d'un hook custom
```typescript
// features/dashboard/hooks/usePlan.ts
import { useQuery } from '@tanstack/react-query'
import { getPlanStatus } from '@/entities/user/api'
export function usePlan() {
return useQuery({
queryKey: ['plan'],
queryFn: getPlanStatus,
staleTime: 5 * 60 * 1000, // 5 min
})
} }
``` ```
@ -205,147 +250,160 @@ export function NomDuComposant({ prop1, prop2 }: Props) {
## 4. Gestion des erreurs ## 4. Gestion des erreurs
### Backend — toujours retourner une erreur structurée ### Côté api-client
```typescript ```typescript
// ✅ Format d'erreur standard // shared/lib/api-client.ts
return c.json({ // En cas de succès : retourne le payload typé T directement (pas d'enveloppe)
error: true, // En cas d'erreur : throw une ApiError typée
code: 'QUOTA_REACHED', // TanStack Query catch automatiquement et rend l'erreur disponible dans .error
message: 'Quota de simulations atteint pour ce plan',
}, 403)
// ❌ Jamais export async function apiFetch<T>(
return c.json({ error: 'error' }, 500) path: string,
throw new Error('Something went wrong') options: RequestInit & { timeoutMs?: number } = {}
``` ): Promise<T> {
// 1. Ajouter headers (Authorization Bearer, X-API-Version, Content-Type)
### Codes d'erreur standardisés // 2. Appliquer AbortController pour timeout
``` // 3. Retry avec backoff exponentiel sur erreurs réseau + 5xx
AUTH_REQUIRED → 401 Pas de JWT ou JWT invalide // 4. Parser la réponse :
PLAN_INSUFFICIENT → 403 Feature non disponible pour ce plan // - Si 2xx → retourner le JSON tel quel typé T
QUOTA_REACHED → 403 Quota de simulations épuisé // - Si 4xx/5xx → parser l'erreur backend { error: true, code, message } → throw ApiError
INVALID_PLAN → 400 Valeur de plan inconnue
SIMULATION_NOT_FOUND → 404 Simulation inexistante ou non accessible
STRIPE_WEBHOOK_INVALID → 400 Signature webhook invalide
INTERNAL_ERROR → 500 Erreur serveur inattendue
```
### Frontend — toujours gérer les erreurs API
```typescript
// ✅ Pattern obligatoire pour tout appel API
try {
const result = await api.simulations.create(data)
// succès
} catch (error) {
if (error.code === 'QUOTA_REACHED') {
// afficher modal upgrade
} else if (error.code === 'PLAN_INSUFFICIENT') {
// afficher paywall
} else {
// afficher message d'erreur générique
}
} }
``` ```
Le format exact des erreurs backend est documenté dans `ARCHITECTURE.md` section 5 (confirmé par audit du 2026-04-17).
### Côté hook (utilisation)
```typescript
const mutation = useMutation({
mutationFn: submitProduction,
onError: (err: ApiError) => {
switch (err.code) {
case 'QUOTA_REACHED':
openUpgradeModal()
break
case 'PLAN_INSUFFICIENT':
openPaywall()
break
case 'AUTH_REQUIRED':
redirectToLogin()
break
case 'VALIDATION_ERROR':
case 'INVALID_BODY':
toast.error('Données invalides. Vérifiez votre saisie.')
break
default:
toast.error('Une erreur est survenue. Réessayez dans quelques instants.')
}
},
})
```
> **Note** : `VALIDATION_ERROR` et `INVALID_BODY` sont deux codes backend pour la même classe d'erreur (corps invalide). Côté frontend, on les gère de la même manière. L'unification côté backend est tracée dans `TECH_DEBT.md` backend (TD-15).
### Messages utilisateur
- En français
- Clairs et non techniques
- Orientés action
```
// ✅ Bons messages
"Vous avez utilisé vos 5 simulations gratuites. Passez en Standard pour continuer."
"Le mode Examen est réservé au plan Premium."
"Une erreur est survenue. Réessayez dans quelques instants."
// ❌ Mauvais messages
"Erreur 403 : quota_reached"
"TypeError: Cannot read property 'plan' of undefined"
```
--- ---
## 5. Sécurité — vérifications obligatoires ## 5. Communication avec Hermann
### Toute route backend qui touche à des données utilisateur doit :
```typescript
// 1. Vérifier l'authentification
app.use(authMiddleware) // vérifie le JWT Supabase
// 2. Vérifier le plan si nécessaire
app.use(planMiddleware('exam_mode')) // vérifie la permission
// 3. Vérifier que la ressource appartient à l'utilisateur
const simulation = await db.getSimulation(id)
if (simulation.user_id !== currentUser.id) {
return c.json({ error: true, code: 'AUTH_REQUIRED' }, 401)
}
```
### La vérification des permissions se fait TOUJOURS côté backend
Le frontend peut masquer des boutons selon le plan — c'est de l'UX.
Mais la vérification réelle se fait dans le middleware backend.
Un utilisateur malveillant peut appeler l'API directement sans passer par le frontend.
---
## 6. Communication avec Hermann
### Format du plan (avant implémentation) ### Format du plan (avant implémentation)
``` ```
📋 PLAN — [nom de la tâche] 📋 PLAN — [nom de la tâche]
Fichiers modifiés : Fichiers modifiés :
- src/routes/simulations.ts (ajouter la vérification de quota) - src/features/dashboard/pages/DashboardPage.tsx (ajouter affichage conditionnel)
- src/lib/access.ts (ajouter la fonction canUserSimulate) - src/entities/user/lib.ts (ajouter helper formatRemainingSimulations)
Fichiers créés : Fichiers créés :
- src/lib/__tests__/canUserSimulate.test.ts - src/features/dashboard/components/RemainingSimulationsBadge.tsx
- src/features/dashboard/components/__tests__/RemainingSimulationsBadge.test.tsx
Fichiers supprimés : Fichiers supprimés : aucun
- aucun
Risques identifiés : Risques identifiés :
- La modification de access.ts peut affecter toutes les routes - La modification de DashboardPage peut affecter l'affichage pour les 3 plans
qui utilisent getPlanPermissions → vérifier après modification → vérifier les 3 comptes de test après implémentation
Étapes : Étapes :
1. Créer la fonction canUserSimulate dans access.ts 1. Créer le composant RemainingSimulationsBadge + son test
2. Écrire le test canUserSimulate.test.ts 2. Créer le helper formatRemainingSimulations dans entities/user/lib.ts + test
3. Ajouter la vérification dans la route POST /simulations 3. Intégrer dans DashboardPage
En attente de validation avant de commencer. En attente de validation avant de commencer.
``` ```
### Format du résumé (après implémentation) ### Format du résumé (après implémentation)
``` ```
✅ RÉSUMÉ — [nom de la tâche] ✅ RÉSUMÉ — [nom de la tâche]
Modifié : Modifié :
- src/routes/simulations.ts : ajout vérification quota ligne 42 - src/features/dashboard/pages/DashboardPage.tsx : intégration badge ligne 42
- src/lib/access.ts : ajout fonction canUserSimulate
Créé : Créé :
- src/lib/__tests__/canUserSimulate.test.ts (7 tests) - src/features/dashboard/components/RemainingSimulationsBadge.tsx
- src/entities/user/lib.ts : fonction formatRemainingSimulations
+ 2 fichiers de tests (3 tests supplémentaires)
Tests : 41/41 passés ✅ Typecheck : OK
Tests : 27/27 passés ✅
Tests manuels à rejouer : Tests manuels à rejouer :
- Golden Dataset Groupe 2 (B2, B7) — quota Free - Golden Dataset Groupe 2 (B1) — Dashboard Free avec compteur
- Golden Dataset Groupe 3 (C2) — illimité Standard - Golden Dataset Groupe 3 (C1) — Dashboard Standard sans compteur
Message de commit proposé :
feat(dashboard): afficher le compteur de simulations restantes (Free)
``` ```
### Quand Claude Code doit s'arrêter et demander ### Quand Claude Code doit s'arrêter et demander
- Un test automatisé échoue après modification - Un test automatisé échoue après modification
- La tâche nécessite de modifier plus de 3 fichiers par étape - La tâche nécessite de modifier plus de 3 fichiers par étape
- Une décision architecturale non documentée est requise - Une décision architecturale non documentée est requise
- Une ambiguïté sur le comportement attendu selon un plan tarifaire - Une ambiguïté sur le comportement attendu selon un plan tarifaire
- Le backend doit être modifié (session différente, dépôt différent)
- Une clé privée serait nécessaire côté frontend - Une clé privée serait nécessaire côté frontend
--- ---
## 7. Checklist de démarrage de session ## 6. Checklist de démarrage de session
Avant chaque session Claude Code, vérifier : Avant chaque session Claude Code, vérifier :
``` ```
[ ] Les 4 documents de référence ont été lus [ ] Les documents de référence ont été lus
(DEVELOPMENT_PRINCIPLES.md, ARCHITECTURE.md, (ARCHITECTURE.md, DEVELOPMENT_PRINCIPLES.md,
PLANS_TARIFAIRES.md, PARCOURS_UTILISATEURS.md) PLANS_TARIFAIRES.md, PARCOURS_UTILISATEURS.md)
[ ] L'environnement de test est dans l'état attendu [ ] Les ADRs pertinents ont été consultés
(voir TEST_ENVIRONMENT.md — script de réinitialisation) (docs/adr/)
[ ] L'environnement de dev fonctionne
(npm run dev, pas d'erreur au démarrage)
[ ] Les tests automatisés sont tous verts [ ] Les tests automatisés sont tous verts
(npm run test → 0 échec) (npm run typecheck && npm run test → 0 échec)
[ ] Un commit Git propre existe avant de commencer [ ] Un commit Git propre existe avant de commencer
(état de repli en cas de régression)
[ ] La tâche de la session est clairement définie [ ] La tâche de la session est clairement définie
(une seule tâche par session) (une seule tâche par session)
@ -353,70 +411,53 @@ Avant chaque session Claude Code, vérifier :
--- ---
## 8. Checklist de fin de session ## 7. Checklist de fin de session
Avant de clôturer chaque session Claude Code :
``` ```
[ ] Les tests automatisés sont tous verts [ ] Les tests automatisés sont tous verts
(npm run test → 0 échec) (npm run typecheck && npm run test → 0 échec)
[ ] Le résumé de session a été produit [ ] Le résumé de session a été produit
(fichiers modifiés, tests passés)
[ ] Les tests manuels du Golden Dataset ont été rejoués [ ] Les tests manuels du Golden Dataset ont été rejoués
(groupes concernés par les modifications)
[ ] Un commit Git a été fait avec un message clair [ ] Un commit Git a été fait avec un message clair
Format : "feat: [description]" ou "fix: [description]" Format : "feat(<scope>): ...", "fix(<scope>): ...", "refactor(<scope>): ..."
Scopes possibles : auth, dashboard, simulation, t2-live, billing, shared, docs
Exemples : Exemples :
"feat: ajout vérification quota simulations free" "feat(dashboard): affichage conditionnel selon le plan"
"fix: correction rapport flouté plan découverte" "fix(auth): corriger la redirection après logout"
"refactor: extraction logique permissions dans access.ts" "refactor(entities/user): extraire hasAccess dans un fichier dédié"
[ ] DEVELOPMENT_PRINCIPLES.md et ARCHITECTURE.md [ ] docs/CHANGELOG.md mis à jour avec un résumé de la session
sont à jour si une décision a changé
[ ] ARCHITECTURE.md / SECURITY.md mis à jour si une décision a changé
``` ```
--- ---
## 9. Messages d'erreur utilisateur — ton et format ## 8. Ce que Claude Code ne doit jamais faire
Les messages affichés à l'utilisateur doivent être :
- En français
- Clairs et non techniques
- Orientés action (que faire ensuite)
- Non condescendants
``` ```
// ✅ Bons messages ❌ Modifier entities/user/access.ts sans prévenir (source de vérité partagée avec le backend)
"Vous avez utilisé vos 5 simulations gratuites.
Passez en Standard pour continuer votre préparation."
"Le mode Examen est réservé au plan Premium.
Passez en Premium pour vous entraîner en conditions réelles."
"Une erreur est survenue. Veuillez réessayer dans quelques instants."
// ❌ Mauvais messages
"Erreur 403 : quota_reached"
"Vous n'êtes pas autorisé à effectuer cette action."
"Internal server error"
```
---
## 10. Ce que Claude Code ne doit jamais faire
```
❌ Modifier lib/access.ts sans signaler que TOUTES les permissions sont impactées
❌ Ajouter une dépendance npm sans demander la validation ❌ Ajouter une dépendance npm sans demander la validation
Modifier le schéma Supabase directement dans le dashboard ❌ Appeler Supabase pour autre chose que l'auth
❌ Écrire une logique de plan en dur (if plan === 'premium') ❌ Écrire une logique de plan en dur (if plan === 'premium')
❌ Exposer une clé privée dans le frontend ❌ Exposer une clé privée dans le frontend
❌ Supprimer des données utilisateur (productions, profiles)
❌ Modifier plus de 3 fichiers sans validation intermédiaire ❌ Modifier plus de 3 fichiers sans validation intermédiaire
❌ Passer à l'étape suivante si un test est rouge ❌ Passer à l'étape suivante si un test est rouge
❌ Prendre une décision architecturale sans la documenter ❌ Prendre une décision architecturale sans la documenter
❌ Coder sans plan validé, même pour "juste une petite modification" ❌ Coder sans plan validé, même pour "juste une petite modification"
❌ Utiliser dangerouslySetInnerHTML sans DOMPurify
❌ Créer un worktree Git
❌ Modifier le backend depuis une session frontend (dépôt différent)
``` ```
---
## 9. Historique
| Version | Date | Changements |
|---|---|---|
| 1.0 | 2026-04-17 | Création, adaptée de la version backend |

View file

@ -1,154 +1,170 @@
# GOLDEN_DATASET.md — Expria / Coach TCF Canada # GOLDEN_DATASET.md — Expria Frontend
> **Document de référence — Version 1.0** > **Document de référence — Version 1.0**
> Ce fichier contient les tests manuels à rejouer après CHAQUE session Claude Code, > Ce fichier contient les tests manuels à rejouer après CHAQUE session Claude Code frontend, avant de déployer sur Cloudflare Pages.
> avant de passer à la session suivante ou de déployer en production.
> Un seul test en rouge = la modification est refusée, on revient en arrière. > Un seul test en rouge = la modification est refusée, on revient en arrière.
>
> Complément frontend du `GOLDEN_DATASET.md` backend. Les deux doivent passer avant un déploiement couplé.
--- ---
## Principe d'utilisation ## Principe d'utilisation
1. Avant chaque session Claude Code : sauvegarder le code (commit Git) 1. Avant chaque session Claude Code : commit Git propre
2. Après chaque session Claude Code : rejouer TOUS les tests du groupe concerné 2. Après chaque session Claude Code : rejouer TOUS les tests du groupe concerné
3. Si un test échoue : ne pas continuer, identifier la régression, corriger d'abord 3. Si un test échoue : ne pas continuer, identifier la régression, corriger d'abord
4. En cas de doute : rejouer le dataset complet (section 7) 4. En cas de doute : rejouer le groupe Z (smoke test complet)
**Comptes de test à créer dans Supabase avant de commencer :** **Environnement de test :**
- URL frontend local : `http://localhost:5173`
- URL backend : `https://api.expria.app` (ou local si dev simultané)
- Navigateurs à couvrir : Chrome + Firefox + Safari mobile (via DevTools mobile emulation minimum)
| Compte | Plan | Usage | **Comptes de test (identiques au backend) :**
| Compte | Plan | Mot de passe |
|---|---|---| |---|---|---|
| test.free@expria.local | free | Tester les parcours Free | | test.free@gmail.com | free | Expria2025!test |
| test.standard@expria.local | standard | Tester les parcours Standard | | test.standard@gmail.com | standard | Expria2025!test |
| test.premium@expria.local | premium | Tester les parcours Premium | | test.premium@gmail.com | premium | Expria2025!test |
| test.quota@expria.local | free (5/5 simulations utilisées) | Tester le blocage quota | | test.quota@gmail.com | free (5/5 utilisées) | Expria2025!test |
> Ces comptes sont créés via script SQL dans Supabase (ne pas les créer manuellement).
> Voir `supabase/seeds/test_accounts.sql`.
--- ---
## Groupe 1 — Authentification ## Groupe A — Authentification et routing
Ces tests vérifient que l'accès à l'application fonctionne correctement. | # | Test | Compte | Résultat attendu | ✅/❌ |
| # | Test | Compte | Résultat attendu | ✅ / ❌ |
|---|---|---|---|---| |---|---|---|---|---|
| A1 | Inscription avec email valide | Nouveau compte | Compte créé, plan = "free", redirection dashboard | | | A1 | Arriver sur `/` sans être connecté | — | Page Home publique affichée | |
| A2 | Connexion avec email + mot de passe valides | test.free | Dashboard Free affiché, compteur simulations visible | | | A2 | Cliquer "Se connecter" depuis Home | — | Redirection `/login`, formulaire visible | |
| A3 | Connexion avec mot de passe incorrect | test.free | Message d'erreur, pas de redirection | | | A3 | Inscription avec email + mot de passe valides | nouveau | Compte créé, plan=free, redirection `/dashboard` | |
| A4 | Déconnexion | test.free | Redirection page d'accueil, session détruite | | | A4 | Connexion avec identifiants corrects | test.free | Redirection `/dashboard`, plan Free affiché | |
| A5 | Accès direct à /dashboard sans être connecté | — | Redirection vers /login | | | A5 | Connexion avec mot de passe incorrect | test.free | Message d'erreur en français, pas de redirection | |
| A6 | Déconnexion depuis le menu utilisateur | test.free | Redirection `/`, session invalidée | |
| A7 | Accès direct à `/dashboard` sans auth | — | Redirection `/login` (ProtectedRoute) | |
| A8 | Accès direct à `/t2-live` en tant que Free | test.free | Redirection ou PaywallModal "Exclusivité Premium" | |
| A9 | Session JWT expirée pendant navigation | test.free | Message "Session expirée", redirection `/login` | |
| A10 | Rafraîchir la page après login | test.free | Reste connecté, dashboard réaffiché | |
--- ---
## Groupe 2 — Plan Free (Découverte) ## Groupe B — Plan Free (parcours complet)
Ces tests vérifient le parcours complet d'un utilisateur Free. | # | Test | Compte | Résultat attendu | ✅/❌ |
| # | Test | Compte | Résultat attendu | ✅ / ❌ |
|---|---|---|---|---| |---|---|---|---|---|
| B1 | Dashboard Free affiché après connexion | test.free | Compteur "X/5 simulations", aperçu flouté du dashboard Premium visible | | | B1 | Dashboard Free après connexion | test.free | Compteur "X/5 simulations", aperçu flouté du dashboard Premium visible | |
| B2 | Lancer une simulation EE Tâche 1 | test.free (quota < 5) | Interface de production affichée, pas de tips | | | B2 | Badge compteur simulations affiché | test.free | Visible en permanence dans le header du dashboard | |
| B3 | Soumettre une production EE | test.free (quota < 5) | Rapport affiché : score + NCLC visibles, critères floutés avec cadenas | | | B3 | Lancer une simulation EE T1 | test.free (quota < 5) | Interface de production affichée, pas de tips visibles | |
| B4 | Vérifier le rapport flouté | test.free | Au moins 1 élément flouté avec mention "Disponible en Standard" | | | B4 | Soumettre une production EE | test.free | Rapport affiché : score + NCLC visibles, critères floutés avec cadenas | |
| B5 | Lancer une simulation EO Tâche 1 | test.free (quota < 5) | Interface d'enregistrement audio affichée | | | B5 | Rapport flouté avec mentions correctes | test.free | "Disponible en Standard" + bouton upgrade visible | |
| B6 | Cliquer sur EO Tâche 2 live | test.free | Cadenas affiché + message "Exclusivité Premium" | | | B6 | Lancer une simulation EO T1 | test.free | Interface d'enregistrement audio, pas d'erreur microphone | |
| B7 | Tenter une 6e simulation | test.quota | Modal de blocage avec boutons "Standard" et "Premium" | | | B7 | Tenter EO T2 live depuis le sélecteur de tâches | test.free | Cadenas + message "Exclusivité Premium" | |
| B8 | Cliquer "Plus tard" dans le modal de blocage | test.quota | Modal fermé, pas de redirection | | | B8 | Atteindre la 6e simulation | test.quota | Modal de blocage : "5/5 utilisées" + 2 boutons (Standard/Premium) + "Plus tard" | |
| B9 | Cliquer "Mode Examen" | test.free | Cadenas + message "Exclusivité Premium" | | | B9 | Cliquer "Plus tard" dans le modal | test.quota | Modal fermé, dashboard visible, pas de redirection | |
| B10 | Cliquer "Mode Examen" | test.free | Cadenas + message "Exclusivité Premium" | |
| B11 | Tenter accès URL direct `/exam-mode` | test.free | Redirection ou PaywallModal | |
--- ---
## Groupe 3 — Plan Standard ## Groupe C — Plan Standard
Ces tests vérifient le parcours complet d'un utilisateur Standard. | # | Test | Compte | Résultat attendu | ✅/❌ |
| # | Test | Compte | Résultat attendu | ✅ / ❌ |
|---|---|---|---|---| |---|---|---|---|---|
| C1 | Dashboard Standard après connexion | test.standard | Historique visible, bouton "Choisir une tâche" actif, mode examen verrouillé | | | C1 | Dashboard Standard après connexion | test.standard | Historique visible, pas de compteur simulations, bouton "Choisir une tâche" actif | |
| C2 | Lancer une simulation EE sans limite | test.standard | Simulation accessible sans vérification de quota | | | C2 | Lancer simulation EE sans limite | test.standard | Accès direct, aucune vérification de quota visible | |
| C3 | Toggle "Suggestions d'idées" activé | test.standard | Suggestions d'idées visibles pendant la simulation | | | C3 | Toggle "Suggestions d'idées" activé | test.standard | Suggestions visibles pendant la simulation | |
| C4 | Toggle "Mode focus" activé | test.standard | Tips masqués pendant la simulation | | | C4 | Toggle "Mode focus" activé | test.standard | Tips masqués pendant la simulation | |
| C5 | Soumettre une production EE | test.standard | Rapport complet : score, critères, erreurs, modèle, exercices — rien flouté | | | C5 | Rapport complet après soumission EE | test.standard | Score, critères détaillés, erreurs expliquées, modèle, exercices — rien flouté | |
| C6 | Vérifier l'enregistrement dans le dashboard | test.standard | Production apparaît dans l'historique avec date, tâche, score | | | C6 | Production apparaît dans le dashboard | test.standard | Date, tâche, score affichés dans la liste | |
| C7 | Cliquer sur une production dans l'historique | test.standard | Rapport complet de cette production affiché | | | C7 | Cliquer une production dans l'historique | test.standard | Rapport complet de cette production réaffiché | |
| C8 | Cliquer sur "Mode Examen" | test.standard | Message "Réservé au plan Premium" + bouton upgrade | | | C8 | Cliquer "Mode Examen" | test.standard | Message "Réservé au plan Premium" + bouton upgrade | |
| C9 | Cliquer sur "EO Tâche 2 live" | test.standard | Cadenas + message "Exclusivité Premium" | | | C9 | Cliquer "EO Tâche 2 live" | test.standard | Cadenas + message "Exclusivité Premium" | |
| C10 | Indice de préparation après 5 productions | test.standard (5 prod.) | Section indice visible avec score et message interprétatif | | | C10 | Après 5 productions : indice de préparation | test.standard | Section indice visible avec score et message interprétatif | |
| C11 | Upgrade Standard → Premium : prorata affiché | test.standard | Avant confirmation, montant prorata visible (ex : "~10€ aujourd'hui") | |
--- ---
## Groupe 4 — Plan Premium ## Groupe D — Plan Premium
Ces tests vérifient le parcours complet d'un utilisateur Premium. | # | Test | Compte | Résultat attendu | ✅/❌ |
| # | Test | Compte | Résultat attendu | ✅ / ❌ |
|---|---|---|---|---| |---|---|---|---|---|
| D1 | Dashboard Premium après connexion | test.premium | Historique, indice, bouton "Lancer un examen" actif, T2 live accessible | | | D1 | Dashboard Premium après connexion | test.premium | Historique, indice, patterns, bouton examen actif, T2 live accessible | |
| D2 | Accéder à EO Tâche 2 live | test.premium | Page de préparation T2 affichée, bouton "Démarrer le dialogue" | | | D2 | Accéder à EO T2 live | test.premium | Page préparation T2, bouton "Démarrer le dialogue" actif | |
| D3 | Démarrer le dialogue T2 | test.premium | L'IA prend la parole en premier, audio reçu et joué | | | D3 | Démarrer le dialogue T2 | test.premium | État "Connecting" puis "Listening", l'IA prend la parole en premier | |
| D4 | Répondre en audio (T2) | test.premium | L'IA réagit après la réponse du candidat | | | D4 | Répondre en audio à l'IA | test.premium | L'IA réagit après la réponse du candidat, état oscille listening/speaking | |
| D5 | Fin de dialogue T2 | test.premium | Rapport complet affiché, production enregistrée avec tag "T2 Live" | | | D5 | Fin de dialogue T2 | test.premium | Rapport complet affiché, production enregistrée avec tag "T2 Live" | |
| D6 | Lancer mode Examen EE | test.premium | Page d'avertissement affichée avant démarrage | | | D6 | Déconnexion WebSocket en cours de T2 | test.premium | État "Error" affiché, message utilisateur clair, option de reprise | |
| D7 | Confirmer le lancement Examen EE | test.premium | 3 tâches visibles, timer 60:00 démarré, inarrêtable | | | D7 | Lancer mode Examen EE | test.premium | Page d'avertissement affichée avant démarrage | |
| D8 | Vérifier le blocage à T=0 (Examen EE) | test.premium | Zone de texte figée, message "Temps écoulé", envoi automatique | | | D8 | Confirmer Examen EE | test.premium | 3 tâches visibles, timer 60:00 démarré, inarrêtable | |
| D9 | Lancer mode Examen EO | test.premium | Timer 12:00 démarré, enregistrement actif | | | D9 | Blocage à T=0 (Examen EE) | test.premium | Zone de texte figée, message "Temps écoulé", envoi auto | |
| D10 | Analyse des patterns (5+ productions) | test.premium | Section "Mon profil" affiche erreurs récurrentes classées par type | | | D10 | Lancer mode Examen EO | test.premium | Timer 12:00, enregistrement actif, tâches enchaînées | |
| D11 | Exercices long terme générés | test.premium | Exercices distincts de ceux du rapport individuel | | | D11 | Analyse patterns (5+ productions) | test.premium | Section "Mon profil" avec erreurs récurrentes classées | |
--- ---
## Groupe 5 — Paiements et changements de plan ## Groupe E — Paiements Stripe
Ces tests vérifient que le système de paiement fonctionne sans régression. > ⚠️ Utiliser les cartes de test Stripe :
> - Carte valide : `4242 4242 4242 4242` (date future, CVC libre)
> - Carte refusée : `4000 0000 0000 0002`
> ⚠️ Ces tests utilisent les cartes de test Stripe. | # | Test | Compte | Résultat attendu | ✅/❌ |
> Carte valide : `4242 4242 4242 4242` — expiry : n'importe quelle date future — CVC : n'importe lequel.
> Carte refusée : `4000 0000 0000 0002`
| # | Test | Compte | Résultat attendu | ✅ / ❌ |
|---|---|---|---|---| |---|---|---|---|---|
| E1 | Upgrade Free → Standard | test.free | Redirection Stripe, paiement, retour dashboard Standard, plan = "standard" | | | E1 | Upgrade Free → Standard (Stripe Checkout) | test.free | Redirection full page vers Stripe, paiement, retour dashboard Standard | |
| E2 | Upgrade Free → Premium | test.free | Redirection Stripe, paiement, retour dashboard Premium, plan = "premium" | | | E2 | Invalidation du cache plan après paiement | test.free → standard | usePlan() refetch automatiquement, dashboard bascule sans recharger la page | |
| E3 | Upgrade Standard → Premium (prorata) | test.standard | Montant prorata affiché avant confirmation, accès Premium après paiement | | | E3 | Upgrade Free → Premium | test.free | Même flux que E1, plan=premium après retour | |
| E4 | Paiement refusé | test.free | Message d'erreur Stripe, plan inchangé, pas de régression | | | E4 | Upgrade Standard → Premium avec prorata | test.standard | Montant prorata affiché avant confirmation, accès Premium immédiat | |
| E5 | Accès immédiat après paiement | test.free | Dashboard du nouveau plan affiché sans délai ni reconnexion | | | E5 | Paiement refusé (carte 4000 0000 0000 0002) | test.free | Message d'erreur Stripe clair, plan inchangé | |
| E6 | Plan mis à jour dans Supabase | test.free | Colonne `plan` dans la table `profiles` reflète le nouveau plan | | | E6 | Annuler au milieu du Checkout | test.free | Retour sur `/billing` ou `/pricing`, plan inchangé | |
--- ---
## Groupe 6 — Sécurité et permissions ## Groupe F — Sécurité et permissions
Ces tests vérifient qu'un utilisateur ne peut pas accéder à ce qu'il ne devrait pas. | # | Test | Compte | Résultat attendu | ✅/❌ |
| # | Test | Compte | Résultat attendu | ✅ / ❌ |
|---|---|---|---|---| |---|---|---|---|---|
| F1 | Accès direct URL /t2-live sans être Premium | test.standard | Redirection ou message "Accès réservé au Premium" | | | F1 | URL directe `/t2-live` en Standard | test.standard | Redirection ou PaywallModal, pas d'accès à la page | |
| F2 | Appel API POST /corrections/ee sans JWT | — (non connecté) | Réponse 401 Unauthorized | | | F2 | Inspecter DevTools → clés privées | — | Aucune clé `SERVICE_ROLE`, `GEMINI`, `STRIPE_SECRET` visible | |
| F3 | Appel API GET /simulations d'un autre utilisateur | test.free | Réponse 403 ou liste vide (RLS Supabase) | | | F3 | Inspecter DevTools → JWT en clair dans localStorage | test.free | JWT Supabase visible (normal, c'est un access token) mais pas de refresh token exposé | |
| F4 | Tentative de lancer examen via API (plan standard) | test.standard | Réponse 403 — plan insuffisant | | | F4 | Modifier le plan dans DevTools via Redux/state | test.free | La modification locale n'a aucun effet — le backend reste l'autorité | |
| F5 | Variable GEMINI_API_KEY visible dans le frontend | — | Introuvable dans le code source JavaScript chargé par le navigateur | | | F5 | Rapport contenant des caractères HTML potentiellement malicieux | test.standard | Rendu comme texte, pas comme HTML (aucune exécution) | |
| F6 | CSP header présent dans la réponse HTTP | — | `Content-Security-Policy` défini dans les headers Cloudflare Pages | |
| F7 | Console navigateur : pas de log de JWT ou données perso | test.free | Aucun `console.log` contenant email, token, payload API | |
--- ---
## Groupe 7 — Dataset complet (smoke test) ## Groupe G — Responsive mobile
À rejouer avant chaque déploiement en production. Tests à rejouer sur DevTools mobile emulation (iPhone SE, iPhone 12, Samsung Galaxy) ET sur vrai mobile si possible.
Ce sont les 10 scénarios les plus critiques, un par type de parcours.
| # | Test | Résultat attendu | ✅/❌ |
|---|---|---|---|
| G1 | Page Home lisible sur écran 375px | Pas de débordement horizontal, CTA accessible | |
| G2 | Formulaire de login sur mobile | Champs bien dimensionnés, clavier virtuel ne cache pas le bouton | |
| G3 | Dashboard Free sur mobile | Compteur visible, aperçu flouté lisible | |
| G4 | Simulation EE sur mobile | Zone de texte utilisable, pas de zoom intempestif | |
| G5 | Enregistrement audio EO sur mobile | Permission microphone demandée, enregistrement fonctionnel | |
| G6 | T2 live sur mobile (Premium) | WebSocket fonctionne, audio bidirectionnel OK | |
| G7 | Modal PaywallModal sur mobile | Scrollable si contenu déborde, bouton fermeture accessible | |
---
## Groupe Z — Smoke test (avant chaque déploiement)
Les 10 scénarios les plus critiques, à rejouer dans l'ordre avant chaque déploiement production.
| # | Test | Description rapide | | # | Test | Description rapide |
|---|---|---| |---|---|---|
| Z1 | Inscription + simulation Free | Nouvel utilisateur → simulation → rapport flouté | | Z1 | Inscription + première simulation Free | Compte créé → simulation → rapport flouté visible |
| Z2 | Blocage quota Free | 6e simulation → modal de blocage | | Z2 | Blocage quota Free | 6e simulation → modal de blocage |
| Z3 | Simulation Standard complète | Login → simulation → rapport complet → dashboard | | Z3 | Simulation Standard complète | Login → simulation → rapport complet → dashboard |
| Z4 | Mode examen bloqué en Standard | Bouton mode examen → message upgrade | | Z4 | Mode examen bloqué en Standard | Bouton mode examen → message upgrade |
| Z5 | T2 live Premium | Login → T2 live → dialogue → rapport | | Z5 | T2 live Premium | Login → T2 live → dialogue → rapport |
| Z6 | Mode examen EE complet | Lancement → timer → T=0 → envoi auto → rapport | | Z6 | Mode examen EE complet | Lancement → timer → T=0 → envoi auto → rapport |
| Z7 | Paiement Free → Standard | Checkout Stripe → webhook → dashboard Standard | | Z7 | Paiement Free → Standard | Stripe Checkout → retour dashboard Standard sans rechargement |
| Z8 | Prorata Standard → Premium | Montant affiché → confirmation → accès Premium | | Z8 | Prorata Standard → Premium | Montant affiché → confirmation → accès Premium immédiat |
| Z9 | Sécurité JWT | Appel API sans token → 401 | | Z9 | Déconnexion + accès protégé | Logout → accès `/dashboard` → redirection `/login` |
| Z10 | Déconnexion + accès protégé | Déconnexion → accès /dashboard → redirection /login | | Z10 | Responsive mobile Home + Login | Affichage correct sur iPhone SE |
--- ---
@ -160,7 +176,7 @@ Test échoue
1. Noter le numéro du test et le comportement observé 1. Noter le numéro du test et le comportement observé
2. NE PAS continuer la session Claude Code 2. NE PAS continuer la session Claude Code
3. Identifier le fichier modifié qui a causé la régression 3. Identifier le fichier modifié qui a causé la régression
4. Revenir au commit Git précédent (git revert ou git checkout) 4. Revenir au commit Git précédent
5. Analyser la cause avec Claude (chat, pas code) 5. Analyser la cause avec Claude (chat, pas code)
6. Reformuler le prompt en ajoutant la contrainte manquante 6. Reformuler le prompt en ajoutant la contrainte manquante
7. Relancer la session Claude Code 7. Relancer la session Claude Code
@ -171,8 +187,16 @@ Test échoue
## Historique des sessions ## Historique des sessions
> Remplir après chaque session Claude Code. > Remplir après chaque session Claude Code frontend.
| Date | Session | Tests rejoués | Résultat | Notes | | Date | Session | Tests rejoués | Résultat | Notes |
|---|---|---|---|---| |---|---|---|---|---|
| — | — | — | — | — | | — | — | — | — | — |
---
## Historique de ce document
| Version | Date | Changements |
|---|---|---|
| 1.0 | 2026-04-17 | Création initiale, 55 tests frontend |

File diff suppressed because it is too large Load diff

View file

@ -1,365 +1,260 @@
# TEST_ENVIRONMENT.md — Expria / Coach TCF Canada # TEST_ENVIRONMENT.md — Expria Frontend
> **Document de référence — Version 1.0** > **Document de référence — Version 1.0**
> Ce document décrit comment créer et réinitialiser l'environnement de test. > Ce document décrit comment configurer et utiliser l'environnement de test frontend.
> Les comptes de test permettent de rejouer tous les parcours utilisateur > Complément au `TEST_ENVIRONMENT.md` backend (qui décrit la base de données Supabase et les comptes de test).
> sans créer de vrais abonnements ni passer par Stripe.
--- ---
## 1. Principe ## 1. Principe
L'environnement de test est une configuration connue et reproductible de la base de données. L'environnement de test frontend permet de faire tourner le code React localement, connecté soit au backend de production (`api.expria.app`), soit à une instance backend locale (pour tests isolés).
Il consiste en 4 comptes Supabase préconfigurés, un par situation critique.
**Règles absolues :** **Règles absolues :**
- Ces comptes n'existent que dans l'environnement de développement / staging - Ne jamais utiliser de comptes réels (utilisateurs payants) pour les tests
- Jamais en production - Utiliser exclusivement les 4 comptes `@gmail.com` documentés dans le backend
- Les emails se terminent par `@expria.local` — bloqués à l'inscription dans le code - Ne jamais committer les fichiers `.env` (présents dans `.gitignore`)
- Les mots de passe sont documentés ici — ne jamais les utiliser pour de vrais comptes
--- ---
## 2. Les 4 comptes de test ## 2. Prérequis
| Compte | Plan | simulations_used | Cas testé | | Outil | Version minimum | Vérification |
|---|---|---|
| Node.js | 20.x LTS | `node --version` |
| npm | 10.x | `npm --version` |
| Git | 2.x | `git --version` |
| Navigateur moderne | Chrome 120+, Firefox 120+, Safari 17+ | — |
Recommandé :
- VS Code (ou Cursor) avec extensions : ESLint, Prettier, Tailwind CSS IntelliSense, Vitest
- DevTools React (extension navigateur)
---
## 3. Configuration — mode dev connecté au backend de production
C'est le mode par défaut. Le frontend local appelle `https://api.expria.app`.
### Fichier `.env` à créer à la racine du projet
```
# URL du backend de production
VITE_API_URL=https://api.expria.app
# Supabase (clé publique uniquement)
VITE_SUPABASE_URL=https://<project>.supabase.co
VITE_SUPABASE_ANON_KEY=<clé publique à obtenir auprès de Hermann>
# Flags de features (à ajuster selon le sprint)
VITE_ENABLE_T2_LIVE=false
# Monitoring (optionnel)
VITE_SENTRY_DSN=
```
**Les vraies valeurs** sont à récupérer dans :
- Le coffre-fort de Hermann (1Password / Bitwarden)
- Ou auprès de Hermann directement
Après modification du `.env`, redémarrer le dev server (Vite charge les env au démarrage seulement).
### Lancement
```bash
npm install
npm run dev
```
Le frontend est disponible sur `http://localhost:5173` (port Vite par défaut).
---
## 4. Configuration — mode dev connecté au backend local
Utile quand on veut :
- Tester une modification backend avant déploiement
- Déboguer un flux complet frontend + backend
- Travailler sans connexion internet stable
### Prérequis
Le backend doit tourner localement dans un autre terminal :
```bash
cd D:\expria-backend
npm run dev
# Backend disponible sur http://localhost:3000 (vérifier le port exact)
```
### Fichier `.env` (ajustement)
```
VITE_API_URL=http://localhost:3000
# Le reste reste identique
```
### Points d'attention
- **CORS** : le backend doit autoriser `http://localhost:5173` dans sa configuration CORS. À vérifier dans `expria-backend/src/index.ts`. Si CORS bloque les requêtes, voir SEC-02 dans `SECURITY.md`.
- **WebSocket T2** : devient `ws://localhost:3000/t2/live` (non sécurisé en dev local). Acceptable pour le dev.
- **Supabase** : reste en production même en dev local (Supabase n'a pas de mode local simple). Les comptes de test `@gmail.com` fonctionnent identiquement.
---
## 5. Comptes de test (rappel)
Identiques aux comptes documentés dans `expria-backend/docs/TEST_ENVIRONMENT.md` :
| Email | Plan | Simulations utilisées | Cas testé |
|---|---|---|---| |---|---|---|---|
| test.free@expria.local | free | 0 | Parcours Free normal | | test.free@gmail.com | free | 0 | Parcours Free normal |
| test.standard@expria.local | standard | 12 | Parcours Standard complet | | test.standard@gmail.com | standard | 12 | Parcours Standard complet |
| test.premium@expria.local | premium | 28 | Parcours Premium complet | | test.premium@gmail.com | premium | 28 | Parcours Premium complet |
| test.quota@expria.local | free | 5 | Blocage quota Free | | test.quota@gmail.com | free | 5 | Blocage quota Free |
**Mot de passe pour tous les comptes de test :** `Expria2025!test` **Mot de passe commun :** `Expria2025!test`
Les comptes sont créés par le script SQL de `expria-backend/docs/TEST_ENVIRONMENT.md` §3. Si un compte manque ou est corrompu, demander à Hermann de rejouer le script côté Supabase.
--- ---
## 3. Script de création — à exécuter dans Supabase SQL Editor ## 6. Scénarios de test spécifiques
> ⚠️ À exécuter UNE SEULE FOIS dans l'environnement de développement. ### 6.1 Simuler un utilisateur Free qui atteint le quota
> Ne jamais exécuter en production.
```sql 1. Se connecter avec `test.free@gmail.com`
-- ============================================================= 2. Utiliser `test.quota@gmail.com` à la place (qui a déjà 5/5 utilisées)
-- EXPRIA — Création des comptes de test 3. Tenter de lancer une simulation → modal de blocage doit apparaître
-- Environnement : développement / staging uniquement
-- =============================================================
-- Étape 1 : Créer les utilisateurs dans auth.users **Ne pas** essayer de forcer le compteur en soumettant 5 simulations réelles — ça consomme des appels à DeepSeek (payant) et pollue la base.
-- (Supabase gère le hash du mot de passe automatiquement)
INSERT INTO auth.users ( ### 6.2 Simuler un changement de plan
id,
email,
encrypted_password,
email_confirmed_at,
created_at,
updated_at,
raw_app_meta_data,
raw_user_meta_data,
is_super_admin,
role
) VALUES
(
'00000000-0000-0000-0000-000000000001',
'test.free@expria.local',
crypt('Expria2025!test', gen_salt('bf')),
NOW(), NOW(), NOW(),
'{"provider":"email","providers":["email"]}',
'{}', false, 'authenticated'
),
(
'00000000-0000-0000-0000-000000000002',
'test.standard@expria.local',
crypt('Expria2025!test', gen_salt('bf')),
NOW(), NOW(), NOW(),
'{"provider":"email","providers":["email"]}',
'{}', false, 'authenticated'
),
(
'00000000-0000-0000-0000-000000000003',
'test.premium@expria.local',
crypt('Expria2025!test', gen_salt('bf')),
NOW(), NOW(), NOW(),
'{"provider":"email","providers":["email"]}',
'{}', false, 'authenticated'
),
(
'00000000-0000-0000-0000-000000000004',
'test.quota@expria.local',
crypt('Expria2025!test', gen_salt('bf')),
NOW(), NOW(), NOW(),
'{"provider":"email","providers":["email"]}',
'{}', false, 'authenticated'
)
ON CONFLICT (id) DO NOTHING;
-- Étape 2 : Créer les profils dans la table profiles Pour tester le flux d'upgrade sans vraiment payer :
INSERT INTO profiles ( 1. Aller sur `/pricing` avec `test.free@gmail.com`
id, 2. Cliquer "Choisir Standard"
email, 3. Sur la page Stripe Checkout, utiliser la carte de test `4242 4242 4242 4242`
plan, 4. Date d'expiration : n'importe laquelle dans le futur
simulations_used, 5. CVC : n'importe lequel (3 chiffres)
stripe_customer_id,
stripe_subscription_id,
plan_expires_at,
created_at,
updated_at
) VALUES
(
'00000000-0000-0000-0000-000000000001',
'test.free@expria.local',
'free', 0, NULL, NULL, NULL,
NOW(), NOW()
),
(
'00000000-0000-0000-0000-000000000002',
'test.standard@expria.local',
'standard', 12, 'cus_test_standard', 'sub_test_standard',
NOW() + INTERVAL '14 days',
NOW(), NOW()
),
(
'00000000-0000-0000-0000-000000000003',
'test.premium@expria.local',
'premium', 28, 'cus_test_premium', 'sub_test_premium',
NOW() + INTERVAL '21 days',
NOW(), NOW()
),
(
'00000000-0000-0000-0000-000000000004',
'test.quota@expria.local',
'free', 5, NULL, NULL, NULL,
NOW(), NOW()
)
ON CONFLICT (id) DO UPDATE SET
plan = EXCLUDED.plan,
simulations_used = EXCLUDED.simulations_used,
stripe_customer_id = EXCLUDED.stripe_customer_id,
stripe_subscription_id = EXCLUDED.stripe_subscription_id,
plan_expires_at = EXCLUDED.plan_expires_at,
updated_at = NOW();
-- Étape 3 : Insérer des productions de test pour les comptes Standard et Premium Le webhook Stripe met à jour `plan = 'standard'` dans Supabase. Pour revenir à l'état initial après test, Hermann peut rejouer le script de reset de `expria-backend/docs/TEST_ENVIRONMENT.md` §5.
-- (nécessaire pour tester le dashboard, l'historique, et l'analyse des patterns)
INSERT INTO productions ( ### 6.3 Simuler un paiement refusé
id, user_id, tache, mode, contenu, score, nclc, rapport, created_at
) VALUES
-- 5 productions pour test.standard (active l'indice de préparation)
(gen_random_uuid(), '00000000-0000-0000-0000-000000000002',
'EE_T1', 'entrainement',
'Texte de production test EE T1 — compte standard',
14.5, 8,
'{"criteres":[{"nom":"Cohérence","score":3},{"nom":"Lexique","score":4}],"erreurs":["Connecteurs logiques insuffisants"],"exercices":["Exercice connecteurs"]}',
NOW() - INTERVAL '10 days'),
(gen_random_uuid(), '00000000-0000-0000-0000-000000000002',
'EE_T2', 'entrainement',
'Texte de production test EE T2 — compte standard',
15.0, 8,
'{"criteres":[{"nom":"Cohérence","score":4},{"nom":"Lexique","score":3}],"erreurs":["Vocabulaire limité"],"exercices":["Exercice vocabulaire"]}',
NOW() - INTERVAL '8 days'),
(gen_random_uuid(), '00000000-0000-0000-0000-000000000002',
'EE_T3', 'entrainement',
'Texte de production test EE T3 — compte standard',
13.5, 7,
'{"criteres":[{"nom":"Cohérence","score":3},{"nom":"Lexique","score":3}],"erreurs":["Structure argumentative faible"],"exercices":["Exercice argumentation"]}',
NOW() - INTERVAL '6 days'),
(gen_random_uuid(), '00000000-0000-0000-0000-000000000002',
'EO_T1', 'entrainement',
NULL, 14.0, 8,
'{"criteres":[{"nom":"Phonologie","score":4},{"nom":"Lexique","score":3}],"erreurs":["Liaisons manquantes"],"exercices":["Exercice liaisons"]}',
NOW() - INTERVAL '4 days'),
(gen_random_uuid(), '00000000-0000-0000-0000-000000000002',
'EO_T3', 'entrainement',
NULL, 15.5, 9,
'{"criteres":[{"nom":"Phonologie","score":4},{"nom":"Lexique","score":4}],"erreurs":[],"exercices":[]}',
NOW() - INTERVAL '2 days'),
-- 7 productions pour test.premium (active patterns + indice) Même flux que 6.2 mais avec la carte `4000 0000 0000 0002`. Stripe retourne "paiement refusé". Le plan reste inchangé, le message d'erreur doit s'afficher clairement.
(gen_random_uuid(), '00000000-0000-0000-0000-000000000003',
'EE_T1', 'entrainement', ### 6.4 Simuler une session expirée
'Texte production EE T1 — premium',
16.0, 9, 1. Se connecter normalement
'{"criteres":[{"nom":"Cohérence","score":4},{"nom":"Lexique","score":4}],"erreurs":["Connecteurs logiques"],"exercices":["Connecteurs"]}', 2. Ouvrir DevTools → Application → Local Storage
NOW() - INTERVAL '20 days'), 3. Trouver la clé contenant le JWT Supabase (`sb-<project>-auth-token`)
(gen_random_uuid(), '00000000-0000-0000-0000-000000000003', 4. Modifier la valeur pour casser le token (changer 1 caractère)
'EE_T2', 'entrainement', 5. Rafraîchir la page
'Texte production EE T2 — premium', 6. Le frontend doit détecter le JWT invalide → message "Session expirée" + redirect `/login`
15.5, 9,
'{"criteres":[{"nom":"Cohérence","score":4},{"nom":"Lexique","score":4}],"erreurs":["Connecteurs logiques"],"exercices":["Connecteurs"]}', Ce test vérifie SEC-06 (gestion des sessions expirées).
NOW() - INTERVAL '16 days'),
(gen_random_uuid(), '00000000-0000-0000-0000-000000000003', ### 6.5 Simuler T2 Live sans avoir Premium
'EE_T3', 'examen',
'Texte production EE T3 — premium examen', 1. Se connecter avec `test.free@gmail.com` ou `test.standard@gmail.com`
17.0, 10, 2. Accéder à l'URL `/t2-live` directement
'{"criteres":[{"nom":"Cohérence","score":5},{"nom":"Lexique","score":4}],"erreurs":[],"exercices":[]}', 3. Le frontend doit afficher un PaywallModal ou rediriger
NOW() - INTERVAL '12 days'), 4. Si malgré tout une requête WebSocket est envoyée, le backend doit fermer la connexion avec close code 4003 (`PLAN_INSUFFICIENT`)
(gen_random_uuid(), '00000000-0000-0000-0000-000000000003',
'EO_T1', 'entrainement', ---
NULL, 16.5, 9,
'{"criteres":[{"nom":"Phonologie","score":4},{"nom":"Lexique","score":4}],"erreurs":["Connecteurs logiques"],"exercices":["Connecteurs oraux"]}', ## 7. Matrice de compatibilité navigateurs
NOW() - INTERVAL '9 days'),
(gen_random_uuid(), '00000000-0000-0000-0000-000000000003', Ces navigateurs sont ceux que Claude Code doit considérer comme supportés. Tester au minimum sur les deux premiers.
'EO_T2_LIVE', 'entrainement',
NULL, 15.0, 8, | Navigateur | Version minimum | Priorité de test | Notes |
'{"criteres":[{"nom":"Interaction","score":4},{"nom":"Phonologie","score":3}],"erreurs":["Hésitations fréquentes"],"exercices":["Fluidité orale"]}', |---|---|---|---|
NOW() - INTERVAL '6 days'), | Chrome (desktop) | 120 | 🔴 obligatoire | Majoritaire chez les utilisateurs |
(gen_random_uuid(), '00000000-0000-0000-0000-000000000003', | Chrome Mobile (Android) | 120 | 🔴 obligatoire | Audience Afrique = mobile-first |
'EO_T3', 'entrainement', | Safari Mobile (iOS) | 17 | 🟡 recommandé | Audience Canada = iPhone courant |
NULL, 16.0, 9, | Firefox (desktop) | 120 | 🟢 optionnel | Usage faible |
'{"criteres":[{"nom":"Phonologie","score":4},{"nom":"Lexique","score":4}],"erreurs":["Connecteurs logiques"],"exercices":["Connecteurs oraux"]}', | Safari Desktop | 17 | 🟢 optionnel | Niche |
NOW() - INTERVAL '3 days'),
(gen_random_uuid(), '00000000-0000-0000-0000-000000000003', **Attention particulière pour mobile Android (Afrique) :**
'EE_T1', 'examen', - Connexions 3G/4G instables → vérifier que retry dans `api-client.ts` gère bien
'Texte production EE T1 examen — premium', - RAM limitée → éviter les listes illimitées, paginer
17.5, 10, - Clavier virtuel qui masque les inputs → vérifier scroll automatique
'{"criteres":[{"nom":"Cohérence","score":5},{"nom":"Lexique","score":4}],"erreurs":[],"exercices":[]}',
NOW() - INTERVAL '1 day') Test mobile rapide via DevTools : Chrome → F12 → Toggle device toolbar → sélectionner "iPhone 12" ou "Galaxy S20".
ON CONFLICT DO NOTHING;
Test mobile réel : idéal avant chaque release production, via un vrai téléphone ou un service comme BrowserStack (payant).
---
## 8. Simulation de conditions réseau dégradées
Dans Chrome DevTools → Network tab → Throttling dropdown :
- **Fast 3G** : simule une connexion 3G typique (Afrique)
- **Slow 3G** : simule une connexion 2G (zones rurales)
- **Offline** : simule une perte réseau
Tests à effectuer en mode "Fast 3G" :
- Login : doit répondre en < 10s
- Submit simulation : timeout à 30s doit fonctionner
- T2 Live : doit tenir la connexion WebSocket ou échouer gracieusement
---
## 9. Procédure avant chaque session Claude Code
```
[ ] D:\expria-frontend existe et contient docs/ à jour
[ ] .env est configuré (demander à Hermann si nécessaire)
[ ] npm install a été exécuté récemment (dernière modification package.json)
[ ] npm run dev démarre sans erreur
[ ] Login test.free@gmail.com fonctionne
[ ] Un commit Git propre existe avant de commencer
[ ] Les documents de référence ont été lus (cf. ONBOARDING.md §7)
``` ```
--- ---
## 4. Script de vérification — confirmer que les comptes existent ## 10. Procédure après chaque session Claude Code
```sql
-- Vérifier les profils créés
SELECT
id,
email,
plan,
simulations_used,
plan_expires_at
FROM profiles
WHERE email LIKE '%@expria.local'
ORDER BY email;
-- Vérifier les productions créées
SELECT
p.email,
prod.tache,
prod.mode,
prod.score,
prod.created_at
FROM productions prod
JOIN profiles p ON p.id = prod.user_id
WHERE p.email LIKE '%@expria.local'
ORDER BY p.email, prod.created_at;
``` ```
[ ] npm run typecheck : 0 erreur
**Résultat attendu :** 4 profils, 12 productions au total. [ ] npm run test : 0 échec
[ ] Les tests du Golden Dataset concernés ont été rejoués (cf. GOLDEN_DATASET.md)
--- [ ] Si modifications d'état (profils de test modifiés par des actions de test),
rejouer le script de reset backend
## 5. Script de réinitialisation — remettre l'environnement à zéro [ ] Un commit Git a été fait avec un message clair
[ ] CHANGELOG.md mis à jour
> À utiliser quand les comptes de test ont été modifiés par des sessions de test
> et qu'on veut repartir d'un état propre.
```sql
-- Supprimer les productions de test
DELETE FROM productions
WHERE user_id IN (
SELECT id FROM profiles WHERE email LIKE '%@expria.local'
);
-- Remettre les profils à leur état initial
UPDATE profiles SET
plan = 'free',
simulations_used = 0,
stripe_customer_id = NULL,
stripe_subscription_id = NULL,
plan_expires_at = NULL,
updated_at = NOW()
WHERE email = 'test.free@expria.local';
UPDATE profiles SET
plan = 'standard',
simulations_used = 12,
stripe_customer_id = 'cus_test_standard',
stripe_subscription_id = 'sub_test_standard',
plan_expires_at = NOW() + INTERVAL '14 days',
updated_at = NOW()
WHERE email = 'test.standard@expria.local';
UPDATE profiles SET
plan = 'premium',
simulations_used = 28,
stripe_customer_id = 'cus_test_premium',
stripe_subscription_id = 'sub_test_premium',
plan_expires_at = NOW() + INTERVAL '21 days',
updated_at = NOW()
WHERE email = 'test.premium@expria.local';
UPDATE profiles SET
plan = 'free',
simulations_used = 5,
stripe_customer_id = NULL,
stripe_subscription_id = NULL,
plan_expires_at = NULL,
updated_at = NOW()
WHERE email = 'test.quota@expria.local';
-- Réinsérer les productions (copier-coller le bloc INSERT de la section 3)
``` ```
--- ---
## 6. Bloquer les inscriptions @expria.local en production ## 11. Debugging
Ajouter cette validation dans le backend (middleware d'inscription) : ### Erreur "CORS blocked" au démarrage
- Vérifier que le backend autorise `http://localhost:5173`
- Vérifier que `VITE_API_URL` dans `.env` pointe vers la bonne URL backend
- Voir SEC-02 dans `SECURITY.md`
```typescript ### Erreur "Invalid JWT" après login
// src/middleware/auth.ts — backend Hono - Le JWT expire au bout de ~1h, se déconnecter et se reconnecter
- Vider le localStorage (DevTools → Application → Clear site data)
- Vérifier que `VITE_SUPABASE_URL` correspond bien au projet Supabase utilisé par le backend
const BLOCKED_EMAIL_DOMAINS = ['@expria.local'] ### Les variables d'environnement ne sont pas prises en compte
- Vite charge `.env` au démarrage uniquement — redémarrer `npm run dev` après modification
- Les variables doivent commencer par `VITE_` pour être exposées côté client
- Vérifier qu'il n'y a pas d'espace autour du `=` dans `.env`
export function validateEmail(email: string): boolean { ### WebSocket T2 Live ne se connecte pas
const isBlocked = BLOCKED_EMAIL_DOMAINS.some(domain => - Vérifier `VITE_ENABLE_T2_LIVE=true` dans `.env`
email.toLowerCase().endsWith(domain) - Vérifier que l'utilisateur est bien Premium
) - Ouvrir DevTools → Network → filtrer par "WS" pour voir le handshake WebSocket
if (isBlocked) return false - Vérifier les close codes : 4001 = auth, 4003 = plan insuffisant
return true
}
```
Et dans la route d'inscription :
```typescript
// src/routes/auth.ts
app.post('/auth/register', async (c) => {
const { email, password } = await c.req.json()
if (!validateEmail(email)) {
return c.json({ error: 'Email non autorisé' }, 400)
}
// ... suite de l'inscription
})
```
--- ---
## 7. Procédure complète — première mise en place ## 12. Historique
``` | Version | Date | Changements |
Étape 1 : Ouvrir Supabase Dashboard → SQL Editor |---|---|---|
Étape 2 : Copier-coller le script de la section 3 | 1.0 | 2026-04-17 | Création initiale |
Étape 3 : Exécuter
Étape 4 : Copier-coller le script de vérification (section 4)
Étape 5 : Vérifier : 4 profils + 12 productions affichés
Étape 6 : Tester une connexion avec test.free@expria.local
dans l'application (mot de passe : Expria2025!test)
Étape 7 : Vérifier que le dashboard Free s'affiche correctement
```
---
## 8. Procédure — avant chaque session Golden Dataset
```
Étape 1 : Exécuter le script de réinitialisation (section 5)
Étape 2 : Exécuter le script de vérification (section 4)
Étape 3 : Confirmer que les 4 profils sont dans l'état attendu
Étape 4 : Lancer les tests du Golden Dataset
```