admin/e2e/admin/auth.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

134 lines
5.1 KiB
TypeScript

import { expect, test } from '@playwright/test'
import { createAccessToken, nestRefreshCookie } from '@/e2e/fixtures/session'
test.describe('admin auth page (/auth)', () => {
test('validates mobile input before requesting an OTP', async ({ page }) => {
let requestCount = 0
await page.route('**/api/v1/auth/request-otp', async (route) => {
requestCount += 1
await route.fulfill({ status: 200, contentType: 'application/json', body: '{}' })
})
await page.goto('/auth')
await page.getByLabel('شماره موبایل').fill('09123')
await page.getByRole('button', { name: 'ادامه' }).click()
await expect(page.getByText('شماره موبایل معتبر نیست').first()).toBeVisible()
expect(requestCount).toBe(0)
})
test('requests and verifies OTP for an admin, then lands on the dashboard', async ({ page }) => {
const expiresAt = new Date(Date.now() + 30 * 24 * 60 * 60 * 1000).toISOString()
const accessToken = createAccessToken('admin')
await page.route('**/api/v1/**', (route) =>
route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify({ success: true, data: { items: [], meta: {} } }),
})
)
await page.route('**/api/v1/auth/request-otp', (route) =>
route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify({ success: true, data: { purpose: 'login', expiresIn: 120, alreadySent: false } }),
})
)
await page.route('**/api/v1/auth/verify-otp', (route) =>
route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify({
success: true,
data: {
accessToken,
sessionId: 'admin-session',
expiresAt,
status: 'active',
},
}),
})
)
await page.goto('/auth?redirect=%2Fdashboard')
await page.getByLabel('شماره موبایل').fill('۰۹۱۲۳۴۵۶۷۸۹')
await page.getByRole('button', { name: 'ادامه' }).click()
await expect(page.getByText('کد ارسال‌شده را وارد کنید')).toBeVisible()
await page.locator('input').first().fill('1234')
await page.getByRole('button', { name: 'ورود' }).click()
await expect(page).toHaveURL(/\/dashboard/)
const storedUser = await page.evaluate(() => JSON.parse(localStorage.getItem('user') ?? 'null'))
expect(storedUser).toMatchObject({
userId: 'admin-id',
role: 'admin',
sessionId: 'admin-session',
status: 'active',
})
expect(storedUser.refreshToken).toBeUndefined()
})
})
test.describe('admin logout', () => {
test('logs out locally even when server logout fails', async ({ context, page }) => {
const accessToken = createAccessToken('admin')
let nestLogoutCalls = 0
await context.addCookies([
{ name: 'accessToken', value: accessToken, domain: '127.0.0.1', path: '/' },
nestRefreshCookie(),
{ name: 'userRole', value: 'admin', domain: '127.0.0.1', path: '/' },
{ name: 'userStatus', value: 'active', domain: '127.0.0.1', path: '/' },
])
const plantedRefresh = (await context.cookies()).find((cookie) => cookie.name === 'ghabilee_refresh')
expect(plantedRefresh).toMatchObject({ httpOnly: true, path: '/', sameSite: 'Lax' })
await page.route('**/api/v1/**', (route) => route.fulfill({ json: { success: true, data: { items: [], meta: {} } } }))
await page.route('**/api/v1/auth/logout', (route) => {
nestLogoutCalls += 1
return route.fulfill({ status: 503, json: { message: 'unavailable' } })
})
// Set session in localStorage once — do not use addInitScript, or logout's
// hard navigation to `/` would re-plant the session.
await page.goto('/dashboard')
await page.evaluate(
({ token }) => {
localStorage.setItem(
'user',
JSON.stringify({
accessToken: token,
userId: 'admin-id',
role: 'admin',
sessionId: 'admin-session',
AccessTokenExpireTime: Date.now() + 3_600_000,
refreshTokenExpireTime: Date.now() + 86_400_000,
status: 'active',
})
)
},
{ token: accessToken }
)
await page.reload()
await page.getByRole('button', { name: 'منوی حساب کاربری' }).click()
await page.getByRole('menuitem', { name: 'خروج' }).click()
await expect(page.getByText('برای خروج مطمئن هستید؟')).toBeVisible()
await page.getByRole('button', { name: 'تأیید' }).click()
await expect(page).toHaveURL('http://127.0.0.1:3102/')
await expect.poll(() => page.evaluate(() => localStorage.getItem('user'))).toBeNull()
await expect.poll(async () => (await context.cookies()).find((cookie) => cookie.name === 'ghabilee_refresh') ?? null).toBeNull()
const cookies = await context.cookies()
expect(nestLogoutCalls).toBe(3)
expect(cookies.find((cookie) => cookie.name === 'accessToken')).toBeUndefined()
expect(cookies.find((cookie) => cookie.name === '__Host-ghabilee_refresh')).toBeUndefined()
})
})