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

342 lines
11 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 { Role, TaskStatus } 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,
} from '../helpers/e2e-http.helper.js';
describe('BTasksController (e2e)', () => {
let ctx: E2EContext;
beforeAll(async () => {
ctx = await createE2EContext();
});
afterAll(async () => {
await closeE2EContext(ctx);
});
async function publishPendingTask(deviceId: number, targetPressure: number) {
const response = await request(ctx.app.getHttpServer())
.post('/b/tasks/publish')
.set('Authorization', `Bearer ${ctx.tokens[Role.DOCTOR]}`)
.send({
items: [
{
deviceId,
targetPressure,
},
],
});
expectSuccessEnvelope(response, 201);
return response.body.data as { id: number; status: TaskStatus };
}
describe('POST /b/tasks/publish', () => {
it('成功DOCTOR 可发布任务', async () => {
const response = await request(ctx.app.getHttpServer())
.post('/b/tasks/publish')
.set('Authorization', `Bearer ${ctx.tokens[Role.DOCTOR]}`)
.send({
engineerId: ctx.fixtures.users.engineerAId,
items: [
{
deviceId: ctx.fixtures.devices.deviceA2Id,
targetPressure: 120,
},
],
});
expectSuccessEnvelope(response, 201);
expect(response.body.data.status).toBe(TaskStatus.PENDING);
});
it('失败:可调压设备使用非法挡位返回 400', async () => {
const response = await request(ctx.app.getHttpServer())
.post('/b/tasks/publish')
.set('Authorization', `Bearer ${ctx.tokens[Role.DOCTOR]}`)
.send({
items: [
{
deviceId: ctx.fixtures.devices.deviceA2Id,
targetPressure: 126,
},
],
});
expectErrorEnvelope(response, 400, '压力值不在该植入物配置的挡位范围内');
});
it('失败:发布跨院设备任务返回 404', async () => {
const response = await request(ctx.app.getHttpServer())
.post('/b/tasks/publish')
.set('Authorization', `Bearer ${ctx.tokens[Role.DOCTOR]}`)
.send({
items: [
{
deviceId: ctx.fixtures.devices.deviceB1Id,
targetPressure: 120,
},
],
});
expectErrorEnvelope(response, 404, '存在设备不在当前医院或设备不存在');
});
it('角色矩阵DOCTOR/DIRECTOR/LEADER 可进入业务,其余角色 403未登录 401', async () => {
await assertRoleMatrix({
name: 'POST /b/tasks/publish role matrix',
tokens: ctx.tokens,
expectedStatusByRole: {
[Role.SYSTEM_ADMIN]: 403,
[Role.HOSPITAL_ADMIN]: 403,
[Role.DIRECTOR]: 400,
[Role.LEADER]: 400,
[Role.DOCTOR]: 400,
[Role.ENGINEER]: 403,
},
sendAsRole: async (_role, token) =>
request(ctx.app.getHttpServer())
.post('/b/tasks/publish')
.set('Authorization', `Bearer ${token}`)
.send({}),
sendWithoutToken: async () =>
request(ctx.app.getHttpServer()).post('/b/tasks/publish').send({}),
});
});
});
describe('POST /b/tasks/accept', () => {
it('成功ENGINEER 可接收待处理任务', async () => {
const task = await publishPendingTask(
ctx.fixtures.devices.deviceA2Id,
140,
);
const response = await request(ctx.app.getHttpServer())
.post('/b/tasks/accept')
.set('Authorization', `Bearer ${ctx.tokens[Role.ENGINEER]}`)
.send({ taskId: task.id });
expectSuccessEnvelope(response, 201);
expect(response.body.data.status).toBe(TaskStatus.ACCEPTED);
expect(response.body.data.engineerId).toBe(
ctx.fixtures.users.engineerAId,
);
});
it('失败:接收不存在任务返回 404', async () => {
const response = await request(ctx.app.getHttpServer())
.post('/b/tasks/accept')
.set('Authorization', `Bearer ${ctx.tokens[Role.ENGINEER]}`)
.send({ taskId: 99999999 });
expectErrorEnvelope(response, 404, '任务不存在或不属于当前医院');
});
it('状态机失败:重复接收返回 409', async () => {
const task = await publishPendingTask(
ctx.fixtures.devices.deviceA3Id,
120,
);
const firstAccept = await request(ctx.app.getHttpServer())
.post('/b/tasks/accept')
.set('Authorization', `Bearer ${ctx.tokens[Role.ENGINEER]}`)
.send({ taskId: task.id });
expectSuccessEnvelope(firstAccept, 201);
const secondAccept = await request(ctx.app.getHttpServer())
.post('/b/tasks/accept')
.set('Authorization', `Bearer ${ctx.tokens[Role.ENGINEER]}`)
.send({ taskId: task.id });
expectErrorEnvelope(secondAccept, 409, '仅待接收任务可执行接收');
});
it('角色矩阵:仅 ENGINEER 可进入业务,其他角色 403未登录 401', async () => {
await assertRoleMatrix({
name: 'POST /b/tasks/accept role matrix',
tokens: ctx.tokens,
expectedStatusByRole: {
[Role.SYSTEM_ADMIN]: 403,
[Role.HOSPITAL_ADMIN]: 403,
[Role.DIRECTOR]: 403,
[Role.LEADER]: 403,
[Role.DOCTOR]: 403,
[Role.ENGINEER]: 404,
},
sendAsRole: async (_role, token) =>
request(ctx.app.getHttpServer())
.post('/b/tasks/accept')
.set('Authorization', `Bearer ${token}`)
.send({ taskId: 99999999 }),
sendWithoutToken: async () =>
request(ctx.app.getHttpServer())
.post('/b/tasks/accept')
.send({ taskId: 99999999 }),
});
});
});
describe('POST /b/tasks/complete', () => {
it('成功ENGINEER 完成已接收任务并同步设备压力', async () => {
const targetPressure = 140;
const task = await publishPendingTask(
ctx.fixtures.devices.deviceA1Id,
targetPressure,
);
const acceptResponse = await request(ctx.app.getHttpServer())
.post('/b/tasks/accept')
.set('Authorization', `Bearer ${ctx.tokens[Role.ENGINEER]}`)
.send({ taskId: task.id });
expectSuccessEnvelope(acceptResponse, 201);
const completeResponse = await request(ctx.app.getHttpServer())
.post('/b/tasks/complete')
.set('Authorization', `Bearer ${ctx.tokens[Role.ENGINEER]}`)
.send({ taskId: task.id });
expectSuccessEnvelope(completeResponse, 201);
expect(completeResponse.body.data.status).toBe(TaskStatus.COMPLETED);
const device = await ctx.prisma.device.findUnique({
where: { id: ctx.fixtures.devices.deviceA1Id },
select: { currentPressure: true },
});
expect(device?.currentPressure).toBe(targetPressure);
});
it('失败:完成不存在任务返回 404', async () => {
const response = await request(ctx.app.getHttpServer())
.post('/b/tasks/complete')
.set('Authorization', `Bearer ${ctx.tokens[Role.ENGINEER]}`)
.send({ taskId: 99999999 });
expectErrorEnvelope(response, 404, '任务不存在或不属于当前医院');
});
it('状态机失败:未接收任务直接完成返回 409', async () => {
const task = await publishPendingTask(
ctx.fixtures.devices.deviceA2Id,
100,
);
const response = await request(ctx.app.getHttpServer())
.post('/b/tasks/complete')
.set('Authorization', `Bearer ${ctx.tokens[Role.ENGINEER]}`)
.send({ taskId: task.id });
expectErrorEnvelope(response, 409, '仅已接收任务可执行完成');
});
it('角色矩阵:仅 ENGINEER 可进入业务,其他角色 403未登录 401', async () => {
await assertRoleMatrix({
name: 'POST /b/tasks/complete role matrix',
tokens: ctx.tokens,
expectedStatusByRole: {
[Role.SYSTEM_ADMIN]: 403,
[Role.HOSPITAL_ADMIN]: 403,
[Role.DIRECTOR]: 403,
[Role.LEADER]: 403,
[Role.DOCTOR]: 403,
[Role.ENGINEER]: 404,
},
sendAsRole: async (_role, token) =>
request(ctx.app.getHttpServer())
.post('/b/tasks/complete')
.set('Authorization', `Bearer ${token}`)
.send({ taskId: 99999999 }),
sendWithoutToken: async () =>
request(ctx.app.getHttpServer())
.post('/b/tasks/complete')
.send({ taskId: 99999999 }),
});
});
});
describe('POST /b/tasks/cancel', () => {
it('成功DOCTOR 可取消自己创建的任务', async () => {
const task = await publishPendingTask(
ctx.fixtures.devices.deviceA3Id,
120,
);
const response = await request(ctx.app.getHttpServer())
.post('/b/tasks/cancel')
.set('Authorization', `Bearer ${ctx.tokens[Role.DOCTOR]}`)
.send({ taskId: task.id });
expectSuccessEnvelope(response, 201);
expect(response.body.data.status).toBe(TaskStatus.CANCELLED);
});
it('失败:取消不存在任务返回 404', async () => {
const response = await request(ctx.app.getHttpServer())
.post('/b/tasks/cancel')
.set('Authorization', `Bearer ${ctx.tokens[Role.DOCTOR]}`)
.send({ taskId: 99999999 });
expectErrorEnvelope(response, 404, '任务不存在或不属于当前医院');
});
it('状态机失败:已完成任务不可取消返回 409', async () => {
const task = await publishPendingTask(
ctx.fixtures.devices.deviceA2Id,
160,
);
const acceptResponse = await request(ctx.app.getHttpServer())
.post('/b/tasks/accept')
.set('Authorization', `Bearer ${ctx.tokens[Role.ENGINEER]}`)
.send({ taskId: task.id });
expectSuccessEnvelope(acceptResponse, 201);
const completeResponse = await request(ctx.app.getHttpServer())
.post('/b/tasks/complete')
.set('Authorization', `Bearer ${ctx.tokens[Role.ENGINEER]}`)
.send({ taskId: task.id });
expectSuccessEnvelope(completeResponse, 201);
const cancelResponse = await request(ctx.app.getHttpServer())
.post('/b/tasks/cancel')
.set('Authorization', `Bearer ${ctx.tokens[Role.DOCTOR]}`)
.send({ taskId: task.id });
expectErrorEnvelope(cancelResponse, 409, '仅待接收/已接收任务可取消');
});
it('角色矩阵DOCTOR/DIRECTOR/LEADER 可进入业务,其余角色 403未登录 401', async () => {
await assertRoleMatrix({
name: 'POST /b/tasks/cancel role matrix',
tokens: ctx.tokens,
expectedStatusByRole: {
[Role.SYSTEM_ADMIN]: 403,
[Role.HOSPITAL_ADMIN]: 403,
[Role.DIRECTOR]: 404,
[Role.LEADER]: 404,
[Role.DOCTOR]: 404,
[Role.ENGINEER]: 403,
},
sendAsRole: async (_role, token) =>
request(ctx.app.getHttpServer())
.post('/b/tasks/cancel')
.set('Authorization', `Bearer ${token}`)
.send({ taskId: 99999999 }),
sendWithoutToken: async () =>
request(ctx.app.getHttpServer())
.post('/b/tasks/cancel')
.send({ taskId: 99999999 }),
});
});
});
});