/** * Sprint 16 — flows 02 auth (4 flows): * 2.1 signup → token → /api/me payload sane * 2.2 login через форму /login → редирект на /dashboard * 2.3 refresh_token обновляет access_token * 2.4 неправильный пароль → 4xx */ import { expect, test } from '@playwright/test' import { request, baseUrl } from '../factories/api-client.js' import { OrgFactory } from '../factories/OrgFactory.js' test.describe('flow 02 — auth', () => { test('2.1 signup + token + /api/me возвращают консистентный payload @smoke', async () => { const b = await OrgFactory.for('auth21').build() const me = await request<{ sub: string; email: string; roles: string[]; orgId: string }>( '/api/me', { token: b.session.accessToken }, ) expect(me.email).toBe(b.session.email) expect(me.orgId).toBe(b.session.orgId) expect(me.roles).toContain('Admin') }) test('2.2 login форма редиректит из /login на внутреннюю страницу', async ({ page }) => { const b = await OrgFactory.for('auth22').build() await page.goto('/login') await page.getByLabel('Email').fill(b.session.email) await page.getByLabel('Пароль').fill(b.session.password) await page.getByRole('button', { name: /войти/i }).click() // После login юзер уходит с /login. Куда конкретно зависит от onboarding-state // (на свежей org может быть Onboarding-page по «/», на seeded — /dashboard). await page.waitForURL((url) => !url.pathname.includes('/login'), { timeout: 15_000 }) expect(page.url()).not.toContain('/login') }) test('2.3 refresh_token успешно меняет на новый access_token', async () => { const b = await OrgFactory.for('auth23').build() const body = new URLSearchParams({ grant_type: 'refresh_token', refresh_token: b.session.refreshToken, client_id: 'food-market-web', scope: 'openid profile email roles api offline_access', }).toString() const r = await request<{ access_token: string; refresh_token: string }>( '/connect/token', { body, headers: { 'Content-Type': 'application/x-www-form-urlencoded' } }, ) expect(r.access_token).not.toBe(b.session.accessToken) expect(r.access_token.length).toBeGreaterThan(100) }) test('2.4 неправильный пароль → 400 invalid_grant', async () => { const b = await OrgFactory.for('auth24').build() const body = new URLSearchParams({ grant_type: 'password', username: b.session.email, password: 'WrongPass!', client_id: 'food-market-web', scope: 'openid profile email roles api offline_access', }).toString() const resp = await fetch(`${baseUrl}/connect/token`, { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, body, }) expect(resp.status).toBe(400) const j = await resp.json() as { error: string } expect(j.error).toBe('invalid_grant') }) })