Les parents doivent pouvoir suivre la scolarité de leurs enfants (notes, emploi du temps, devoirs). Cela nécessite un lien formalisé entre le compte parent et le compte élève, géré par les administrateurs. Le lien est établi soit manuellement via l'interface d'administration, soit automatiquement lors de l'activation du compte parent lorsque l'invitation inclut un élève cible. Ce lien conditionne l'accès aux données scolaires de l'enfant (autorisations vérifiées par un voter dédié).
117 lines
4.5 KiB
TypeScript
117 lines
4.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-actlink-admin@example.com';
|
|
const ADMIN_PASSWORD = 'ActLinkTest123';
|
|
const STUDENT_EMAIL = 'e2e-actlink-student@example.com';
|
|
const STUDENT_PASSWORD = 'StudentTest123';
|
|
const UNIQUE_SUFFIX = Date.now();
|
|
const PARENT_EMAIL = `e2e-actlink-parent-${UNIQUE_SUFFIX}@example.com`;
|
|
const PARENT_PASSWORD = 'ParentActivation1!';
|
|
|
|
let studentUserId: string;
|
|
let activationToken: string;
|
|
|
|
function extractUserId(output: string): string {
|
|
const match = output.match(/User ID\s+([a-f0-9-]{36})/i);
|
|
if (!match) {
|
|
throw new Error(`Could not extract User ID from command output:\n${output}`);
|
|
}
|
|
return match[1];
|
|
}
|
|
|
|
test.describe('Activation with Parent-Child Auto-Link', () => {
|
|
test.describe.configure({ mode: 'serial' });
|
|
|
|
test.beforeAll(async () => {
|
|
const projectRoot = join(__dirname, '../..');
|
|
const composeFile = join(projectRoot, 'compose.yaml');
|
|
|
|
// Create admin user
|
|
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' }
|
|
);
|
|
|
|
// Create student user and capture userId
|
|
const studentOutput = execSync(
|
|
`docker compose -f "${composeFile}" exec -T php php bin/console app:dev:create-test-user --tenant=ecole-alpha --email=${STUDENT_EMAIL} --password=${STUDENT_PASSWORD} --role=ROLE_ELEVE 2>&1`,
|
|
{ encoding: 'utf-8' }
|
|
);
|
|
studentUserId = extractUserId(studentOutput);
|
|
|
|
// Clean up any existing guardian links for this student
|
|
try {
|
|
execSync(
|
|
`docker compose -f "${composeFile}" exec -T php php bin/console dbal:run-sql "DELETE FROM student_guardians WHERE student_id = '${studentUserId}'" 2>&1`,
|
|
{ encoding: 'utf-8' }
|
|
);
|
|
} catch {
|
|
// Ignore cleanup errors
|
|
}
|
|
|
|
// Create activation token for parent WITH student-id for auto-linking
|
|
const tokenOutput = execSync(
|
|
`docker compose -f "${composeFile}" exec -T php php bin/console app:dev:create-test-activation-token --email=${PARENT_EMAIL} --role=PARENT --tenant=ecole-alpha --student-id=${studentUserId} 2>&1`,
|
|
{ encoding: 'utf-8' }
|
|
);
|
|
|
|
const tokenMatch = tokenOutput.match(/Token\s+([a-f0-9-]{36})/i);
|
|
if (!tokenMatch) {
|
|
throw new Error(`Could not extract token from command output:\n${tokenOutput}`);
|
|
}
|
|
activationToken = tokenMatch[1];
|
|
});
|
|
|
|
test('[P1] should activate parent account and auto-link to student', async ({ page }) => {
|
|
// Navigate to the activation page
|
|
await page.goto(`${ALPHA_URL}/activate/${activationToken}`);
|
|
|
|
// Wait for the activation form to load
|
|
await expect(page.locator('#password')).toBeVisible({ timeout: 10000 });
|
|
|
|
// Fill the password form
|
|
await page.locator('#password').fill(PARENT_PASSWORD);
|
|
await page.locator('#passwordConfirmation').fill(PARENT_PASSWORD);
|
|
|
|
// Wait for validation to pass and submit
|
|
const submitButton = page.getByRole('button', { name: /activer mon compte/i });
|
|
await expect(submitButton).toBeEnabled({ timeout: 5000 });
|
|
await submitButton.click();
|
|
|
|
// Should redirect to login with activated=true
|
|
await page.waitForURL(/\/login\?activated=true/, { timeout: 15000 });
|
|
|
|
// Now login as admin to verify the auto-link
|
|
await page.locator('#email').fill(ADMIN_EMAIL);
|
|
await page.locator('#password').fill(ADMIN_PASSWORD);
|
|
await page.getByRole('button', { name: /se connecter/i }).click();
|
|
await expect(page).toHaveURL(/\/dashboard/, { timeout: 10000 });
|
|
|
|
// Navigate to the student's page to check guardian list
|
|
await page.goto(`${ALPHA_URL}/admin/students/${studentUserId}`);
|
|
|
|
// Wait for the guardian section to load
|
|
await expect(page.locator('.guardian-section')).toBeVisible({ timeout: 10000 });
|
|
await expect(
|
|
page.locator('.guardian-list')
|
|
).toBeVisible({ timeout: 10000 });
|
|
|
|
// The auto-linked parent should appear in the guardian list
|
|
const guardianItem = page.locator('.guardian-item').first();
|
|
await expect(guardianItem).toBeVisible();
|
|
// Auto-linking uses RelationshipType::OTHER → label "Autre"
|
|
await expect(guardianItem).toContainText('Autre');
|
|
});
|
|
});
|