refactor(frontend): 重命名前端项目为 gym-manage-web
This commit is contained in:
@@ -0,0 +1,72 @@
|
||||
import { describe, it, expect } from 'vitest'
|
||||
import { mount } from '@vue/test-utils'
|
||||
import MenuItem from '@/components/MenuItem.vue'
|
||||
|
||||
describe('MenuItem 组件', () => {
|
||||
it('应该正确接收菜单项 props', () => {
|
||||
const menu = {
|
||||
id: 1,
|
||||
name: '仪表盘',
|
||||
path: '/dashboard',
|
||||
icon: 'Odometer',
|
||||
sort: 1
|
||||
}
|
||||
|
||||
const wrapper = mount(MenuItem, {
|
||||
props: { menu },
|
||||
global: {
|
||||
stubs: {
|
||||
'el-menu-item': {
|
||||
template: '<div><slot /></div>'
|
||||
},
|
||||
'el-sub-menu': {
|
||||
template: '<div><slot name="title" /><slot /></div>'
|
||||
},
|
||||
'el-icon': {
|
||||
template: '<div><slot /></div>'
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
expect(wrapper.props('menu')).toEqual(menu)
|
||||
})
|
||||
|
||||
it('应该正确处理有子菜单的菜单项', () => {
|
||||
const menu = {
|
||||
id: 2,
|
||||
name: '系统管理',
|
||||
path: '/system',
|
||||
icon: 'Setting',
|
||||
sort: 2,
|
||||
children: [
|
||||
{
|
||||
id: 3,
|
||||
name: '用户管理',
|
||||
path: '/users',
|
||||
sort: 1
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
const wrapper = mount(MenuItem, {
|
||||
props: { menu },
|
||||
global: {
|
||||
stubs: {
|
||||
'el-menu-item': {
|
||||
template: '<div><slot /></div>'
|
||||
},
|
||||
'el-sub-menu': {
|
||||
template: '<div><slot name="title" /><slot /></div>'
|
||||
},
|
||||
'el-icon': {
|
||||
template: '<div><slot /></div>'
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
expect(wrapper.props('menu')).toEqual(menu)
|
||||
expect(wrapper.props('menu').children).toHaveLength(1)
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,124 @@
|
||||
import { describe, it, expect, beforeEach } from 'vitest'
|
||||
import { mount } from '@vue/test-utils'
|
||||
import { createPinia, setActivePinia } from 'pinia'
|
||||
import { permissionDirective } from '@/directives/permission'
|
||||
import { usePermissionStore } from '@/stores/permission'
|
||||
|
||||
describe('v-permission 指令', () => {
|
||||
beforeEach(() => {
|
||||
setActivePinia(createPinia())
|
||||
localStorage.clear()
|
||||
})
|
||||
|
||||
describe('角色检查', () => {
|
||||
it('有角色时应该显示元素', () => {
|
||||
const store = usePermissionStore()
|
||||
store.setPermissionData({
|
||||
roles: ['admin'],
|
||||
permissions: [],
|
||||
menus: []
|
||||
})
|
||||
|
||||
const wrapper = mount({
|
||||
template: '<button v-permission:role="\'admin\'">管理员按钮</button>',
|
||||
directives: {
|
||||
permission: permissionDirective
|
||||
}
|
||||
})
|
||||
|
||||
expect(wrapper.find('button').isVisible()).toBe(true)
|
||||
})
|
||||
|
||||
it('无角色时应该隐藏元素', () => {
|
||||
const store = usePermissionStore()
|
||||
store.setPermissionData({
|
||||
roles: ['user'],
|
||||
permissions: [],
|
||||
menus: []
|
||||
})
|
||||
|
||||
const wrapper = mount({
|
||||
template: '<button v-permission:role="\'admin\'">管理员按钮</button>',
|
||||
directives: {
|
||||
permission: permissionDirective
|
||||
}
|
||||
})
|
||||
|
||||
expect(wrapper.find('button').isVisible()).toBe(false)
|
||||
})
|
||||
|
||||
it('支持数组参数(满足任一即可)', () => {
|
||||
const store = usePermissionStore()
|
||||
store.setPermissionData({
|
||||
roles: ['user'],
|
||||
permissions: [],
|
||||
menus: []
|
||||
})
|
||||
|
||||
const wrapper = mount({
|
||||
template: '<button v-permission:role="[\'admin\', \'user\']">按钮</button>',
|
||||
directives: {
|
||||
permission: permissionDirective
|
||||
}
|
||||
})
|
||||
|
||||
expect(wrapper.find('button').isVisible()).toBe(true)
|
||||
})
|
||||
})
|
||||
|
||||
describe('权限检查', () => {
|
||||
it('有权限时应该显示元素', () => {
|
||||
const store = usePermissionStore()
|
||||
store.setPermissionData({
|
||||
roles: [],
|
||||
permissions: ['user:delete'],
|
||||
menus: []
|
||||
})
|
||||
|
||||
const wrapper = mount({
|
||||
template: '<button v-permission:permission="\'user:delete\'">删除用户</button>',
|
||||
directives: {
|
||||
permission: permissionDirective
|
||||
}
|
||||
})
|
||||
|
||||
expect(wrapper.find('button').isVisible()).toBe(true)
|
||||
})
|
||||
|
||||
it('无权限时应该隐藏元素', () => {
|
||||
const store = usePermissionStore()
|
||||
store.setPermissionData({
|
||||
roles: [],
|
||||
permissions: ['user:read'],
|
||||
menus: []
|
||||
})
|
||||
|
||||
const wrapper = mount({
|
||||
template: '<button v-permission:permission="\'user:delete\'">删除用户</button>',
|
||||
directives: {
|
||||
permission: permissionDirective
|
||||
}
|
||||
})
|
||||
|
||||
expect(wrapper.find('button').isVisible()).toBe(false)
|
||||
})
|
||||
|
||||
it('支持简写形式(默认权限检查)', () => {
|
||||
const store = usePermissionStore()
|
||||
store.setPermissionData({
|
||||
roles: [],
|
||||
permissions: ['user:create'],
|
||||
menus: []
|
||||
})
|
||||
|
||||
const wrapper = mount({
|
||||
template: '<button v-permission="\'user:create\'">创建用户</button>',
|
||||
directives: {
|
||||
permission: permissionDirective
|
||||
}
|
||||
})
|
||||
|
||||
expect(wrapper.find('button').isVisible()).toBe(true)
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,291 @@
|
||||
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')
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,167 @@
|
||||
import { describe, it, expect, beforeEach } from 'vitest'
|
||||
import { setActivePinia, createPinia } from 'pinia'
|
||||
import { usePermissionStore } from '@/stores/permission'
|
||||
|
||||
describe('Permission Store', () => {
|
||||
beforeEach(() => {
|
||||
setActivePinia(createPinia())
|
||||
localStorage.clear()
|
||||
})
|
||||
|
||||
describe('基础功能', () => {
|
||||
it('应该正确初始化状态', () => {
|
||||
const store = usePermissionStore()
|
||||
|
||||
expect(store.roles).toEqual([])
|
||||
expect(store.permissions).toEqual([])
|
||||
expect(store.menus).toEqual([])
|
||||
expect(store.loaded).toBe(false)
|
||||
})
|
||||
|
||||
it('应该正确设置权限数据', () => {
|
||||
const store = usePermissionStore()
|
||||
|
||||
store.setPermissionData({
|
||||
roles: ['admin'],
|
||||
permissions: ['user:read', 'user:delete'],
|
||||
menus: [
|
||||
{
|
||||
id: 1,
|
||||
name: '仪表盘',
|
||||
path: '/dashboard',
|
||||
icon: 'Odometer',
|
||||
sort: 1
|
||||
}
|
||||
]
|
||||
})
|
||||
|
||||
expect(store.roles).toEqual(['admin'])
|
||||
expect(store.permissions).toEqual(['user:read', 'user:delete'])
|
||||
expect(store.menus).toHaveLength(1)
|
||||
expect(store.loaded).toBe(true)
|
||||
})
|
||||
|
||||
it('应该正确清除权限数据', () => {
|
||||
const store = usePermissionStore()
|
||||
|
||||
store.setPermissionData({
|
||||
roles: ['admin'],
|
||||
permissions: ['user:read'],
|
||||
menus: []
|
||||
})
|
||||
|
||||
store.clearPermissionData()
|
||||
|
||||
expect(store.roles).toEqual([])
|
||||
expect(store.permissions).toEqual([])
|
||||
expect(store.menus).toEqual([])
|
||||
expect(store.loaded).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
describe('权限检查方法', () => {
|
||||
it('应该正确检查单个角色', () => {
|
||||
const store = usePermissionStore()
|
||||
store.setPermissionData({
|
||||
roles: ['admin', 'user'],
|
||||
permissions: [],
|
||||
menus: []
|
||||
})
|
||||
|
||||
expect(store.hasRole('admin')).toBe(true)
|
||||
expect(store.hasRole('manager')).toBe(false)
|
||||
})
|
||||
|
||||
it('应该正确检查多个角色(满足任一即可)', () => {
|
||||
const store = usePermissionStore()
|
||||
store.setPermissionData({
|
||||
roles: ['user'],
|
||||
permissions: [],
|
||||
menus: []
|
||||
})
|
||||
|
||||
expect(store.hasRole(['admin', 'user'])).toBe(true)
|
||||
expect(store.hasRole(['admin', 'manager'])).toBe(false)
|
||||
})
|
||||
|
||||
it('应该正确检查单个权限', () => {
|
||||
const store = usePermissionStore()
|
||||
store.setPermissionData({
|
||||
roles: [],
|
||||
permissions: ['user:read', 'user:delete'],
|
||||
menus: []
|
||||
})
|
||||
|
||||
expect(store.hasPermission('user:read')).toBe(true)
|
||||
expect(store.hasPermission('user:create')).toBe(false)
|
||||
})
|
||||
|
||||
it('应该正确检查多个权限(满足任一即可)', () => {
|
||||
const store = usePermissionStore()
|
||||
store.setPermissionData({
|
||||
roles: [],
|
||||
permissions: ['user:read'],
|
||||
menus: []
|
||||
})
|
||||
|
||||
expect(store.hasPermission(['user:read', 'user:create'])).toBe(true)
|
||||
expect(store.hasPermission(['user:create', 'user:update'])).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
describe('localStorage 持久化', () => {
|
||||
it('应该正确保存到 localStorage', () => {
|
||||
const store = usePermissionStore()
|
||||
|
||||
store.setPermissionData({
|
||||
roles: ['admin'],
|
||||
permissions: ['user:read'],
|
||||
menus: [
|
||||
{
|
||||
id: 1,
|
||||
name: '仪表盘',
|
||||
path: '/dashboard',
|
||||
sort: 1
|
||||
}
|
||||
]
|
||||
})
|
||||
|
||||
const stored = localStorage.getItem('permission')
|
||||
expect(stored).toBeTruthy()
|
||||
|
||||
const data = JSON.parse(stored!)
|
||||
expect(data.roles).toEqual(['admin'])
|
||||
expect(data.permissions).toEqual(['user:read'])
|
||||
expect(data.menus).toHaveLength(1)
|
||||
})
|
||||
|
||||
it('应该正确从 localStorage 恢复', () => {
|
||||
localStorage.setItem('permission', JSON.stringify({
|
||||
roles: ['user'],
|
||||
permissions: ['user:read:self'],
|
||||
menus: []
|
||||
}))
|
||||
|
||||
const store = usePermissionStore()
|
||||
store.initFromStorage()
|
||||
|
||||
expect(store.roles).toEqual(['user'])
|
||||
expect(store.permissions).toEqual(['user:read:self'])
|
||||
expect(store.loaded).toBe(true)
|
||||
})
|
||||
|
||||
it('清除数据时应该同时清除 localStorage', () => {
|
||||
const store = usePermissionStore()
|
||||
|
||||
store.setPermissionData({
|
||||
roles: ['admin'],
|
||||
permissions: [],
|
||||
menus: []
|
||||
})
|
||||
|
||||
store.clearPermissionData()
|
||||
|
||||
expect(localStorage.getItem('permission')).toBeNull()
|
||||
})
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user