docs: enrichir ARCHITECTURE, DEVELOPMENT_PRINCIPLES, GOLDEN_DATASET, TESTS_AUTOMATISES, TEST_ENVIRONMENT
This commit is contained in:
parent
52b8e9d011
commit
f343fb4696
5 changed files with 1649 additions and 1476 deletions
1012
docs/ARCHITECTURE.md
1012
docs/ARCHITECTURE.md
File diff suppressed because it is too large
Load diff
|
|
@ -1,10 +1,8 @@
|
|||
# DEVELOPMENT_PRINCIPLES.md — Expria / Coach TCF Canada
|
||||
# DEVELOPMENT_PRINCIPLES.md — Expria Frontend
|
||||
|
||||
> **Document de référence — Version 1.0**
|
||||
> Ce document définit les règles que Claude Code doit lire et respecter
|
||||
> à chaque session de développement, sans exception.
|
||||
> Il est conçu pour un projet maintenu par un fondateur non-technique
|
||||
> assisté par l'IA.
|
||||
> Ce document définit les règles que toute session Claude Code sur le frontend doit lire et respecter, sans exception.
|
||||
> Adapté de `DEVELOPMENT_PRINCIPLES.md` du backend — les principes sont les mêmes, les règles spécifiques sont ajustées.
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -13,10 +11,12 @@
|
|||
**Avant d'écrire la moindre ligne de code, tu dois :**
|
||||
|
||||
1. Lire ce fichier en entier
|
||||
2. Lire ARCHITECTURE.md
|
||||
3. Lire PLANS_TARIFAIRES.md
|
||||
4. Annoncer : "Documents lus. Voici mon plan pour cette session."
|
||||
5. Produire un plan détaillé et attendre la validation
|
||||
2. Lire `ARCHITECTURE.md` frontend
|
||||
3. Lire `PLANS_TARIFAIRES.md` (copie du backend)
|
||||
4. Lire `PARCOURS_UTILISATEURS.md` (section du plan concerné)
|
||||
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".**
|
||||
|
||||
|
|
@ -24,7 +24,7 @@
|
|||
|
||||
## 1. Le cycle de travail obligatoire
|
||||
|
||||
Chaque modification, petite ou grande, suit ce cycle sans exception :
|
||||
Identique au backend :
|
||||
|
||||
```
|
||||
ÉTAPE 1 — ANALYSE
|
||||
|
|
@ -47,13 +47,15 @@ Chaque modification, petite ou grande, suit ce cycle sans exception :
|
|||
Maximum 3 fichiers touchés par étape
|
||||
|
||||
ÉTAPE 4 — VÉRIFICATION
|
||||
Lancer npm run test (backend)
|
||||
Annoncer le résultat : "X tests passés, 0 échecs"
|
||||
Lancer npm run typecheck
|
||||
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
|
||||
|
||||
ÉTAPE 5 — RÉSUMÉ
|
||||
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
|
||||
Claude Code ne commence jamais à coder sans plan validé.
|
||||
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
|
||||
Si une tâche nécessite de modifier plus de 3 fichiers,
|
||||
elle est découpée en plusieurs étapes avec validation intermédiaire.
|
||||
Chaque étape est testée avant de passer à la suivante.
|
||||
Si une tâche nécessite de modifier plus de 3 fichiers, elle est découpée en plusieurs étapes avec validation intermédiaire.
|
||||
|
||||
### Règle C — Tests verts avant de continuer
|
||||
Après chaque étape d'implémentation :
|
||||
`npm run typecheck` doit retourner 0 erreur.
|
||||
`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
|
||||
Toutes les vérifications de permissions lisent depuis `lib/access.ts`.
|
||||
Exemple interdit :
|
||||
```typescript
|
||||
// ❌ JAMAIS
|
||||
if (user.plan === 'premium') { ... }
|
||||
if (user.plan !== 'free') { ... }
|
||||
```
|
||||
Exemple correct :
|
||||
```typescript
|
||||
|
||||
// ✅ TOUJOURS
|
||||
const perms = getPlanPermissions(user.plan)
|
||||
if (perms.exam_mode) { ... }
|
||||
import { hasAccess } from '@/entities/user/lib'
|
||||
if (hasAccess(user.plan, 'exam_mode')) { ... }
|
||||
```
|
||||
|
||||
Voir ADR 005 pour le détail.
|
||||
|
||||
### 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`
|
||||
- `GEMINI_API_KEY`
|
||||
- `DEEPSEEK_API_KEY`
|
||||
- `STRIPE_SECRET_KEY`
|
||||
- `STRIPE_WEBHOOK_SECRET`
|
||||
|
||||
Le frontend n'a accès qu'à :
|
||||
- `VITE_SUPABASE_ANON_KEY` (clé publique Supabase)
|
||||
- `VITE_API_URL` (URL du backend)
|
||||
### Règle F — Jamais d'appel direct à Supabase pour des données métier
|
||||
Supabase côté frontend est **exclusivement** utilisé pour l'auth :
|
||||
```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
|
||||
Toute modification du schéma Supabase passe par un fichier de migration
|
||||
dans `supabase/migrations/`.
|
||||
Jamais de modification directe dans le dashboard Supabase sans fichier de migration correspondant.
|
||||
// ❌ Interdit
|
||||
supabase.from('productions').select()
|
||||
supabase.from('profiles').update(...)
|
||||
```
|
||||
|
||||
### Règle G — Jamais de suppression de données utilisateur
|
||||
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.
|
||||
Toute lecture/écriture de données métier passe par le backend Hono.
|
||||
|
||||
### Règle H — 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.
|
||||
### Règle G — access.ts doit rester identique au backend
|
||||
`src/entities/user/access.ts` est une **copie bit-à-bit** de `expria-backend/src/lib/access.ts`.
|
||||
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.
|
||||
|
||||
### 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
|
||||
|
||||
### 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)
|
||||
controllers/ → camelCase.ts (simulationController.ts)
|
||||
middleware/ → camelCase.ts (auth.ts, plan.ts)
|
||||
lib/ → camelCase.ts (access.ts, deepseek.ts)
|
||||
__tests__/ → camelCase.test.ts (canUserSimulate.test.ts)
|
||||
```
|
||||
src/
|
||||
app/ → camelCase.tsx (providers.tsx, router.tsx)
|
||||
entities/
|
||||
<domaine>/
|
||||
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
|
||||
|
||||
```typescript
|
||||
// Variables : camelCase
|
||||
const userPlan = 'premium'
|
||||
const simulationsUsed = 5
|
||||
|
||||
// Fonctions : camelCase, verbe en premier
|
||||
function getUserPlan(userId: string) { }
|
||||
function canUserSimulate(profile: Profile) { }
|
||||
function updateUserPlan(userId: string, plan: Plan) { }
|
||||
function hasAccess(plan: Plan, feature: BooleanPermission): boolean { }
|
||||
function canSimulate(plan: Plan, used: number): SimulationCheck { }
|
||||
|
||||
// Composants React : PascalCase
|
||||
function DashboardPage() { }
|
||||
function PaywallModal() { }
|
||||
|
||||
// Hooks : use + PascalCase (convention React)
|
||||
function useAuth() { }
|
||||
function usePlan() { }
|
||||
|
||||
// Types et interfaces : PascalCase
|
||||
type Plan = 'free' | 'standard' | 'premium'
|
||||
interface UserProfile { }
|
||||
interface SimulationResult { }
|
||||
|
||||
// Constantes globales : SCREAMING_SNAKE_CASE
|
||||
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
|
||||
// Pattern obligatoire pour toutes les routes
|
||||
app.post('/simulations', authMiddleware, planMiddleware('simulation'), async (c) => {
|
||||
// 1. Récupérer et valider les données entrantes
|
||||
const body = await c.req.json()
|
||||
// features/dashboard/pages/DashboardPage.tsx
|
||||
|
||||
// 2. Logique métier (déléguer au controller)
|
||||
const result = await simulationController.create(body, c.get('user'))
|
||||
import { usePlan } from '../hooks/usePlan'
|
||||
import { hasAccess } from '@/entities/user/lib'
|
||||
|
||||
// 3. Retourner la réponse
|
||||
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) {
|
||||
export function DashboardPage() {
|
||||
// 1. Hooks en premier
|
||||
const { user } = useAuth()
|
||||
const perms = usePlan()
|
||||
const { plan, isLoading, error } = usePlan()
|
||||
|
||||
// 2. Handlers
|
||||
const handleClick = () => { }
|
||||
// 2. Gestion des états de chargement et d'erreur
|
||||
if (isLoading) return <Spinner />
|
||||
if (error) return <ErrorView error={error} />
|
||||
|
||||
// 3. Rendu conditionnel selon le plan
|
||||
if (!perms.dashboard) return <PaywallModal feature="dashboard" />
|
||||
// 3. Logique de permission via hasAccess
|
||||
if (!hasAccess(plan.plan, 'dashboard')) {
|
||||
return <DashboardFreeAperçu />
|
||||
}
|
||||
|
||||
// 4. JSX
|
||||
return (
|
||||
<div>...</div>
|
||||
)
|
||||
// 4. Rendu principal
|
||||
return <DashboardComplete plan={plan} />
|
||||
}
|
||||
```
|
||||
|
||||
### 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
|
||||
|
||||
### Backend — toujours retourner une erreur structurée
|
||||
### Côté api-client
|
||||
|
||||
```typescript
|
||||
// ✅ Format d'erreur standard
|
||||
return c.json({
|
||||
error: true,
|
||||
code: 'QUOTA_REACHED',
|
||||
message: 'Quota de simulations atteint pour ce plan',
|
||||
}, 403)
|
||||
// shared/lib/api-client.ts
|
||||
// En cas de succès : retourne le payload typé T directement (pas d'enveloppe)
|
||||
// En cas d'erreur : throw une ApiError typée
|
||||
// TanStack Query catch automatiquement et rend l'erreur disponible dans .error
|
||||
|
||||
// ❌ Jamais
|
||||
return c.json({ error: 'error' }, 500)
|
||||
throw new Error('Something went wrong')
|
||||
```
|
||||
|
||||
### Codes d'erreur standardisés
|
||||
```
|
||||
AUTH_REQUIRED → 401 Pas de JWT ou JWT invalide
|
||||
PLAN_INSUFFICIENT → 403 Feature non disponible pour ce plan
|
||||
QUOTA_REACHED → 403 Quota de simulations épuisé
|
||||
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
|
||||
}
|
||||
export async function apiFetch<T>(
|
||||
path: string,
|
||||
options: RequestInit & { timeoutMs?: number } = {}
|
||||
): Promise<T> {
|
||||
// 1. Ajouter headers (Authorization Bearer, X-API-Version, Content-Type)
|
||||
// 2. Appliquer AbortController pour timeout
|
||||
// 3. Retry avec backoff exponentiel sur erreurs réseau + 5xx
|
||||
// 4. Parser la réponse :
|
||||
// - Si 2xx → retourner le JSON tel quel typé T
|
||||
// - Si 4xx/5xx → parser l'erreur backend { error: true, code, message } → throw ApiError
|
||||
}
|
||||
```
|
||||
|
||||
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
|
||||
|
||||
### 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
|
||||
## 5. Communication avec Hermann
|
||||
|
||||
### Format du plan (avant implémentation)
|
||||
|
||||
```
|
||||
📋 PLAN — [nom de la tâche]
|
||||
|
||||
Fichiers modifiés :
|
||||
- src/routes/simulations.ts (ajouter la vérification de quota)
|
||||
- src/lib/access.ts (ajouter la fonction canUserSimulate)
|
||||
- src/features/dashboard/pages/DashboardPage.tsx (ajouter affichage conditionnel)
|
||||
- src/entities/user/lib.ts (ajouter helper formatRemainingSimulations)
|
||||
|
||||
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 :
|
||||
- aucun
|
||||
Fichiers supprimés : aucun
|
||||
|
||||
Risques identifiés :
|
||||
- La modification de access.ts peut affecter toutes les routes
|
||||
qui utilisent getPlanPermissions → vérifier après modification
|
||||
- La modification de DashboardPage peut affecter l'affichage pour les 3 plans
|
||||
→ vérifier les 3 comptes de test après implémentation
|
||||
|
||||
Étapes :
|
||||
1. Créer la fonction canUserSimulate dans access.ts
|
||||
2. Écrire le test canUserSimulate.test.ts
|
||||
3. Ajouter la vérification dans la route POST /simulations
|
||||
1. Créer le composant RemainingSimulationsBadge + son test
|
||||
2. Créer le helper formatRemainingSimulations dans entities/user/lib.ts + test
|
||||
3. Intégrer dans DashboardPage
|
||||
|
||||
En attente de validation avant de commencer.
|
||||
```
|
||||
|
||||
### Format du résumé (après implémentation)
|
||||
|
||||
```
|
||||
✅ RÉSUMÉ — [nom de la tâche]
|
||||
|
||||
Modifié :
|
||||
- src/routes/simulations.ts : ajout vérification quota ligne 42
|
||||
- src/lib/access.ts : ajout fonction canUserSimulate
|
||||
- src/features/dashboard/pages/DashboardPage.tsx : intégration badge ligne 42
|
||||
|
||||
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 :
|
||||
- Golden Dataset Groupe 2 (B2, B7) — quota Free
|
||||
- Golden Dataset Groupe 3 (C2) — illimité Standard
|
||||
- Golden Dataset Groupe 2 (B1) — Dashboard Free avec compteur
|
||||
- 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
|
||||
|
||||
- Un test automatisé échoue après modification
|
||||
- La tâche nécessite de modifier plus de 3 fichiers par étape
|
||||
- Une décision architecturale non documentée est requise
|
||||
- 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
|
||||
|
||||
---
|
||||
|
||||
## 7. Checklist de démarrage de session
|
||||
## 6. Checklist de démarrage de session
|
||||
|
||||
Avant chaque session Claude Code, vérifier :
|
||||
|
||||
```
|
||||
[ ] Les 4 documents de référence ont été lus
|
||||
(DEVELOPMENT_PRINCIPLES.md, ARCHITECTURE.md,
|
||||
[ ] Les documents de référence ont été lus
|
||||
(ARCHITECTURE.md, DEVELOPMENT_PRINCIPLES.md,
|
||||
PLANS_TARIFAIRES.md, PARCOURS_UTILISATEURS.md)
|
||||
|
||||
[ ] L'environnement de test est dans l'état attendu
|
||||
(voir TEST_ENVIRONMENT.md — script de réinitialisation)
|
||||
[ ] Les ADRs pertinents ont été consultés
|
||||
(docs/adr/)
|
||||
|
||||
[ ] L'environnement de dev fonctionne
|
||||
(npm run dev, pas d'erreur au démarrage)
|
||||
|
||||
[ ] 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
|
||||
(état de repli en cas de régression)
|
||||
|
||||
[ ] La tâche de la session est clairement définie
|
||||
(une seule tâche par session)
|
||||
|
|
@ -353,70 +411,53 @@ Avant chaque session Claude Code, vérifier :
|
|||
|
||||
---
|
||||
|
||||
## 8. Checklist de fin de session
|
||||
|
||||
Avant de clôturer chaque session Claude Code :
|
||||
## 7. Checklist de fin de session
|
||||
|
||||
```
|
||||
[ ] 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
|
||||
(fichiers modifiés, tests passé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
|
||||
Format : "feat: [description]" ou "fix: [description]"
|
||||
Format : "feat(<scope>): ...", "fix(<scope>): ...", "refactor(<scope>): ..."
|
||||
Scopes possibles : auth, dashboard, simulation, t2-live, billing, shared, docs
|
||||
|
||||
Exemples :
|
||||
"feat: ajout vérification quota simulations free"
|
||||
"fix: correction rapport flouté plan découverte"
|
||||
"refactor: extraction logique permissions dans access.ts"
|
||||
"feat(dashboard): affichage conditionnel selon le plan"
|
||||
"fix(auth): corriger la redirection après logout"
|
||||
"refactor(entities/user): extraire hasAccess dans un fichier dédié"
|
||||
|
||||
[ ] DEVELOPMENT_PRINCIPLES.md et ARCHITECTURE.md
|
||||
sont à jour si une décision a changé
|
||||
[ ] docs/CHANGELOG.md mis à jour avec un résumé de la session
|
||||
|
||||
[ ] ARCHITECTURE.md / SECURITY.md mis à jour si une décision a changé
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 9. Messages d'erreur utilisateur — ton et format
|
||||
|
||||
Les messages affichés à l'utilisateur doivent être :
|
||||
- En français
|
||||
- Clairs et non techniques
|
||||
- Orientés action (que faire ensuite)
|
||||
- Non condescendants
|
||||
## 8. Ce que Claude Code ne doit jamais faire
|
||||
|
||||
```
|
||||
// ✅ Bons messages
|
||||
"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
|
||||
❌ Modifier entities/user/access.ts sans prévenir (source de vérité partagée avec le backend)
|
||||
❌ 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')
|
||||
❌ Exposer une clé privée dans le frontend
|
||||
❌ Supprimer des données utilisateur (productions, profiles)
|
||||
❌ Modifier plus de 3 fichiers sans validation intermédiaire
|
||||
❌ Passer à l'étape suivante si un test est rouge
|
||||
❌ Prendre une décision architecturale sans la documenter
|
||||
❌ 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 |
|
||||
|
|
|
|||
|
|
@ -1,154 +1,170 @@
|
|||
# GOLDEN_DATASET.md — Expria / Coach TCF Canada
|
||||
# GOLDEN_DATASET.md — Expria Frontend
|
||||
|
||||
> **Document de référence — Version 1.0**
|
||||
> Ce fichier contient les tests manuels à rejouer après CHAQUE session Claude Code,
|
||||
> avant de passer à la session suivante ou de déployer en production.
|
||||
> Ce fichier contient les tests manuels à rejouer après CHAQUE session Claude Code frontend, avant de déployer sur Cloudflare Pages.
|
||||
> 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
|
||||
|
||||
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é
|
||||
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.standard@expria.local | standard | Tester les parcours Standard |
|
||||
| test.premium@expria.local | premium | Tester les parcours Premium |
|
||||
| test.quota@expria.local | free (5/5 simulations utilisées) | Tester le blocage quota |
|
||||
|
||||
> Ces comptes sont créés via script SQL dans Supabase (ne pas les créer manuellement).
|
||||
> Voir `supabase/seeds/test_accounts.sql`.
|
||||
| test.free@gmail.com | free | Expria2025!test |
|
||||
| test.standard@gmail.com | standard | Expria2025!test |
|
||||
| test.premium@gmail.com | premium | Expria2025!test |
|
||||
| test.quota@gmail.com | free (5/5 utilisées) | Expria2025!test |
|
||||
|
||||
---
|
||||
|
||||
## 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 | |
|
||||
| A2 | Connexion avec email + mot de passe valides | test.free | Dashboard Free affiché, compteur simulations visible | |
|
||||
| A3 | Connexion avec mot de passe incorrect | test.free | Message d'erreur, pas de redirection | |
|
||||
| A4 | Déconnexion | test.free | Redirection page d'accueil, session détruite | |
|
||||
| A5 | Accès direct à /dashboard sans être connecté | — | Redirection vers /login | |
|
||||
| A1 | Arriver sur `/` sans être connecté | — | Page Home publique affichée | |
|
||||
| A2 | Cliquer "Se connecter" depuis Home | — | Redirection `/login`, formulaire visible | |
|
||||
| A3 | Inscription avec email + mot de passe valides | nouveau | Compte créé, plan=free, redirection `/dashboard` | |
|
||||
| A4 | Connexion avec identifiants corrects | test.free | Redirection `/dashboard`, plan Free affiché | |
|
||||
| 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 | |
|
||||
| B2 | Lancer une simulation EE Tâche 1 | test.free (quota < 5) | Interface de production affichée, pas de tips | |
|
||||
| B3 | Soumettre une production EE | test.free (quota < 5) | Rapport affiché : score + NCLC visibles, critères floutés avec cadenas | |
|
||||
| B4 | Vérifier le rapport flouté | test.free | Au moins 1 élément flouté avec mention "Disponible en Standard" | |
|
||||
| B5 | Lancer une simulation EO Tâche 1 | test.free (quota < 5) | Interface d'enregistrement audio affichée | |
|
||||
| B6 | Cliquer sur EO Tâche 2 live | test.free | Cadenas affiché + message "Exclusivité Premium" | |
|
||||
| B7 | Tenter une 6e simulation | test.quota | Modal de blocage avec boutons "Standard" et "Premium" | |
|
||||
| B8 | Cliquer "Plus tard" dans le modal de blocage | test.quota | Modal fermé, pas de redirection | |
|
||||
| B9 | Cliquer "Mode Examen" | test.free | Cadenas + message "Exclusivité Premium" | |
|
||||
| B1 | Dashboard Free après connexion | test.free | Compteur "X/5 simulations", aperçu flouté du dashboard Premium visible | |
|
||||
| B2 | Badge compteur simulations affiché | test.free | Visible en permanence dans le header du dashboard | |
|
||||
| B3 | Lancer une simulation EE T1 | test.free (quota < 5) | Interface de production affichée, pas de tips visibles | |
|
||||
| B4 | Soumettre une production EE | test.free | Rapport affiché : score + NCLC visibles, critères floutés avec cadenas | |
|
||||
| B5 | Rapport flouté avec mentions correctes | test.free | "Disponible en Standard" + bouton upgrade visible | |
|
||||
| B6 | Lancer une simulation EO T1 | test.free | Interface d'enregistrement audio, pas d'erreur microphone | |
|
||||
| B7 | Tenter EO T2 live depuis le sélecteur de tâches | test.free | Cadenas + message "Exclusivité Premium" | |
|
||||
| B8 | Atteindre la 6e simulation | test.quota | Modal de blocage : "5/5 utilisées" + 2 boutons (Standard/Premium) + "Plus tard" | |
|
||||
| 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é | |
|
||||
| C2 | Lancer une simulation EE sans limite | test.standard | Simulation accessible sans vérification de quota | |
|
||||
| C3 | Toggle "Suggestions d'idées" activé | test.standard | Suggestions d'idées visibles pendant la simulation | |
|
||||
| C1 | Dashboard Standard après connexion | test.standard | Historique visible, pas de compteur simulations, bouton "Choisir une tâche" actif | |
|
||||
| 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 visibles 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é | |
|
||||
| C6 | Vérifier l'enregistrement dans le dashboard | test.standard | Production apparaît dans l'historique avec date, tâche, score | |
|
||||
| C7 | Cliquer sur une production dans l'historique | test.standard | Rapport complet de cette production affiché | |
|
||||
| C8 | Cliquer sur "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" | |
|
||||
| C10 | Indice de préparation après 5 productions | test.standard (5 prod.) | Section indice visible avec score et message interprétatif | |
|
||||
| C5 | Rapport complet après soumission EE | test.standard | Score, critères détaillés, erreurs expliquées, modèle, exercices — rien flouté | |
|
||||
| C6 | Production apparaît dans le dashboard | test.standard | Date, tâche, score affichés dans la liste | |
|
||||
| C7 | Cliquer une production dans l'historique | test.standard | Rapport complet de cette production réaffiché | |
|
||||
| C8 | Cliquer "Mode Examen" | test.standard | Message "Réservé au plan Premium" + bouton upgrade | |
|
||||
| C9 | Cliquer "EO Tâche 2 live" | test.standard | Cadenas + message "Exclusivité Premium" | |
|
||||
| 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 | |
|
||||
| D2 | Accéder à EO Tâche 2 live | test.premium | Page de préparation T2 affichée, bouton "Démarrer le dialogue" | |
|
||||
| D3 | Démarrer le dialogue T2 | test.premium | L'IA prend la parole en premier, audio reçu et joué | |
|
||||
| D4 | Répondre en audio (T2) | test.premium | L'IA réagit après la réponse du candidat | |
|
||||
| D1 | Dashboard Premium après connexion | test.premium | Historique, indice, patterns, bouton examen actif, T2 live accessible | |
|
||||
| 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 | État "Connecting" puis "Listening", l'IA prend la parole en premier | |
|
||||
| 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" | |
|
||||
| D6 | Lancer mode Examen EE | test.premium | Page d'avertissement affichée avant démarrage | |
|
||||
| D7 | Confirmer le lancement Examen EE | test.premium | 3 tâches visibles, timer 60:00 démarré, inarrêtable | |
|
||||
| D8 | Vérifier le blocage à T=0 (Examen EE) | test.premium | Zone de texte figée, message "Temps écoulé", envoi automatique | |
|
||||
| D9 | Lancer mode Examen EO | test.premium | Timer 12:00 démarré, enregistrement actif | |
|
||||
| D10 | Analyse des patterns (5+ productions) | test.premium | Section "Mon profil" affiche erreurs récurrentes classées par type | |
|
||||
| D11 | Exercices long terme générés | test.premium | Exercices distincts de ceux du rapport individuel | |
|
||||
| D6 | Déconnexion WebSocket en cours de T2 | test.premium | État "Error" affiché, message utilisateur clair, option de reprise | |
|
||||
| D7 | Lancer mode Examen EE | test.premium | Page d'avertissement affichée avant démarrage | |
|
||||
| D8 | Confirmer Examen EE | test.premium | 3 tâches visibles, timer 60:00 démarré, inarrêtable | |
|
||||
| D9 | Blocage à T=0 (Examen EE) | test.premium | Zone de texte figée, message "Temps écoulé", envoi auto | |
|
||||
| D10 | Lancer mode Examen EO | test.premium | Timer 12:00, enregistrement actif, tâches enchaînées | |
|
||||
| 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.
|
||||
> 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 | ✅ / ❌ |
|
||||
| # | Test | Compte | Résultat attendu | ✅/❌ |
|
||||
|---|---|---|---|---|
|
||||
| E1 | Upgrade Free → Standard | test.free | Redirection Stripe, paiement, retour dashboard Standard, plan = "standard" | |
|
||||
| E2 | Upgrade Free → Premium | test.free | Redirection Stripe, paiement, retour dashboard Premium, plan = "premium" | |
|
||||
| E3 | Upgrade Standard → Premium (prorata) | test.standard | Montant prorata affiché avant confirmation, accès Premium après paiement | |
|
||||
| E4 | Paiement refusé | test.free | Message d'erreur Stripe, plan inchangé, pas de régression | |
|
||||
| E5 | Accès immédiat après paiement | test.free | Dashboard du nouveau plan affiché sans délai ni reconnexion | |
|
||||
| E6 | Plan mis à jour dans Supabase | test.free | Colonne `plan` dans la table `profiles` reflète le nouveau plan | |
|
||||
| E1 | Upgrade Free → Standard (Stripe Checkout) | test.free | Redirection full page vers Stripe, paiement, retour dashboard Standard | |
|
||||
| E2 | Invalidation du cache plan après paiement | test.free → standard | usePlan() refetch automatiquement, dashboard bascule sans recharger la page | |
|
||||
| E3 | Upgrade Free → Premium | test.free | Même flux que E1, plan=premium après retour | |
|
||||
| E4 | Upgrade Standard → Premium avec prorata | test.standard | Montant prorata affiché avant confirmation, accès Premium immédiat | |
|
||||
| E5 | Paiement refusé (carte 4000 0000 0000 0002) | test.free | Message d'erreur Stripe clair, plan inchangé | |
|
||||
| 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" | |
|
||||
| F2 | Appel API POST /corrections/ee sans JWT | — (non connecté) | Réponse 401 Unauthorized | |
|
||||
| F3 | Appel API GET /simulations d'un autre utilisateur | test.free | Réponse 403 ou liste vide (RLS Supabase) | |
|
||||
| F4 | Tentative de lancer examen via API (plan standard) | test.standard | Réponse 403 — plan insuffisant | |
|
||||
| F5 | Variable GEMINI_API_KEY visible dans le frontend | — | Introuvable dans le code source JavaScript chargé par le navigateur | |
|
||||
| F1 | URL directe `/t2-live` en Standard | test.standard | Redirection ou PaywallModal, pas d'accès à la page | |
|
||||
| F2 | Inspecter DevTools → clés privées | — | Aucune clé `SERVICE_ROLE`, `GEMINI`, `STRIPE_SECRET` visible | |
|
||||
| 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 | Modifier le plan dans DevTools via Redux/state | test.free | La modification locale n'a aucun effet — le backend reste l'autorité | |
|
||||
| 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.
|
||||
Ce sont les 10 scénarios les plus critiques, un par type de parcours.
|
||||
Tests à rejouer sur DevTools mobile emulation (iPhone SE, iPhone 12, Samsung Galaxy) ET sur vrai mobile si possible.
|
||||
|
||||
| # | 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 |
|
||||
|---|---|---|
|
||||
| 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 |
|
||||
| Z3 | Simulation Standard complète | Login → simulation → rapport complet → dashboard |
|
||||
| Z4 | Mode examen bloqué en Standard | Bouton mode examen → message upgrade |
|
||||
| Z5 | T2 live Premium | Login → T2 live → dialogue → rapport |
|
||||
| Z6 | Mode examen EE complet | Lancement → timer → T=0 → envoi auto → rapport |
|
||||
| Z7 | Paiement Free → Standard | Checkout Stripe → webhook → dashboard Standard |
|
||||
| Z8 | Prorata Standard → Premium | Montant affiché → confirmation → accès Premium |
|
||||
| Z9 | Sécurité JWT | Appel API sans token → 401 |
|
||||
| Z10 | Déconnexion + accès protégé | Déconnexion → accès /dashboard → redirection /login |
|
||||
| Z7 | Paiement Free → Standard | Stripe Checkout → retour dashboard Standard sans rechargement |
|
||||
| Z8 | Prorata Standard → Premium | Montant affiché → confirmation → accès Premium immédiat |
|
||||
| Z9 | Déconnexion + accès protégé | Logout → 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é
|
||||
2. NE PAS continuer la session Claude Code
|
||||
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)
|
||||
6. Reformuler le prompt en ajoutant la contrainte manquante
|
||||
7. Relancer la session Claude Code
|
||||
|
|
@ -171,8 +187,16 @@ Test échoue
|
|||
|
||||
## 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 |
|
||||
|---|---|---|---|---|
|
||||
| — | — | — | — | — |
|
||||
|
||||
---
|
||||
|
||||
## 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
|
|
@ -1,365 +1,260 @@
|
|||
# TEST_ENVIRONMENT.md — Expria / Coach TCF Canada
|
||||
# TEST_ENVIRONMENT.md — Expria Frontend
|
||||
|
||||
> **Document de référence — Version 1.0**
|
||||
> Ce document décrit comment créer et réinitialiser l'environnement de test.
|
||||
> Les comptes de test permettent de rejouer tous les parcours utilisateur
|
||||
> sans créer de vrais abonnements ni passer par Stripe.
|
||||
> Ce document décrit comment configurer et utiliser l'environnement de test frontend.
|
||||
> Complément au `TEST_ENVIRONMENT.md` backend (qui décrit la base de données Supabase et les comptes de test).
|
||||
|
||||
---
|
||||
|
||||
## 1. Principe
|
||||
|
||||
L'environnement de test est une configuration connue et reproductible de la base de données.
|
||||
Il consiste en 4 comptes Supabase préconfigurés, un par situation critique.
|
||||
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).
|
||||
|
||||
**Règles absolues :**
|
||||
- Ces comptes n'existent que dans l'environnement de développement / staging
|
||||
- Jamais en production
|
||||
- Les emails se terminent par `@expria.local` — bloqués à l'inscription dans le code
|
||||
- Les mots de passe sont documentés ici — ne jamais les utiliser pour de vrais comptes
|
||||
- Ne jamais utiliser de comptes réels (utilisateurs payants) pour les tests
|
||||
- Utiliser exclusivement les 4 comptes `@gmail.com` documentés dans le backend
|
||||
- Ne jamais committer les fichiers `.env` (présents dans `.gitignore`)
|
||||
|
||||
---
|
||||
|
||||
## 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.standard@expria.local | standard | 12 | Parcours Standard complet |
|
||||
| test.premium@expria.local | premium | 28 | Parcours Premium complet |
|
||||
| test.quota@expria.local | free | 5 | Blocage quota Free |
|
||||
| test.free@gmail.com | free | 0 | Parcours Free normal |
|
||||
| test.standard@gmail.com | standard | 12 | Parcours Standard complet |
|
||||
| test.premium@gmail.com | premium | 28 | Parcours Premium complet |
|
||||
| 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.
|
||||
> Ne jamais exécuter en production.
|
||||
### 6.1 Simuler un utilisateur Free qui atteint le quota
|
||||
|
||||
```sql
|
||||
-- =============================================================
|
||||
-- EXPRIA — Création des comptes de test
|
||||
-- Environnement : développement / staging uniquement
|
||||
-- =============================================================
|
||||
1. Se connecter avec `test.free@gmail.com`
|
||||
2. Utiliser `test.quota@gmail.com` à la place (qui a déjà 5/5 utilisées)
|
||||
3. Tenter de lancer une simulation → modal de blocage doit apparaître
|
||||
|
||||
-- Étape 1 : Créer les utilisateurs dans auth.users
|
||||
-- (Supabase gère le hash du mot de passe automatiquement)
|
||||
**Ne pas** essayer de forcer le compteur en soumettant 5 simulations réelles — ça consomme des appels à DeepSeek (payant) et pollue la base.
|
||||
|
||||
INSERT INTO auth.users (
|
||||
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;
|
||||
### 6.2 Simuler un changement de plan
|
||||
|
||||
-- Étape 2 : Créer les profils dans la table profiles
|
||||
INSERT INTO profiles (
|
||||
id,
|
||||
email,
|
||||
plan,
|
||||
simulations_used,
|
||||
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();
|
||||
Pour tester le flux d'upgrade sans vraiment payer :
|
||||
1. Aller sur `/pricing` avec `test.free@gmail.com`
|
||||
2. Cliquer "Choisir Standard"
|
||||
3. Sur la page Stripe Checkout, utiliser la carte de test `4242 4242 4242 4242`
|
||||
4. Date d'expiration : n'importe laquelle dans le futur
|
||||
5. CVC : n'importe lequel (3 chiffres)
|
||||
|
||||
-- Étape 3 : Insérer des productions de test pour les comptes Standard et Premium
|
||||
-- (nécessaire pour tester le dashboard, l'historique, et l'analyse des patterns)
|
||||
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.
|
||||
|
||||
INSERT INTO productions (
|
||||
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'),
|
||||
### 6.3 Simuler un paiement refusé
|
||||
|
||||
-- 7 productions pour test.premium (active patterns + indice)
|
||||
(gen_random_uuid(), '00000000-0000-0000-0000-000000000003',
|
||||
'EE_T1', 'entrainement',
|
||||
'Texte production EE T1 — premium',
|
||||
16.0, 9,
|
||||
'{"criteres":[{"nom":"Cohérence","score":4},{"nom":"Lexique","score":4}],"erreurs":["Connecteurs logiques"],"exercices":["Connecteurs"]}',
|
||||
NOW() - INTERVAL '20 days'),
|
||||
(gen_random_uuid(), '00000000-0000-0000-0000-000000000003',
|
||||
'EE_T2', 'entrainement',
|
||||
'Texte production EE T2 — premium',
|
||||
15.5, 9,
|
||||
'{"criteres":[{"nom":"Cohérence","score":4},{"nom":"Lexique","score":4}],"erreurs":["Connecteurs logiques"],"exercices":["Connecteurs"]}',
|
||||
NOW() - INTERVAL '16 days'),
|
||||
(gen_random_uuid(), '00000000-0000-0000-0000-000000000003',
|
||||
'EE_T3', 'examen',
|
||||
'Texte production EE T3 — premium examen',
|
||||
17.0, 10,
|
||||
'{"criteres":[{"nom":"Cohérence","score":5},{"nom":"Lexique","score":4}],"erreurs":[],"exercices":[]}',
|
||||
NOW() - INTERVAL '12 days'),
|
||||
(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"]}',
|
||||
NOW() - INTERVAL '9 days'),
|
||||
(gen_random_uuid(), '00000000-0000-0000-0000-000000000003',
|
||||
'EO_T2_LIVE', 'entrainement',
|
||||
NULL, 15.0, 8,
|
||||
'{"criteres":[{"nom":"Interaction","score":4},{"nom":"Phonologie","score":3}],"erreurs":["Hésitations fréquentes"],"exercices":["Fluidité orale"]}',
|
||||
NOW() - INTERVAL '6 days'),
|
||||
(gen_random_uuid(), '00000000-0000-0000-0000-000000000003',
|
||||
'EO_T3', 'entrainement',
|
||||
NULL, 16.0, 9,
|
||||
'{"criteres":[{"nom":"Phonologie","score":4},{"nom":"Lexique","score":4}],"erreurs":["Connecteurs logiques"],"exercices":["Connecteurs oraux"]}',
|
||||
NOW() - INTERVAL '3 days'),
|
||||
(gen_random_uuid(), '00000000-0000-0000-0000-000000000003',
|
||||
'EE_T1', 'examen',
|
||||
'Texte production EE T1 examen — premium',
|
||||
17.5, 10,
|
||||
'{"criteres":[{"nom":"Cohérence","score":5},{"nom":"Lexique","score":4}],"erreurs":[],"exercices":[]}',
|
||||
NOW() - INTERVAL '1 day')
|
||||
ON CONFLICT DO NOTHING;
|
||||
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.
|
||||
|
||||
### 6.4 Simuler une session expirée
|
||||
|
||||
1. Se connecter normalement
|
||||
2. Ouvrir DevTools → Application → Local Storage
|
||||
3. Trouver la clé contenant le JWT Supabase (`sb-<project>-auth-token`)
|
||||
4. Modifier la valeur pour casser le token (changer 1 caractère)
|
||||
5. Rafraîchir la page
|
||||
6. Le frontend doit détecter le JWT invalide → message "Session expirée" + redirect `/login`
|
||||
|
||||
Ce test vérifie SEC-06 (gestion des sessions expirées).
|
||||
|
||||
### 6.5 Simuler T2 Live sans avoir Premium
|
||||
|
||||
1. Se connecter avec `test.free@gmail.com` ou `test.standard@gmail.com`
|
||||
2. Accéder à l'URL `/t2-live` directement
|
||||
3. Le frontend doit afficher un PaywallModal ou rediriger
|
||||
4. Si malgré tout une requête WebSocket est envoyée, le backend doit fermer la connexion avec close code 4003 (`PLAN_INSUFFICIENT`)
|
||||
|
||||
---
|
||||
|
||||
## 7. Matrice de compatibilité navigateurs
|
||||
|
||||
Ces navigateurs sont ceux que Claude Code doit considérer comme supportés. Tester au minimum sur les deux premiers.
|
||||
|
||||
| Navigateur | Version minimum | Priorité de test | Notes |
|
||||
|---|---|---|---|
|
||||
| Chrome (desktop) | 120 | 🔴 obligatoire | Majoritaire chez les utilisateurs |
|
||||
| Chrome Mobile (Android) | 120 | 🔴 obligatoire | Audience Afrique = mobile-first |
|
||||
| Safari Mobile (iOS) | 17 | 🟡 recommandé | Audience Canada = iPhone courant |
|
||||
| Firefox (desktop) | 120 | 🟢 optionnel | Usage faible |
|
||||
| Safari Desktop | 17 | 🟢 optionnel | Niche |
|
||||
|
||||
**Attention particulière pour mobile Android (Afrique) :**
|
||||
- Connexions 3G/4G instables → vérifier que retry dans `api-client.ts` gère bien
|
||||
- RAM limitée → éviter les listes illimitées, paginer
|
||||
- Clavier virtuel qui masque les inputs → vérifier scroll automatique
|
||||
|
||||
Test mobile rapide via DevTools : Chrome → F12 → Toggle device toolbar → sélectionner "iPhone 12" ou "Galaxy S20".
|
||||
|
||||
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;
|
||||
```
|
||||
|
||||
**Résultat attendu :** 4 profils, 12 productions au total.
|
||||
|
||||
---
|
||||
|
||||
## 5. Script de réinitialisation — remettre l'environnement à zéro
|
||||
|
||||
> À 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)
|
||||
[ ] npm run typecheck : 0 erreur
|
||||
[ ] 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
|
||||
[ ] Un commit Git a été fait avec un message clair
|
||||
[ ] CHANGELOG.md mis à jour
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 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
|
||||
// src/middleware/auth.ts — backend Hono
|
||||
### Erreur "Invalid JWT" après login
|
||||
- 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 {
|
||||
const isBlocked = BLOCKED_EMAIL_DOMAINS.some(domain =>
|
||||
email.toLowerCase().endsWith(domain)
|
||||
)
|
||||
if (isBlocked) return false
|
||||
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
|
||||
})
|
||||
```
|
||||
### WebSocket T2 Live ne se connecte pas
|
||||
- Vérifier `VITE_ENABLE_T2_LIVE=true` dans `.env`
|
||||
- Vérifier que l'utilisateur est bien Premium
|
||||
- Ouvrir DevTools → Network → filtrer par "WS" pour voir le handshake WebSocket
|
||||
- Vérifier les close codes : 4001 = auth, 4003 = plan insuffisant
|
||||
|
||||
---
|
||||
|
||||
## 7. Procédure complète — première mise en place
|
||||
## 12. Historique
|
||||
|
||||
```
|
||||
Étape 1 : Ouvrir Supabase Dashboard → SQL Editor
|
||||
Étape 2 : Copier-coller le script de la section 3
|
||||
É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
|
||||
```
|
||||
| Version | Date | Changements |
|
||||
|---|---|---|
|
||||
| 1.0 | 2026-04-17 | Création initiale |
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue