feat: Permettre au super admin de se connecter et accéder à son dashboard
Le super admin (table super_admins, master DB) ne pouvait pas se connecter via /api/login car ce firewall n'utilisait que le provider tenant. De même, le JWT n'était pas enrichi pour les super admins, l'endpoint /api/me/roles les rejetait, et le frontend redirigeait systématiquement vers /dashboard. Un chain provider (super_admin + tenant) résout l'authentification, le JwtPayloadEnricher et MyRolesProvider gèrent désormais les deux types d'utilisateurs, et le frontend redirige selon le rôle après login.
This commit is contained in:
208
frontend/e2e/super-admin.spec.ts
Normal file
208
frontend/e2e/super-admin.spec.ts
Normal file
@@ -0,0 +1,208 @@
|
||||
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');
|
||||
|
||||
// Extract port from PLAYWRIGHT_BASE_URL or use default (4173 matches playwright.config.ts)
|
||||
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 REGULAR_PASSWORD = 'TestPassword123';
|
||||
|
||||
function getSuperAdminEmail(browserName: string): string {
|
||||
return `e2e-sadmin-${browserName}@test.com`;
|
||||
}
|
||||
|
||||
function getRegularUserEmail(browserName: string): string {
|
||||
return `e2e-sadmin-regular-${browserName}@example.com`;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line no-empty-pattern
|
||||
test.beforeAll(async ({}, testInfo) => {
|
||||
const browserName = testInfo.project.name;
|
||||
const saEmail = getSuperAdminEmail(browserName);
|
||||
const regularEmail = getRegularUserEmail(browserName);
|
||||
|
||||
try {
|
||||
// Create a test super admin
|
||||
const saResult = 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' }
|
||||
);
|
||||
console.warn(
|
||||
`[${browserName}] Super admin created or exists:`,
|
||||
saResult.includes('already exists') ? 'exists' : 'created'
|
||||
);
|
||||
|
||||
// Create a regular user (for access control test)
|
||||
const userResult = execSync(
|
||||
`docker compose -f "${composeFile}" exec -T php php bin/console app:dev:create-test-user --email=${regularEmail} --password=${REGULAR_PASSWORD} 2>&1`,
|
||||
{ encoding: 'utf-8' }
|
||||
);
|
||||
console.warn(
|
||||
`[${browserName}] Regular user created or exists:`,
|
||||
userResult.includes('already exists') ? 'exists' : 'created'
|
||||
);
|
||||
} catch (error) {
|
||||
console.error(`[${browserName}] Failed to create test users:`, 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()
|
||||
]);
|
||||
}
|
||||
|
||||
test.describe('Super Admin', () => {
|
||||
test.describe('Login & Redirect', () => {
|
||||
test('super admin login redirects to /super-admin/dashboard', async ({ page }, testInfo) => {
|
||||
const email = getSuperAdminEmail(testInfo.project.name);
|
||||
|
||||
await loginAsSuperAdmin(page, email);
|
||||
|
||||
await expect(page).toHaveURL(/\/super-admin\/dashboard/);
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('Dashboard', () => {
|
||||
test('dashboard displays stats cards', async ({ page }, testInfo) => {
|
||||
const email = getSuperAdminEmail(testInfo.project.name);
|
||||
|
||||
await loginAsSuperAdmin(page, email);
|
||||
|
||||
// The dashboard should show stat cards
|
||||
await expect(page.locator('.stat-card').first()).toBeVisible({ timeout: 10000 });
|
||||
|
||||
// Verify dashboard heading
|
||||
await expect(page.getByRole('heading', { name: 'Dashboard' })).toBeVisible();
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('Navigation', () => {
|
||||
test('navigates to establishments page', async ({ page }, testInfo) => {
|
||||
const email = getSuperAdminEmail(testInfo.project.name);
|
||||
|
||||
await loginAsSuperAdmin(page, email);
|
||||
|
||||
const etablissementsLink = page.getByRole('link', { name: /établissements/i });
|
||||
await Promise.all([
|
||||
page.waitForURL('**/super-admin/establishments', { timeout: 10000 }),
|
||||
etablissementsLink.click()
|
||||
]);
|
||||
|
||||
await expect(page).toHaveURL(/\/super-admin\/establishments/);
|
||||
await expect(
|
||||
page.getByRole('heading', { name: /établissements/i })
|
||||
).toBeVisible();
|
||||
});
|
||||
|
||||
test('establishments page has create button', async ({ page }, testInfo) => {
|
||||
const email = getSuperAdminEmail(testInfo.project.name);
|
||||
|
||||
await loginAsSuperAdmin(page, email);
|
||||
|
||||
// Navigate via SPA link (page.goto would reload and lose in-memory token)
|
||||
const etablissementsLink = page.getByRole('link', { name: /établissements/i });
|
||||
await Promise.all([
|
||||
page.waitForURL('**/super-admin/establishments', { timeout: 10000 }),
|
||||
etablissementsLink.click()
|
||||
]);
|
||||
|
||||
await expect(page.getByRole('heading', { name: /établissements/i })).toBeVisible({
|
||||
timeout: 10000
|
||||
});
|
||||
|
||||
// Check "Nouvel établissement" button/link
|
||||
await expect(
|
||||
page.getByRole('link', { name: /nouvel établissement/i })
|
||||
).toBeVisible();
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('Create Establishment Form', () => {
|
||||
test('new establishment form has required fields', async ({ page }, testInfo) => {
|
||||
const email = getSuperAdminEmail(testInfo.project.name);
|
||||
|
||||
await loginAsSuperAdmin(page, email);
|
||||
|
||||
// Navigate via SPA links (page.goto would reload and lose in-memory token)
|
||||
const etablissementsLink = page.getByRole('link', { name: /établissements/i });
|
||||
await Promise.all([
|
||||
page.waitForURL('**/super-admin/establishments', { timeout: 10000 }),
|
||||
etablissementsLink.click()
|
||||
]);
|
||||
|
||||
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()
|
||||
]);
|
||||
|
||||
// Verify form fields
|
||||
await expect(page.locator('#name')).toBeVisible({ timeout: 10000 });
|
||||
await expect(page.locator('#subdomain')).toBeVisible();
|
||||
await expect(page.locator('#adminEmail')).toBeVisible();
|
||||
|
||||
// Submit button should be disabled when empty
|
||||
const submitButton = page.getByRole('button', {
|
||||
name: /créer l'établissement/i
|
||||
});
|
||||
await expect(submitButton).toBeDisabled();
|
||||
|
||||
// Fill in the form
|
||||
await page.locator('#name').fill('École Test E2E');
|
||||
await page.locator('#adminEmail').fill('admin-e2e@test.com');
|
||||
|
||||
// Subdomain should be auto-generated
|
||||
await expect(page.locator('#subdomain')).not.toHaveValue('');
|
||||
|
||||
// Submit button should be enabled
|
||||
await expect(submitButton).toBeEnabled();
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('Access Control', () => {
|
||||
test('regular user is redirected away from /super-admin', async ({ page }, testInfo) => {
|
||||
const regularEmail = getRegularUserEmail(testInfo.project.name);
|
||||
|
||||
// Login as regular user on alpha tenant
|
||||
await page.goto(`${ALPHA_URL}/login`);
|
||||
await page.locator('#email').fill(regularEmail);
|
||||
await page.locator('#password').fill(REGULAR_PASSWORD);
|
||||
|
||||
const submitButton = page.getByRole('button', { name: /se connecter/i });
|
||||
await Promise.all([
|
||||
page.waitForURL('**/dashboard', { timeout: 30000 }),
|
||||
submitButton.click()
|
||||
]);
|
||||
|
||||
// Try to navigate to super-admin area
|
||||
await page.goto(`${ALPHA_URL}/super-admin/dashboard`);
|
||||
|
||||
// Should be redirected away (to /dashboard since not super admin)
|
||||
await expect(page).not.toHaveURL(/\/super-admin/, { timeout: 10000 });
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user