Files
novalon-manage-system/novalon-manage-web/src/__tests__/router/permission.guard.test.ts
T
张翔 7420afa380 feat(权限): 实现基于角色的路由权限控制
- 新增路由元信息类型定义 (requiresAuth, roles, title)
- 实现路由守卫中的角色权限校验逻辑
- 新增 403 禁止访问页面
- 提取权限校验函数 checkRoutePermission,提高可测试性
- 修复 JSON.parse 异常处理,增强健壮性
- 优化页面标题动态设置

测试优化:
- 重构 global-setup.ts,支持 JAR 文件启动后端服务
- 优化测试用例等待逻辑,减少硬编码延迟
- 简化 playwright 配置,移除多浏览器支持
- 新增路由权限守卫单元测试

关联需求:权限系统完善
2026-04-08 15:29:03 +08:00

292 lines
7.8 KiB
TypeScript

import { describe, it, expect, beforeEach } from 'vitest'
import { createRouter, createWebHistory } from 'vue-router'
import type { RouteRecordRaw } from 'vue-router'
const mockLocalStorage = {
store: {} as Record<string, string>,
getItem(key: string) {
return this.store[key] || null
},
setItem(key: string, value: string) {
this.store[key] = value
},
removeItem(key: string) {
delete this.store[key]
},
clear() {
this.store = {}
}
}
Object.defineProperty(window, 'localStorage', {
value: mockLocalStorage
})
const createTestRouter = (routes: RouteRecordRaw[]) => {
return createRouter({
history: createWebHistory(),
routes
})
}
describe('路由守卫权限检查', () => {
beforeEach(() => {
mockLocalStorage.clear()
})
describe('基础认证检查', () => {
it('未登录用户访问受保护路由应重定向到登录页', async () => {
const routes: RouteRecordRaw[] = [
{
path: '/login',
name: 'Login',
component: { template: '<div>Login</div>' }
},
{
path: '/',
component: { template: '<div>Layout</div>' },
meta: { requiresAuth: true },
children: [
{
path: 'dashboard',
name: 'Dashboard',
component: { template: '<div>Dashboard</div>' }
}
]
}
]
const router = createTestRouter(routes)
router.beforeEach((to, _from, next) => {
const token = localStorage.getItem('token')
if (to.meta.requiresAuth && !token) {
next('/login')
} else {
next()
}
})
await router.push('/dashboard')
expect(router.currentRoute.value.path).toBe('/login')
})
it('已登录用户访问受保护路由应允许通过', async () => {
mockLocalStorage.setItem('token', 'valid-token')
const routes: RouteRecordRaw[] = [
{
path: '/login',
name: 'Login',
component: { template: '<div>Login</div>' }
},
{
path: '/',
component: { template: '<div>Layout</div>' },
meta: { requiresAuth: true },
children: [
{
path: 'dashboard',
name: 'Dashboard',
component: { template: '<div>Dashboard</div>' }
}
]
}
]
const router = createTestRouter(routes)
router.beforeEach((to, _from, next) => {
const token = localStorage.getItem('token')
if (to.meta.requiresAuth && !token) {
next('/login')
} else {
next()
}
})
await router.push('/dashboard')
expect(router.currentRoute.value.path).toBe('/dashboard')
})
})
describe('角色权限检查', () => {
it('普通用户访问管理员路由应重定向到403页面', async () => {
mockLocalStorage.setItem('token', 'valid-token')
mockLocalStorage.setItem('roles', JSON.stringify(['user']))
const routes: RouteRecordRaw[] = [
{
path: '/login',
name: 'Login',
component: { template: '<div>Login</div>' }
},
{
path: '/403',
name: 'Forbidden',
component: { template: '<div>403 Forbidden</div>' }
},
{
path: '/',
component: { template: '<div>Layout</div>' },
meta: { requiresAuth: true },
children: [
{
path: 'dashboard',
name: 'Dashboard',
component: { template: '<div>Dashboard</div>' }
},
{
path: 'users',
name: 'UserManagement',
component: { template: '<div>UserManagement</div>' },
meta: { roles: ['admin'] }
}
]
}
]
const router = createTestRouter(routes)
router.beforeEach((to, _from, next) => {
const token = localStorage.getItem('token')
const rolesStr = localStorage.getItem('roles')
const userRoles = rolesStr ? JSON.parse(rolesStr) : []
if (to.meta.requiresAuth && !token) {
next('/login')
return
}
if (to.meta.roles && Array.isArray(to.meta.roles)) {
const hasRole = to.meta.roles.some((role: string) => userRoles.includes(role))
if (!hasRole) {
next('/403')
return
}
}
next()
})
await router.push('/users')
expect(router.currentRoute.value.path).toBe('/403')
})
it('管理员用户访问管理员路由应允许通过', async () => {
mockLocalStorage.setItem('token', 'valid-token')
mockLocalStorage.setItem('roles', JSON.stringify(['admin']))
const routes: RouteRecordRaw[] = [
{
path: '/login',
name: 'Login',
component: { template: '<div>Login</div>' }
},
{
path: '/403',
name: 'Forbidden',
component: { template: '<div>403 Forbidden</div>' }
},
{
path: '/',
component: { template: '<div>Layout</div>' },
meta: { requiresAuth: true },
children: [
{
path: 'dashboard',
name: 'Dashboard',
component: { template: '<div>Dashboard</div>' }
},
{
path: 'users',
name: 'UserManagement',
component: { template: '<div>UserManagement</div>' },
meta: { roles: ['admin'] }
}
]
}
]
const router = createTestRouter(routes)
router.beforeEach((to, _from, next) => {
const token = localStorage.getItem('token')
const rolesStr = localStorage.getItem('roles')
const userRoles = rolesStr ? JSON.parse(rolesStr) : []
if (to.meta.requiresAuth && !token) {
next('/login')
return
}
if (to.meta.roles && Array.isArray(to.meta.roles)) {
const hasRole = to.meta.roles.some((role: string) => userRoles.includes(role))
if (!hasRole) {
next('/403')
return
}
}
next()
})
await router.push('/users')
expect(router.currentRoute.value.path).toBe('/users')
})
it('无角色要求的路由所有登录用户都可访问', async () => {
mockLocalStorage.setItem('token', 'valid-token')
mockLocalStorage.setItem('roles', JSON.stringify(['user']))
const routes: RouteRecordRaw[] = [
{
path: '/login',
name: 'Login',
component: { template: '<div>Login</div>' }
},
{
path: '/',
component: { template: '<div>Layout</div>' },
meta: { requiresAuth: true },
children: [
{
path: 'dashboard',
name: 'Dashboard',
component: { template: '<div>Dashboard</div>' }
}
]
}
]
const router = createTestRouter(routes)
router.beforeEach((to, _from, next) => {
const token = localStorage.getItem('token')
const rolesStr = localStorage.getItem('roles')
const userRoles = rolesStr ? JSON.parse(rolesStr) : []
if (to.meta.requiresAuth && !token) {
next('/login')
return
}
if (to.meta.roles && Array.isArray(to.meta.roles)) {
const hasRole = to.meta.roles.some((role: string) => userRoles.includes(role))
if (!hasRole) {
next('/403')
return
}
}
next()
})
await router.push('/dashboard')
expect(router.currentRoute.value.path).toBe('/dashboard')
})
})
})