pediatric-ai-scribe-v3/e2e/tests/auth-screen.spec.js
Daniel e58aa1b996
Some checks failed
Forgejo Docker Build / Root app tests (push) Successful in 49s
Forgejo Docker Build / Build Docker image (push) Successful in 7s
Forgejo Docker Build / End-to-end (browser) (push) Failing after 6s
refactor: sign-in codes and registration invitations leave; the SSO has both
Sign-in is email → code at sso.pedshub.com, and new accounts come from an
invitation link minted there, so the app's own code emails and invite codes
recorded a path nobody can take. Gone: the login-code routes and their rate
limiters, the invite admin API and card, the invite field on the register
form, the "email me a code / use my password" choice on the sign-in screen
(an email now leads straight to the password), both utility modules, and
the invite-only setting. A migration drops login_codes and
registration_invites.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Dv6sqaY6Vq3ChZHMem3cnU
2026-09-13 06:11:00 +02:00

92 lines
4.6 KiB
JavaScript

// ============================================================
// AUTH SCREEN — unauthenticated landing page structure.
// These tests do NOT use the `authedPage` fixture; they visit the
// app with a fresh context (no cookie) and assert the sign-in
// form + register + forgot-password transitions render correctly.
// ============================================================
const { test, expect, E2E_BASE } = require('../fixtures');
test.describe('Unauthenticated auth screen', () => {
// Use the base test that doesn't auto-login.
//
// Signing in is a stepped flow, not one form: email first, then a choice
// between a password and an emailed code. The password field exists in the
// DOM from the start but stays hidden until that choice is made, so asserting
// it visible on the landing screen tests a page that no longer exists.
test('landing asks for the email only, and hides the rest of the flow', async ({ page }) => {
await page.goto(E2E_BASE + '/');
await expect(page.locator('#auth-screen')).toBeVisible({ timeout: 10000 });
await expect(page.locator('#login-email')).toBeVisible();
await expect(page.locator('#btn-login-continue')).toBeVisible();
// Later steps are present but not yet offered.
await expect(page.locator('#login-password')).toBeHidden();
await expect(page.locator('#btn-local-login')).toBeHidden();
// main app body must be hidden while unauthenticated
await expect(page.locator('#main-app')).toBeHidden();
});
test('an email leads straight to the password', async ({ page }) => {
await page.goto(E2E_BASE + '/');
await page.waitForSelector('#auth-screen', { timeout: 10000 });
await page.fill('#login-email', 'someone@ped-ai.test');
await page.click('#btn-login-continue');
// The address is fixed once the flow has moved past it; "use a different
// email" is how you go back, and it only appears after the first step.
await expect(page.locator('#login-email')).toHaveJSProperty('readOnly', true);
await expect(page.locator('#login-change-email')).toBeVisible();
await expect(page.locator('#login-password')).toBeVisible();
await expect(page.locator('#btn-local-login')).toBeVisible();
});
test('the register link follows the registration setting', async ({ page }) => {
// Hidden by default and shown only when registration is enabled, which the
// seed turns on. No invitation field: invitations are the SSO's.
await page.goto(E2E_BASE + '/');
await page.waitForSelector('#auth-screen', { timeout: 10000 });
await expect(page.locator('#show-register')).toBeVisible();
await expect(page.locator('#register-form')).toHaveCount(1);
await page.click('#show-register');
await expect(page.locator('#reg-invite')).toHaveCount(0);
});
test('register form DOM is wired correctly if manually unhidden', async ({ page }) => {
await page.goto(E2E_BASE + '/');
await page.waitForSelector('#auth-screen', { timeout: 10000 });
// Force the link visible so we can exercise the swap path — useful for
// future tests that want to validate the full register flow.
await page.locator('#show-register').evaluate(el => { el.style.display = ''; });
await page.click('#show-register');
await expect(page.locator('#register-form')).toBeVisible();
await expect(page.locator('#reg-name')).toBeVisible();
await expect(page.locator('#reg-email')).toBeVisible();
await expect(page.locator('#reg-password')).toBeVisible();
await page.click('#show-login');
await expect(page.locator('#login-form')).toBeVisible();
await expect(page.locator('#register-form')).toBeHidden();
});
test('clicking "Forgot password?" swaps to forgot form', async ({ page }) => {
await page.goto(E2E_BASE + '/');
await page.waitForSelector('#auth-screen', { timeout: 10000 });
await page.click('#show-forgot');
await expect(page.locator('#forgot-form')).toBeVisible();
await expect(page.locator('#forgot-email')).toBeVisible();
await expect(page.locator('#login-form')).toBeHidden();
// Back link returns to login
await page.click('#show-login-2');
await expect(page.locator('#login-form')).toBeVisible();
await expect(page.locator('#forgot-form')).toBeHidden();
});
test('password minlength enforces 8 chars in register form', async ({ page }) => {
await page.goto(E2E_BASE + '/');
await page.waitForSelector('#auth-screen', { timeout: 10000 });
// Attribute check doesn't require the element to be visible.
const pw = page.locator('#reg-password');
await expect(pw).toHaveAttribute('minlength', '8');
await expect(pw).toHaveAttribute('type', 'password');
});
});