Compare commits

..

2 Commits

Author SHA1 Message Date
Felipe Cardoso
189ad948ac Mark dead code in users API related to is_superuser checks with # pragma: no cover 2025-11-01 15:54:58 +01:00
Felipe Cardoso
e2a8656f81 Improve navigation and URL validation in Playwright authentication tests
- Replaced `waitForTimeout` with `Promise.all` for navigation events to improve reliability.
- Updated URL assertions to support regex patterns for handling query parameters.
- Adjusted worker count in `playwright.config.ts` for improved performance in local environments.
2025-11-01 15:49:28 +01:00
5 changed files with 45 additions and 26 deletions

View File

@@ -146,9 +146,10 @@ async def update_current_user(
Users cannot elevate their own permissions (is_superuser). Users cannot elevate their own permissions (is_superuser).
""" """
# Prevent users from making themselves superuser # Prevent users from making themselves superuser
if getattr(user_update, 'is_superuser', None) is not None: # NOTE: UserUpdate schema doesn't include is_superuser, so this is dead code
logger.warning(f"User {current_user.id} attempted to modify is_superuser field") if getattr(user_update, 'is_superuser', None) is not None: # pragma: no cover
raise AuthorizationError( logger.warning(f"User {current_user.id} attempted to modify is_superuser field") # pragma: no cover
raise AuthorizationError( # pragma: no cover
message="Cannot modify superuser status", message="Cannot modify superuser status",
error_code=ErrorCode.INSUFFICIENT_PERMISSIONS error_code=ErrorCode.INSUFFICIENT_PERMISSIONS
) )
@@ -265,9 +266,10 @@ async def update_user(
) )
# Prevent non-superusers from modifying superuser status # Prevent non-superusers from modifying superuser status
if getattr(user_update, 'is_superuser', None) is not None and not current_user.is_superuser: # NOTE: UserUpdate schema doesn't include is_superuser, so this is dead code
logger.warning(f"User {current_user.id} attempted to modify is_superuser field") if getattr(user_update, 'is_superuser', None) is not None and not current_user.is_superuser: # pragma: no cover
raise AuthorizationError( logger.warning(f"User {current_user.id} attempted to modify is_superuser field") # pragma: no cover
raise AuthorizationError( # pragma: no cover
message="Cannot modify superuser status", message="Cannot modify superuser status",
error_code=ErrorCode.INSUFFICIENT_PERMISSIONS error_code=ErrorCode.INSUFFICIENT_PERMISSIONS
) )

View File

@@ -76,21 +76,29 @@ test.describe('Login Flow', () => {
}); });
test('should navigate to forgot password page', async ({ page }) => { test('should navigate to forgot password page', async ({ page }) => {
// Click forgot password link // Click forgot password link - use Promise.all to wait for navigation
await page.getByRole('link', { name: 'Forgot password?' }).click(); const forgotLink = page.getByRole('link', { name: 'Forgot password?' });
await page.waitForTimeout(1000);
// Should navigate to password reset page await Promise.all([
page.waitForURL('/password-reset', { timeout: 10000 }),
forgotLink.click()
]);
// Should be on password reset page
await expect(page).toHaveURL('/password-reset'); await expect(page).toHaveURL('/password-reset');
await expect(page.locator('h2')).toContainText('Reset your password'); await expect(page.locator('h2')).toContainText('Reset your password');
}); });
test('should navigate to register page', async ({ page }) => { test('should navigate to register page', async ({ page }) => {
// Click sign up link // Click sign up link - use Promise.all to wait for navigation
await page.getByRole('link', { name: 'Sign up' }).click(); const signupLink = page.getByRole('link', { name: 'Sign up' });
await page.waitForTimeout(1000);
// Should navigate to register page await Promise.all([
page.waitForURL('/register', { timeout: 10000 }),
signupLink.click()
]);
// Should be on register page
await expect(page).toHaveURL('/register'); await expect(page).toHaveURL('/register');
await expect(page.locator('h2')).toContainText('Create your account'); await expect(page.locator('h2')).toContainText('Create your account');
}); });

View File

@@ -24,7 +24,8 @@ test.describe('Password Reset Request Flow', () => {
await page.waitForTimeout(1000); await page.waitForTimeout(1000);
// Should stay on password reset page (validation failed) // Should stay on password reset page (validation failed)
await expect(page).toHaveURL('/password-reset'); // URL might have query params, so use regex
await expect(page).toHaveURL(/\/password-reset/);
}); });
test('should show validation error for invalid email', async ({ page }) => { test('should show validation error for invalid email', async ({ page }) => {
@@ -51,14 +52,15 @@ test.describe('Password Reset Request Flow', () => {
}); });
test('should navigate back to login page', async ({ page }) => { test('should navigate back to login page', async ({ page }) => {
// Click back to login link // Click back to login link - use Promise.all to wait for navigation
const loginLink = page.getByRole('link', { name: 'Back to login' }); const loginLink = page.getByRole('link', { name: 'Back to login' });
await loginLink.click();
// Wait for navigation await Promise.all([
await page.waitForTimeout(1000); page.waitForURL('/login', { timeout: 10000 }),
loginLink.click()
]);
// Should navigate to login page // Should be on login page
await expect(page).toHaveURL('/login'); await expect(page).toHaveURL('/login');
await expect(page.locator('h2')).toContainText('Sign in to your account'); await expect(page.locator('h2')).toContainText('Sign in to your account');
}); });
@@ -191,10 +193,15 @@ test.describe('Password Reset Confirm Flow', () => {
// Navigate without token to trigger error state // Navigate without token to trigger error state
await page.goto('/password-reset/confirm'); await page.goto('/password-reset/confirm');
// Click request new reset link - use specific link selector // Click request new reset link - use Promise.all to wait for navigation
await page.getByRole('link', { name: 'Request new reset link' }).click(); const resetLink = page.getByRole('link', { name: 'Request new reset link' });
// Should navigate to password reset request page await Promise.all([
page.waitForURL('/password-reset', { timeout: 10000 }),
resetLink.click()
]);
// Should be on password reset request page
await expect(page).toHaveURL('/password-reset'); await expect(page).toHaveURL('/password-reset');
await expect(page.locator('h2')).toContainText('Reset your password'); await expect(page.locator('h2')).toContainText('Reset your password');
}); });

View File

@@ -47,7 +47,8 @@ test.describe('Registration Flow', () => {
await page.waitForTimeout(1000); await page.waitForTimeout(1000);
// Should stay on register page (validation failed) // Should stay on register page (validation failed)
await expect(page).toHaveURL('/register'); // URL might have query params, so use regex
await expect(page).toHaveURL(/\/register/);
}); });
test('should show validation error for short first name', async ({ page }) => { test('should show validation error for short first name', async ({ page }) => {
@@ -92,7 +93,8 @@ test.describe('Registration Flow', () => {
await page.waitForTimeout(1000); await page.waitForTimeout(1000);
// Should stay on register page (validation failed) // Should stay on register page (validation failed)
await expect(page).toHaveURL('/register'); // URL might have query params, so use regex
await expect(page).toHaveURL(/\/register/);
}); });
test('should show error for duplicate email', async ({ page }) => { test('should show error for duplicate email', async ({ page }) => {

View File

@@ -18,7 +18,7 @@ export default defineConfig({
/* Retry on CI and locally to handle flaky tests */ /* Retry on CI and locally to handle flaky tests */
retries: process.env.CI ? 2 : 1, retries: process.env.CI ? 2 : 1,
/* Limit workers to prevent test interference */ /* Limit workers to prevent test interference */
workers: process.env.CI ? 1 : 4, workers: process.env.CI ? 1 : 12,
/* Reporter to use. See https://playwright.dev/docs/test-reporters */ /* Reporter to use. See https://playwright.dev/docs/test-reporters */
reporter: 'html', reporter: 'html',
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */ /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */