veza/tests/e2e/audit/ethical/01-principles.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

149 lines
6 KiB
TypeScript

import { test, expect } from '@chromatic-com/playwright';
import { loginViaAPI, navigateTo } from '../helpers';
import { TEST_USERS, ROUTES } from '../design-tokens';
test.describe('ÉTHIQUE — Anti-gamification, métriques privées, pas de dark patterns', () => {
test.beforeEach(async ({ page }) => {
await loginViaAPI(page, TEST_USERS.listener.email, TEST_USERS.listener.password);
});
test('Aucun compteur de "likes" ou "plays" visible publiquement', async ({ page }) => {
// Les métriques de popularité ne doivent PAS être visibles pour les listeners
// Elles sont réservées aux créateurs dans leur dashboard analytics
const pagesToCheck = ['/feed', '/discover', '/dashboard', '/library'];
for (const path of pagesToCheck) {
await navigateTo(page, path);
const metricsExposed = await page.evaluate(() => {
const body = document.body.textContent?.toLowerCase() || '';
const issues: string[] = [];
// Patterns de métriques publiques interdites
// Note: "plays", "views", "likes" en tant que compteurs visibles sont interdits
// Mais "play" en tant que bouton d'action est OK
document.querySelectorAll('[class*="play-count"], [class*="like-count"], [class*="view-count"], [data-testid*="play-count"], [data-testid*="like-count"]').forEach(el => {
if (getComputedStyle(el).display !== 'none') {
issues.push(`Métrique publique visible: ${el.className} — texte: "${el.textContent?.trim().slice(0, 30)}"`);
}
});
return issues;
});
for (const issue of metricsExposed) {
console.log(`[ETHICAL] ${path}: ${issue}`);
}
expect(metricsExposed.length,
`Métriques de popularité visibles publiquement sur ${path} (interdit par CLAUDE.md §4):\n${metricsExposed.join('\n')}`
).toBe(0);
}
});
test('Aucun élément de gamification (XP, streak, badge, leaderboard)', async ({ page }) => {
const pagesToCheck = ['/dashboard', '/profile', '/settings', '/library'];
for (const path of pagesToCheck) {
await navigateTo(page, path);
const gamificationElements = await page.evaluate(() => {
const body = document.body.textContent || '';
const issues: string[] = [];
// Patterns de gamification interdits
const forbidden = [
/\bXP\b/,
/\bstreak\b/i,
/\bleaderboard\b/i,
/\blevel\s*up\b/i,
/\bclassement\b/i,
/\bscore\b(?!.*password)/i, // "score" sauf "password score"
];
for (const pattern of forbidden) {
if (pattern.test(body)) {
const match = body.match(pattern);
if (match) {
issues.push(`Texte de gamification trouvé: "${match[0]}" (interdit par CLAUDE.md §3)`);
}
}
}
// Vérifier les éléments UI de gamification
document.querySelectorAll('[class*="streak"], [class*="xp-"], [class*="leaderboard"], [class*="level-up"], [data-testid*="streak"], [data-testid*="xp"]').forEach(el => {
if (getComputedStyle(el).display !== 'none') {
issues.push(`Élément de gamification: ${el.className.toString().slice(0, 50)}`);
}
});
return issues;
});
for (const issue of gamificationElements) {
console.log(`[ETHICAL] ${path}: ${issue}`);
}
expect(gamificationElements.length,
`Éléments de gamification sur ${path}:\n${gamificationElements.join('\n')}`
).toBe(0);
}
});
test('Pas de dark patterns UX — désinscription facile', async ({ page }) => {
await navigateTo(page, '/settings');
// Vérifier qu'il y a un moyen de supprimer son compte
const body = await page.textContent('body') || '';
const hasAccountDeletion = /supprimer|delete.*account|effacer.*compte|close.*account|fermer.*compte|désinscription/i.test(body);
console.log(`[ETHICAL] Option de suppression de compte visible: ${hasAccountDeletion}`);
// Vérifier qu'il n'y a pas de dark pattern "Êtes-vous sûr de vouloir partir ?"
// avec des boutons de taille asymétrique
});
test('Pas de notifications push manipulatrices', async ({ page }) => {
// Vérifier que l'app ne demande pas les permissions de notification de manière agressive
const notificationPermission = await page.evaluate(() => {
return Notification?.permission || 'not-supported';
}).catch(() => 'not-supported');
console.log(`[ETHICAL] Notification permission: ${notificationPermission}`);
// L'app ne devrait pas demander les permissions push automatiquement
});
test('Le feed est chronologique (pas de ranking comportemental)', async ({ page }) => {
await navigateTo(page, '/feed');
// Vérifier qu'il n'y a pas de "Recommandé pour vous" basé sur des algorithmes comportementaux
const body = await page.textContent('body') || '';
const behavioralPatterns = /recommended for you|basé sur.*écoutes|algorithme|trending|populaire|for you/i;
const hasBehavioralRanking = behavioralPatterns.test(body);
if (hasBehavioralRanking) {
console.log(`[ETHICAL] Pattern de ranking comportemental détecté dans le feed`);
}
// La découverte doit être par tags/genres déclaratifs, pas par comportement
});
test('Pas d\'imports AI/ML/blockchain interdits dans le bundle', async ({ page }) => {
await navigateTo(page, '/dashboard');
// Vérifier qu'aucun script chargé ne contient des références aux bibliothèques interdites
const scripts = await page.evaluate(() => {
const scriptTags = document.querySelectorAll('script[src]');
return Array.from(scriptTags).map(s => s.getAttribute('src') || '');
});
const forbidden = ['tensorflow', 'pytorch', 'sklearn', 'web3', 'ethers', 'metamask', 'nft'];
const violations = scripts.filter(src =>
forbidden.some(f => src.toLowerCase().includes(f))
);
expect(violations.length,
`Scripts interdits chargés: ${violations.join(', ')}`
).toBe(0);
});
});