Replace the header "Byt lösenord" link with an Inställningar menu for changing email or password. Email changes are two-step: request with password, confirmation link to the new address, then password again on confirm so a wrong inbox cannot take over the account. - Backend: EmailChangeService, V10 email_change_tokens, confirm API - Frontend: ChangeEmailPage, ConfirmEmailChangePage, header dropdown - E2E: account-settings round-trips, Mailpit verification, wrong-password guard - Flyway: V9 restore for dev DBs, CI migration checks, V10 for email tokens Co-authored-by: Cursor <cursoragent@cursor.com>
215 lines
6.6 KiB
TypeScript
215 lines
6.6 KiB
TypeScript
import { test, expect } from '@playwright/test'
|
|
|
|
test.describe('Header auth state', () => {
|
|
test('shows login and register links when not authenticated', async ({
|
|
page,
|
|
}) => {
|
|
await page.goto('/')
|
|
const header = page.locator('header')
|
|
await expect(header.getByRole('link', { name: 'Logga in' })).toBeVisible()
|
|
await expect(
|
|
header.getByRole('link', { name: 'Registrera' }),
|
|
).toBeVisible()
|
|
})
|
|
|
|
test('does not show logout button when not authenticated', async ({
|
|
page,
|
|
}) => {
|
|
await page.goto('/')
|
|
const header = page.locator('header')
|
|
await expect(
|
|
header.getByRole('button', { name: 'Logga ut' }),
|
|
).not.toBeVisible()
|
|
})
|
|
|
|
test('shows email and logout when authenticated', async ({ page }) => {
|
|
const jwt = makeJwt({ sub: 'test@bilhej.se', role: 'user' })
|
|
await page.goto('/')
|
|
await page.evaluate(
|
|
(token) => localStorage.setItem('auth_token', token),
|
|
jwt,
|
|
)
|
|
await page.goto('/')
|
|
|
|
const header = page.locator('header')
|
|
await expect(header.getByText('test@bilhej.se')).toBeVisible()
|
|
await expect(
|
|
header.getByRole('button', { name: 'Logga ut' }),
|
|
).toBeVisible()
|
|
})
|
|
|
|
test('shows orders link when authenticated', async ({ page }) => {
|
|
const jwt = makeJwt({ sub: 'test@bilhej.se', role: 'user' })
|
|
await page.goto('/')
|
|
await page.evaluate(
|
|
(token) => localStorage.setItem('auth_token', token),
|
|
jwt,
|
|
)
|
|
await page.goto('/')
|
|
|
|
const header = page.locator('header')
|
|
const ordersLink = header.getByRole('link', {
|
|
name: 'Mina beställningar',
|
|
})
|
|
await expect(ordersLink).toBeVisible()
|
|
await expect(ordersLink).toHaveAttribute('href', '/orders')
|
|
})
|
|
|
|
test('hides login and register links when authenticated', async ({
|
|
page,
|
|
}) => {
|
|
const jwt = makeJwt({ sub: 'test@bilhej.se', role: 'user' })
|
|
await page.goto('/')
|
|
await page.evaluate(
|
|
(token) => localStorage.setItem('auth_token', token),
|
|
jwt,
|
|
)
|
|
await page.goto('/')
|
|
|
|
const header = page.locator('header')
|
|
await expect(
|
|
header.getByRole('link', { name: 'Logga in' }),
|
|
).not.toBeVisible()
|
|
await expect(
|
|
header.getByRole('link', { name: 'Registrera' }),
|
|
).not.toBeVisible()
|
|
})
|
|
|
|
test('logout restores login and register links', async ({ page }) => {
|
|
const jwt = makeJwt({ sub: 'test@bilhej.se', role: 'user' })
|
|
await page.goto('/')
|
|
await page.evaluate(
|
|
(token) => localStorage.setItem('auth_token', token),
|
|
jwt,
|
|
)
|
|
await page.goto('/')
|
|
|
|
const header = page.locator('header')
|
|
await header.getByRole('button', { name: 'Logga ut' }).click()
|
|
|
|
await expect(
|
|
header.getByRole('link', { name: 'Logga in' }),
|
|
).toBeVisible()
|
|
await expect(
|
|
header.getByRole('link', { name: 'Registrera' }),
|
|
).toBeVisible()
|
|
await expect(
|
|
header.getByRole('button', { name: 'Logga ut' }),
|
|
).not.toBeVisible()
|
|
await expect(header.getByText('test@bilhej.se')).not.toBeVisible()
|
|
})
|
|
|
|
test('logout redirects to home page', async ({ page }) => {
|
|
const jwt = makeJwt({ sub: 'test@bilhej.se', role: 'user' })
|
|
await page.goto('/orders')
|
|
await page.evaluate(
|
|
(token) => localStorage.setItem('auth_token', token),
|
|
jwt,
|
|
)
|
|
await page.goto('/orders')
|
|
await page.waitForURL('/orders')
|
|
|
|
await page.locator('header').getByRole('button', { name: 'Logga ut' }).click()
|
|
|
|
await expect(page).toHaveURL('/')
|
|
})
|
|
|
|
test('shows admin link when admin is authenticated', async ({ page }) => {
|
|
const jwt = makeJwt({ sub: 'admin@bilhalsning.se', role: 'admin' })
|
|
await page.goto('/')
|
|
await page.evaluate(
|
|
(token) => localStorage.setItem('auth_token', token),
|
|
jwt,
|
|
)
|
|
await page.goto('/')
|
|
|
|
const header = page.locator('header')
|
|
const adminLink = header.getByRole('link', { name: 'Admin' })
|
|
await expect(adminLink).toBeVisible()
|
|
await expect(adminLink).toHaveAttribute('href', '/admin')
|
|
})
|
|
|
|
test('does not show admin link for regular user', async ({ page }) => {
|
|
const jwt = makeJwt({ sub: 'test@bilhej.se', role: 'user' })
|
|
await page.goto('/')
|
|
await page.evaluate(
|
|
(token) => localStorage.setItem('auth_token', token),
|
|
jwt,
|
|
)
|
|
await page.goto('/')
|
|
|
|
const header = page.locator('header')
|
|
await expect(
|
|
header.getByRole('link', { name: 'Admin' }),
|
|
).not.toBeVisible()
|
|
})
|
|
|
|
test('shows settings button when authenticated', async ({ page }) => {
|
|
await authenticateUser(page)
|
|
|
|
const header = page.locator('header')
|
|
await expect(
|
|
header.getByRole('button', { name: 'Inställningar' }),
|
|
).toBeVisible()
|
|
})
|
|
|
|
test('settings menu links to change email and password pages', async ({
|
|
page,
|
|
}) => {
|
|
await authenticateUser(page)
|
|
|
|
const header = page.locator('header')
|
|
const settingsButton = header.getByRole('button', { name: 'Inställningar' })
|
|
await settingsButton.click()
|
|
|
|
const menu = header.getByRole('menu')
|
|
await expect(
|
|
menu.getByRole('menuitem', { name: 'Byt e-postadress' }),
|
|
).toHaveAttribute('href', '/andra-epost')
|
|
await expect(
|
|
menu.getByRole('menuitem', { name: 'Byt lösenord' }),
|
|
).toHaveAttribute('href', '/andra-losenord')
|
|
})
|
|
|
|
test('highlights settings button on change password page', async ({
|
|
page,
|
|
}) => {
|
|
await authenticateUser(page)
|
|
await page.goto('/andra-losenord')
|
|
|
|
const settingsButton = page
|
|
.locator('header')
|
|
.getByRole('button', { name: 'Inställningar' })
|
|
await expect(settingsButton).toHaveClass(/app-header__settings-trigger--active/)
|
|
await expect(
|
|
page.getByRole('heading', { name: 'Byt lösenord' }),
|
|
).toBeVisible()
|
|
})
|
|
|
|
test('highlights settings button on change email page', async ({ page }) => {
|
|
await authenticateUser(page)
|
|
await page.goto('/andra-epost')
|
|
|
|
const settingsButton = page
|
|
.locator('header')
|
|
.getByRole('button', { name: 'Inställningar' })
|
|
await expect(settingsButton).toHaveClass(/app-header__settings-trigger--active/)
|
|
await expect(
|
|
page.getByRole('heading', { name: 'Byt e-postadress' }),
|
|
).toBeVisible()
|
|
})
|
|
})
|
|
|
|
async function authenticateUser(page: import('@playwright/test').Page) {
|
|
const jwt = makeJwt({ sub: 'test@bilhej.se', role: 'user' })
|
|
await page.goto('/')
|
|
await page.evaluate((token) => localStorage.setItem('auth_token', token), jwt)
|
|
await page.goto('/')
|
|
}
|
|
|
|
function makeJwt(payload: Record<string, unknown>): string {
|
|
const header = btoa(JSON.stringify({ alg: 'HS256', typ: 'JWT' }))
|
|
const body = btoa(JSON.stringify(payload))
|
|
const signature = 'test-sig'
|
|
return `${header}.${body}.${signature}`
|
|
}
|