Les enseignants ont besoin de moyennes à jour immédiatement après la publication ou modification des notes, sans attendre un batch nocturne. Le système recalcule via Domain Events synchrones : statistiques d'évaluation (min/max/moyenne/médiane), moyennes matières pondérées (normalisation /20), et moyenne générale par élève. Les résultats sont stockés dans des tables dénormalisées avec cache Redis (TTL 5 min). Trois endpoints API exposent les données avec contrôle d'accès par rôle. Une commande console permet le backfill des données historiques au déploiement.
90 lines
3.5 KiB
TypeScript
90 lines
3.5 KiB
TypeScript
import { test, expect } from '@playwright/test';
|
|
import { execSync } from 'child_process';
|
|
import { join, dirname } from 'path';
|
|
import { fileURLToPath } from 'url';
|
|
|
|
const __filename = fileURLToPath(import.meta.url);
|
|
const __dirname = dirname(__filename);
|
|
|
|
const baseUrl = process.env.PLAYWRIGHT_BASE_URL || 'http://localhost:4173';
|
|
const urlMatch = baseUrl.match(/:(\d+)$/);
|
|
const PORT = urlMatch ? urlMatch[1] : '4173';
|
|
const ALPHA_URL = `http://ecole-alpha.classeo.local:${PORT}`;
|
|
|
|
const ADMIN_EMAIL = 'e2e-creation-admin@example.com';
|
|
const ADMIN_PASSWORD = 'CreationTest123';
|
|
const UNIQUE_SUFFIX = Date.now();
|
|
const INVITED_EMAIL = `e2e-invited-prof-${UNIQUE_SUFFIX}@example.com`;
|
|
|
|
test.describe('User Creation', () => {
|
|
test.describe.configure({ mode: 'serial' });
|
|
|
|
test.beforeAll(async () => {
|
|
const projectRoot = join(__dirname, '../..');
|
|
const composeFile = join(projectRoot, 'compose.yaml');
|
|
|
|
execSync(
|
|
`docker compose -f "${composeFile}" exec -T php php bin/console app:dev:create-test-user --tenant=ecole-alpha --email=${ADMIN_EMAIL} --password=${ADMIN_PASSWORD} --role=ROLE_ADMIN 2>&1`,
|
|
{ encoding: 'utf-8' }
|
|
);
|
|
});
|
|
|
|
async function loginAsAdmin(page: import('@playwright/test').Page) {
|
|
await page.goto(`${ALPHA_URL}/login`);
|
|
await page.locator('#email').fill(ADMIN_EMAIL);
|
|
await page.locator('#password').fill(ADMIN_PASSWORD);
|
|
await Promise.all([
|
|
page.waitForURL(/\/dashboard/, { timeout: 60000 }),
|
|
page.getByRole('button', { name: /se connecter/i }).click()
|
|
]);
|
|
}
|
|
|
|
test('admin can invite a user with roles array', async ({ page }) => {
|
|
await loginAsAdmin(page);
|
|
await page.goto(`${ALPHA_URL}/admin/users`);
|
|
|
|
// Wait for users table or empty state to load
|
|
await expect(
|
|
page.locator('.users-table, .empty-state')
|
|
).toBeVisible({ timeout: 10000 });
|
|
|
|
// Click "Inviter un utilisateur"
|
|
await page.getByRole('button', { name: /inviter un utilisateur/i }).first().click();
|
|
|
|
// Modal should appear
|
|
await expect(page.locator('#modal-title')).toBeVisible();
|
|
await expect(page.locator('#modal-title')).toHaveText('Inviter un utilisateur');
|
|
|
|
// Fill in the form
|
|
await page.locator('#user-firstname').fill('Marie');
|
|
await page.locator('#user-lastname').fill('Curie');
|
|
await page.locator('#user-email').fill(INVITED_EMAIL);
|
|
|
|
// Select "Enseignant" role via checkbox (this sends roles[] without role singular)
|
|
await page.locator('.role-checkbox-label', { hasText: 'Enseignant' }).click();
|
|
|
|
// Submit the form (target the modal's submit button specifically)
|
|
const modal = page.locator('.modal');
|
|
await modal.getByRole('button', { name: "Envoyer l'invitation" }).click();
|
|
|
|
// Verify success message
|
|
await expect(page.locator('.alert-success')).toBeVisible({ timeout: 10000 });
|
|
await expect(page.locator('.alert-success')).toContainText(INVITED_EMAIL);
|
|
|
|
// Search for the newly created user (pagination may hide them beyond page 1)
|
|
const searchInput = page.locator('input[type="search"]');
|
|
await searchInput.fill(INVITED_EMAIL);
|
|
await page.waitForTimeout(500);
|
|
await page.waitForLoadState('networkidle');
|
|
|
|
// Verify the user appears in the table
|
|
await expect(page.locator('.users-table')).toBeVisible({ timeout: 10000 });
|
|
const newUserRow = page.locator('tr', { has: page.locator(`text=${INVITED_EMAIL}`) });
|
|
await expect(newUserRow).toBeVisible();
|
|
await expect(newUserRow).toContainText('Marie');
|
|
await expect(newUserRow).toContainText('Curie');
|
|
await expect(newUserRow).toContainText('Enseignant');
|
|
await expect(newUserRow).toContainText('En attente');
|
|
});
|
|
});
|