veza/tests/e2e/15-routes-coverage.spec.ts
senke 6fad0ad68d fix: stabilize frontend — 98 TS errors to 0, align API endpoints, optimize bundle
- Fix 98 TypeScript errors across 37 files:
  - Service layer double-unwrapping (subscriptionService, distributionService, gearService)
  - Self-referencing variables in SearchPageResults
  - FeedView/ExploreView .posts→.items alignment
  - useQueueSync Zustand subscribe API
  - AdminAuditLogsView missing interface fields
  - Toast proxy type, interceptor type narrowing
  - 22 unused imports/variables removed
  - 5 storybook mock data fixes

- Align frontend API calls with backend endpoints:
  - Analytics: useAnalyticsView now calls /creator/analytics/dashboard (was /analytics)
  - Chat: chatService uses /conversations (was mock data), WS URL from backend token
  - Dashboard StatsSection: uses real /dashboard API data (was hardcoded zeros)
  - Settings: suppress 2FA toast error when endpoint unavailable

- Fix marketplace products: seed uses 'active' status (was 'published')
- Enrich seed: admin follows all creators (feed has content)

- Optimize bundle: vendor catch-all 793KB→318KB gzip (-60%)
  Split into vendor-charts, vendor-emoji, vendor-swagger, vendor-media, etc.

- Clean repo: remove ~100 orphaned screenshots, audit reports, logs from root

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-24 21:18:49 +01:00

362 lines
16 KiB
TypeScript

import { test, expect } from '@chromatic-com/playwright';
import { loginViaAPI, CONFIG, navigateTo, assertNotBroken } from './helpers';
// =============================================================================
// ROUTES — Couverture complète des routes @feature-routes
//
// Ce fichier teste chaque route du routeur qui n'est pas couverte par
// les autres fichiers de test. Objectif : aucune route sans test.
// =============================================================================
test.describe('ROUTES — Pages publiques (auth non requise) @feature-routes', () => {
test('01. Page /verify-email se charge (sans token, affiche message)', async ({ page }) => {
await navigateTo(page, '/verify-email');
const body = await page.textContent('body') || '';
expect(body).not.toMatch(/500|Internal Server Error/i);
expect(body.length).toBeGreaterThan(100);
// Without a token, should show an informational message or error
const hasMessage = /verify|vérif|token|email|lien|link|invalid|expire/i.test(body);
console.log(` /verify-email (no token): ${hasMessage ? 'message shown' : 'page loaded'} (${body.length} chars)`);
});
test('02. Page /reset-password se charge (sans token, affiche formulaire ou message)', async ({ page }) => {
await navigateTo(page, '/reset-password');
const body = await page.textContent('body') || '';
expect(body).not.toMatch(/500|Internal Server Error/i);
expect(body.length).toBeGreaterThan(100);
// Without a token, should show a form to enter email or an error
const hasContent = /reset|réinitialiser|password|mot de passe|email|token|invalid|expire/i.test(body);
console.log(` /reset-password (no token): ${hasContent ? 'content shown' : 'page loaded'} (${body.length} chars)`);
});
test('03. Page /forgot-password se charge', async ({ page }) => {
await navigateTo(page, '/forgot-password');
const body = await page.textContent('body') || '';
expect(body).not.toMatch(/500|Internal Server Error/i);
expect(body.length).toBeGreaterThan(100);
const hasForm = /email|forgot|oublié|réinitialiser|reset/i.test(body);
console.log(` /forgot-password: ${hasForm ? 'form shown' : 'page loaded'} (${body.length} chars)`);
});
test('04. Page /design-system se charge', async ({ page }) => {
await navigateTo(page, '/design-system');
await page.waitForTimeout(2_000);
const body = await page.textContent('body') || '';
// design-system may not exist — should either load or redirect to 404/login
expect(body).not.toMatch(/500|Internal Server Error|crash|TypeError/i);
// Page may be minimal (redirect to 404 or login) — just check it's not blank
expect(body.trim().length).toBeGreaterThan(10);
const url = page.url();
console.log(` /design-system: ended at ${url} (${body.length} chars)`);
});
});
test.describe('ROUTES — Pages d\'erreur @feature-routes', () => {
test('05. Page /404 se charge avec message explicite', async ({ page }) => {
await navigateTo(page, '/404');
await page.waitForTimeout(2_000);
const body = await page.textContent('body') || '';
expect(body).not.toMatch(/500|Internal Server Error/i);
// The 404 page may be compact — just ensure it has some content
expect(body.trim().length).toBeGreaterThan(10);
// Check for 404 content or that we're on the right page
const has404 = /404|not found|introuvable|page.*exist|non trouvée/i.test(body) || page.url().includes('/404');
expect(has404).toBeTruthy();
console.log(` /404: proper 404 message displayed (${body.length} chars)`);
});
test('06. Page /500 se charge avec message explicite', async ({ page }) => {
await navigateTo(page, '/500');
await page.waitForTimeout(2_000);
const body = await page.textContent('body') || '';
// Page may be minimal — just check it's not blank
expect(body.trim().length).toBeGreaterThan(10);
// /500 might redirect to 404 or show a server error page
const hasErrorPage = /500|erreur|error|server|serveur|something went wrong|problem/i.test(body) ||
/404|not found/i.test(body) || page.url().includes('/404') || page.url().includes('/login');
console.log(` /500: ${hasErrorPage ? 'error page shown' : 'page loaded'} at ${page.url()} (${body.length} chars)`);
});
test('07. Route wildcard inconnue redirige vers /404 @critical', async ({ page }) => {
await navigateTo(page, '/this-route-absolutely-does-not-exist-xyz-98765');
const body = await page.textContent('body') || '';
expect(body).not.toMatch(/crash|TypeError|Cannot read/i);
expect(body.length).toBeGreaterThan(50);
const url = page.url();
const is404 = /404|not found|introuvable/i.test(body) || url.includes('/404');
expect(is404).toBeTruthy();
console.log(` Wildcard route: redirected to ${url}`);
});
test('08. Route wildcard avec path profond redirige vers /404', async ({ page }) => {
await navigateTo(page, '/a/b/c/d/e/f/nonexistent');
const body = await page.textContent('body') || '';
expect(body).not.toMatch(/crash|TypeError|Cannot read/i);
expect(body.length).toBeGreaterThan(50);
const url = page.url();
const handled = /404|not found|introuvable|login/i.test(body) ||
url.includes('/404') || url.includes('/login');
console.log(` Deep wildcard: ended at ${url} (${handled ? 'handled' : 'check behavior'})`);
});
});
test.describe('ROUTES — Pages protegees non couvertes @feature-routes', () => {
test.beforeEach(async ({ page }) => {
await loginViaAPI(page, CONFIG.users.listener.email, CONFIG.users.listener.password);
});
test('09. Page /queue se charge @feature-player', async ({ page }) => {
await navigateTo(page, '/queue');
const body = await page.textContent('body') || '';
expect(body).not.toMatch(/500|Internal Server Error|crash|TypeError/i);
expect(body.length).toBeGreaterThan(100);
const hasContent = /queue|file d'attente|lecture|play|empty|vide|aucun/i.test(body);
console.log(` /queue: ${hasContent ? 'content shown' : 'page loaded'} (${body.length} chars)`);
});
test('10. Page /distribution se charge @feature-distribution', async ({ page }) => {
await navigateTo(page, '/distribution');
const body = await page.textContent('body') || '';
expect(body).not.toMatch(/500|Internal Server Error|crash|TypeError/i);
expect(body.length).toBeGreaterThan(100);
const url = page.url();
console.log(` /distribution: ended at ${url} (${body.length} chars)`);
});
test('11. Page /support se charge @feature-support', async ({ page }) => {
// Track server errors (5xx) during navigation
let has5xx = false;
page.on('response', (res) => {
if (res.status() >= 500) has5xx = true;
});
await navigateTo(page, '/support');
const body = await page.textContent('body') || '';
// /support may not be implemented — accept 404 pages, error-boundary UIs, or redirects
// Only fail on actual crashes or 500 server errors
expect(body).not.toMatch(/crash|TypeError|Cannot read/i);
expect(has5xx).toBe(false);
expect(body.length).toBeGreaterThan(50);
const url = page.url();
const hasContent = /support|aide|help|ticket|contact|404|not found/i.test(body);
console.log(` /support: ${hasContent ? 'content shown' : 'page loaded'} at ${url} (${body.length} chars)`);
});
test('12. Page /checkout/complete se charge (sans commande, etat approprie)', async ({ page }) => {
await navigateTo(page, '/checkout/complete');
const body = await page.textContent('body') || '';
expect(body).not.toMatch(/crash|TypeError|Cannot read/i);
expect(body.length).toBeGreaterThan(50);
const url = page.url();
// Without an order, should show an error/empty state or redirect
const handled = /no order|aucune commande|not found|error|success|merci|thank/i.test(body) ||
url.includes('/marketplace') || url.includes('/dashboard') || url.includes('/404');
console.log(` /checkout/complete (no order): ended at ${url} (${body.length} chars)`);
});
test('13. Page /playlists/favoris redirige vers la playlist favoris @feature-playlists', async ({ page }) => {
await navigateTo(page, '/playlists/favoris');
const body = await page.textContent('body') || '';
expect(body).not.toMatch(/crash|TypeError|Cannot read/i);
expect(body.length).toBeGreaterThan(50);
const url = page.url();
// Should either show favorites playlist or redirect to /playlists
const handled = /favoris|favorites|liked|playlist/i.test(body) ||
url.includes('/playlists') || url.includes('/library');
console.log(` /playlists/favoris: ended at ${url} (${body.length} chars)`);
});
test('14. Page /marketplace se charge @feature-marketplace', async ({ page }) => {
await navigateTo(page, '/marketplace');
const body = await page.textContent('body') || '';
expect(body).not.toMatch(/500|Internal Server Error|crash|TypeError/i);
expect(body.length).toBeGreaterThan(100);
console.log(` /marketplace: loaded (${body.length} chars)`);
});
test('15. Page /analytics se charge (creator/listener)', async ({ page }) => {
await navigateTo(page, '/analytics');
const body = await page.textContent('body') || '';
expect(body).not.toMatch(/crash|TypeError|Cannot read/i);
expect(body.length).toBeGreaterThan(50);
const url = page.url();
console.log(` /analytics: ended at ${url} (${body.length} chars)`);
});
test('16. Page /upload se charge', async ({ page }) => {
await navigateTo(page, '/upload');
const body = await page.textContent('body') || '';
expect(body).not.toMatch(/crash|TypeError|Cannot read/i);
expect(body.length).toBeGreaterThan(50);
const url = page.url();
console.log(` /upload: ended at ${url} (${body.length} chars)`);
});
test('17. Page /listen-together se charge @feature-social', async ({ page }) => {
await navigateTo(page, '/listen-together');
const body = await page.textContent('body') || '';
expect(body).not.toMatch(/crash|TypeError|Cannot read/i);
expect(body.length).toBeGreaterThan(50);
const url = page.url();
console.log(` /listen-together: ended at ${url} (${body.length} chars)`);
});
});
test.describe('ROUTES — Routes parametrees avec parametres invalides @feature-routes', () => {
test.beforeEach(async ({ page }) => {
await loginViaAPI(page, CONFIG.users.listener.email, CONFIG.users.listener.password);
});
test('18. Page /playlists/shared/invalid-token affiche erreur ou 404', async ({ page }) => {
await navigateTo(page, '/playlists/shared/invalid-token-xyz-99999');
const body = await page.textContent('body') || '';
expect(body).not.toMatch(/crash|TypeError|Cannot read/i);
expect(body.length).toBeGreaterThan(50);
const url = page.url();
const handled = /not found|introuvable|404|error|invalid|invalide|expired|expiré/i.test(body) ||
url.includes('/404') || url.includes('/playlists');
console.log(` /playlists/shared/invalid: ${handled ? 'error shown' : 'page loaded'} at ${url}`);
});
test('19. Page /chat/join/invalid-token affiche erreur ou 404', async ({ page }) => {
await navigateTo(page, '/chat/join/invalid-token-abc-11111');
const body = await page.textContent('body') || '';
expect(body).not.toMatch(/crash|TypeError|Cannot read/i);
expect(body.length).toBeGreaterThan(50);
const url = page.url();
const handled = /not found|introuvable|404|error|invalid|invalide|expired|expiré|chat/i.test(body) ||
url.includes('/404') || url.includes('/chat');
console.log(` /chat/join/invalid: ${handled ? 'error shown' : 'page loaded'} at ${url}`);
});
test('20. Page /listen-together/invalid-session affiche erreur ou 404', async ({ page }) => {
await navigateTo(page, '/listen-together/invalid-session-xyz-77777');
const body = await page.textContent('body') || '';
expect(body).not.toMatch(/crash|TypeError|Cannot read/i);
expect(body.length).toBeGreaterThan(50);
const url = page.url();
const handled = /not found|introuvable|404|error|invalid|invalide|session|expired/i.test(body) ||
url.includes('/404') || url.includes('/listen-together');
console.log(` /listen-together/invalid: ${handled ? 'error shown' : 'page loaded'} at ${url}`);
});
test('21. Page /tracks/invalid-uuid affiche erreur ou 404', async ({ page }) => {
await navigateTo(page, '/tracks/not-a-valid-uuid-at-all');
const body = await page.textContent('body') || '';
expect(body).not.toMatch(/crash|TypeError|Cannot read/i);
expect(body.length).toBeGreaterThan(50);
const url = page.url();
const handled = /not found|introuvable|404|error/i.test(body) || url.includes('/404');
console.log(` /tracks/invalid-uuid: ${handled ? 'error shown' : 'page loaded'} at ${url}`);
});
test('22. Page /u/nonexistent-user affiche erreur ou 404', async ({ page }) => {
await navigateTo(page, '/u/this-user-absolutely-does-not-exist-zzz');
const body = await page.textContent('body') || '';
expect(body).not.toMatch(/crash|TypeError|Cannot read/i);
expect(body.length).toBeGreaterThan(50);
const url = page.url();
const handled = /not found|introuvable|404|error|n'existe pas|does not exist/i.test(body) ||
url.includes('/404');
console.log(` /u/nonexistent: ${handled ? 'error shown' : 'page loaded'} at ${url}`);
});
test('23. Page /playlists/:id/edit redirige vers /playlists/:id ou affiche erreur', async ({ page }) => {
// Use a fake playlist ID
await navigateTo(page, '/playlists/fake-playlist-id-12345/edit');
const body = await page.textContent('body') || '';
expect(body).not.toMatch(/crash|TypeError|Cannot read/i);
expect(body.length).toBeGreaterThan(50);
const url = page.url();
// Should redirect to the playlist page, show 404, or show an error
const handled = /not found|introuvable|404|error|playlist/i.test(body) ||
url.includes('/playlists') || url.includes('/404');
console.log(` /playlists/:id/edit (invalid): ${handled ? 'handled' : 'page loaded'} at ${url}`);
});
test('24. Page /marketplace/products/invalid-id affiche erreur ou 404', async ({ page }) => {
await navigateTo(page, '/marketplace/products/nonexistent-product-zzz');
const body = await page.textContent('body') || '';
expect(body).not.toMatch(/crash|TypeError|Cannot read/i);
expect(body.length).toBeGreaterThan(50);
const url = page.url();
const handled = /not found|introuvable|404|error/i.test(body) ||
url.includes('/404') || url.includes('/marketplace');
console.log(` /marketplace/products/invalid: ${handled ? 'error shown' : 'page loaded'} at ${url}`);
});
});
test.describe('ROUTES — Protection des routes (redirection sans auth) @feature-routes', () => {
test('25. Routes protegees redirigent vers /login sans auth', async ({ page }) => {
const protectedRoutes = [
'/queue',
'/distribution',
'/support',
'/analytics',
'/upload',
'/listen-together',
'/checkout/complete',
];
for (const route of protectedRoutes) {
await page.goto(route, { waitUntil: 'domcontentloaded' });
await page.waitForLoadState('networkidle').catch(() => {});
const url = page.url();
const redirected = url.includes('/login') || url.includes('/register');
console.log(` ${route} (no auth): ${redirected ? 'redirected to login' : 'ended at ' + url}`);
// Should either redirect to login or not crash
const body = await page.textContent('body') || '';
expect(body).not.toMatch(/crash|TypeError|Cannot read/i);
}
});
});