Extract admin dashboard from ghabilee-frontend2 into a dedicated Next.js app for backoffice.ghabilee.ir (no SEO indexing / Clarity).
134 lines
5.1 KiB
TypeScript
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()
|
|
})
|
|
})
|