Files
Toju/e2e/tests/auth/login-return-url.spec.ts

154 lines
5.7 KiB
TypeScript

import { test, expect } from '../../fixtures/multi-client';
import { LoginPage } from '../../pages/login.page';
import { RegisterPage } from '../../pages/register.page';
interface TestUser {
username: string;
displayName: string;
password: string;
}
test.describe('Login returnUrl handling', () => {
test.describe.configure({ timeout: 120_000 });
test('unwraps nested login returnUrl chains after successful login', async ({ createClient }) => {
const client = await createClient();
const { page } = client;
const suffix = uniqueName('nested-return');
const user: TestUser = {
username: `user_${suffix}`,
displayName: 'Return Url User',
password: 'TestPass123!'
};
await test.step('Create an account', async () => {
const registerPage = new RegisterPage(page);
await registerPage.goto();
await registerPage.register(user.username, user.displayName, user.password);
await expect(page).toHaveURL(/\/dashboard/, { timeout: 15_000 });
});
await test.step('Log out and open a deeply nested login returnUrl', async () => {
await logout(page);
const nestedReturnUrl = '/login?returnUrl=%2Flogin%3FreturnUrl%3D%252Fservers';
await page.goto(`/login?returnUrl=${encodeURIComponent(nestedReturnUrl)}`, {
waitUntil: 'domcontentloaded'
});
await expect(page.locator('#login-username')).toBeVisible({ timeout: 15_000 });
});
await test.step('Login lands on the original destination instead of looping on /login', async () => {
const loginPage = new LoginPage(page);
await loginPage.login(user.username, user.password);
await expect(page).toHaveURL(/\/servers/, { timeout: 15_000 });
await expect(page).not.toHaveURL(/returnUrl=.*login/);
});
});
test('redirects unauthenticated /servers visits to login and returns there after login', async ({ createClient }) => {
const client = await createClient();
const { page } = client;
const suffix = uniqueName('servers-return');
const user: TestUser = {
username: `user_${suffix}`,
displayName: 'Servers Return User',
password: 'TestPass123!'
};
await test.step('Create an account and log out', async () => {
const registerPage = new RegisterPage(page);
await registerPage.goto();
await registerPage.register(user.username, user.displayName, user.password);
await expect(page).toHaveURL(/\/dashboard/, { timeout: 15_000 });
await logout(page);
});
await test.step('Visiting /servers sends the user to a single-level login returnUrl', async () => {
await page.goto('/servers', { waitUntil: 'domcontentloaded' });
await expect(page).toHaveURL(/\/login/, { timeout: 15_000 });
await expect(page).toHaveURL(/returnUrl=%2Fservers/);
await expect(page).not.toHaveURL(/returnUrl=.*login/);
});
await test.step('Logging in returns to /servers', async () => {
const loginPage = new LoginPage(page);
await loginPage.login(user.username, user.password);
await expect(page).toHaveURL(/\/servers/, { timeout: 15_000 });
await expect(page.locator('app-server-browser')).toBeVisible({ timeout: 15_000 });
});
});
test('lets a returning user log back in after an expired session redirect', async ({ createClient }) => {
const client = await createClient();
const { page } = client;
const suffix = uniqueName('expired-session');
const user: TestUser = {
username: `user_${suffix}`,
displayName: 'Expired Session User',
password: 'TestPass123!'
};
await test.step('Create an account', async () => {
const registerPage = new RegisterPage(page);
await registerPage.goto();
await registerPage.register(user.username, user.displayName, user.password);
await expect(page).toHaveURL(/\/dashboard/, { timeout: 15_000 });
});
await test.step('Simulate an expired session while keeping the persisted user id', async () => {
await page.evaluate(() => {
const storageKey = 'metoyou.authTokens';
const raw = localStorage.getItem(storageKey);
if (!raw) {
return;
}
const parsed = JSON.parse(raw) as Record<string, { token: string; expiresAt: number }>;
const expiredStore = Object.fromEntries(
Object.entries(parsed).map(([url, entry]) => [url, { ...entry, expiresAt: 0 }])
);
localStorage.setItem(storageKey, JSON.stringify(expiredStore));
});
await page.goto('/servers', { waitUntil: 'domcontentloaded' });
await expect(page).toHaveURL(/\/login/, { timeout: 15_000 });
await expect(page).toHaveURL(/returnUrl=%2Fservers/);
await expect(page).not.toHaveURL(/returnUrl=.*login/);
});
await test.step('The user can authenticate again and reach /servers', async () => {
const loginPage = new LoginPage(page);
await loginPage.login(user.username, user.password);
await expect(page).toHaveURL(/\/servers/, { timeout: 15_000 });
await expect(page.locator('app-server-browser')).toBeVisible({ timeout: 15_000 });
});
});
});
async function logout(page: import('@playwright/test').Page): Promise<void> {
const menuButton = page.getByRole('button', { name: 'Menu' });
const logoutButton = page.getByRole('button', { name: 'Logout' });
await expect(menuButton).toBeVisible({ timeout: 10_000 });
await menuButton.click();
await expect(logoutButton).toBeVisible({ timeout: 10_000 });
await logoutButton.click();
await expect(page).toHaveURL(/\/login/, { timeout: 15_000 });
}
function uniqueName(prefix: string): string {
return `${prefix}-${Date.now().toString(36)}-${Math.random().toString(36)
.slice(2, 8)}`;
}