import { test, expect, type Page } from '@playwright/test'; // Network-level mocks for the private-mode UX. The backend integration // tests (api_private_mode.rs) cover the actual gate; here we only // verify that the SvelteKit universal load redirects anonymous // visitors to /login and then back to where they were going. const userFixture = { id: 'user-1', username: 'alice', created_at: '2026-01-01T00:00:00Z', is_admin: false }; const emptyPage = { items: [], page: { limit: 50, offset: 0, total: null } }; async function stubPrivateInstance(page: Page) { let loggedIn = false; // The flag that flips the gate on. Frontend reads it in // `+layout.ts` to decide whether to redirect. await page.route('**/api/v1/auth/config', async (route) => { await route.fulfill({ status: 200, contentType: 'application/json', body: JSON.stringify({ self_register_enabled: false, private_mode: true }) }); }); await page.route('**/api/v1/auth/me', async (route) => { if (loggedIn) { await route.fulfill({ status: 200, contentType: 'application/json', body: JSON.stringify({ user: userFixture }) }); } else { await route.fulfill({ status: 401, contentType: 'application/json', body: JSON.stringify({ error: { code: 'unauthenticated', message: 'unauthenticated' } }) }); } }); await page.route('**/api/v1/auth/login', async (route) => { loggedIn = true; await route.fulfill({ status: 200, contentType: 'application/json', body: JSON.stringify({ user: userFixture }) }); }); // The real backend would 401 these too in private mode; we stub // success so the post-login navigation can render the home page // without an additional redirect cycle. await page.route('**/api/v1/mangas*', async (route) => { await route.fulfill({ status: 200, contentType: 'application/json', body: JSON.stringify(emptyPage) }); }); } test('private mode: anonymous visit to / redirects to /login?next=%2F', async ({ page }) => { await stubPrivateInstance(page); await page.goto('/'); await expect(page).toHaveURL(/\/login\?next=%2F$/); await expect(page.getByTestId('login-username')).toBeVisible(); }); test('private mode: register link is hidden', async ({ page }) => { await stubPrivateInstance(page); await page.goto('/login'); await expect(page.getByTestId('nav-login')).toBeVisible(); // self_register_enabled is the effective value (false in private // mode regardless of ALLOW_SELF_REGISTER), so the navbar must // never render the register affordance here. await expect(page.getByTestId('nav-register')).toHaveCount(0); }); test('private mode: after login the user lands back on the requested page', async ({ page }) => { await stubPrivateInstance(page); // Visit a deep link → bounced to /login with next= preserving it. await page.goto('/'); await expect(page).toHaveURL(/\/login\?next=%2F$/); await page.getByTestId('login-username').fill('alice'); await page.getByTestId('login-password').fill('hunter2hunter2'); await page.getByTestId('login-submit').click(); // Authenticated → can now reach the home page without bouncing. await expect(page.getByTestId('session-user')).toContainText('alice'); });