Files
goauto/web/tests/e2e/goauto-menu-permission.spec.ts
T

150 lines
6.7 KiB
TypeScript

import { expect, test } from '@playwright/test'
const modules = [
['/devices', '/goauto/devices/index', 'GoAutoDeviceList', '设备列表', 'monitor'],
['/pdd-products', '/goauto/pdd-products/index', 'GoAutoPddProducts', 'PDD 商品', 'shopping'],
['/shopee-products', '/goauto/shopee-products/index', 'GoAutoShopeeProducts', '虾皮商品', 'goods'],
['/syb-products', '/goauto/syb-products/index', 'GoAutoSybProducts', 'SYB 商品', 'tickets'],
['/syb-shops', '/goauto/syb-shops/index', 'GoAutoSybShops', 'SYB 店铺', 'shopping'],
['/syb-sync-runs', '/goauto/syb-sync-runs/index', 'GoAutoSybSyncRuns', 'SYB 同步记录', 'time'],
['/syb-inner-codes', '/goauto/syb-inner-codes/index', 'GoAutoSybInnerCodes', '档口入库码', 'list'],
['/collection-rules', '/goauto/collection-rules/index', 'GoAutoCollectionRules', '采集规则', 'code'],
['/collection-tasks', '/goauto/collection-tasks/index', 'GoAutoCollectionTasks', '采集任务', 'list'],
['/purchase-tasks', '/goauto/purchase-tasks/index', 'GoAutoPurchaseTasks', '采购管理', 'shopping'],
['/ai-matching-settings', '/goauto/ai-matching-settings/index', 'GoAutoAiMatchingSettings', 'AI 规格匹配', 'setting']
] as const
const menuGroups = [
{
path: '/collection-purchase',
name: 'GoAutoCollectionPurchase',
title: '采集采购',
icon: 'shopping',
modules: [modules[3], modules[5], modules[6], modules[2], modules[1], modules[8], modules[9]]
},
{
path: '/collection-management',
name: 'GoAutoCollectionManagement',
title: '采采管理',
icon: 'system',
modules: [modules[4], modules[7], modules[0], modules[10]]
}
] as const
function childMenu(module: typeof modules[number]) {
const [path, component, name, title, icon] = module
return {
path: `${path}/index`,
component,
visible: '0',
menuName: name,
title,
icon
}
}
function groupedMenus(visibleModules: typeof modules[number][]) {
const allowed = new Set(visibleModules.map(module => module[2]))
return menuGroups.flatMap(group => {
const children = group.modules.filter(module => allowed.has(module[2])).map(childMenu)
if (children.length === 0) return []
return [{
path: group.path,
component: 'Layout',
visible: '0',
menuName: group.name,
title: group.title,
icon: group.icon,
children
}]
})
}
async function mockSession(page: any, role: 'admin' | 'purchaser', visibleModules: typeof modules[number][]) {
await page.context().addCookies([{
name: 'Admin-Token',
value: 'menu-permission-test-token',
domain: 'localhost',
path: '/'
}])
await page.route('**/api/**', async (route: any) => {
const url = new URL(route.request().url())
if (url.pathname.startsWith('/src/api/')) return route.continue()
if (url.pathname.endsWith('/api/v1/getinfo')) {
return route.fulfill({ json: { code: 200, data: { roles: [role], name: role, avatar: '', introduction: '', permissions: [] } } })
}
if (url.pathname.endsWith('/api/v1/menurole')) {
return route.fulfill({ json: { code: 200, data: groupedMenus(visibleModules) } })
}
return route.fulfill({ json: { code: 200, data: [] } })
})
}
test('管理员深链接展开所属分组并保留全部 GoAuto 页面', async ({ page }) => {
await mockSession(page, 'admin', [...modules])
await page.goto('/#/pdd-products/index')
await expect(page).toHaveURL(/#\/pdd-products\/index$/)
await expect(page.locator('.sidebar-container').getByText('采集采购', { exact: true }).first()).toBeVisible()
await expect(page.locator('.sidebar-container').getByText('采采管理', { exact: true }).first()).toBeVisible()
await expect(page.locator('.sidebar-container').getByText('PDD 商品', { exact: true }).first()).toBeVisible()
await expect(page.locator('.sidebar-container .is-active').getByText('PDD 商品', { exact: true })).toBeVisible()
await page.goto('/#/ai-matching-settings/index')
await expect(page).toHaveURL(/#\/ai-matching-settings\/index$/)
await expect(page.locator('.sidebar-container').getByText('AI 规格匹配', { exact: true }).first()).toBeVisible()
await expect(page.locator('.sidebar-container .is-active').getByText('AI 规格匹配', { exact: true })).toBeVisible()
})
test('采购员只看到授权菜单且不能通过地址访问 AI 配置', async ({ page }) => {
const purchaserModules = modules.filter(module => module[2] !== 'GoAutoAiMatchingSettings')
await mockSession(page, 'purchaser', purchaserModules)
await page.goto('/#/devices/index')
await expect(page.locator('.sidebar-container').getByText('采集采购', { exact: true }).first()).toBeVisible()
await expect(page.locator('.sidebar-container').getByText('采采管理', { exact: true }).first()).toBeVisible()
await expect(page.locator('.sidebar-container').getByText('SYB 店铺', { exact: true }).first()).toBeVisible()
await expect(page.locator('.sidebar-container').getByText('设备列表', { exact: true }).first()).toBeVisible()
await expect(page.locator('.sidebar-container').getByText('AI 规格匹配', { exact: true })).toHaveCount(0)
await page.goto('/#/ai-matching-settings/index')
await expect(page).toHaveURL(/#\/dashboard$/)
await expect(page.locator('.sidebar-container').getByText('AI 规格匹配', { exact: true })).toHaveCount(0)
})
test('没有 GoAuto 菜单权限时仍可进入工作台', async ({ page }) => {
await mockSession(page, 'purchaser', [])
await page.goto('/#/purchase-tasks/index')
await expect(page).toHaveURL(/#\/dashboard$/)
await expect(page.getByText('工作台', { exact: true }).first()).toBeVisible()
await expect(page.locator('.sidebar-container').getByText('采购管理', { exact: true })).toHaveCount(0)
})
test('角色菜单加载失败时清除会话并返回登录页', async ({ page }) => {
await page.context().addCookies([{
name: 'Admin-Token',
value: 'menu-permission-test-token',
domain: 'localhost',
path: '/'
}])
await page.route('**/api/**', async route => {
const url = new URL(route.request().url())
if (url.pathname.startsWith('/src/api/')) return route.continue()
if (url.pathname.endsWith('/api/v1/getinfo')) {
return route.fulfill({ json: { code: 200, data: { roles: ['admin'], name: 'admin', avatar: '', introduction: '', permissions: [] } } })
}
if (url.pathname.endsWith('/api/v1/menurole')) {
return route.fulfill({ json: { code: 500, message: '菜单服务不可用' } })
}
return route.fulfill({ json: { code: 200, data: [] } })
})
await page.goto('/#/dashboard')
await expect(page).toHaveURL(/#\/login\?redirect=\/dashboard$/)
await expect.poll(async () => {
const cookies = await page.context().cookies()
return cookies.some(cookie => cookie.name === 'Admin-Token')
}).toBe(false)
})