implement permissions
This commit is contained in:
@@ -0,0 +1,47 @@
|
||||
import { describe, it, expect } from 'vitest'
|
||||
import { ADMIN_ONLY_PATH_PREFIXES, isAdminOnlyPath } from '../permissions'
|
||||
|
||||
describe('ADMIN_ONLY_PATH_PREFIXES', () => {
|
||||
it('contains all 7 admin-only paths', () => {
|
||||
expect(ADMIN_ONLY_PATH_PREFIXES).toHaveLength(7)
|
||||
expect(ADMIN_ONLY_PATH_PREFIXES).toContain('/users')
|
||||
expect(ADMIN_ONLY_PATH_PREFIXES).toContain('/roles')
|
||||
expect(ADMIN_ONLY_PATH_PREFIXES).toContain('/route-groups')
|
||||
expect(ADMIN_ONLY_PATH_PREFIXES).toContain('/mq-status')
|
||||
expect(ADMIN_ONLY_PATH_PREFIXES).toContain('/failed-messages')
|
||||
expect(ADMIN_ONLY_PATH_PREFIXES).toContain('/logs/requests')
|
||||
expect(ADMIN_ONLY_PATH_PREFIXES).toContain('/logs/operations')
|
||||
})
|
||||
})
|
||||
|
||||
describe('isAdminOnlyPath', () => {
|
||||
it('matches exact admin-only paths', () => {
|
||||
for (const path of ADMIN_ONLY_PATH_PREFIXES) {
|
||||
expect(isAdminOnlyPath(path)).toBe(true)
|
||||
}
|
||||
})
|
||||
|
||||
it('matches sub-paths via prefix matching', () => {
|
||||
expect(isAdminOnlyPath('/users/123')).toBe(true)
|
||||
expect(isAdminOnlyPath('/users/123/edit')).toBe(true)
|
||||
expect(isAdminOnlyPath('/roles/5')).toBe(true)
|
||||
expect(isAdminOnlyPath('/logs/requests/detail')).toBe(true)
|
||||
expect(isAdminOnlyPath('/logs/operations/42')).toBe(true)
|
||||
expect(isAdminOnlyPath('/failed-messages/retry')).toBe(true)
|
||||
})
|
||||
|
||||
it('does not match non-admin paths', () => {
|
||||
expect(isAdminOnlyPath('/')).toBe(false)
|
||||
expect(isAdminOnlyPath('/login')).toBe(false)
|
||||
expect(isAdminOnlyPath('/products')).toBe(false)
|
||||
expect(isAdminOnlyPath('/orders')).toBe(false)
|
||||
expect(isAdminOnlyPath('/dashboard')).toBe(false)
|
||||
})
|
||||
|
||||
it('does not match prefix-similar but distinct paths', () => {
|
||||
expect(isAdminOnlyPath('/users-export')).toBe(false)
|
||||
expect(isAdminOnlyPath('/roles-backup')).toBe(false)
|
||||
expect(isAdminOnlyPath('/route-groups-old')).toBe(false)
|
||||
expect(isAdminOnlyPath('/mq-status-history')).toBe(false)
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,25 @@
|
||||
/**
|
||||
* 仅管理员可访问的路径前缀列表。
|
||||
* 路由守卫使用前缀匹配:以列表中任意项开头的路径都会被拦截。
|
||||
*
|
||||
* 同步要求:此列表必须与 MainLayout.vue 中 adminOnly: true 的菜单项保持一致。
|
||||
*/
|
||||
export const ADMIN_ONLY_PATH_PREFIXES: readonly string[] = [
|
||||
'/users',
|
||||
'/roles',
|
||||
'/route-groups',
|
||||
'/mq-status',
|
||||
'/failed-messages',
|
||||
'/logs/requests',
|
||||
'/logs/operations',
|
||||
] as const
|
||||
|
||||
/**
|
||||
* 判断给定路径是否属于 admin-only 路径(前缀匹配)。
|
||||
* 例如 '/users/123' 会匹配 '/users' 前缀。
|
||||
*/
|
||||
export function isAdminOnlyPath(path: string): boolean {
|
||||
return ADMIN_ONLY_PATH_PREFIXES.some(
|
||||
(prefix) => path === prefix || path.startsWith(prefix + '/'),
|
||||
)
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
import { describe, it, expect, beforeEach } from 'vitest'
|
||||
import { setActivePinia, createPinia } from 'pinia'
|
||||
import { useUserStore } from '../user'
|
||||
import { fakeJwtWithRole } from '@/test/helpers'
|
||||
|
||||
describe('Permission: user store 权限判断', () => {
|
||||
beforeEach(() => {
|
||||
@@ -11,6 +12,7 @@ describe('Permission: user store 权限判断', () => {
|
||||
describe('isAdmin', () => {
|
||||
it('administrator 角色返回 true', () => {
|
||||
const store = useUserStore()
|
||||
store.setToken(fakeJwtWithRole('administrator'), 'rt')
|
||||
store.setUser({ id: 1, username: 'admin', email: 'a@a.com', role: 'administrator', status: 1 })
|
||||
|
||||
expect(store.isAdmin).toBe(true)
|
||||
@@ -40,6 +42,7 @@ describe('Permission: user store 权限判断', () => {
|
||||
describe('hasPermission', () => {
|
||||
it('admin 对 write 操作返回 true', () => {
|
||||
const store = useUserStore()
|
||||
store.setToken(fakeJwtWithRole('administrator'), 'rt')
|
||||
store.setUser({ id: 1, username: 'admin', email: 'a@a.com', role: 'administrator', status: 1 })
|
||||
|
||||
expect(store.hasPermission('write')).toBe(true)
|
||||
@@ -47,6 +50,7 @@ describe('Permission: user store 权限判断', () => {
|
||||
|
||||
it('admin 对 read 操作返回 true', () => {
|
||||
const store = useUserStore()
|
||||
store.setToken(fakeJwtWithRole('administrator'), 'rt')
|
||||
store.setUser({ id: 1, username: 'admin', email: 'a@a.com', role: 'administrator', status: 1 })
|
||||
|
||||
expect(store.hasPermission('read')).toBe(true)
|
||||
@@ -80,17 +84,20 @@ describe('Permission: user store 权限判断', () => {
|
||||
})
|
||||
|
||||
describe('角色切换响应', () => {
|
||||
it('用户角色从 accessor 切换为 administrator 后 isAdmin 变为 true', () => {
|
||||
it('用户 token 从 accessor 切换为 administrator 后 isAdmin 变为 true', () => {
|
||||
const store = useUserStore()
|
||||
store.setToken(fakeJwtWithRole('accessor'), 'rt')
|
||||
store.setUser({ id: 1, username: 'u', email: 'u@u.com', role: 'accessor', status: 1 })
|
||||
expect(store.isAdmin).toBe(false)
|
||||
|
||||
store.setToken(fakeJwtWithRole('administrator'), 'rt')
|
||||
store.setUser({ id: 1, username: 'u', email: 'u@u.com', role: 'administrator', status: 1 })
|
||||
expect(store.isAdmin).toBe(true)
|
||||
})
|
||||
|
||||
it('logout 后 isAdmin 恢复为 false', () => {
|
||||
const store = useUserStore()
|
||||
store.setToken(fakeJwtWithRole('administrator'), 'rt')
|
||||
store.setUser({ id: 1, username: 'a', email: 'a@a.com', role: 'administrator', status: 1 })
|
||||
expect(store.isAdmin).toBe(true)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user