2. 扩展患者手术与材料模型,更新种子数据 3. 新增字典模块,增强设备植入目录管理能力 4. 重构患者后台服务与表单链路,统一权限与参数校验 5. 管理台新增字典页面并改造患者/设备页面与路由权限 6. 补充字典及相关领域 e2e 测试并更新文档"
262 lines
9.0 KiB
TypeScript
262 lines
9.0 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'),
|
||
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);
|
||
});
|
||
});
|
||
});
|