admin/e2e/shared/access-control.spec.ts
alisaza e1eaf5eff5 feat: initial ghabilee-admin backoffice app
Extract admin dashboard from ghabilee-frontend2 into a dedicated Next.js
app for backoffice.ghabilee.ir (no SEO indexing / Clarity).
2026-09-05 13:12:59 +03:30

149 lines
5.0 KiB
TypeScript

import { expect, test, type BrowserContext, type Page } from '@playwright/test'
import { authenticateAs, createAccessToken } from '@/e2e/fixtures/session'
const authenticate = async (context: BrowserContext, page: Page, role: 'admin' | 'user', status: 'active' | 'pending' = 'active') => {
if (status === 'active') {
await authenticateAs(context, page, role)
return
}
await context.addCookies([
{ name: 'accessToken', value: createAccessToken(role), domain: '127.0.0.1', path: '/' },
{ name: 'userRole', value: role, domain: '127.0.0.1', path: '/' },
{ name: 'userStatus', value: status, domain: '127.0.0.1', path: '/' },
])
}
test('redirects a guest from a private consumer route to home with auth modal intent', async ({ page }) => {
await page.route('**/api/v1/**', (route) =>
route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify({ success: true, data: { items: [], meta: {} } }),
})
)
await page.goto('/chats')
await expect(page).toHaveURL(/\/(\?|$)/)
await expect(page).not.toHaveURL(/\/auth/)
await expect(page.getByPlaceholder('مثال ۰۹۱۲۳۴۵۶۷۸۹')).toBeVisible()
})
test('redirects a guest from an admin route to the admin auth page', async ({ page }) => {
await page.goto('/dashboard')
await expect(page).toHaveURL(/\/auth\?redirect=%2Fdashboard$/)
// Auth mobile step has no page title — assert the OTP entry form instead.
await expect(page.getByLabel('شماره موبایل')).toBeVisible()
await expect(page.getByRole('button', { name: 'ادامه' })).toBeVisible()
})
test('allows public discovery routes without a session', async ({ page }) => {
await page.route('**/api/v1/**', (route) =>
route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify({ success: true, data: { items: [], meta: {} } }),
})
)
const response = await page.goto('/')
await expect(page).toHaveURL('http://127.0.0.1:3102/')
await expect(page.locator('body')).toBeVisible()
expect(response?.headers()['content-security-policy']).toContain("frame-ancestors 'none'")
expect(response?.headers()['x-content-type-options']).toBe('nosniff')
expect(response?.headers()['referrer-policy']).toBe('strict-origin-when-cross-origin')
})
test('prevents a consumer from opening admin routes', async ({ context, page }) => {
await authenticate(context, page, 'user')
await page.goto('/dashboard')
await expect(page).toHaveURL('http://127.0.0.1:3102/')
})
test('ignores a forged userRole=admin cookie when the access JWT is a consumer', async ({ context, page }) => {
const accessToken = createAccessToken('user')
await context.addCookies([
{ name: 'accessToken', value: accessToken, domain: '127.0.0.1', path: '/' },
{ name: 'userRole', value: 'admin', domain: '127.0.0.1', path: '/' },
{ name: 'userStatus', value: 'active', domain: '127.0.0.1', path: '/' },
])
await page.addInitScript(
({ token }) => {
localStorage.setItem(
'user',
JSON.stringify({
accessToken: token,
userId: 'user-id',
role: 'user',
sessionId: 'user-session',
AccessTokenExpireTime: Date.now() + 3_600_000,
refreshTokenExpireTime: Date.now() + 86_400_000,
status: 'active',
})
)
},
{ token: accessToken }
)
await page.route('**/api/v1/**', (route) =>
route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify({ success: true, data: { items: [], meta: {} } }),
})
)
await page.goto('/dashboard')
await expect(page).toHaveURL('http://127.0.0.1:3102/')
})
test('redirects an admin away from the consumer shell', async ({ context, page }) => {
await authenticate(context, page, 'admin')
await page.goto('/')
await expect(page).toHaveURL(/\/dashboard$/)
})
test('forces pending consumers to complete profile in the auth modal', async ({ context, page }) => {
const accessToken = createAccessToken('user')
await context.addCookies([
{ name: 'accessToken', value: accessToken, domain: '127.0.0.1', path: '/' },
{ name: 'userRole', value: 'user', domain: '127.0.0.1', path: '/' },
{ name: 'userStatus', value: 'pending', domain: '127.0.0.1', path: '/' },
])
await page.addInitScript(
({ token }) => {
localStorage.setItem(
'user',
JSON.stringify({
accessToken: token,
userId: 'user-id',
role: 'user',
sessionId: 'user-session',
AccessTokenExpireTime: Date.now() + 3_600_000,
refreshTokenExpireTime: Date.now() + 86_400_000,
status: 'pending',
})
)
},
{ token: accessToken }
)
await page.route('**/api/v1/**', (route) =>
route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify({ success: true, data: { items: [], meta: {} } }),
})
)
await page.goto('/profile')
await expect(page).toHaveURL(/\/profile$/)
await expect(page.getByText('تکمیل اطلاعات پروفایل')).toBeVisible()
})