expria-backend/docs/TEST_ENVIRONMENT.md

12 KiB

TEST_ENVIRONMENT.md — Expria / Coach TCF Canada

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.


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.

Règles absolues :

  • Ces comptes n'existent que dans l'environnement de développement / staging
  • Jamais en production
  • Les emails se terminent par @gmail.com — bloqués à l'inscription dans le code
  • Les mots de passe sont documentés ici — ne jamais les utiliser pour de vrais comptes

2. Les 4 comptes de test

Compte Plan simulations_used Cas testé
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


3. Script de création — à exécuter dans Supabase SQL Editor

⚠️ À exécuter UNE SEULE FOIS dans l'environnement de développement. Ne jamais exécuter en production.

-- =============================================================
-- EXPRIA — Création des comptes de test
-- Environnement : développement / staging uniquement
-- =============================================================

-- Étape 1 : Créer les utilisateurs dans auth.users
-- (Supabase gère le hash du mot de passe automatiquement)

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@gmail.com',
    crypt('Expria2025!test', gen_salt('bf')),
    NOW(), NOW(), NOW(),
    '{"provider":"email","providers":["email"]}',
    '{}', false, 'authenticated'
  ),
  (
    '00000000-0000-0000-0000-000000000002',
    'test.standard@gmail.com',
    crypt('Expria2025!test', gen_salt('bf')),
    NOW(), NOW(), NOW(),
    '{"provider":"email","providers":["email"]}',
    '{}', false, 'authenticated'
  ),
  (
    '00000000-0000-0000-0000-000000000003',
    'test.premium@gmail.com',
    crypt('Expria2025!test', gen_salt('bf')),
    NOW(), NOW(), NOW(),
    '{"provider":"email","providers":["email"]}',
    '{}', false, 'authenticated'
  ),
  (
    '00000000-0000-0000-0000-000000000004',
    'test.quota@gmail.com',
    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
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@gmail.com',
    'free', 0, NULL, NULL, NULL,
    NOW(), NOW()
  ),
  (
    '00000000-0000-0000-0000-000000000002',
    'test.standard@gmail.com',
    'standard', 12, 'cus_test_standard', 'sub_test_standard',
    NOW() + INTERVAL '14 days',
    NOW(), NOW()
  ),
  (
    '00000000-0000-0000-0000-000000000003',
    'test.premium@gmail.com',
    'premium', 28, 'cus_test_premium', 'sub_test_premium',
    NOW() + INTERVAL '21 days',
    NOW(), NOW()
  ),
  (
    '00000000-0000-0000-0000-000000000004',
    'test.quota@gmail.com',
    '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
-- (nécessaire pour tester le dashboard, l'historique, et l'analyse des patterns)

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'),

  -- 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;

4. Script de vérification — confirmer que les comptes existent

-- Vérifier les profils créés
SELECT
  id,
  email,
  plan,
  simulations_used,
  plan_expires_at
FROM profiles
WHERE email LIKE '%@gmail.com'
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 '%@gmail.com'
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.

-- Supprimer les productions de test
DELETE FROM productions
WHERE user_id IN (
  SELECT id FROM profiles WHERE email LIKE '%@gmail.com'
);

-- 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@gmail.com';

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@gmail.com';

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@gmail.com';

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@gmail.com';

-- Réinsérer les productions (copier-coller le bloc INSERT de la section 3)

6. Bloquer les inscriptions @gmail.com en production

Ajouter cette validation dans le backend (middleware d'inscription) :

// src/middleware/auth.ts — backend Hono

const BLOCKED_EMAIL_DOMAINS = ['@gmail.com']

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 :

// 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

É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@gmail.com
           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