鉴权改为登录态回库校验,新增 tokenValidAfter 失效时间,支持密码变更与 seed 重置后旧 token 立即失效 患者字段由 idCardHash 统一迁移为 idCard,新增身份证标准化逻辑并同步 C 端生命周期查询参数 组织模块增加小组删除限制(有成员时返回 409)并补充中文错误消息 任务取消接口支持可选 reason 字段(先透传事件层) 补齐 Prisma 迁移、文档说明和 E2E 用例(含设备模块与 token 失效场景)
162 lines
5.2 KiB
TypeScript
162 lines
5.2 KiB
TypeScript
import request from 'supertest';
|
||
import { DeviceStatus, Role } from '../../../src/generated/prisma/enums.js';
|
||
import {
|
||
closeE2EContext,
|
||
createE2EContext,
|
||
type E2EContext,
|
||
} from '../helpers/e2e-context.helper.js';
|
||
import { assertRoleMatrix } from '../helpers/e2e-matrix.helper.js';
|
||
import {
|
||
expectErrorEnvelope,
|
||
expectSuccessEnvelope,
|
||
uniqueSeedValue,
|
||
} from '../helpers/e2e-http.helper.js';
|
||
|
||
describe('BDevicesController (e2e)', () => {
|
||
let ctx: E2EContext;
|
||
|
||
beforeAll(async () => {
|
||
ctx = await createE2EContext();
|
||
});
|
||
|
||
afterAll(async () => {
|
||
await closeE2EContext(ctx);
|
||
});
|
||
|
||
async function createDevice(token: string, patientId: number) {
|
||
const response = await request(ctx.app.getHttpServer())
|
||
.post('/b/devices')
|
||
.set('Authorization', `Bearer ${token}`)
|
||
.send({
|
||
snCode: uniqueSeedValue('device-sn'),
|
||
currentPressure: 118,
|
||
status: DeviceStatus.ACTIVE,
|
||
patientId,
|
||
});
|
||
|
||
expectSuccessEnvelope(response, 201);
|
||
return response.body.data as {
|
||
id: number;
|
||
snCode: string;
|
||
status: DeviceStatus;
|
||
patient: { id: number };
|
||
};
|
||
}
|
||
|
||
describe('GET /b/devices', () => {
|
||
it('成功:SYSTEM_ADMIN 可分页查询设备列表', async () => {
|
||
const response = await request(ctx.app.getHttpServer())
|
||
.get('/b/devices')
|
||
.set('Authorization', `Bearer ${ctx.tokens[Role.SYSTEM_ADMIN]}`);
|
||
|
||
expectSuccessEnvelope(response, 200);
|
||
expect(Array.isArray(response.body.data.list)).toBe(true);
|
||
expect(response.body.data.total).toBeGreaterThan(0);
|
||
});
|
||
|
||
it('成功:HOSPITAL_ADMIN 仅能看到本院设备', async () => {
|
||
const response = await request(ctx.app.getHttpServer())
|
||
.get('/b/devices')
|
||
.set('Authorization', `Bearer ${ctx.tokens[Role.HOSPITAL_ADMIN]}`);
|
||
|
||
expectSuccessEnvelope(response, 200);
|
||
const hospitalIds = (
|
||
response.body.data.list as Array<{
|
||
patient?: { hospital?: { id: number } };
|
||
}>
|
||
)
|
||
.map((item) => item.patient?.hospital?.id)
|
||
.filter(Boolean);
|
||
|
||
expect(hospitalIds.every((id) => id === ctx.fixtures.hospitalAId)).toBe(
|
||
true,
|
||
);
|
||
});
|
||
|
||
it('角色矩阵:仅 SYSTEM_ADMIN/HOSPITAL_ADMIN 可访问列表,其他角色 403,未登录 401', async () => {
|
||
await assertRoleMatrix({
|
||
name: 'GET /b/devices role matrix',
|
||
tokens: ctx.tokens,
|
||
expectedStatusByRole: {
|
||
[Role.SYSTEM_ADMIN]: 200,
|
||
[Role.HOSPITAL_ADMIN]: 200,
|
||
[Role.DIRECTOR]: 403,
|
||
[Role.LEADER]: 403,
|
||
[Role.DOCTOR]: 403,
|
||
[Role.ENGINEER]: 403,
|
||
},
|
||
sendAsRole: async (_role, token) =>
|
||
request(ctx.app.getHttpServer())
|
||
.get('/b/devices')
|
||
.set('Authorization', `Bearer ${token}`),
|
||
sendWithoutToken: async () =>
|
||
request(ctx.app.getHttpServer()).get('/b/devices'),
|
||
});
|
||
});
|
||
});
|
||
|
||
describe('设备 CRUD 流程', () => {
|
||
it('成功:HOSPITAL_ADMIN 可创建设备', async () => {
|
||
const created = await createDevice(
|
||
ctx.tokens[Role.HOSPITAL_ADMIN],
|
||
ctx.fixtures.patients.patientA1Id,
|
||
);
|
||
|
||
expect(created.status).toBe(DeviceStatus.ACTIVE);
|
||
expect(created.patient.id).toBe(ctx.fixtures.patients.patientA1Id);
|
||
expect(created.snCode).toMatch(/^DEVICE-SN-/);
|
||
});
|
||
|
||
it('失败:HOSPITAL_ADMIN 绑定跨院患者返回 403', async () => {
|
||
const response = await request(ctx.app.getHttpServer())
|
||
.post('/b/devices')
|
||
.set('Authorization', `Bearer ${ctx.tokens[Role.HOSPITAL_ADMIN]}`)
|
||
.send({
|
||
snCode: uniqueSeedValue('cross-hospital-device'),
|
||
currentPressure: 120,
|
||
status: DeviceStatus.ACTIVE,
|
||
patientId: ctx.fixtures.patients.patientB1Id,
|
||
});
|
||
|
||
expectErrorEnvelope(response, 403, '仅可绑定当前权限范围内患者');
|
||
});
|
||
|
||
it('成功:SYSTEM_ADMIN 可更新设备状态与归属患者', async () => {
|
||
const created = await createDevice(
|
||
ctx.tokens[Role.SYSTEM_ADMIN],
|
||
ctx.fixtures.patients.patientA1Id,
|
||
);
|
||
|
||
const response = await request(ctx.app.getHttpServer())
|
||
.patch(`/b/devices/${created.id}`)
|
||
.set('Authorization', `Bearer ${ctx.tokens[Role.SYSTEM_ADMIN]}`)
|
||
.send({
|
||
status: DeviceStatus.INACTIVE,
|
||
patientId: ctx.fixtures.patients.patientA2Id,
|
||
currentPressure: 99,
|
||
});
|
||
|
||
expectSuccessEnvelope(response, 200);
|
||
expect(response.body.data.status).toBe(DeviceStatus.INACTIVE);
|
||
expect(response.body.data.patient.id).toBe(
|
||
ctx.fixtures.patients.patientA2Id,
|
||
);
|
||
expect(response.body.data.currentPressure).toBe(99);
|
||
});
|
||
|
||
it('成功:SYSTEM_ADMIN 可删除未被任务引用的设备', async () => {
|
||
const created = await createDevice(
|
||
ctx.tokens[Role.SYSTEM_ADMIN],
|
||
ctx.fixtures.patients.patientA1Id,
|
||
);
|
||
|
||
const response = await request(ctx.app.getHttpServer())
|
||
.delete(`/b/devices/${created.id}`)
|
||
.set('Authorization', `Bearer ${ctx.tokens[Role.SYSTEM_ADMIN]}`);
|
||
|
||
expectSuccessEnvelope(response, 200);
|
||
expect(response.body.data.id).toBe(created.id);
|
||
});
|
||
});
|
||
});
|