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 { describe, it, expect, beforeEach } from 'vitest'
|
||||||
import { setActivePinia, createPinia } from 'pinia'
|
import { setActivePinia, createPinia } from 'pinia'
|
||||||
import { useUserStore } from '../user'
|
import { useUserStore } from '../user'
|
||||||
|
import { fakeJwtWithRole } from '@/test/helpers'
|
||||||
|
|
||||||
describe('Permission: user store 权限判断', () => {
|
describe('Permission: user store 权限判断', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
@@ -11,6 +12,7 @@ describe('Permission: user store 权限判断', () => {
|
|||||||
describe('isAdmin', () => {
|
describe('isAdmin', () => {
|
||||||
it('administrator 角色返回 true', () => {
|
it('administrator 角色返回 true', () => {
|
||||||
const store = useUserStore()
|
const store = useUserStore()
|
||||||
|
store.setToken(fakeJwtWithRole('administrator'), 'rt')
|
||||||
store.setUser({ id: 1, username: 'admin', email: 'a@a.com', role: 'administrator', status: 1 })
|
store.setUser({ id: 1, username: 'admin', email: 'a@a.com', role: 'administrator', status: 1 })
|
||||||
|
|
||||||
expect(store.isAdmin).toBe(true)
|
expect(store.isAdmin).toBe(true)
|
||||||
@@ -40,6 +42,7 @@ describe('Permission: user store 权限判断', () => {
|
|||||||
describe('hasPermission', () => {
|
describe('hasPermission', () => {
|
||||||
it('admin 对 write 操作返回 true', () => {
|
it('admin 对 write 操作返回 true', () => {
|
||||||
const store = useUserStore()
|
const store = useUserStore()
|
||||||
|
store.setToken(fakeJwtWithRole('administrator'), 'rt')
|
||||||
store.setUser({ id: 1, username: 'admin', email: 'a@a.com', role: 'administrator', status: 1 })
|
store.setUser({ id: 1, username: 'admin', email: 'a@a.com', role: 'administrator', status: 1 })
|
||||||
|
|
||||||
expect(store.hasPermission('write')).toBe(true)
|
expect(store.hasPermission('write')).toBe(true)
|
||||||
@@ -47,6 +50,7 @@ describe('Permission: user store 权限判断', () => {
|
|||||||
|
|
||||||
it('admin 对 read 操作返回 true', () => {
|
it('admin 对 read 操作返回 true', () => {
|
||||||
const store = useUserStore()
|
const store = useUserStore()
|
||||||
|
store.setToken(fakeJwtWithRole('administrator'), 'rt')
|
||||||
store.setUser({ id: 1, username: 'admin', email: 'a@a.com', role: 'administrator', status: 1 })
|
store.setUser({ id: 1, username: 'admin', email: 'a@a.com', role: 'administrator', status: 1 })
|
||||||
|
|
||||||
expect(store.hasPermission('read')).toBe(true)
|
expect(store.hasPermission('read')).toBe(true)
|
||||||
@@ -80,17 +84,20 @@ describe('Permission: user store 权限判断', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
describe('角色切换响应', () => {
|
describe('角色切换响应', () => {
|
||||||
it('用户角色从 accessor 切换为 administrator 后 isAdmin 变为 true', () => {
|
it('用户 token 从 accessor 切换为 administrator 后 isAdmin 变为 true', () => {
|
||||||
const store = useUserStore()
|
const store = useUserStore()
|
||||||
|
store.setToken(fakeJwtWithRole('accessor'), 'rt')
|
||||||
store.setUser({ id: 1, username: 'u', email: 'u@u.com', role: 'accessor', status: 1 })
|
store.setUser({ id: 1, username: 'u', email: 'u@u.com', role: 'accessor', status: 1 })
|
||||||
expect(store.isAdmin).toBe(false)
|
expect(store.isAdmin).toBe(false)
|
||||||
|
|
||||||
|
store.setToken(fakeJwtWithRole('administrator'), 'rt')
|
||||||
store.setUser({ id: 1, username: 'u', email: 'u@u.com', role: 'administrator', status: 1 })
|
store.setUser({ id: 1, username: 'u', email: 'u@u.com', role: 'administrator', status: 1 })
|
||||||
expect(store.isAdmin).toBe(true)
|
expect(store.isAdmin).toBe(true)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('logout 后 isAdmin 恢复为 false', () => {
|
it('logout 后 isAdmin 恢复为 false', () => {
|
||||||
const store = useUserStore()
|
const store = useUserStore()
|
||||||
|
store.setToken(fakeJwtWithRole('administrator'), 'rt')
|
||||||
store.setUser({ id: 1, username: 'a', email: 'a@a.com', role: 'administrator', status: 1 })
|
store.setUser({ id: 1, username: 'a', email: 'a@a.com', role: 'administrator', status: 1 })
|
||||||
expect(store.isAdmin).toBe(true)
|
expect(store.isAdmin).toBe(true)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user