Lorsqu'un super-admin crée un établissement via l'interface, le système doit automatiquement créer la base tenant, exécuter les migrations, créer le premier utilisateur admin et envoyer l'invitation — le tout de manière asynchrone pour ne pas bloquer la réponse HTTP. Ce mécanisme rend chaque établissement opérationnel dès sa création sans intervention manuelle sur l'infrastructure.
206 lines
7.7 KiB
TypeScript
206 lines
7.7 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 projectRoot = join(__dirname, '../..');
|
|
const composeFile = join(projectRoot, 'compose.yaml');
|
|
|
|
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 SA_PASSWORD = 'SuperAdmin123';
|
|
const UNIQUE_SUFFIX = Date.now();
|
|
|
|
function getSuperAdminEmail(browserName: string): string {
|
|
return `e2e-prov-sa-${browserName}@test.com`;
|
|
}
|
|
|
|
// eslint-disable-next-line no-empty-pattern
|
|
test.beforeAll(async ({}, testInfo) => {
|
|
const browserName = testInfo.project.name;
|
|
const saEmail = getSuperAdminEmail(browserName);
|
|
|
|
try {
|
|
execSync(
|
|
`docker compose -f "${composeFile}" exec -T php php bin/console app:dev:create-test-super-admin --email=${saEmail} --password=${SA_PASSWORD} 2>&1`,
|
|
{ encoding: 'utf-8' }
|
|
);
|
|
} catch (error) {
|
|
console.error(`[${browserName}] Failed to create super admin:`, error);
|
|
}
|
|
});
|
|
|
|
async function loginAsSuperAdmin(
|
|
page: import('@playwright/test').Page,
|
|
email: string
|
|
) {
|
|
await page.goto(`${ALPHA_URL}/login`);
|
|
await expect(page.getByRole('heading', { name: /connexion/i })).toBeVisible();
|
|
|
|
await page.locator('#email').fill(email);
|
|
await page.locator('#password').fill(SA_PASSWORD);
|
|
|
|
const submitButton = page.getByRole('button', { name: /se connecter/i });
|
|
await Promise.all([
|
|
page.waitForURL('**/super-admin/dashboard', { timeout: 30000 }),
|
|
submitButton.click()
|
|
]);
|
|
}
|
|
|
|
async function navigateToEstablishments(page: import('@playwright/test').Page) {
|
|
const link = page.getByRole('link', { name: /établissements/i });
|
|
await Promise.all([
|
|
page.waitForURL('**/super-admin/establishments', { timeout: 10000 }),
|
|
link.click()
|
|
]);
|
|
await expect(page.getByRole('heading', { name: /établissements/i })).toBeVisible({ timeout: 10000 });
|
|
}
|
|
|
|
async function navigateToCreateForm(page: import('@playwright/test').Page) {
|
|
const newLink = page.getByRole('link', { name: /nouvel établissement/i });
|
|
await expect(newLink).toBeVisible({ timeout: 10000 });
|
|
await Promise.all([
|
|
page.waitForURL('**/super-admin/establishments/new', { timeout: 10000 }),
|
|
newLink.click()
|
|
]);
|
|
await expect(page.locator('#name')).toBeVisible({ timeout: 10000 });
|
|
}
|
|
|
|
test.describe('Establishment Provisioning (Story 2-17) [P1]', () => {
|
|
test.describe.configure({ mode: 'serial' });
|
|
|
|
test.describe('Subdomain Auto-generation', () => {
|
|
test('[P1] typing name auto-generates subdomain', async ({ page }, testInfo) => {
|
|
const email = getSuperAdminEmail(testInfo.project.name);
|
|
await loginAsSuperAdmin(page, email);
|
|
await navigateToEstablishments(page);
|
|
await navigateToCreateForm(page);
|
|
|
|
await page.locator('#name').fill('École Saint-Exupéry');
|
|
|
|
// Subdomain should be auto-generated: accents removed, spaces→hyphens, lowercase
|
|
await expect(page.locator('#subdomain')).toHaveValue('ecole-saint-exupery');
|
|
});
|
|
|
|
test('[P2] subdomain suffix .classeo.fr is displayed', async ({ page }, testInfo) => {
|
|
const email = getSuperAdminEmail(testInfo.project.name);
|
|
await loginAsSuperAdmin(page, email);
|
|
await navigateToEstablishments(page);
|
|
await navigateToCreateForm(page);
|
|
|
|
await expect(page.locator('.subdomain-suffix')).toHaveText('.classeo.fr');
|
|
});
|
|
});
|
|
|
|
test.describe('Create Establishment Flow', () => {
|
|
const establishmentName = `E2E Test ${UNIQUE_SUFFIX}`;
|
|
const adminEmailForEstab = `admin-prov-${UNIQUE_SUFFIX}@test.com`;
|
|
|
|
test('[P1] submitting form creates establishment and redirects to list', async ({ page }, testInfo) => {
|
|
const email = getSuperAdminEmail(testInfo.project.name);
|
|
await loginAsSuperAdmin(page, email);
|
|
await navigateToEstablishments(page);
|
|
await navigateToCreateForm(page);
|
|
|
|
// Fill in the form
|
|
await page.locator('#name').fill(establishmentName);
|
|
await page.locator('#adminEmail').fill(adminEmailForEstab);
|
|
|
|
// Subdomain should be auto-generated
|
|
const subdomain = await page.locator('#subdomain').inputValue();
|
|
expect(subdomain.length).toBeGreaterThan(0);
|
|
|
|
// Submit
|
|
const submitButton = page.getByRole('button', { name: /créer l'établissement/i });
|
|
await expect(submitButton).toBeEnabled();
|
|
|
|
const apiResponsePromise = page.waitForResponse(
|
|
(resp) => resp.url().includes('/super-admin/establishments') && resp.request().method() === 'POST'
|
|
);
|
|
|
|
await submitButton.click();
|
|
|
|
// Verify API returns establishment in provisioning status
|
|
const apiResponse = await apiResponsePromise;
|
|
expect(apiResponse.status()).toBeLessThan(400);
|
|
const body = await apiResponse.json();
|
|
expect(body.status).toBe('provisioning');
|
|
|
|
// Should redirect back to establishments list
|
|
await page.waitForURL('**/super-admin/establishments', { timeout: 15000 });
|
|
await expect(page.getByRole('heading', { name: /établissements/i })).toBeVisible({ timeout: 10000 });
|
|
});
|
|
|
|
test('[P1] created establishment appears in the list', async ({ page }, testInfo) => {
|
|
const email = getSuperAdminEmail(testInfo.project.name);
|
|
await loginAsSuperAdmin(page, email);
|
|
await navigateToEstablishments(page);
|
|
|
|
// The establishment created in previous test should be visible
|
|
await expect(page.locator('table')).toBeVisible({ timeout: 10000 });
|
|
await expect(page.locator('td', { hasText: establishmentName })).toBeVisible({ timeout: 10000 });
|
|
});
|
|
|
|
test('[P1] created establishment has a visible status badge', async ({ page }, testInfo) => {
|
|
const email = getSuperAdminEmail(testInfo.project.name);
|
|
await loginAsSuperAdmin(page, email);
|
|
await navigateToEstablishments(page);
|
|
|
|
// Find the row for our establishment
|
|
const row = page.locator('tr', { has: page.locator(`text=${establishmentName}`) });
|
|
await expect(row).toBeVisible({ timeout: 10000 });
|
|
|
|
// Status badge should be visible (provisioning status already verified via API response in creation test)
|
|
const badge = row.locator('.badge');
|
|
await expect(badge).toBeVisible();
|
|
await expect(badge).not.toHaveText('');
|
|
});
|
|
});
|
|
|
|
test.describe('Form Validation', () => {
|
|
test('[P2] submit button disabled with empty fields', async ({ page }, testInfo) => {
|
|
const email = getSuperAdminEmail(testInfo.project.name);
|
|
await loginAsSuperAdmin(page, email);
|
|
await navigateToEstablishments(page);
|
|
await navigateToCreateForm(page);
|
|
|
|
const submitButton = page.getByRole('button', { name: /créer l'établissement/i });
|
|
await expect(submitButton).toBeDisabled();
|
|
});
|
|
|
|
test('[P2] submit button enabled when all fields filled', async ({ page }, testInfo) => {
|
|
const email = getSuperAdminEmail(testInfo.project.name);
|
|
await loginAsSuperAdmin(page, email);
|
|
await navigateToEstablishments(page);
|
|
await navigateToCreateForm(page);
|
|
|
|
await page.locator('#name').fill('Test School');
|
|
await page.locator('#adminEmail').fill('admin@test.com');
|
|
|
|
const submitButton = page.getByRole('button', { name: /créer l'établissement/i });
|
|
await expect(submitButton).toBeEnabled();
|
|
});
|
|
|
|
test('[P2] cancel button returns to establishments list', async ({ page }, testInfo) => {
|
|
const email = getSuperAdminEmail(testInfo.project.name);
|
|
await loginAsSuperAdmin(page, email);
|
|
await navigateToEstablishments(page);
|
|
await navigateToCreateForm(page);
|
|
|
|
const cancelLink = page.getByRole('link', { name: /annuler/i });
|
|
await Promise.all([
|
|
page.waitForURL('**/super-admin/establishments', { timeout: 10000 }),
|
|
cancelLink.click()
|
|
]);
|
|
|
|
await expect(page.getByRole('heading', { name: /établissements/i })).toBeVisible();
|
|
});
|
|
});
|
|
});
|