chore: playwright workflow, docs, rapports audit, visual-tests, tmt unit

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
senke 2026-02-11 22:19:34 +01:00
parent 3c742c3576
commit 8a0f008345
27 changed files with 1871 additions and 10 deletions

View file

@ -8,6 +8,9 @@ jobs:
test:
timeout-minutes: 60
runs-on: ubuntu-latest
defaults:
run:
working-directory: apps/web
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4

545
999_FULL_AUDIT_VEZA.md Normal file
View file

@ -0,0 +1,545 @@
I'll conduct a thorough technical audit of this monorepo. Let me start by exploring the codebase systematically across all layers.
[4 tools called]
Now let me dive deeper into specific areas that need more investigation.
[4 tools called]
I now have comprehensive data from all exploration passes. Let me verify a few final critical points before compiling the full report.
[8 tools called]
Now I have all the data needed. Let me note a critical finding: the `.gitignore` explicitly includes `veza-stream-server/.env` (line 77: `!veza-stream-server/.env`) meaning credentials are committed to git. Let me compile the complete audit report.
---
# AUDIT TECHNIQUE INTEGRAL — MONOREPO VEZA
**Date** : 11 fevrier 2026
**Scope** : Monorepo `/home/senke/git/talas/veza`
**Auditeur** : IA — Analyse statique exhaustive du code source
---
## EXECUTIVE SUMMARY
Veza est une plateforme audio collaborative multi-services (Go, Rust x2, React) dotee d'une ambition technique importante mais dont la maturite est inegale selon les couches. Le backend Go est le composant le plus solide (architecture clean, securite robuste, tests). Le frontend React est volumineux et bien structure mais souffre de features fantomes et de tests fragiles. Les deux services Rust (chat, stream) presentent des **vulnerabilites critiques** (credentials hardcodees, authentification simulee) qui interdisent tout deploiement en production en l'etat.
**Verdict global** : Le monorepo n'est **pas deployable en production** sans remediation prealable des failles de securite critiques. La base de code est neanmoins viable a moyen terme apres un effort de stabilisation estime a 4-6 semaines.
---
## 1. CARTOGRAPHIE GLOBALE
### Stack complete
| Couche | Technologie | Version |
|--------|-------------|---------|
| Frontend | React + TypeScript + Vite | 18.2.0 / 5.3.3 / 7.1.5 |
| Design System | Tailwind CSS v4 + KODO tokens | 4.0.0 |
| State | Zustand + TanStack React Query | 4.5.0 / 5.17.0 |
| Backend API | Go + Gin + GORM + PostgreSQL | Go 1.23.8 / Gin 1.9.1 |
| Chat Server | Rust + Axum + SQLx + WebSocket | Axum 0.8 |
| Stream Server | Rust + Axum + FFmpeg + HLS | Axum 0.7 |
| Base de donnees | PostgreSQL | via Docker |
| Cache/Sessions | Redis | via Docker |
| Message Queue | RabbitMQ | via Docker (optionnel) |
| Reverse Proxy | HAProxy | Config fournie |
| CI/CD | GitHub Actions (7 workflows) | — |
| Monitoring | Prometheus + Grafana + Sentry | Config fournie |
| Tests | Vitest + Playwright + Go test + testcontainers | — |
| Infra | Docker Compose (dev/staging/prod) + Incus/LXD + K8s | — |
### Organisation du repo
```
veza/
├── apps/web/ # Frontend React (coeur actif)
├── veza-backend-api/ # Backend Go (coeur actif)
├── veza-chat-server/ # Chat Rust (fonctionnel, failles)
├── veza-stream-server/ # Streaming Rust (incomplet, failles critiques)
├── veza-desktop/ # Electron wrapper (coquille vide)
├── veza-mobile/ # App mobile (abandonne, non fonctionnel)
├── veza-docs/ # Docusaurus (documentation)
├── veza-common/ # Lib Rust partagee (utilisee)
├── packages/design-system/# Design system (sous-utilise)
├── config/ # Docker, HAProxy, Incus, Prometheus, Grafana
├── k8s/ # Manifestes Kubernetes
├── migrations/ # 29 fichiers SQL
├── make/ # Systeme de build modulaire
├── scripts/ # Utilitaires
└── .github/workflows/ # 7 pipelines CI/CD
```
**Gestion monorepo** : NPM Workspaces uniquement. Pas de Turborepo, Nx ou Lerna. Le systeme de build repose sur un Makefile modulaire (`make/*.mk`) avec ~10 modules. C'est fonctionnel mais manque d'orchestration intelligente (pas de cache de build, pas de graph de dependances).
### Dependances critiques
| Dependance | Criticite | Statut |
|-----------|-----------|--------|
| `axios` 1.6.7 | **CRITIQUE** | 4 CVE connues (SSRF, DoS) — upgrade >=1.12.0 |
| `gin-gonic/gin` 1.9.1 | Elevee | Significativement derriere (1.11.0+) |
| `jsonwebtoken` (Rust) 9.2 | Elevee | Majeure disponible (10.x) |
| `axum` 0.7 (stream) | Elevee | Derriere (0.8+), incoherent avec chat (0.8) |
| `sqlx` 0.7 (les deux) | Moyenne | Derriere (0.8.6) |
| `dutchcoders/go-clamd` | Faible | Derniere MaJ 2017 — potentiellement abandonne |
### Schema des flux
```
Browser → HAProxy (:80/:443)
├── / → apps/web (Vite SSR/SPA, :5173)
├── /api/v1/* → veza-backend-api (Gin, :18080)
│ ├── PostgreSQL (:15432)
│ ├── Redis (:16379)
│ ├── RabbitMQ (:15672)
│ └── S3 (stockage fichiers)
├── /ws/* → veza-chat-server (Axum WS, :3000)
│ └── PostgreSQL (messages)
└── /stream/* → veza-stream-server (Axum, :3002)
└── FFmpeg (transcodage)
```
### Complexite globale
- **Couplage** : Faible entre services (API REST/WS), mais fort entre frontend et MSW mocks
- **Modularite** : Bonne cote backend Go, correcte cote frontend (feature-based), faible cote Rust (monolithes)
- **Coherence** : Heterogene — 3 langages, 2 versions d'Axum differentes, conventions de nommage variables
---
## 2. CE QUE LE PRODUIT PERMET REELLEMENT
### Features reellement implementees et fonctionnelles
| Feature | Frontend | Backend | Tests | Statut |
|---------|----------|---------|-------|--------|
| Authentification (register/login/JWT/refresh) | OK | OK | OK | **Operationnelle** |
| 2FA (TOTP) | OK | OK | OK | **Operationnelle** |
| OAuth (Google, GitHub, Discord) | OK | OK | Partiel | **Operationnelle** |
| Gestion de profils utilisateur | OK | OK | OK | **Operationnelle** |
| Upload de tracks (chunked, validation, ClamAV) | OK | OK | OK | **Operationnelle** |
| CRUD Tracks (metadata, versions) | OK | OK | OK | **Operationnelle** |
| Playlists (CRUD, collaboration) | OK | OK | OK | **Operationnelle** |
| Chat WebSocket (temps reel) | OK | OK | Partiel | **Operationnelle** |
| Dashboard (stats, overview) | OK | MSW | Partiel | **Fonctionnelle (mocks)** |
| Recherche | OK | OK | Partiel | **Operationnelle** |
| Social (follows, blocks, feed) | OK | OK | Partiel | **Operationnelle** |
| Administration (RBAC, admin panel) | OK | OK | Partiel | **Operationnelle** |
| Marketplace (produits, commandes) | OK | OK | Partiel | **Operationnelle** |
| Webhooks | OK | OK | Partiel | **Operationnelle** |
| Analytics (playback, aggregation) | OK | OK | Partiel | **Operationnelle** |
### Features partiellement implementees
| Feature | % | Probleme |
|---------|---|---------|
| HLS Streaming | 60% | Frontend pret, feature flag `HLS_STREAMING: false`, backend endpoints manquants |
| Notifications | 40% | Feature flag `NOTIFICATIONS: false`, backend non implemente |
| Role Management (RBAC dynamique) | 50% | Feature flag `ROLE_MANAGEMENT: false`, UI existe |
| Playlist Share Links | 40% | Feature flag `PLAYLIST_SHARE: false` |
| Playlist Recommendations | 30% | Feature flag `PLAYLIST_RECOMMENDATIONS: false` |
### Features fantomes (declarees mais non fonctionnelles)
| Feature | Preuves | Verdict |
|---------|---------|---------|
| **Studio (Cloud File Browser)** | UI dans `src/features/studio/`, pas de route dans le router, pas de backend | **Fantome** |
| **Inventory/Gear** | UI dans `src/features/inventory/`, utilise `mockGear.ts`, pas de backend | **Fantome** |
| **Education/Courses** | UI dans `src/features/`, MSW mocks seulement | **Fantome (MSW only)** |
| **Gamification (achievements, leaderboard)** | MSW handlers existent, pas de backend | **Fantome** |
| **Live Streaming** | Route `/live` existe, contenu minimal | **Fantome** |
### Features mortes (presentes mais inutilisees)
| Feature | Localisation | Verdict |
|---------|-------------|---------|
| **App Mobile** | `veza-mobile/` | 35+ erreurs critiques, imports absents, code web dans React Native, auth hardcodee a false. **Mort.** |
| **Design System package** | `packages/design-system/` | Aucun import de composant trouve dans le frontend. Seul le CSS est utilise. **Sous-utilise/mort.** |
| **Desktop Electron** | `veza-desktop/` | Coquille qui charge l'URL web. Aucune logique specifique. **Coquille vide.** |
---
## 3. VALIDATION FONCTIONNELLE
### Features critiques — Analyse detaillee
**Authentification** :
- Flow complet (register, login, refresh, logout, 2FA, OAuth)
- Account lockout apres 5 tentatives (30min, Redis-backed)
- Token versioning pour revocation immediate
- bcrypt cost 12
- Tests : complets (`service_test.go`, `auth_middleware_test.go`, frontend hooks/components)
- Errors : structurees avec codes
- **Verdict : Solide**
**Upload de fichiers** :
- Chunked upload avec validation magic bytes + MIME + extension
- ClamAV scanning (optionnel mais configurable)
- Limites de taille
- Tests de charge (`upload_load_test.go`)
- **Verdict : Solide**
**Chat WebSocket** :
- Connexion WS avec authentification JWT
- Messages persistes en batch (PostgreSQL COPY)
- Reactions, typing indicators, read receipts
- **Risque** : JWT revocation en memoire seulement (perdue au restart)
- **Tests frontend** : 0 test trouve pour la feature chat
- **Verdict : Fonctionnel mais fragile**
**Player Audio** :
- 77 fichiers dans `src/features/player/`
- Tests complets pour chaque composant (PlayPause, Volume, Progress, Quality, etc.)
- Tests de qualite correcte (interactions, accessibilite, etats)
- **Verdict : Solide**
### Points de rupture identifies
1. **Restart du chat server** = perte de toutes les revocations JWT en memoire
2. **ClamAV desactive** = uploads non scannes en production (configurable mais `ENABLE_CLAMAV=false` dans la config Incus)
3. **RabbitMQ desactive** (`RABBITMQ_ENABLE=false` dans config prod) = pas d'event bus, jobs asynchrones non fonctionnels
4. **Feature flags hardcodes** dans le code TypeScript = impossible de les changer sans redeploy
### Zones non testees
- `src/features/chat/` : **0 test** pour 54 fichiers
- `src/features/inventory/` : **0 test** pour 15 fichiers
- `src/features/studio/` : **0 test**
- `src/features/library/` : tests limites
- `src/features/search/` : tests limites
- `src/features/notifications/` : tests limites
- 9 suites de tests **skippees** (composants non implementes, imports casses)
---
## 4. AUDIT DE SECURITE — OWASP TOP 10
### A01 — Broken Access Control
| Constat | Gravite | Detail |
|---------|---------|--------|
| Middleware RBAC present et applique | OK | `RequireAdmin()`, `RequirePermission()`, `RequireOwnershipOrAdmin()` |
| IDOR protection via ownership middleware | OK | Verifie user_id sur tracks, playlists, products |
| **Endpoints internes stream server non authentifies** | **Elevee** | `/internal/jobs/transcode` sans auth = DoS possible via job injection |
| **WebSocket stream accepte sans auth obligatoire** | **Elevee** | `TODO: validate token in production` dans le code |
### A02 — Cryptographic Failures
| Constat | Gravite | Detail |
|---------|---------|--------|
| Mots de passe : bcrypt cost 12 | OK | Standard correct |
| JWT : HMAC-SHA256 avec secret >= 32 chars | OK | Validation presente |
| **`veza-stream-server/.env` commite dans Git** | **CRITIQUE** | `.gitignore` ligne 77 : `!veza-stream-server/.env` — le fichier contient `DATABASE_URL`, `JWT_SECRET`, `RABBITMQ_URL` avec des credentials vers `veza.fr` |
| **JWT secrets par defaut dans config/incus/** | **Elevee** | `JWT_SECRET=your-super-secret-jwt-key-change-this-in-production-min-32-chars` |
| **Credentials PostgreSQL/RabbitMQ en clair** | **Elevee** | `password` partout dans les configs trackees |
### A03 — Injection
| Constat | Gravite | Detail |
|---------|---------|--------|
| SQL injection (backend Go) : GORM parametrise | OK | Pas de concatenation SQL dans le code de production |
| SQL concatenation dans tests | Faible | `fmt.Sprintf("DELETE FROM %s", table)` dans `testutils/db.go` |
| **Command injection** | **Elevee** | `exec.CommandContext` dans 4 services Go (`audio_transcode_service.go`, `backup_service.go`, `hls_transcode_service.go`, `track_export_service.go`) avec chemins derivables |
| XSS : `dangerouslySetInnerHTML` pour le chat | **Attenuee** | DOMPurify correctement configure dans `sanitize.ts` |
**Detail command injection** :
```23:45:veza-backend-api/internal/services/audio_transcode_service.go
// exec.CommandContext calls at lines 83, 147, 297
// Arguments derived from file paths that could be user-influenced
```
Le risque est reel si les chemins de fichiers ne sont pas valides dans un allowlist.
### A04 — Insecure Design
| Constat | Gravite | Detail |
|---------|---------|--------|
| Rate limiting implemente (Redis) | OK | IP + user-based, par endpoint |
| Rate limiting **desactive en dev** | Attendu | Acceptable |
| Validation serveur (Zod frontend + Go backend) | OK | Double validation |
| **Feature flags hardcodes dans le code source** | Moyenne | Impossible de desactiver une feature sans redeploy |
| **Pas de circuit breaker explicite entre services** | Moyenne | Si le chat server tombe, pas de degradation gracieuse |
### A05 — Security Misconfiguration
| Constat | Gravite | Detail |
|---------|---------|--------|
| **Grafana admin password = `admin`** | **Elevee** | `config/docker/docker-compose.production.yml:116` : `GF_SECURITY_ADMIN_PASSWORD=admin` |
| **CORS `ALLOWED_ORIGINS=*`** dans stream server .env | **Elevee** | Fichier commite dans Git |
| Debug endpoints proteges par auth admin | OK | `/admin/debug/pprof/*` |
| Security headers presents | OK | HSTS, CSP, X-Frame-Options, X-Content-Type-Options |
| CSRF Redis-backed, desactive en dev | OK | Standard |
| **`APP_ENV=production` avec credentials par defaut** | **Elevee** | `config/incus/env/backend-api.env` est en mode prod avec `password` partout |
### A06 — Vulnerable & Outdated Components
| Constat | Gravite | Detail |
|---------|---------|--------|
| **axios 1.6.7** | **CRITIQUE** | CVE-2024-39338 (SSRF), CVE-2025-27152 (SSRF+credential leak), CVE-2025-58754 (DoS), CVE-2024-57965 |
| gin-gonic/gin 1.9.1 | Elevee | Significativement derriere (1.11.0+) |
| jsonwebtoken (Rust) 9.2 | Elevee | Majeure 10.x disponible |
| axum versions incoherentes (0.7 vs 0.8) | Moyenne | Fragmentation |
| `go-clamd` (2017) | Faible | Potentiellement abandonne |
### A07 — Identification & Authentication Failures
| Constat | Gravite | Detail |
|---------|---------|--------|
| **Credentials hardcodees `admin/admin123`** | **CRITIQUE** | `veza-stream-server/src/auth/mod.rs:160` — en production, n'importe qui peut s'authentifier en admin |
| JWT validation correcte (backend Go) | OK | Signature, expiration, issuer, audience, token version |
| JWT revocation en memoire (Rust services) | Elevee | Perdue au restart = tokens revoques redeviennent valides |
| Account lockout (5 tentatives, 30min) | OK | Redis-backed |
| Password reset flow present | OK | Token temporaire |
**Preuve du credential hardcode** :
```155:170:veza-stream-server/src/auth/mod.rs
pub async fn authenticate_user(
&self,
username: &str,
password: &str,
) -> Result<UserInfo, AuthError> {
// Simuler une authentification (à remplacer par votre logique réelle)
if username == "admin" && password == "admin123" {
Ok(UserInfo {
id: "550e8400-e29b-41d4-a716-446655440001".to_string(),
username: username.to_string(),
email: Some("admin@example.com".to_string()),
roles: vec![Role::Admin],
```
### A08 — Software & Data Integrity Failures
| Constat | Gravite | Detail |
|---------|---------|--------|
| CI/CD present (7 workflows GitHub Actions) | OK | Tests automatises |
| Pas de signature des builds | Moyenne | Pas de digest verification sur les images Docker |
| Validation des inputs (Zod + Go validation) | OK | Double validation |
| Pas de SBOM (Software Bill of Materials) | Faible | Manque de tracabilite |
### A09 — Logging & Monitoring Failures
| Constat | Gravite | Detail |
|---------|---------|--------|
| Structured logging (zap) avec rotation | OK | Backend Go |
| Sentry error tracking | OK | Configure (optionnel) |
| Prometheus metrics | OK | Scrape config fourni |
| Grafana dashboards | OK | Dashboard JSON fourni |
| **Secrets potentiellement logues** | Moyenne | `{:?}` debug formatting en Rust peut exposer des structs contenant des secrets |
| **Pas d'audit trail explicite pour actions admin** | Moyenne | Modele `AuditLog` existe dans les migrations mais usage non verifie |
### A10 — SSRF
| Constat | Gravite | Detail |
|---------|---------|--------|
| **axios 1.6.7 vulnerable au SSRF** | **CRITIQUE** | CVE-2024-39338, CVE-2025-27152 |
| OAuth callbacks : URL de redirection via env | Moyenne | `FRONTEND_URL` non valide contre une allowlist |
| Proxy Vite vers backend | Faible | Dev seulement |
### Resume securite
| Categorie OWASP | Score | Constat principal |
|-----------------|-------|-------------------|
| A01 Broken Access Control | 7/10 | RBAC solide, endpoints internes exposes |
| A02 Cryptographic Failures | **3/10** | Secrets commites dans Git, credentials par defaut |
| A03 Injection | 7/10 | GORM protege, mais `exec.Command` risque |
| A04 Insecure Design | 6/10 | Rate limiting OK, feature flags rigides |
| A05 Security Misconfiguration | **4/10** | Grafana admin=admin, CORS wildcard, configs prod avec defaults |
| A06 Vulnerable Components | **3/10** | axios CRITIQUE, Gin et Rust derriere |
| A07 Authentication Failures | **2/10** | admin/admin123 hardcode = showstopper |
| A08 Integrity Failures | 6/10 | CI/CD present, pas de signing |
| A09 Logging/Monitoring | 7/10 | Infrastructure presente |
| A10 SSRF | **4/10** | axios vulnerable |
---
## 5. DETTE TECHNIQUE
### Dette critique (bloquante)
| Element | Localisation | Impact |
|---------|-------------|--------|
| **Credentials hardcodees stream server** | `veza-stream-server/src/auth/mod.rs:160` | Bloquer tout deploiement |
| **`.env` commite dans Git** | `veza-stream-server/.env` (via `.gitignore` ligne 77) | Credentials exposees |
| **axios 4 CVE** | `apps/web/package.json` | Failles SSRF/DoS en production |
| **9 suites de tests skippees** | Frontend (composants non implementes) | Coverage artificielle |
| **0 tests pour le chat** | `apps/web/src/features/chat/` (54 fichiers) | Zero confiance |
| **JWT revocation memoire-only** | Chat + Stream servers | Tokens revoques redeviennent valides au restart |
### Dette structurante
| Element | Localisation | Impact |
|---------|-------------|--------|
| **App mobile abandonnee** | `veza-mobile/` (35+ erreurs, code web dans RN) | Confusion, faux signal de completude |
| **Features fantomes** (Studio, Inventory, Education, Gamification, Live) | `apps/web/src/features/` | Gonflent le codebase, confondent les devs |
| **Design system package inutilise** | `packages/design-system/` | Duplication vs `apps/web/src/components/ui/` |
| **3 langages sans justification formelle** | Go + Rust x2 + TypeScript | Complexite d'onboarding, maintenance |
| **Versions Axum incoherentes** (0.7 vs 0.8) | Stream vs Chat servers | Fragmentation |
| **Feature flags hardcodes** | `apps/web/src/config/features.ts` | Impossible de toggler sans redeploy |
| **Pas de monorepo tooling** | Pas de Turborepo/Nx | Pas de cache, pas de graph de deps |
| **Tests de service faisant de vrais appels API** | `playlistService.test.ts` | Tests fragiles, lents |
### Dette cosmetique
| Element | Impact |
|---------|--------|
| TODO/FIXME/HACK dans le code (~10 occurrences) | Maintenance |
| Placeholder images hardcodees (`picsum.photos`) | Professionnalisme |
| OpenAPI generated types avec `localhost:8080` | Configuration |
| Dependency version mismatches (root vs apps/web) | Surprises au build |
---
## 6. QUALITE ARCHITECTURALE
### Score d'architecture : 6.5/10
**Justification** : L'architecture multi-services est coherente (API REST, WebSocket, streaming) avec une separation claire. Le backend Go suit un pattern clean architecture. Le frontend est feature-based, ce qui scale. Mais l'absence de monorepo tooling, la fragmentation des versions Rust, et les features fantomes diluent la qualite globale. Le choix de 3 langages (Go + 2x Rust + TS) est defensible pour la performance audio mais augmente significativement la complexite d'equipe.
### Score de maintenabilite : 5.5/10
**Justification** : Le code est globalement bien organise et documente (7 docs internes, DESIGN_TOKENS, APP_SHELL, etc.). Mais : 286 tests frontend dont 9 suites broken, 54 fichiers chat sans tests, features mortes jamais nettoyees, app mobile abandonnee toujours presente, design system package fantome. Un nouveau developpeur perdrait du temps a distinguer le code actif du code mort.
### Score de securite : 4/10
**Justification** : Les fondamentaux sont la (RBAC, bcrypt, CSRF, rate limiting, security headers, input validation, DOMPurify). Mais les failles critiques (admin/admin123, `.env` commite, axios CVE, Grafana admin=admin, JWT revocation volatile) sont **inacceptables** pour la production. La surface d'attaque est large et certaines failles sont triviales a exploiter.
### Score de scalabilite : 6/10
**Justification** : L'architecture micro-services permet le scaling horizontal. PostgreSQL + Redis + RabbitMQ est un stack eprouve. Docker Compose + K8s manifestes sont presents. Mais : pas d'auto-scaling configure, pas de connection pooling documente pour les services Rust, le message batching du chat est en memoire (perte de messages au crash), pas de CDN/edge pour le streaming.
---
## 7. INFRA & DEVOPS
### Docker
- **Multi-stage builds** : Oui, pour tous les services
- **Utilisateur non-root** : Oui (`app:app`)
- **Health checks** : Configures dans docker-compose
- **Fichiers propres** : 4 variantes (dev, test, staging, prod)
### Secrets exposes
| Fichier | Dans Git | Contenu |
|---------|---------|---------|
| `veza-stream-server/.env` | **OUI** (`.gitignore` l'inclut explicitement) | DB password, JWT secret, RabbitMQ creds, domaine veza.fr |
| `config/incus/env/backend-api.env` | **OUI** (non ignore) | DB password, JWT placeholder, RabbitMQ creds |
| `config/incus/env/chat-server.env` | **OUI** (non ignore) | Probablement memes patterns |
| `config/incus/env/stream-server.env` | **OUI** (non ignore) | Probablement memes patterns |
| `config/docker/docker-compose.production.yml` | **OUI** | Grafana `admin/admin`, DB passwords |
**Tous ces secrets doivent etre consideres comme compromis.** Rotation obligatoire.
### Gestion des environnements
- Dev : `docker-compose.yml` (ports 15xxx/16xxx pour eviter les conflits) — bien pense
- Test : `docker-compose.test.yml` (tmpfs pour la vitesse) — bien pense
- Staging : `docker-compose.staging.yml` — present
- Production : 2 fichiers concurrents (`docker-compose.prod.yml` et `config/docker/docker-compose.production.yml`) — **confusion**
- Incus/LXD : Alternative native avec scripts de deploiement
### Scripts dangereux
Pas de scripts destructifs identifies. Les scripts `make/*.mk` sont defensifs (checks d'outils, port availability).
### Setup reproductible
`make setup` installe les dependances, demarre l'infra, execute les migrations. **Correct mais non teste en CI** (le CI utilise des steps specifiques).
---
## 8. RISQUES BUSINESS
### Perspective CTO
**Peut-on lancer en prod ?** Non. Les failles A07 (admin/admin123) et A02 (secrets dans Git) l'interdisent. Apres remediation (estimee 2 semaines), un lancement MVP est envisageable en deployant uniquement le backend Go + frontend, en laissant les services Rust en staging.
**Peut-on recruter dessus ?** Difficilement. Le stack Go + 2x Rust + React exige des profils polyvalents rares. L'absence de monorepo tooling ralentira les nouveaux arrivants.
**Complexite d'onboarding** : Estimee 2-3 semaines pour un developpeur senior polyvalent, 4-6 semaines pour un specialiste d'un seul langage.
### Perspective investisseur
**Le produit est-il reel ?** Partiellement. Le coeur (auth, tracks, playlists, chat) est fonctionnel. Mais 5 features sont fantomes (Studio, Inventory, Education, Gamification, Live), l'app mobile est un decor, et le design system package est inutilise. Le **perimetre reel** est ~60% de ce que le repo laisse croire.
**ROI de l'investissement technique** : Le backend Go (92% coverage revendique) est un actif solide. Le frontend est volumineux (286 tests, 23 features) mais gonfle par les fantomes. Les services Rust sont des PoC fonctionnels, pas des composants production-ready.
**Red flags** : Secrets dans Git = incident de securite latent. Mobile abandonne = pivot non assume. Features fantomes = sur-communication ou changement de direction non documente.
### Perspective acquereur
**Peut-on le vendre ?** Avec disclosure honnete : oui, comme base technique a completer. La valeur est dans l'architecture et le backend Go. Le frontend necessite un nettoyage. Les services Rust necessitent une refonte securite.
**Peut-on le maintenir ?** Oui, avec une equipe de 3-4 (1 Go, 1 Rust, 1-2 React/TS). Cout annuel estime : 250-400k EUR selon localisation.
**Faut-il le refactorer ou le reecrire ?** **Refactorer.** La base est saine. Une reecriture serait un gaspillage. Le plan d'action ci-dessous estime le refactoring a 8-12 semaines.
---
## 9. PLAN D'ACTION PRIORISE
### Phase 1 — URGENT : Securite & Stabilite (2-3 semaines)
| Action | Effort | Impact |
|--------|--------|--------|
| Supprimer `admin/admin123` du stream server, implementer auth reelle | **M** | Ferme la faille critique A07 |
| Retirer `!veza-stream-server/.env` du `.gitignore`, nettoyer l'historique Git | **S** | Supprime les secrets du repo |
| Ajouter `config/incus/env/` au `.gitignore` | **S** | Arrete l'hemorragie |
| Rotationner TOUS les secrets exposes (DB, JWT, RabbitMQ, Grafana) | **M** | Invalide les credentials compromises |
| Upgrader `axios` vers >=1.12.0 | **S** | Ferme 4 CVE |
| Implementer JWT revocation persistante (Redis) pour chat + stream | **M** | Tokens revoques restent revoques |
| Changer Grafana admin password | **S** | Ferme A05 |
| Valider URL de redirection OAuth contre allowlist | **S** | Ferme open redirect |
| Ajouter auth sur `/internal/jobs/transcode` | **S** | Ferme DoS vector |
| Implementer auth obligatoire WebSocket stream | **M** | Ferme acces anonyme |
### Phase 2 — STABILISATION (3-4 semaines)
| Action | Effort | Impact |
|--------|--------|--------|
| Ecrire tests pour `features/chat/` (54 fichiers, 0 test) | **L** | Coverage reelle |
| Fixer les 9 suites de tests skippees ou les supprimer | **M** | Arreter les faux positifs |
| Supprimer `veza-mobile/` (ou le mettre dans une branche) | **S** | Nettoyer le code mort |
| Supprimer ou documenter les features fantomes (Studio, Inventory, Education, Gamification, Live) | **M** | Alignement code/realite |
| Upgrader `gin-gonic/gin` vers 1.11.0+ | **M** | Securite + maintien |
| Upgrader `jsonwebtoken` Rust vers 10.x | **M** | Securite |
| Aligner versions Axum (0.7 → 0.8) pour stream server | **L** | Coherence |
| Valider/sanitiser les arguments `exec.Command` | **M** | Fermer command injection |
| Migrer feature flags vers env vars ou backend config | **M** | Flexibilite operationnelle |
| Resoudre la confusion 2 docker-compose prod | **S** | Clarte operationnelle |
### Phase 3 — AMELIORATION & REFONTE (4-6 semaines)
| Action | Effort | Impact |
|--------|--------|--------|
| Introduire Turborepo ou Nx | **L** | Cache de build, graph de deps, DX |
| Implementer secret management (Vault, AWS SM, ou K8s Secrets) | **L** | Securite long terme |
| Completer HLS streaming (backend endpoints) ou retirer le frontend | **L** | Feature promise |
| Ajouter SBOM et dependency scanning en CI | **M** | Conformite |
| Implementer circuit breakers entre services | **M** | Resilience |
| Ajouter CDN pour le streaming audio | **L** | Scalabilite |
| Audit de securite avec `gosec`, `cargo audit`, `npm audit` en CI | **M** | Prevention |
| Setup auto-scaling K8s | **XL** | Production-ready |
| Implementer notifications backend (feature flag → true) | **L** | Feature |
| Documenter l'architecture decision records (ADR) | **M** | Perennite |
---
## CONCLUSION STRATEGIQUE
Ce monorepo est le travail d'une equipe ambitieuse qui a construit un produit reel avec une architecture solide **au niveau conceptuel**. Le backend Go est l'actif le plus mature — clean architecture, securite robuste, tests extensifs. Le frontend React est volumineux et fonctionnel mais souffre de features fantomes qui diluent la credibilite. Les services Rust sont des prototypes techniques prometteurs mais **interdits de production** en l'etat.
Les **3 actions les plus urgentes** sont :
1. Supprimer les credentials hardcodees du stream server
2. Nettoyer les secrets de l'historique Git et les rotationner
3. Upgrader axios (4 CVE actives)
Sans ces 3 actions, **aucun deploiement production ne doit avoir lieu**.
Apres remediation, le produit est **viable comme MVP** avec le backend Go + frontend React. Les services Rust peuvent etre deployed progressivement apres hardening. Le cout total de mise en conformite est estime a **8-12 semaines-developpeur** reparties sur les 3 phases.
**Recommandation finale** : Investissement conditionne a l'execution de la Phase 1 sous 3 semaines, avec audit de validation avant tout deploiement public.

View file

@ -0,0 +1,429 @@
# AUDIT DES TESTS ET DE LA COUVERTURE — VEZA
**Date** : 2025-02-10
**Mandat** : Comité d'architecture — décision sur la fiabilité à moyen terme et la valeur de la base de tests
**Périmètre** : Intégralité du dépôt (monorepo)
**Ton** : Sans complaisance. Faits observables > interprétations.
---
## 0. SYNTHÈSE EXÉCUTIVE (CTO)
### Verdict global
**La base de tests de Veza n'est pas un actif fiable.** Elle ressemble à un dispositif de sécurité (Vitest, Playwright, Jest, Storybook, MSW, 261 tests Go, 288+ tests frontend) mais, en pratique :
- **Les tests unitaires frontend bloquent désormais le build** — le `|| true` a été supprimé (`.github/workflows/ci.yml:148`).
- **Le backend contient 445 échecs documentés** (TEST_FAILS.md), dont 23 P0, 176 tests skippés, 6 panics, 3 erreurs de compilation.
- **Les tests critiques d'authentification (auth middleware, RBAC)** échouent en série (mock expectations non satisfaites).
- **Les workflows frontend-ci et backend-ci pointent vers des chemins inexistants** (`apps/web-frontend`, `apps/backend-api`).
- **La couverture mesurée (seuils 80%) n'est pas vérifiée en CI** — aucune étape n'exige le respect des seuils.
En résumé : **une couverture élevée pourrait masquer une faible résilience.** La CI frontend protège désormais le main (tests bloquants). Le backend reste fragilisé par les échecs documentés et les tests d'intégration en quarantaine.
### Indicateurs clés
| Indicateur | État | Commentaire |
|------------|------|-------------|
| Tests unitaires bloquants | ✅ Oui | `npm run test -- --run` — sans `\|\| true` |
| Tests Go exécutables en CI | ⚠️ Partiel | Seulement `handlers` et `services` avec `-short` |
| Tests E2E en CI | ⚠️ Incertain | playwright.yml sans `working-directory: apps/web` |
| Couverture vérifiée | ❌ Non | Pas de step qui valide les seuils |
| Tests auth / RBAC | ❌ Échouent | Mocks fragiles |
| Tests d'intégration | 🟡 Quarantaine | Beaucoup skippés ou manuels |
### Recommandation stratégique
**Court terme (12 semaines)** : Corriger les chemins des workflows frontend-ci/backend-ci, rendre les tests Go exécutables (exclure les packages qui paniquent), et corriger l'erreur de compilation `playlist_duplicate_transaction_test.go`.
**Moyen terme (13 mois)** : Réparer les tests auth/RBAC, réintégrer les tests d'intégration critiques, et ajouter une étape de validation des seuils de couverture.
**Long terme** : Restructurer la pyramide de tests, éliminer la dette de mocks fragiles, et aligner l'architecture sur la testabilité.
---
## 1. PÉRIMÈTRE D'ANALYSE
### 1.1 Langages et frameworks
| Composant | Langage | Framework | Outil de test |
|-----------|---------|-----------|---------------|
| **Frontend** | TypeScript | React 18, Vite 7, TanStack Query, Zustand | Vitest, Playwright, MSW |
| **Backend API** | Go | Echo/Gin (serveur HTTP) | `go test` |
| **Chat Server** | Rust | Actix/Axum | `cargo test` |
| **Stream Server** | Rust | idem | `cargo test` |
| **Shared** | Rust | veza-common | `cargo test` |
### 1.2 Architecture
- **Monorepo** : `apps/web`, `veza-backend-api`, `veza-chat-server`, `veza-stream-server`, `veza-common`, `packages/`, `fixtures/`
- **Front** : SPA React, feature-based, ~2500+ fichiers TS/TSX
- **Back** : API REST Go, services, handlers, middleware, migrations SQL
- **Rust** : Services temps réel (chat, streaming)
### 1.3 Ce qui est testé vs non testé
| Catégorie | État | Fichiers / Indices |
|-----------|------|--------------------|
| **Testé** | Composants UI, hooks, utils, services frontend, handlers/services Go | ~288 fichiers `.test.ts(x)`, ~261 `*_test.go`, E2E Playwright |
| **Implicitement non testable** | Client API monolithique (2238 lignes), main.tsx (278 lignes), correctifs CSS globaux | `client.ts`, `main.tsx`, `fix-*.css` |
| **Non testé par choix** | Cmd packages Go (migrate, generate-config-docs, etc.) | `t.Skip()` dans packages |
| **Non testé par négligence** | Zones critiques : auth dual (Context + Store), RBAC, permissions, webhooks, marché | Peu de tests sur `authStore` vs `AuthContext`, `webhookService` |
---
## 2. CARTOGRAPHIE DES TESTS EXISTANTS
### 2.1 Typologie
#### Tests unitaires purs
| Composant | Nombre approx. | Répartition | Ratio code critique |
|-----------|----------------|-------------|---------------------|
| **Frontend** | ~250+ | `utils/`, `hooks/`, `components/ui/`, `features/auth/`, `features/playlists/` | Bon sur utils, hooks, auth. Faible sur services API, stores |
| **Backend** | ~150+ | `internal/config/`, `internal/response/`, `internal/validators/`, nombreux handlers | Inégale. Config bien couverte. Handlers/services partiellement |
#### Tests unitaires avec mocks lourds
| Composant | Exemples | Problème |
|-----------|----------|----------|
| **Frontend** | `LoginForm.test.tsx`, `auth.integration.test.tsx` | Mocks de `authStore`, `authService`, `useAuth`, `useLogin`, etc. — 10+ mocks par fichier |
| **Backend** | `auth_middleware_test.go`, `rbac_auth_middleware_test.go` | Mocks `ValidateSession`, `HasRole`, `HasPermission`**échouent** (`0 out of 1 expectation(s) were met`) |
#### Tests d'intégration
| Composant | Fichiers | État |
|-----------|----------|------|
| **Frontend** | `auth.integration.test.tsx`, `playlist.integration.test.tsx`, `collaboration.integration.test.tsx`, `trackUpload.integration.test.tsx` | Présents mais dépendent de mocks MSW |
| **Backend** | `tests/integration/*.go` | Quarantaine. `api_health_test.go` corrigé. `upload_async_polling_test.go` partiellement. `playlist_duplicate_transaction_test.go`**erreur de compilation** (ligne 80) |
#### Tests contractuels
| Composant | Fichier | État |
|-----------|---------|------|
| **Backend** | `tests/contract/api_contract_test.go` | Présent. Non exécuté en CI standard (nécessite `-tags integration`) |
#### Tests end-to-end
| Composant | Fichiers | État |
|-----------|----------|------|
| **Frontend** | `e2e/tests/*.spec.ts` (auth, smoke, playlists, profile, upload, mobile, cross-browser, ui-audit, visual) | Playwright. `globalSetup` requiert API disponible. `workers: 1` (rate limiting). |
| **Tools** | `tools/tests/e2e/specs/*.spec.ts` | Auth, chat, docs, files, profile, stream |
| **Desktop** | `veza-desktop/tests/e2e/*.spec.ts` | Transcoding, library, playlists, settings |
#### Tests de non-régression
| Type | Fichiers | État |
|------|----------|------|
| **Visuels** | `e2e/tests/visual/*.spec.ts`, `visual-regression.spec.ts` | Playwright snapshots. Baselines dans `__snapshots__/` |
| **Storybook** | `scripts/audit-storybook.js`, `test:storybook:playwright` | Audit console/network errors. Workflow séparé |
#### Tests de snapshot
| Composant | Usage | État |
|-----------|-------|------|
| **Frontend** | Vitest snapshots — peu utilisés explicitement | Snapshot tests non dominants |
| **E2E** | Playwright `toHaveScreenshot()` | Utilisé pour visual regression |
### 2.2 Localisation et structure
#### Organisation des dossiers
```
# Frontend
apps/web/src/
├── __tests__/ # Tests transversaux (accessibility, contrast)
├── test/ # setup.ts, test-utils, setup.test.tsx
├── mocks/ # handlers.ts (MSW), test-setup.ts
├── features/*/__tests__/ # Intégration par feature
├── **/*.test.ts(x) # Colocalisés avec le code
└── e2e/ # Playwright (global-setup, tests, utils)
# Backend
veza-backend-api/
├── internal/**/*_test.go # Colocalisés
├── tests/ # integration, contract, security, transactions, etc.
└── docs/ # TEST_FAILS.md, QUARANTINE.md
```
#### Cohérence des conventions
- **Frontend** : `*.test.tsx` ou `*.test.ts` à côté du fichier. Cohérent.
- **Backend** : `*_test.go` standard Go. Cohérent.
- **E2E** : `*.spec.ts` dans `e2e/tests/`. Cohérent.
#### Signes problématiques
| Signe | Fichier / Indice |
|-------|-------------------|
| **Tests orphelins** | `jest.config.js` présent alors que `package.json` utilise `vitest` — configuration dupliquée |
| **Tests redondants** | `PlaylistErrorBoundary.test.tsx` utilise `jest.spyOn` alors que le projet est sur Vitest (`vi` attendu) |
| **Tests trop larges** | `auth.integration.test.tsx` — 600+ lignes, mocks massifs |
| **Tests trop spécifiques** | Certains tests UI vérifient des détails d'implémentation (classes CSS, structure DOM) |
---
## 3. QUALITÉ INTRINSÈQUE DES TESTS
### 3.1 Lisibilité
| Critère | Évaluation | Exemples |
|---------|------------|----------|
| **Clarté de l'intention** | Inégale | `LoginForm.test.tsx` : intentions claires. `auth.integration.test.tsx` : nécessite de lire l'implémentation pour comprendre les mocks |
| **Lisibilité des données** | Correcte | Factories, fixtures dans `fixtures/`. Parfois données inline |
| **Explicitation des cas limites** | Partielle | `serviceErrorHandler.test.ts`, `apiErrorHandler.test.ts` couvrent erreurs. Beaucoup de tests ne couvrent que le chemin heureux |
### 3.2 Robustesse
| Problème | Indice |
|----------|--------|
| **Dépendance à l'ordre** | Vitest exécute en parallèle par défaut. Pas de dépendance explicite à l'ordre |
| **Dépendance à l'horloge** | `MockWebSocket` utilise `setTimeout(100)` — délai fixe. Risque de flakiness si timeout trop court |
| **Dépendance au réseau** | E2E `globalSetup` requiert API. Sans API, les tests E2E échouent |
| **Dépendance au système de fichiers** | Handlers MSW, pas de dépendance directe |
| **Flakiness** | Backend : 14 race conditions (logging), 12 timeouts. Tests middleware auth : couplage fort aux mocks |
### 3.3 Couplage
| Problème | Impact |
|----------|--------|
| **Mocks auth** | Toute refactorisation de l'auth (Context vs Store) casse les tests |
| **Structure DOM** | `screen.getByText(/resend verification email/i)` — couplé au libellé. Changement de traduction = échec |
| **Mock expectations** | Backend : `FAIL: 0 out of 1 expectation(s) were met` — mocks trop stricts, implémentation a changé |
---
## 4. ANALYSE DE LA COUVERTURE
### 4.1 Données brutes
| Composant | Outil | Seuils configurés | Vérification en CI |
|-----------|-------|-------------------|--------------------|
| **Frontend** | Vitest (v8) | branches: 80, functions: 80, lines: 80, statements: 80 | ⚠️ Non vérifié (pas de step `--coverage` en CI) |
| **Frontend** | Jest (legacy) | Idem | Jest non utilisé (Vitest actif) |
| **Backend** | `go test -coverprofile` | — | ✅ Workflow `test-coverage.yml` (séparé, artifacts) |
**Chiffre global** : Non mesuré de manière fiable en CI. Les seuils Vitest sont définis mais aucune étape CI n'exécute `--coverage` ni ne valide les seuils.
### 4.2 Illusions de couverture
| Illusion | Indice |
|----------|--------|
| **Code couvert mais jamais asserté** | Tests qui render sans `expect` sur le comportement. Risque de tests "passants" qui ne valident rien |
| **Chemins heureux uniquement** | Beaucoup de tests ne mockent que le succès. `handleServiceError`, `apiErrorHandler` font exception |
| **Branches d'erreur non exercées** | `client.ts` (2238 lignes) : interceptors, retry, refresh, offline queue — peu de tests directs |
| **Couverture élevée ≠ résilience** | Seuils 80% pourraient être atteints sur des fichiers peu critiques (utils) tout en laissant le cœur métier (auth, RBAC, payments) sous-testé |
---
## 5. ANALYSE PAR DOMAINE MÉTIER
### 5.1 Logique métier à fort risque
| Domaine | Niveau de couverture | Type de tests | Faille potentielle |
|---------|----------------------|---------------|---------------------|
| **Auth / Sessions** | Moyen (front) ; Échec (back) | Unitaires avec mocks ; Middleware Go | Backend : tests auth middleware échouent. Double source auth (Context + Store) non testée |
| **RBAC / Permissions** | Échec | `rbac_auth_middleware_test.go` | Mocks `HasRole`, `HasPermission` non satisfaits. Droits non validés |
| **Upload / Tracks** | Partiel | Intégration quarantaine, handlers | `playlist_duplicate_transaction_test.go` ne compile pas. Upload async partiellement testé |
| **Commerce / Marketplace** | Faible | Peu de tests dédiés | `marketplaceService`, `cartStore``cartStore.test.ts` présent. Services commerce peu couverts |
| **Webhooks** | Faible | `webhook_service_test.go`, `webhook_handlers_test.go` | Présents. Pas de test E2E de livraison |
| **Synchronisation d'état** | Moyen | Offline queue, optimistic updates | `optimisticUpdates.ts` documenté. Peu de tests sur rollback, invalidation |
### 5.2 Invariants métier
- **Propriété des ressources** : `ownership_integration_test.go` présent. Résultat non vérifié en CI.
- **Transactions** : `rbac_transaction_test`, `social_transaction_test`, `playlist_duplicate_transaction_test` — dernier ne compile pas.
---
## 6. TESTABILITÉ DE L'ARCHITECTURE
### 6.1 Points favorables
- **MSW** : Handlers centralisés (~1682 lignes). Développement et tests sans backend.
- **Feature-based** : Structure modulaire. Features isolables.
- **React Query** : Fetch centralisé. Mockable via MSW.
- **Dependency injection** : Go utilise des interfaces. Services injectables.
### 6.2 Points bloquants
| Problème | Fichier / Indice |
|----------|-------------------|
| **Singletons globaux** | `authStore` (Zustand) + `AuthContext` — deux sources. Tests doivent mocker les deux |
| **Dépendances cachées** | `client.ts` — interceptors, retry, CSRF, offline queue. Pas de découpage. Difficile à tester unitairement |
| **Effets de bord** | `main.tsx` — waitForStylesheets, fixDisplayIssues, fixInputFocus. Init complexe |
| **Mocking excessif** | Tests auth : 10+ `vi.mock()`. Toute évolution casse les tests |
### 6.3 Mélange orchestration / logique
- **Pages** : Certaines pages font fetch direct (`useState` + `useEffect` + `apiClient`) au lieu de React Query. Difficile à isoler.
- **Client API** : Logique métier (retry, refresh, validation) mélangée avec transport. Pas de séparation nette.
---
## 7. DETTE DE TEST
### 7.1 Classification
| Dette | Gravité | Coût de correction | Impact sur vélocité |
|------|---------|-------------------|--------------------|
| ~~Tests non bloquants~~ | ~~Critique~~ | — | ✅ **Corrigé** : tests bloquants |
| **Tests Go auth/RBAC échouants** | Critique | Moyen (corriger mocks) | Blocage si activés |
| **176 tests skippés (Backend)** | Élevée | Élevé | Inconnu — tests ignorés |
| **Config Jest obsolète** | Faible | Faible (supprimer) | Confusion |
| **playwright.yml sans working-directory** | Moyenne | Faible | E2E peuvent échouer si exécutés depuis la racine |
| **Workflows frontend/backend ci inutilisables** | Moyenne | Faible (corriger chemins) | Aucun — jamais déclenchés |
### 7.2 Tests lents
- **Vitest** : Timeout observé lors de l'exécution (commande interrompue après 120s). Suite conséquente.
- **Backend** : `go test ./internal/...` timeout également. Suite très volumineuse.
- **E2E** : `workers: 1` — séquentiel. Timeout 60s par test.
### 7.3 Tests fragiles
- **Backend auth** : Changement de signature ou comportement de `ValidateSession` casse tous les tests middleware.
- **Frontend** : Libellés i18n en dur dans les assertions. Changement de traduction = échec.
- **PlaylistErrorBoundary** : Utilise `jest.spyOn` (Jest) au lieu de `vi.spyOn` (Vitest) — incohérence.
---
## 8. CI / AUTOMATISATION
### 8.1 Workflows actifs
| Workflow | Déclenchement | Contenu | Problème |
|----------|---------------|---------|----------|
| **ci.yml** | push/PR main, remediation/*, feature/mvp-complete | Backend (vet, lint, test -short, build), Rust (fmt, build, test chat), Frontend (lint, format, typecheck, **test** (bloquant), build) | ✅ Tests frontend bloquants |
| **playwright.yml** | push/PR main, master | `npx playwright test` | Pas de `working-directory: apps/web`. Dépendances `npm ci` à la racine — peut échouer |
| **storybook-audit.yml** | push/PR apps/web | build-storybook, serve, audit-storybook.js | Correct |
| **test-coverage.yml** (backend) | push/PR main, develop | `go test -coverprofile` | Correct. Artifacts. |
| **frontend-ci.yml** | paths: apps/web-frontend | pnpm test | **Chemin inexistant** (apps/web-frontend) |
| **backend-ci.yml** | paths: apps/backend-api | go test | **Chemin inexistant** (apps/backend-api) |
### 8.2 Fréquence, temps, seuils
- **Fréquence** : À chaque push/PR sur les branches configurées.
- **Temps** : Non mesuré. Playwright timeout 60 min.
- **Seuils bloquants** : Tests unitaires frontend **bloquent** le build. Couverture : pas de seuils vérifiés. Backend : tests exécutés, résultats bloquants si échecs.
### 8.3 Protection du main
**La CI frontend protège désormais le main** — les tests unitaires bloquent le build. **La CI backend reste fragile** : exécution d'un sous-ensemble (`handlers`, `services`, `-short`). Les tests auth/RBAC qui échouent font partie des packages exécutés — la CI backend peut être instable ou les tests skippés. À clarifier.
---
## 9. RISQUES MAJEURS IDENTIFIÉS
### R1. Régression auth / RBAC non détectée
- **Probabilité** : Élevée
- **Impact** : Critique (accès non autorisé, fuite de données)
- **Indice** : Tests auth middleware et RBAC échouent. Toute modification du flux auth n'est pas validée par les tests.
### R2. Déploiement de code cassé
- **Probabilité** : Réduite (frontend)
- **Impact** : Élevé
- **Indice** : Tests frontend bloquants — correction appliquée. Backend : risque persistant si tests skippés ou exclus.
### R3. Désynchronisation auth (Context vs Store)
- **Probabilité** : Moyenne
- **Impact** : Élevé (comportement incohérent, bugs subtils)
- **Indice** : Double source de vérité. Aucun test ne valide la cohérence.
### R4. Régression API non détectée
- **Probabilité** : Moyenne
- **Impact** : Élevé
- **Indice** : Tests d'intégration en quarantaine. Contract tests non exécutés en CI standard. Client API monolithique peu testé.
### R5. E2E fragiles ou non exécutés
- **Probabilité** : Élevée
- **Impact** : Moyen
- **Indice** : `playwright.yml` sans working-directory. `globalSetup` requiert API. Workers=1. Pas de preuve que les E2E passent en CI.
---
## 10. RECOMMANDATIONS STRATÉGIQUES
### 10.1 Court terme (12 semaines)
1. ~~Supprimer `|| true`~~**Fait** : les tests frontend bloquent désormais.
2. **Corriger les chemins** des workflows `frontend-ci.yml` et `backend-ci.yml` (ou les supprimer s'ils sont obsolètes).
3. **Ajouter `working-directory: apps/web`** au workflow `playwright.yml`.
4. **Exclure les packages Go qui paniquent** de la CI : `internal/testutils` (TestRunParallelTests), ou les corriger.
5. **Corriger l'erreur de compilation** `playlist_duplicate_transaction_test.go:80` (utilisation incorrecte de `file`).
### 10.2 Moyen terme (13 mois)
1. **Réparer les tests auth/RBAC** : Aligner les mocks sur l'implémentation actuelle ou refactoriser les mocks pour qu'ils soient moins fragiles.
2. **Réintégrer les tests d'intégration critiques** : `api_health_test.go` (déjà corrigé), contract tests. Exécuter en CI avec tag `integration` sur un environnement dédié.
3. **Ajouter une étape de validation des seuils de couverture** : `vitest run --coverage` et fail si en dessous de 80% sur les répertoires critiques (features/auth, features/playlists, services).
4. **Unifier auth** : Éliminer la double source (Context + Store). Réduire le coût des mocks dans les tests.
5. **Remplacer `jest.spyOn` par `vi.spyOn`** dans tous les tests frontend (ex. PlaylistErrorBoundary.test.tsx).
6. **Découper `client.ts`** : Extraire interceptors, retry, validation en modules testables.
### 10.3 Long terme
1. **Pyramide de tests** : Rééquilibrer — plus de tests unitaires ciblés (logique pure), moins de tests avec mocks massifs. Tests d'intégration avec MSW pour les flux critiques.
2. **Tests par contrat** : OpenAPI comme source de vérité. Tests contractuels automatisés en CI.
3. **Environnement de test dédié** : Base de données, Redis, services pour les tests d'intégration backend. CI nightly ou staging.
4. **Politique de couverture par domaine** : 80% sur auth, RBAC, commerce. 60% sur le reste. Métriques par module.
---
## 11. MÉTRIQUES DE SUIVI
Au-delà du simple % de coverage, proposer :
| Indicateur | Cible | Mesure |
|------------|-------|--------|
| **Taux de tests bloquants** | 100% | Nombre de branches où les tests peuvent échouer sans bloquer → 0 |
| **Taux de tests passants (Backend)** | 95% | (Total - Skip - Fail) / Total |
| **Couverture des branches d'erreur** | 70% | Branches catch/error exercées / total |
| **Temps de suite unitaire** | < 2 min | Frontend Vitest |
| **Temps de suite E2E** | < 15 min | Playwright (chromium uniquement en CI) |
| **Dette de skip** | -10% / mois | Réduire le nombre de tests skippés |
| **Flakiness** | 0% | Nombre de tests flaky sur 10 runs |
---
## 12. RÉFÉRENCES ET FICHIERS CITÉS
| Fichier | Rôle |
|---------|------|
| `apps/web/package.json` | Scripts test, vitest |
| `apps/web/jest.config.js` | Config obsolète (Jest) |
| `apps/web/vitest.config.ts` | Config Vitest, coverage, thresholds |
| `apps/web/src/test/setup.ts` | Setup Vitest, mocks |
| `apps/web/src/mocks/handlers.ts` | MSW handlers |
| `apps/web/e2e/global-setup.ts` | Playwright auth |
| `apps/web/playwright.config.ts` | Config E2E |
| `.github/workflows/ci.yml` | Pipeline principal |
| `.github/workflows/playwright.yml` | E2E |
| `.github/workflows/storybook-audit.yml` | Storybook |
| `veza-backend-api/tests/integration/QUARANTINE.md` | Classification tests |
| `veza-backend-api/docs/TEST_FAILS.md` | Inventaire 445 échecs |
| `veza-backend-api/.github/workflows/test-coverage.yml` | Coverage Go |
---
## 13. FEUILLE DE ROUTE PRIORISÉE
| Priorité | Action | Effort | Impact |
|----------|--------|--------|--------|
| ~~P0~~ | ~~Supprimer `\|\| true`~~ | — | ✅ **Fait** |
| P0 | Corriger `playlist_duplicate_transaction_test.go` (compile) | 2 h | Critique |
| P1 | Ajouter working-directory à playwright.yml | 30 min | Élevé |
| P1 | Corriger ou supprimer frontend-ci/backend-ci workflows | 30 min | Moyen |
| P1 | Exclure/corriger tests Go qui paniquent | 4 h | Élevé |
| P2 | Réparer tests auth middleware (Go) | 8 h | Critique |
| P2 | Validation seuils coverage en CI | 2 h | Élevé |
| P2 | Remplacer jest par vi dans tests frontend | 2 h | Moyen |
| P3 | Découper client.ts | 40 h | Moyen |
| P3 | Unifier auth (Context vs Store) | 24 h | Élevé |
---
**Fin du rapport.**

View file

@ -0,0 +1,463 @@
# Rapport d'Exécution Technique — Veza v1.0
**Date** : 2025-02-10
**Équipe** : Lead Frontend Architect (ex-Spotify), Design Engineer (ex-Discord), Performance Expert
**Objectif** : Valider, affiner et prioriser l'exécution des points de l'audit exhaustif pour atteindre une qualité "Premium Audio Platform".
---
## 1. Verdict Technique Immédiat — Fichiers Critiques
### 1.1 `client.ts` (2238 lignes)
| Critère | Verdict | Détail |
|---------|---------|--------|
| **Maintenabilité** | ⛔ Critique | Fichier monolithique. 5 classes, 2 interceptors, retry, refresh, validation, cache, offline, déduplication. Impossibilité de tester unitairement des parties isolées. |
| **Architecture** | ⚠️ Acceptable | Logique correcte (retry, refresh, CSRF, validation Zod). Mais tout dans un seul fichier. |
| **Risque** | Moyen | Refactoring sans changer les types exportés possible. Dépendances circulaires à éviter. |
**Pourquoi ça fait amateur :** Un fichier de 2200+ lignes suggère un manque de discipline architecturale. Les géants (Spotify, Discord) découpent : interceptors dans des modules, retry dans un utilitaire, refresh dans un service auth dédié.
**Comment font les géants :** Architecture en couches. `apiClient` = instance axios + composition d'interceptors. Chaque interceptor est un fichier < 150 lignes. Les helpers (retry, sanitize) sont des modules testables.
---
### 1.2 `AuthContext.tsx` (108 lignes)
| Critère | Verdict | Détail |
|---------|---------|--------|
| **Rôle** | Redondant | Duplique authStore + useUser. `authService` utilisé directement (authStore utilise `api/auth`). |
| **Usage app** | ❌ Aucun | App ne wrap pas avec `AuthContext.Provider`. Utilisé uniquement dans **Storybook** (decorators). |
| **Conflit** | Oui | 3 fichiers app utilisent `useAuth` de AuthContext : `ProfileView`, `useAuthView`, `useEditProfile`. Ces composants **crashent** si rendus hors Storybook (pas de Provider). |
**Bug confirmé :** `EditProfile` (Settings > Profile) utilise `useEditProfile``useAuth` (AuthContext). L'app n'a pas `AuthContext.Provider`. Donc **Settings > Profile tab = crash** en production.
**Pourquoi ça fait amateur :** Double source de vérité. Storybook fonctionne (AuthProvider), l'app non. Composants qui marchent en Storybook mais crashent en prod.
---
### 1.3 `authStore.ts` (328 lignes)
| Critère | Verdict | Détail |
|---------|---------|--------|
| **Design** | ✅ Solide | Zustand + persist + broadcastSync. Pas de `user` (délégué à React Query via `useUser`). |
| **API** | Propre | `login`, `register`, `logout`, `refreshUser`, `logoutLocal`. Types explicites. |
| **État** | Source principale | Utilisé par App, Header, Sidebar, LoginForm, ProtectedRoute, client.ts (401 → logoutLocal). |
**Points forts :** Gestion httpOnly cookies, refresh avec préservation d'état sur erreurs réseau, sync cross-tabs.
**Pourquoi c'est bien :** Une seule source pour `isAuthenticated`, `isLoading`, `error`. User via React Query (cache, déduplication).
---
### 1.4 `GlobalPlayer.tsx` (301 lignes)
| Critère | Verdict | Détail |
|---------|---------|--------|
| **UI** | ✅ Correcte | Barre glass, gradients, layout responsive. |
| **Waveform** | ❌ Décoratif | `Waveform` utilise `Math.random()` pour hauteurs. Pas de lecture audio. Animation `eq-bounce` décorative. |
| **Problèmes** | Variés | `z-[100]` arbitraire. `translate-y-[200%]` arbitraire. Waveform visible uniquement sur `xl`. |
**Pourquoi ça fait amateur :** Spotify/SoundCloud : waveform = visualisation réelle du signal audio. Veza : barres qui bougent au hasard, indépendantes du son.
**Comment font les géants :** Web Audio API (AnalyserNode) ou Wavesurfer.js pour extraire les données audio et dessiner le waveform. Le seek est possible en cliquant sur le waveform.
---
## 2. Analyse de la Dette Structurelle (Deep Dive)
### 2.1 Auth Dual-Source — Zones de conflit
| Fichier | Import | Comportement |
|---------|--------|--------------|
| `ProtectedRoute.tsx` | `useAuth` (features), `useAuthStore` | OK — useAuth = useAuthStore + useUser |
| `PublicRoute.tsx` | Idem | OK |
| `ProfileView.tsx` | `useAuth` (context) | ❌ Crashe hors Storybook |
| `useAuthView.ts` | `useAuth` (context) | ❌ AuthView utilisé en Storybook seulement |
| `useEditProfile.ts` | `useAuth` (context) | ❌ **EditProfile utilisé dans Settings → CRASH** |
**Plan de migration — Fichier par fichier :**
| Ordre | Fichier | Action |
|-------|---------|--------|
| 1 | `useEditProfile.ts` | Remplacer `import { useAuth } from '@/context/AuthContext'` par `import { useUser } from '@/features/auth/hooks/useUser'`. Utiliser `const { data: user } = useUser()`. |
| 2 | `ProfileView.tsx` | Idem. `const { data: currentUser } = useUser()` à la place de `useAuth()`. |
| 3 | `useAuthView.ts` | `useAuth()` appelé sans destructuring (juste pour vérifier contexte). Remplacer par `useAuthStore()` + `useUser()` si besoin, ou supprimer l'appel si inutile. |
| 4 | `StorybookDecorator` | Remplacer `AuthProvider` (context) par un mock `useAuthStore.setState` ou laisser QueryClient + MSW (useUser fonctionne avec MSW). |
| 5 | `AuthContext.tsx` | Supprimer le fichier. |
| 6 | `AuthContext.test.tsx` | Supprimer ou migrer vers tests authStore. |
**Diff conceptuel pour useEditProfile :**
```diff
- import { useAuth } from '@/context/AuthContext';
+ import { useUser } from '@/features/auth/hooks/useUser';
export function useEditProfile() {
- const { user } = useAuth();
+ const { data: user } = useUser();
// ...
}
```
---
### 2.2 Client API Monolithique — Schéma de découpage
**Structure proposée :**
```
services/api/
├── client.ts # Instance axios + attachement interceptors (150 lignes max)
├── clientConfig.ts # API_TIMEOUTS, baseURL, création instance
├── interceptors/
│ ├── request.ts # CSRF, X-API-Version, validation request (120 lignes)
│ ├── response.ts # Unwrap, validation response, cache, rate limit (200 lignes)
│ └── error.ts # Retry, refresh, 401, toast, offline (250 lignes)
├── lib/
│ ├── validationMetrics.ts # ValidationMetricsTracker, ValidationAlerting
│ ├── networkFailureTracker.ts
│ ├── retry.ts # isRetryableError, getRetryDelay, exponential backoff
│ └── sanitize.ts # sanitizeForLogging, getRequestId
├── auth/
│ ├── refreshQueue.ts # failedQueue, processQueue, isRefreshing
│ └── index.ts
└── index.ts # Re-exports apiClient, deduplicatedApiClient
```
**Garantie types :** `apiClient` reste exporté avec la même signature. Les interceptors sont attachés dans `client.ts`. Les types générés (`ApiResponse`, etc.) ne changent pas.
**Ordre de migration :**
1. Extraire `ValidationMetricsTracker` + `ValidationAlerting``lib/validationMetrics.ts`
2. Extraire `NetworkFailureTracker` + helpers → `lib/networkFailureTracker.ts`
3. Extraire retry logic → `lib/retry.ts`
4. Extraire request interceptor → `interceptors/request.ts`
5. Extraire response success → `interceptors/response.ts`
6. Extraire error handler → `interceptors/error.ts`
7. Réduire `client.ts` à l'assemblage.
---
### 2.3 Correctifs CSS — Analyse et intégration
**fix-input-focus.css :**
- **Problème :** Tailwind `focus:` applique ring/border sur tous les inputs (souris + clavier). Souhait : `focus-visible` uniquement (clavier).
- **Cause :** Composant `Input` (input.tsx) utilise `focus-visible:ring-2` etc. Les navigateurs/situations où `:focus-visible` ne s'applique pas correctement sont comblés par le fix.
- **Solution :** Intégrer dans le composant Input. Ne pas utiliser `focus:` dans les classes Tailwind. Utiliser uniquement `focus-visible:`. Ajouter dans `index.css` :
```css
/* Design System: focus only for keyboard (focus-visible) */
@layer base {
input:focus:not(:focus-visible),
textarea:focus:not(:focus-visible),
select:focus:not(:focus-visible) {
outline: none;
--tw-ring-width: 0;
}
}
```
Puis supprimer `fix-input-focus.css`.
**fix-login-form.css (391 lignes) :**
- **Problème :** Formulaires (login, register) ont des inputs/boutons qui se rétrécissent en flex, des largeurs incorrectes, autofill jaune.
- **Cause racine :**
1. **Autofill** : `-webkit-autofill` garde un fond jaune. Fix : `box-shadow: inset` pour masquer.
2. **Flex shrink** : Parents en `flex` avec `space-y-*` provoquent `min-width: 0` sur enfants. Les inputs n'ont pas `min-w-0` ou `flex-shrink-0` selon le besoin.
3. **Largeur** : `w-full` ne suffit pas si parent a `min-width: 0` et flex.
- **Solution :** Intégrer dans les primitives du Design System :
1. **Autofill** — Dans `index.css` ou `design-system.css` :
```css
@layer base {
input:-webkit-autofill,
input:-webkit-autofill:hover,
input:-webkit-autofill:focus {
-webkit-box-shadow: 0 0 0 1000px var(--background) inset;
-webkit-text-fill-color: var(--foreground);
}
}
```
2. **Input** — Dans `components/ui/input.tsx`, ajouter `min-w-0` et `flex-1` si dans un flex :
```tsx
className={cn(
"flex h-11 w-full min-w-0 rounded-xl ...",
// ...
)}
```
3. **AuthInput** — Vérifier qu'il a `w-full min-w-0` et que le parent form utilise `flex flex-col` avec `space-y-4` sans conflit.
4. **Boutons submit** — S'assurer que `AuthButton` a `w-full min-w-0 flex-shrink-0`.
5. Supprimer `fix-login-form.css` une fois les primitives corrigées.
---
## 3. Analyse "Perception Premium" (UI/UX)
### 3.1 Anatomie du Player — Waveform réactif
**État actuel :** 24 barres avec `height: 20 + Math.random() * 80` et animation CSS. Aucun lien avec l'audio.
**Stack technique pour waveform réactif :**
| Option | Complexité | Qualité | Commentaire |
|--------|------------|---------|-------------|
| **Web Audio API** | Moyenne | Élevée | AnalyserNode + getByteFrequencyData. Barres = niveaux de fréquence. |
| **Wavesurfer.js** | Faible | Très élevée | Waveform canvas, seek intégré. Dépendance ~50kb. |
| **Peaks.js (BBC)** | Moyenne | Élevée | Pré-calcul server-side. Nécessite backend. |
**Recommandation :** **Wavesurfer.js** pour le player expanded (vue détaillée). Pour la barre compacte, garder un EQ animé (Web Audio API) plus léger.
**Implémentation Web Audio API (EQ bars) :**
```tsx
// useAudioAnalyser.ts
const useAudioAnalyser = (audioElement: HTMLAudioElement | null, playing: boolean) => {
const [levels, setLevels] = useState<number[]>(Array(24).fill(0));
const analyserRef = useRef<AnalyserNode | null>(null);
useEffect(() => {
if (!audioElement) return;
const ctx = new AudioContext();
const source = ctx.createMediaElementSource(audioElement);
const analyser = ctx.createAnalyser();
analyser.fftSize = 256;
source.connect(analyser);
analyser.connect(ctx.destination);
analyserRef.current = analyser;
const data = new Uint8Array(analyser.frequencyBinCount);
let raf: number;
const update = () => {
if (!analyserRef.current || !playing) return;
analyserRef.current.getByteFrequencyData(data);
const step = Math.floor(data.length / 24);
const newLevels = Array.from({ length: 24 }, (_, i) => data[i * step] / 255);
setLevels(newLevels);
raf = requestAnimationFrame(update);
};
if (playing) update();
return () => cancelAnimationFrame(raf);
}, [audioElement, playing]);
return levels;
};
```
---
### 3.2 Recherche Header — Plan d'implémentation
**État actuel :** Input avec placeholder "What do you want to play?" sans `onChange` ni navigation.
**Plan :**
1. **State** : Créer `useHeaderSearch()` ou utiliser un store `uiStore.searchQuery` (optionnel).
2. **Navigation** : Sur submit (Enter) ou clic sur suggestion → `navigate(\`/search?q=${encodeURIComponent(query)}\`)`.
3. **Composant** : Remplacer l'input par un composant qui :
- `onKeyDown` (Enter) → navigate
- `onChange` → setQuery (pour suggestions futures)
- Optionnel : dropdown avec suggestions (debounced search API)
**Diff conceptuel :**
```tsx
// Header.tsx
const [searchQuery, setSearchQuery] = useState('');
const navigate = useNavigate();
const handleSearch = (e: React.FormEvent) => {
e.preventDefault();
if (searchQuery.trim()) {
navigate(`/search?q=${encodeURIComponent(searchQuery.trim())}`);
}
};
<form onSubmit={handleSearch} className="flex-1 max-w-md">
<input
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
placeholder="What do you want to play?"
// ...
/>
</form>
```
Temps estimé : 24 h.
---
### 3.3 Skeleton & Transitions — Routes sans LoadingState
| Route | Composant | État actuel | Skeleton proposé |
|-------|------------|-------------|-------------------|
| `/dashboard` | DashboardPage | Present | Déjà OK |
| `/library` | LibraryPage | Mix | LibraryPageSkeleton (grille cartes) |
| `/marketplace` | MarketplaceHome | Spinner générique | MarketplaceSkeleton (grille produits) |
| `/search` | SearchPage | SearchPageSkeleton | OK |
| `/playlists` | PlaylistRoutes | Variable | PlaylistGridSkeleton |
| `/sell` | SellerDashboardView | Spinner | SellerDashboardSkeleton (cards + table) |
| `/profile` | UserProfilePage | UserProfilePageSkeleton | OK |
| `/settings` | SettingsPage | SettingsPageSkeleton | OK |
| `/chat` | ChatPage | Variable | ChatSkeleton (sidebar + messages) |
**Structure type MarketplaceSkeleton :**
```tsx
export function MarketplaceSkeleton() {
return (
<div className="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-4">
{Array.from({ length: 8 }).map((i) => (
<Card key={i} className="overflow-hidden">
<Skeleton className="aspect-square w-full" />
<CardContent className="p-4">
<Skeleton className="h-5 w-3/4 mb-2" />
<Skeleton className="h-4 w-1/2" />
</CardContent>
</Card>
))}
</div>
);
}
```
---
## 4. Audit de Rigueur (Design System)
### 4.1 Fichiers avec valeurs arbitraires
Commande : `node scripts/report-arbitrary-values.mjs`
Résultat du grep manuel : **~40 fichiers** avec `w-[`, `h-[`, `gap-[`, `rounded-[`, etc.
Exemples critiques :
- `GlobalPlayer.tsx` : `z-[100]`, `translate-y-[200%]`, `duration-[var(--duration-normal)]`
- `PlayerControls.tsx` : `text-[8px]` (badge repeat)
- `ChatMessage.tsx`, `Header.tsx`, etc.
### 4.2 Règle ESLint pour bloquer les arbitraires
**Option A — eslint-plugin-tailwindcss :**
```json
// .eslintrc ou eslint.config.js
{
"plugins": ["tailwindcss"],
"rules": {
"tailwindcss/no-arbitrary-value": "warn"
}
}
```
**Option B — Règle custom :**
```js
// eslint-rules/no-arbitrary-tailwind.js
module.exports = {
meta: {
type: 'suggestion',
docs: { description: 'Disallow arbitrary values in Tailwind classes' },
schema: [],
},
create(context) {
const arbitraryPattern = /\b(w|h|gap|p|m|rounded|text|top|left|right|bottom|z)-\[[^\]]+\]/;
return {
JSXAttribute(node) {
if (node.name.name === 'className' && node.value?.type === 'Literal') {
const value = node.value.value || '';
if (arbitraryPattern.test(value)) {
context.report({
node: node.value,
message: 'Avoid arbitrary Tailwind values. Use design tokens or Tailwind scale.',
});
}
}
},
};
},
};
```
**Option C — Tailwind config (recommended) :**
Dans `tailwind.config.ts`, restreindre les valeurs arbitraires via `safelist` ou en n'utilisant que des classes prédéfinies. Approche plus stricte.
**Recommandation :** `eslint-plugin-tailwindcss` avec `no-arbitrary-value: "warn"` en premier. Migration progressive. Puis passer à `"error"` une fois les 40 fichiers corrigés.
---
## 5. Rapport d'Exécution Immédiate — Format "Pourquoi / Comment / Diff"
### 5.1 Recherche Header non fonctionnelle
| | |
|---|---|
| **Pourquoi ça fait amateur** | L'utilisateur voit une barre de recherche évidente (comme Spotify) mais rien ne se passe. Perception de feature incomplète. |
| **Comment font les géants** | Spotify : recherche globale, navigation vers /search, suggestions en temps réel. Discord : command palette Cmd+K. |
| **Diff conceptuel** | Voir section 3.2. Ajouter `value`, `onChange`, `onSubmit``navigate('/search?q=' + query)`. |
---
### 5.2 EditProfile crash (AuthContext)
| | |
|---|---|
| **Pourquoi ça fait amateur** | Un onglet Settings (Profile) qui crashe = bug critique. L'utilisateur ne peut pas éditer son profil. |
| **Comment font les géants** | Une seule source d'auth. Pas de Context qui n'existe que dans les tests. |
| **Diff conceptuel** | `useEditProfile` : remplacer `useAuth` (context) par `useUser` (React Query). 1 ligne modifiée. |
---
### 5.3 Waveform décoratif
| | |
|---|---|
| **Pourquoi ça fait amateur** | Les barres bougent au hasard. L'utilisateur ne voit pas le lien avec la musique. SoundCloud/Spotify : waveform = audio réel. |
| **Comment font les géants** | Web Audio API ou Wavesurfer. Visualisation = données audio réelles. |
| **Diff conceptuel** | Intégrer `useAudioAnalyser` avec `AnalyserNode`. Remplacer `Math.random()` par `levels[i]` du canal fréquentiel. |
---
### 5.4 fix-input-focus.css
| | |
|---|---|
| **Pourquoi ça fait amateur** | Un fichier "fix" indique que le design system n'a pas résolu le problème à la source. |
| **Comment font les géants** | Focus géré dans les composants. `:focus-visible` uniquement. Pas de patch global. |
| **Diff conceptuel** | Ajouter règle `@layer base` dans index.css. Supprimer fix-input-focus.css. Vérifier Input n'utilise pas `focus:` sans `visible`. |
---
### 5.5 fix-login-form.css (391 lignes)
| | |
|---|---|
| **Pourquoi ça fait amateur** | 391 lignes de correctifs avec `!important` = guerre de spécificité. Layout non maîtrisé. |
| **Comment font les géants** | Formulaires avec primitives correctes : `min-w-0`, `flex-1`, autofill géré en amont. |
| **Diff conceptuel** | Intégrer autofill dans design-system.css. Corriger Input, AuthInput, AuthButton avec `min-w-0`. Supprimer fix-login-form.css. |
---
## 6. Priorisation Exécution v1.0
| Priorité | Action | Impact | Effort | Risque |
|----------|--------|--------|--------|--------|
| P0 | Migrer useEditProfile, ProfileView, useAuthView vers useUser | Fix crash Settings | 2 h | Faible |
| P0 | Supprimer AuthContext, mettre à jour Storybook | Une source auth | 2 h | Moyen |
| P1 | Recherche Header fonctionnelle | UX immédiate | 24 h | Faible |
| P1 | Intégrer fix-input-focus dans design system | Réduction dette | 2 h | Faible |
| P2 | Découper client.ts (phases 13) | Maintenabilité | 23 j | Moyen |
| P2 | fix-login-form → primitives | Réduction dette | 1 j | Moyen |
| P3 | Waveform réactif (Web Audio API) | Polish player | 12 j | Moyen |
| P3 | Skeletons Marketplace, SellerDashboard | Perception loaded | 4 h | Faible |
| P4 | ESLint no-arbitrary-value | Rigueur long terme | 2 h | Faible |
---
**Prochaine étape recommandée :** Exécuter P0 (migration AuthContext → useUser) pour éliminer le crash critique avant toute autre action.

View file

@ -696,7 +696,7 @@ Le mock de `RemoveTrackButton` utilisait `onRemoved` au lieu de `onRemove` (prop
---
## 26. CYCLE 20 — PlaylistForm flaky tests (2025-02-10)
r et ## 26. CYCLE 20 — PlaylistForm flaky tests (2025-02-10)
### 26.1 Problèmes

View file

@ -25,5 +25,19 @@ npm install
npm run dev
```
## Docker Production
**Canonical production compose file**: `docker-compose.prod.yml`
```bash
docker compose -f docker-compose.prod.yml up -d
```
**Deprecated** (use docker-compose.prod.yml):
- `docker-compose.production.yml` — legacy, may be removed
- `config/docker/docker-compose.production.yml` — legacy config
See `make/config.mk` for COMPOSE_PROD and deployment docs.
## Documentation
See `docs/` for detailed architecture and development guides.

View file

@ -0,0 +1,46 @@
#!/usr/bin/env bash
# Start the Go backend in the background, then run Vite dev server.
# Run from apps/web (e.g. npm run dev:with-api).
# Backend will listen on port 8080 (veza.fr:8080 when using VITE_DOMAIN=veza.fr).
set -e
ROOT="$(cd "$(dirname "$0")/../.." && pwd)"
BACKEND_DIR="$ROOT/veza-backend-api"
START_BACKEND="$ROOT/scripts/start-backend.sh"
if [ ! -f "$BACKEND_DIR/go.mod" ]; then
echo "Error: backend not found at $BACKEND_DIR"
exit 1
fi
# Start backend in background (same entry point as make dev-backend-api)
echo "Starting backend API on port 8080..."
(
cd "$BACKEND_DIR"
if command -v air &>/dev/null; then
air
else
go run ./cmd/api/main.go
fi
) &
BACKEND_PID=$!
# Kill backend when this script exits (e.g. Ctrl+C)
cleanup() { kill $BACKEND_PID 2>/dev/null || true; exit 0; }
trap cleanup EXIT INT TERM
# Wait for backend to listen
echo "Waiting for backend to be ready..."
for i in 1 2 3 4 5 6 7 8 9 10; do
if curl -s -o /dev/null -w "%{http_code}" "http://127.0.0.1:8080/api/v1/health" 2>/dev/null | grep -q 200; then
echo "Backend ready."
break
fi
if [ $i -eq 10 ]; then
echo "Warning: backend did not respond in time. Starting Vite anyway."
fi
sleep 1
done
# Run Vite (foreground)
exec npm run dev

Binary file not shown.

Before

Width:  |  Height:  |  Size: 35 KiB

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 64 KiB

After

Width:  |  Height:  |  Size: 137 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 64 KiB

After

Width:  |  Height:  |  Size: 137 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 64 KiB

After

Width:  |  Height:  |  Size: 137 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 64 KiB

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 33 KiB

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 37 KiB

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 33 KiB

After

Width:  |  Height:  |  Size: 57 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.9 KiB

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 33 KiB

After

Width:  |  Height:  |  Size: 57 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 33 KiB

After

Width:  |  Height:  |  Size: 57 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 33 KiB

After

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 38 KiB

View file

@ -16,7 +16,7 @@
</head>
<body>
<h1>Visual regression report</h1>
<p class="meta">Threshold: 0.1 | Max diff pixels (pass): 0 | Generated: 2026-02-07T23:52:46.856Z</p>
<p class="meta">Threshold: 0.1 | Max diff pixels (pass): 0 | Generated: 2026-02-10T22:08:03.242Z</p>
<table>
<thead>
<tr>
@ -56,7 +56,7 @@
<tr class="pass">
<td><code>dashboard-desktop-dark.png</code></td>
<td>0</td>
<td>1184000</td>
<td>1173760</td>
<td>Pass</td>
<td></td>
<td>
@ -92,7 +92,7 @@
<tr class="pass">
<td><code>library-desktop-dark.png</code></td>
<td>0</td>
<td>1184000</td>
<td>1173760</td>
<td>Pass</td>
<td></td>
<td>
@ -116,7 +116,7 @@
<tr class="pass">
<td><code>login-desktop-dark.png</code></td>
<td>0</td>
<td>1184000</td>
<td>1173760</td>
<td>Pass</td>
<td></td>
<td>
@ -140,7 +140,7 @@
<tr class="pass">
<td><code>playlists-desktop-dark.png</code></td>
<td>0</td>
<td>1184000</td>
<td>921600</td>
<td>Pass</td>
<td></td>
<td>

View file

@ -28,7 +28,7 @@
"currentPath": "/home/senke/git/talas/veza/apps/web/visual-tests/current/dashboard-desktop-dark.png",
"diffPath": "/home/senke/git/talas/veza/apps/web/visual-tests/diffs/dashboard-desktop-dark.png",
"diffPixels": 0,
"totalPixels": 1184000,
"totalPixels": 1173760,
"error": null,
"pass": true
},
@ -58,7 +58,7 @@
"currentPath": "/home/senke/git/talas/veza/apps/web/visual-tests/current/library-desktop-dark.png",
"diffPath": "/home/senke/git/talas/veza/apps/web/visual-tests/diffs/library-desktop-dark.png",
"diffPixels": 0,
"totalPixels": 1184000,
"totalPixels": 1173760,
"error": null,
"pass": true
},
@ -78,7 +78,7 @@
"currentPath": "/home/senke/git/talas/veza/apps/web/visual-tests/current/login-desktop-dark.png",
"diffPath": "/home/senke/git/talas/veza/apps/web/visual-tests/diffs/login-desktop-dark.png",
"diffPixels": 0,
"totalPixels": 1184000,
"totalPixels": 1173760,
"error": null,
"pass": true
},
@ -98,7 +98,7 @@
"currentPath": "/home/senke/git/talas/veza/apps/web/visual-tests/current/playlists-desktop-dark.png",
"diffPath": "/home/senke/git/talas/veza/apps/web/visual-tests/diffs/playlists-desktop-dark.png",
"diffPixels": 0,
"totalPixels": 1184000,
"totalPixels": 921600,
"error": null,
"pass": true
},

View file

@ -0,0 +1,339 @@
# STRATÉGIE DE COUVERTURE & ORCHESTRATION TMT — VEZA
**Date** : 2025-02-10
**Mandat** : Architecte qualité — redressement de la base de tests
**Audits de référence** : AUDIT_TESTS_ET_COUVERTURE_2025_02.md, AUDIT_FRONTEND_EXHAUSTIF_2025_02.md
**Objectif** : *« Une régression significative ne peut plus passer inaperçue. »*
---
## 0. POSTURE
Décisions appliquées. Pas de pédagogie. Pas de généralités.
---
## 1. REDÉFINITION DE LA COUVERTURE
### 1.1 Définition opérationnelle
> **Couverture** = probabilité que le système se comporte correctement lorsqu'il est modifié.
Trois axes :
| Axe | Signification | Mesure |
|-----|---------------|--------|
| **Logique critique** | Auth, RBAC, commerce, upload, webhooks | Branches exercées sur chemins nominaux ET erreurs |
| **Branches d'erreur** | catch, retry, fallback, validation | % branches catch/error couvertes |
| **Contrats implicites** | API, auth, invariants métier | Tests contractuels, assertions sur schémas |
### 1.2 Cibles chiffrées (différenciées)
| Domaine | Cible branches | Cible lignes | Bloquant |
|---------|----------------|--------------|----------|
| **Auth / RBAC** | ≥ 90% | ≥ 85% | Oui |
| **Commerce / Marketplace** | ≥ 85% | ≥ 80% | Oui |
| **Upload / Tracks** | ≥ 85% | ≥ 80% | Oui |
| **Services API (client.ts, interceptors)** | ≥ 80% | ≥ 75% | Oui |
| **Handlers / Services Go critiques** | ≥ 90% | ≥ 85% | Oui |
| **Hooks / utils frontend** | ≥ 80% | ≥ 80% | Oui |
| **Logique utilitaire** | ≥ 80% | ≥ 80% | Non (warning) |
| **UI pure (composants)** | Indicateur | Indicateur | Non |
**Pourquoi un seuil global unique est architecturalement débile** :
Un seuil global (ex. 80%) masque les zones critiques sous-testées. Le client API (2238 lignes) peut être négligé tandis que des utils triviales gonflent le pourcentage. Les domaines à fort risque (auth, RBAC) doivent avoir des seuils plus élevés et être vérifiés séparément.
---
## 2. STRATÉGIE PAR COUCHE
### 2.1 Backend (Go)
**Fonctions critiques identifiées** (à couvrir en priorité) :
- `internal/core/auth/service.go` — ValidateSession, HasRole, HasPermission
- `internal/api/router.go` — Middleware auth, RBAC
- Handlers : auth, tracks, playlists, marketplace, webhooks
- `internal/database/` — Migrations, prepared statements (chemins erreur)
**Tests à écrire/corriger** :
| Type | Action | Justification |
|------|--------|---------------|
| Chemins nominaux | Corriger mocks auth middleware | Échec actuel : `0 out of 1 expectation(s) were met` |
| Erreurs métier | Ajouter tests sur erreurs 401, 403, 404 | RBAC non validé |
| Erreurs infra | Simuler DB down, timeout | Branches jamais exercées |
**Interdictions** :
- Mocks non alignés avec les interfaces réelles (signatures Go)
- Tests dépendants du timing (supprimer ou utiliser `testify/assert.Eventually`)
### 2.2 Frontend
| Couche | Rôle | Outil | Couverture |
|--------|------|-------|------------|
| **Unitaire** | Hooks, helpers, utils, services purs | Vitest | branches ≥ 80% sur `features/*`, `hooks`, `services` |
| **Intégration** | Pages, flows (MSW) | Vitest + MSW | Pas de logique métier significative non testée hors E2E |
| **E2E** | Flux critiques (auth, playlists, search) | Playwright | Smoke obligatoire |
**Objectif** : *Zéro logique métier significative non testée hors E2E.*
**Zones prioritaires** :
- `authStore` vs `AuthContext` — cohérence (réduire après unification auth)
- `client.ts` — découper puis tester interceptors, retry, offline queue
- `optimisticUpdates.ts` — rollback, invalidation
- `marketplaceService`, `webhookService` — peu couverts
### 2.3 Rust (Chat, Stream)
- **Clippy** : 0 warnings (déjà vital)
- **Tests** : Déjà dans TMT vital
- Pas de changement structurel prévu
---
## 3. ÉLIMINATION DES FAUX POSITIFS
| Élément | Action | Argument |
|---------|--------|----------|
| `npm run test -- --run \|\| true` | **Supprimer** | Tests qui passent malgré régression = illusion |
| Snapshots sans assertion sémantique | **Supprimer ou remplacer** | Snapshots décoratifs ; ajouter `expect` sur comportement |
| Tests qui ne testent que React/framework | **Supprimer** | Redondants avec tests framework |
| `PlaylistErrorBoundary.test.tsx` (jest.spyOn) | **Remplacer** par `vi.spyOn` | Incohérence Vitest |
| Tests Go auth/RBAC avec mocks cassés | **Réparer** | Sinon les exclure explicitement (documenté) |
---
## 4. RÉDUCTION DU MOCKING
**Objectif** : remplacer le mocking par de la **composition testable**.
| Action | Détail |
|--------|--------|
| Extraction de ports/adapters | Interfaces pour `authService`, `apiClient` — injection en tests |
| MSW ciblé | Garder MSW pour les handlers HTTP ; ne pas mocker 10+ modules par test |
| Doubles de test minimaux | Backend : interfaces Go déjà présentes ; utiliser des implémentations stub |
| Unifier auth | Une source (Store) → un mock au lieu de deux |
**Critère** : si un test nécessite > 5 mocks, le design est toxique.
---
## 5. ORCHESTRATION TMT
### 5.1 État actuel (diagnostic)
| Aspect | État | Détail |
|--------|------|--------|
| **TMT utilisé en CI** | ❌ Non | La CI n'appelle jamais `tmt run` |
| **Types de tests TMT** | Partiel | Build, bundle_size, build_perf, no_critical_js ; Go unit (internal) ; Rust clippy/test |
| **Vitest** | ❌ Hors TMT | Exécuté directement dans ci.yml avec `\|\| true` |
| **Playwright** | ❌ Hors TMT | Workflow séparé, sans `working-directory: apps/web` |
| **Contournement** | CI directe | `go test`, `npm run test`, `npx playwright test` sans TMT |
**Conclusion** : TMT est une couche déclarative sous-exploitée. La CI est une projection ad-hoc, pas fidèle à TMT.
### 5.2 Rôle cible de TMT
TMT devient :
- **Point d'entrée unique** pour tous les tests
- Garant de l'ordre, des dépendances, des profils (local / CI / nightly)
### 5.3 Modélisation TMT cible
```
tmt/
├── plans/
│ ├── vital.fmf # Tier 1 — bloque tout
│ ├── integration.fmf # Tier 2 — nécessite infra (DB, Redis)
│ └── nightly.fmf # Tier 3 — E2E, contract, visuels
├── tests/
│ ├── frontend/
│ │ ├── build.sh
│ │ ├── build_perf.sh
│ │ ├── bundle_size.sh
│ │ ├── no_critical_js.sh
│ │ └── unit.sh # NOUVEAU — npm run test -- --run
│ ├── frontend-e2e/
│ │ └── playwright.sh # NOUVEAU — npx playwright test
│ ├── backend/
│ │ ├── unit.sh
│ │ ├── integration.sh
│ │ ├── core_isolation.sh
│ │ ├── startup_time.sh
│ │ └── memory_budget.sh
│ └── services/
│ ├── rust_clippy.sh
│ └── rust_test.sh
└── README.md
```
**Variables d'environnement explicites** :
| Variable | Valeur | Rôle |
|----------|--------|------|
| `GOMAXPROCS` | 1 | Low-power backend |
| `LIBGL_ALWAYS_SOFTWARE` | 1 | GPU désactivé |
| `RUST_BACKTRACE` | 0 | Bruit minimisé |
| `VEZA_TEST_DB_URL` | (optionnel) | Intégration |
| `VEZA_E2E_API_URL` | (optionnel) | E2E |
**Artefacts de sortie** :
| Artefact | Emplacement | Usage |
|----------|-------------|-------|
| Coverage Go | `veza-backend-api/coverage.out` | Rapport, seuils |
| Coverage Vitest | `apps/web/coverage/` | Rapport, seuils |
| Playwright report | `apps/web/playwright-report/` | Debug E2E |
### 5.4 Plans TMT
| Plan | Tier | Contenu | Bloquant |
|------|------|---------|----------|
| **vital** | 1 | build, bundle_size, build_perf, no_critical_js, backend unit, rust clippy/test, **frontend unit** | Oui |
| **legacy** | 2 | integration (backend), startup_time, memory_budget | Non (warning) |
| **nightly** | 3 | E2E Playwright, contract, Storybook audit | Non |
**Changement majeur** : `frontend/unit.sh` ajouté au plan vital. Exécute `npm run test -- --run` sans `|| true`.
---
## 6. COUVERTURE & TMT
| Plan | Couverture produite | Agrégation | Seuils |
|------|---------------------|-------------|--------|
| **vital** | `vitest run --coverage`, `go test -coverprofile` | Rapports séparés par composant | Évalués en CI ; échec si seuils non atteints |
| **legacy** | Idem | Idem | Indicateur |
| **nightly** | E2E (pas de coverage classique) | Playwright HTML report | Binaire (pass/fail) |
**Agrégation** : pas de rapport global unique. Chaque composant (frontend, backend) a ses seuils. CI échoue si un seuil est violé.
**Aucun** `|| true`. Aucune exception silencieuse.
---
## 7. INTÉGRATION CI
**Principe** : La CI est une **projection fidèle** de l'exécution TMT.
### 7.1 Règle
La CI ne doit **jamais** appeler directement `vitest`, `go test`, `playwright`.
Elle appelle **toujours** `tmt run plan --name /vital` (ou équivalent).
### 7.2 Schéma cible
```yaml
# .github/workflows/ci.yml (simplifié)
jobs:
test:
steps:
- uses: actions/checkout@v4
- name: Install TMT
run: pip install tmt
- name: Run Vital Tests
run: tmt run plan --name /vital
# Plus de go test, npm test, etc. directs
```
### 7.3 Workflows à corriger/supprimer
| Workflow | Action | Raison |
|----------|--------|--------|
| `ci.yml` | Remplacer jobs par `tmt run` | Point d'entrée unique |
| `frontend-ci.yml` | Supprimer | Chemin `apps/web-frontend` inexistant |
| `backend-ci.yml` | Supprimer | Chemin `apps/backend-api` inexistant |
| `playwright.yml` | Intégrer dans TMT nightly ou garder séparé avec `working-directory: apps/web` | E2E nécessite runtime différent |
---
## 8. FEUILLE DE ROUTE
### Phase 1 — Rétablissement de la vérité (12 semaines)
| Priorité | Action | Effort |
|----------|--------|--------|
| **P0** | Supprimer `|| true` des tests frontend (ci.yml) | 5 min |
| **P0** | Corriger `playlist_duplicate_transaction_test.go:80` (compile) | 2 h |
| **P0** | Exclure/corriger tests Go qui paniquent (testutils, RoomService) | 4 h |
| **P1** | Ajouter `working-directory: apps/web` à playwright.yml | 15 min |
| **P1** | Supprimer frontend-ci.yml, backend-ci.yml (ou corriger chemins) | 15 min |
**Objectif** : tests qui échouent = build rouge assumé.
### Phase 2 — Couverture ciblée (13 mois)
| Priorité | Action | Effort |
|----------|--------|--------|
| **P1** | Réparer tests auth middleware (Go) | 8 h |
| **P1** | Ajouter `tmt/tests/frontend/unit.sh` et l'intégrer au plan vital | 2 h |
| **P1** | Faire appeler la CI `tmt run plan --name /vital` | 4 h |
| **P2** | Validation seuils coverage en CI (par domaine) | 8 h |
| **P2** | Remplacer jest.spyOn par vi.spyOn | 2 h |
| **P2** | Tests branches d'erreur sur auth, RBAC, client API | 24 h |
**Objectif** : ajout de tests uniquement sur zones critiques ; suppression de tests toxiques.
### Phase 3 — Stabilisation (24 semaines)
| Priorité | Action | Effort |
|----------|--------|--------|
| **P2** | Temps d'exécution suite unitaire < 2 min (frontend) | Variable |
| **P2** | Flakiness = 0 (audit, correction) | Variable |
| **P3** | Découper client.ts (modules testables) | 40 h |
| **P3** | Unifier auth (Context → Store) | 24 h |
**Objectif** : temps maîtrisé ; zéro tolérance au flaky.
---
## 9. LIVRABLE — LISTE PRIORISÉE
### 9.1 Tests à écrire
| Domaine | Fichier / Zone | Type | Priorité |
|---------|----------------|------|----------|
| Auth | `internal/core/auth` | Unit (chemins erreur) | P0 |
| RBAC | `rbac_auth_middleware_test.go` | Corriger mocks | P0 |
| Client API | `client.ts` (après découpage) | Interceptors, retry | P1 |
| Commerce | `marketplaceService`, `cartStore` | Unit | P1 |
| Webhooks | `webhookService`, handlers | Unit + integration | P1 |
| Optimistic | `optimisticUpdates.ts` | Rollback, invalidation | P2 |
### 9.2 Tests à corriger
| Test | Action | Priorité |
|------|--------|----------|
| `auth_middleware_test.go` | Aligner mocks avec ValidateSession | P0 |
| `rbac_auth_middleware_test.go` | Aligner mocks HasRole/HasPermission | P0 |
| `PlaylistErrorBoundary.test.tsx` | jest → vi | P1 |
| `auth.integration.test.tsx` | Réduire mocks, scinder | P2 |
### 9.3 Tests à supprimer
| Test | Raison | Priorité |
|------|--------|----------|
| Snapshots sans assertion sémantique | Illusion de couverture | P2 |
| Tests redondants (framework only) | Aucune valeur métier | P2 |
| Tests orphelins (`jest.config.js` si Jest inutilisé) | Nettoyage | P3 |
---
## 10. RÉSUMÉ EXÉCUTIF
| Principe | Décision |
|----------|----------|
| **Couverture** | Différenciée par domaine ; domaines critiques ≥ 90% branches |
| **Faux positifs** | Suppression de `\|\| true` ; tests cassés = build rouge |
| **Mocking** | Réduction ; composition testable ; ≤ 5 mocks par test |
| **TMT** | Point d'entrée unique ; CI appelle `tmt run` |
| **Phases** | 1. Vérité → 2. Couverture ciblée → 3. Stabilisation |
**Phrase de clôture** : *Une régression significative ne peut plus passer inaperçue* — à condition que les phases 1 et 2 soient exécutées sans compromis.
---
**Fin du document.**

22
tmt/tests/frontend/unit.sh Executable file
View file

@ -0,0 +1,22 @@
#!/bin/bash
set -e
REPO_ROOT=$(git rev-parse --show-toplevel)
WEB_DIR="$REPO_ROOT/apps/web"
echo "📍 Frontend Unit Tests (Vitest)"
echo "📂 Web Directory: $WEB_DIR"
cd "$WEB_DIR"
if [ ! -d "node_modules" ]; then
echo "📦 Installing dependencies..."
npm ci
else
echo "✅ Dependencies found"
fi
echo "🧪 Running unit tests..."
npm run test -- --run
echo "✅ Unit tests passed."