背景:A2 开卡前完成 H1 权限门,确认权限点目录、特殊权限、SoD 三对、六类数据范围与超管隐式全集口径(ADR-001)。 改动: - shared:30 权限点 / 10 模块目录与元数据、特殊权限 6 编码、SoD 三对、角色与权限树契约;/me 改 dataScopes 多角色并集。 - prisma:expand 迁移新增 permissions / data_scopes;种子按原型口径重写角色权限、数据范围与 8 项特殊授予。 - api:角色 CRUD / 复制 / 停用 / 删除、权限点启动同步、SoD 40304、系统角色保护、六类范围 ScopeContext 与 CustomerDirectory 端口、变更审计与 outbox。 - web:方案 B 主从分栏三角色页面(列表 / 编辑器 / 新建),权限树、范围选择、特殊权限二次确认、保存影响、SoD 冲突、只读态与模板预填。 - 测试:角色集成 13 条 + A2 e2e 1 条;A1 全量回归。 验证:format:check / lint / typecheck / test(306)/ test:int(27)/ A2 e2e(1,复跑通过)/ openapi:export 全绿。
89 lines
3.1 KiB
TypeScript
89 lines
3.1 KiB
TypeScript
import type { MeResponse } from '@lingxi/shared';
|
|
import { beforeEach, describe, expect, it } from 'vitest';
|
|
|
|
import { authUserFromMe, DEMO_USER, isDemoSession, useAuthStore } from './auth-store';
|
|
|
|
const USER_ID = '01HZZZZZZZZZZZZZZZZZZZZZZ1';
|
|
const ROLE_ID = '01HZZZZZZZZZZZZZZZZZZZZZZ2';
|
|
const GROUP_ID = '01HZZZZZZZZZZZZZZZZZZZZZZ3';
|
|
|
|
const me: MeResponse = {
|
|
user: {
|
|
id: USER_ID,
|
|
name: '沈知远',
|
|
phoneMasked: '133****0071',
|
|
account: 'shen.zhiyuan',
|
|
departmentId: GROUP_ID,
|
|
departmentName: '平台技术部',
|
|
groupId: null,
|
|
groupName: '部门直属',
|
|
roles: [{ id: ROLE_ID, code: 'super_admin', name: '超级管理员', isSystem: true }],
|
|
status: 'active',
|
|
lastLoginAt: '2026-11-02T10:02:00.000Z',
|
|
createdAt: '2026-09-01T00:00:00.000Z',
|
|
},
|
|
roles: [{ id: ROLE_ID, code: 'super_admin', name: '超级管理员', isSystem: true }],
|
|
permissions: ['user:view', 'user:manage'],
|
|
dataScopes: [{ type: 'all' }],
|
|
mustChangePassword: false,
|
|
};
|
|
|
|
describe('会话状态', () => {
|
|
beforeEach(() => {
|
|
window.localStorage.clear();
|
|
useAuthStore.getState().clearSession();
|
|
});
|
|
|
|
it('/me 同步用户、权限与首登改密标记', () => {
|
|
useAuthStore.getState().setSession({
|
|
accessToken: 'token',
|
|
refreshToken: 'refresh',
|
|
user: { id: USER_ID, name: '临时', account: 'tmp' },
|
|
permissions: [],
|
|
});
|
|
useAuthStore.getState().syncFromMe({ ...me, mustChangePassword: true });
|
|
const state = useAuthStore.getState();
|
|
expect(state.user?.name).toBe('沈知远');
|
|
expect(state.user?.roleName).toBe('超级管理员');
|
|
expect(state.permissions).toEqual(['user:view', 'user:manage']);
|
|
expect(state.mustChangePassword).toBe(true);
|
|
|
|
useAuthStore.getState().markPasswordChanged();
|
|
expect(useAuthStore.getState().mustChangePassword).toBe(false);
|
|
});
|
|
|
|
it('会话持久化与登出清理', () => {
|
|
useAuthStore.getState().setSession({
|
|
accessToken: 'token',
|
|
user: authUserFromMe(me),
|
|
permissions: me.permissions,
|
|
});
|
|
expect(window.localStorage.getItem('lingxi.auth.session')).toContain('token');
|
|
useAuthStore.getState().clearSession();
|
|
expect(window.localStorage.getItem('lingxi.auth.session')).toBeNull();
|
|
expect(useAuthStore.getState().user).toBeNull();
|
|
});
|
|
|
|
it('刷新令牌只更新 token,不动用户信息', () => {
|
|
useAuthStore.getState().setSession({
|
|
accessToken: 'old',
|
|
refreshToken: 'refresh-old',
|
|
user: authUserFromMe(me),
|
|
permissions: me.permissions,
|
|
});
|
|
useAuthStore.getState().setTokens({ accessToken: 'new', refreshToken: 'refresh-new' });
|
|
expect(useAuthStore.getState().accessToken).toBe('new');
|
|
expect(useAuthStore.getState().refreshToken).toBe('refresh-new');
|
|
expect(useAuthStore.getState().user?.id).toBe(USER_ID);
|
|
});
|
|
|
|
it('演示会话识别与演示权限写入', () => {
|
|
useAuthStore.getState().signInDemo();
|
|
const state = useAuthStore.getState();
|
|
expect(isDemoSession(state.accessToken)).toBe(true);
|
|
expect(state.user).toEqual(DEMO_USER);
|
|
expect(state.permissions.length).toBeGreaterThan(0);
|
|
expect(isDemoSession('real-token')).toBe(false);
|
|
});
|
|
});
|