tyt-api-nest/test/e2e/specs/devices.e2e-spec.ts
EL 73082225f6 "1. 新增系统字典与全局植入目录相关表结构及迁移
2. 扩展患者手术与材料模型,更新种子数据
3. 新增字典模块,增强设备植入目录管理能力
4. 重构患者后台服务与表单链路,统一权限与参数校验
5. 管理台新增字典页面并改造患者/设备页面与路由权限
6. 补充字典及相关领域 e2e 测试并更新文档"
2026-03-19 20:42:17 +08:00

262 lines
9.0 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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'),
status: DeviceStatus.ACTIVE,
patientId,
});
expectSuccessEnvelope(response, 201);
return response.body.data as {
id: number;
snCode: string;
currentPressure: number;
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('植入物型号字典', () => {
it('成功DOCTOR 可查询可见型号字典', async () => {
const response = await request(ctx.app.getHttpServer())
.get('/b/devices/catalogs')
.set('Authorization', `Bearer ${ctx.tokens[Role.DOCTOR]}`);
expectSuccessEnvelope(response, 200);
expect(
(response.body.data as Array<{ modelCode: string }>).some(
(item) => item.modelCode === 'SEED-ADJUSTABLE-VALVE',
),
).toBe(true);
});
it('成功SYSTEM_ADMIN 可新增、更新并删除全局植入物目录', async () => {
const createResponse = await request(ctx.app.getHttpServer())
.post('/b/devices/catalogs')
.set('Authorization', `Bearer ${ctx.tokens[Role.SYSTEM_ADMIN]}`)
.send({
modelCode: uniqueSeedValue('catalog').toUpperCase(),
manufacturer: 'Global Vendor',
name: '全局可调压阀',
isPressureAdjustable: true,
pressureLevels: [70, 90, 110],
notes: '测试全局目录',
});
expectSuccessEnvelope(createResponse, 201);
expect(createResponse.body.data.pressureLevels).toEqual([70, 90, 110]);
const updateResponse = await request(ctx.app.getHttpServer())
.patch(`/b/devices/catalogs/${createResponse.body.data.id}`)
.set('Authorization', `Bearer ${ctx.tokens[Role.SYSTEM_ADMIN]}`)
.send({
name: '全局可调压阀-更新版',
pressureLevels: [80, 100, 120],
});
expectSuccessEnvelope(updateResponse, 200);
expect(updateResponse.body.data.name).toBe('全局可调压阀-更新版');
expect(updateResponse.body.data.pressureLevels).toEqual([80, 100, 120]);
const deleteResponse = await request(ctx.app.getHttpServer())
.delete(`/b/devices/catalogs/${createResponse.body.data.id}`)
.set('Authorization', `Bearer ${ctx.tokens[Role.SYSTEM_ADMIN]}`);
expectSuccessEnvelope(deleteResponse, 200);
expect(deleteResponse.body.data.id).toBe(createResponse.body.data.id);
});
it('角色矩阵:仅 SYSTEM_ADMIN 可维护全局植入物目录', async () => {
await assertRoleMatrix({
name: 'POST /b/devices/catalogs role matrix',
tokens: ctx.tokens,
expectedStatusByRole: {
[Role.SYSTEM_ADMIN]: 201,
[Role.HOSPITAL_ADMIN]: 403,
[Role.DIRECTOR]: 403,
[Role.LEADER]: 403,
[Role.DOCTOR]: 403,
[Role.ENGINEER]: 403,
},
sendAsRole: async (_role, token) =>
request(ctx.app.getHttpServer())
.post('/b/devices/catalogs')
.set('Authorization', `Bearer ${token}`)
.send({
modelCode: uniqueSeedValue('catalog-role').toUpperCase(),
manufacturer: 'Role Matrix Vendor',
name: '角色矩阵目录',
isPressureAdjustable: true,
pressureLevels: [50, 80],
}),
sendWithoutToken: async () =>
request(ctx.app.getHttpServer())
.post('/b/devices/catalogs')
.send({
modelCode: uniqueSeedValue('catalog-anon').toUpperCase(),
manufacturer: 'Anon Vendor',
name: '匿名目录',
}),
});
});
});
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.currentPressure).toBe(0);
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'),
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,
});
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(0);
});
it('失败:设备实例接口不允许手工更新 currentPressure', 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({
currentPressure: 99,
});
expectErrorEnvelope(response, 400, 'currentPressure should not exist');
});
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);
});
});
});