新增 B 端上传接口与列表接口,统一文件上传和分页查询能力 上传能力支持医院级数据隔离:系统管理员需显式指定医院,院内角色按登录医院自动隔离 图片上传自动压缩并转为 webp,视频上传自动转码并压缩为 mp4,普通文件按原始类型存储 增加上传目录与公开访问能力,统一输出可直接预览的访问地址 前端新增影像库页面,支持按类型筛选、关键字检索、分页浏览、在线预览与原文件访问 前端新增通用上传组件,支持在页面内复用并返回上传结果 管理后台新增影像库菜单与路由,并补充页面级角色权限控制 患者手术相关表单接入上传复用能力,支持术前资料与设备标签上传回填 新增上传模块 e2e 用例,覆盖成功路径、权限矩阵与关键失败场景 补充上传模块文档与安装依赖说明,完善工程内使用说明
738 lines
29 KiB
TypeScript
738 lines
29 KiB
TypeScript
import request from 'supertest';
|
||
import { 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('Organization Controllers (e2e)', () => {
|
||
let ctx: E2EContext;
|
||
|
||
beforeAll(async () => {
|
||
ctx = await createE2EContext();
|
||
});
|
||
|
||
afterAll(async () => {
|
||
await closeE2EContext(ctx);
|
||
});
|
||
|
||
describe('HospitalsController', () => {
|
||
describe('POST /b/organization/hospitals', () => {
|
||
it('成功:SYSTEM_ADMIN 可创建医院', async () => {
|
||
const response = await request(ctx.app.getHttpServer())
|
||
.post('/b/organization/hospitals')
|
||
.set('Authorization', `Bearer ${ctx.tokens[Role.SYSTEM_ADMIN]}`)
|
||
.send({ name: uniqueSeedValue('组织-医院') });
|
||
|
||
expectSuccessEnvelope(response, 201);
|
||
expect(response.body.data.name).toContain('组织-医院');
|
||
});
|
||
|
||
it('失败:非系统管理员创建返回 403', async () => {
|
||
const response = await request(ctx.app.getHttpServer())
|
||
.post('/b/organization/hospitals')
|
||
.set('Authorization', `Bearer ${ctx.tokens[Role.HOSPITAL_ADMIN]}`)
|
||
.send({ name: uniqueSeedValue('组织-医院-失败') });
|
||
|
||
expectErrorEnvelope(response, 403, '无权限执行当前操作');
|
||
});
|
||
|
||
it('角色矩阵:仅 SYSTEM_ADMIN 可进入业务,其他角色 403,未登录 401', async () => {
|
||
await assertRoleMatrix({
|
||
name: 'POST /b/organization/hospitals role matrix',
|
||
tokens: ctx.tokens,
|
||
expectedStatusByRole: {
|
||
[Role.SYSTEM_ADMIN]: 400,
|
||
[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/organization/hospitals')
|
||
.set('Authorization', `Bearer ${token}`)
|
||
.send({}),
|
||
sendWithoutToken: async () =>
|
||
request(ctx.app.getHttpServer())
|
||
.post('/b/organization/hospitals')
|
||
.send({}),
|
||
});
|
||
});
|
||
});
|
||
|
||
describe('GET /b/organization/hospitals', () => {
|
||
it('成功:SYSTEM_ADMIN 可查询医院列表', async () => {
|
||
const response = await request(ctx.app.getHttpServer())
|
||
.get('/b/organization/hospitals')
|
||
.set('Authorization', `Bearer ${ctx.tokens[Role.SYSTEM_ADMIN]}`);
|
||
|
||
expectSuccessEnvelope(response, 200);
|
||
expect(response.body.data).toHaveProperty('list');
|
||
});
|
||
|
||
it('失败:未登录返回 401', async () => {
|
||
const response = await request(ctx.app.getHttpServer()).get(
|
||
'/b/organization/hospitals',
|
||
);
|
||
expectErrorEnvelope(response, 401, '缺少 Bearer Token');
|
||
});
|
||
|
||
it('角色矩阵:SYSTEM_ADMIN/HOSPITAL_ADMIN/DIRECTOR/LEADER 可访问,其余角色 403,未登录 401', async () => {
|
||
await assertRoleMatrix({
|
||
name: 'GET /b/organization/hospitals role matrix',
|
||
tokens: ctx.tokens,
|
||
expectedStatusByRole: {
|
||
[Role.SYSTEM_ADMIN]: 200,
|
||
[Role.HOSPITAL_ADMIN]: 200,
|
||
[Role.DIRECTOR]: 200,
|
||
[Role.LEADER]: 200,
|
||
[Role.DOCTOR]: 403,
|
||
[Role.ENGINEER]: 403,
|
||
},
|
||
sendAsRole: async (_role, token) =>
|
||
request(ctx.app.getHttpServer())
|
||
.get('/b/organization/hospitals')
|
||
.set('Authorization', `Bearer ${token}`),
|
||
sendWithoutToken: async () =>
|
||
request(ctx.app.getHttpServer()).get('/b/organization/hospitals'),
|
||
});
|
||
});
|
||
});
|
||
|
||
describe('GET /b/organization/hospitals/:id', () => {
|
||
it('成功:HOSPITAL_ADMIN 可查询本院详情', async () => {
|
||
const response = await request(ctx.app.getHttpServer())
|
||
.get(`/b/organization/hospitals/${ctx.fixtures.hospitalAId}`)
|
||
.set('Authorization', `Bearer ${ctx.tokens[Role.HOSPITAL_ADMIN]}`);
|
||
|
||
expectSuccessEnvelope(response, 200);
|
||
expect(response.body.data.id).toBe(ctx.fixtures.hospitalAId);
|
||
});
|
||
|
||
it('失败:HOSPITAL_ADMIN 查询他院返回 403', async () => {
|
||
const response = await request(ctx.app.getHttpServer())
|
||
.get(`/b/organization/hospitals/${ctx.fixtures.hospitalBId}`)
|
||
.set('Authorization', `Bearer ${ctx.tokens[Role.HOSPITAL_ADMIN]}`);
|
||
|
||
expectErrorEnvelope(response, 403, '院管仅可操作本院组织数据');
|
||
});
|
||
|
||
it('角色矩阵:SYSTEM_ADMIN/HOSPITAL_ADMIN/DIRECTOR/LEADER 可访问,其余角色 403,未登录 401', async () => {
|
||
await assertRoleMatrix({
|
||
name: 'GET /b/organization/hospitals/:id role matrix',
|
||
tokens: ctx.tokens,
|
||
expectedStatusByRole: {
|
||
[Role.SYSTEM_ADMIN]: 200,
|
||
[Role.HOSPITAL_ADMIN]: 200,
|
||
[Role.DIRECTOR]: 200,
|
||
[Role.LEADER]: 200,
|
||
[Role.DOCTOR]: 403,
|
||
[Role.ENGINEER]: 403,
|
||
},
|
||
sendAsRole: async (_role, token) =>
|
||
request(ctx.app.getHttpServer())
|
||
.get(`/b/organization/hospitals/${ctx.fixtures.hospitalAId}`)
|
||
.set('Authorization', `Bearer ${token}`),
|
||
sendWithoutToken: async () =>
|
||
request(ctx.app.getHttpServer()).get(
|
||
`/b/organization/hospitals/${ctx.fixtures.hospitalAId}`,
|
||
),
|
||
});
|
||
});
|
||
});
|
||
|
||
describe('PATCH /b/organization/hospitals/:id', () => {
|
||
it('成功:HOSPITAL_ADMIN 可更新本院名称', async () => {
|
||
const originalName = 'Seed Hospital A';
|
||
const nextName = uniqueSeedValue('医院更新');
|
||
|
||
const response = await request(ctx.app.getHttpServer())
|
||
.patch(`/b/organization/hospitals/${ctx.fixtures.hospitalAId}`)
|
||
.set('Authorization', `Bearer ${ctx.tokens[Role.HOSPITAL_ADMIN]}`)
|
||
.send({ name: nextName });
|
||
|
||
expectSuccessEnvelope(response, 200);
|
||
|
||
const rollbackResponse = await request(ctx.app.getHttpServer())
|
||
.patch(`/b/organization/hospitals/${ctx.fixtures.hospitalAId}`)
|
||
.set('Authorization', `Bearer ${ctx.tokens[Role.HOSPITAL_ADMIN]}`)
|
||
.send({ name: originalName });
|
||
expectSuccessEnvelope(rollbackResponse, 200);
|
||
});
|
||
|
||
it('失败:HOSPITAL_ADMIN 更新他院返回 403', async () => {
|
||
const response = await request(ctx.app.getHttpServer())
|
||
.patch(`/b/organization/hospitals/${ctx.fixtures.hospitalBId}`)
|
||
.set('Authorization', `Bearer ${ctx.tokens[Role.HOSPITAL_ADMIN]}`)
|
||
.send({ name: uniqueSeedValue('跨院更新失败') });
|
||
|
||
expectErrorEnvelope(response, 403, '院管仅可操作本院组织数据');
|
||
});
|
||
|
||
it('角色矩阵:SYSTEM_ADMIN/HOSPITAL_ADMIN 可进入业务,其他角色 403,未登录 401', async () => {
|
||
await assertRoleMatrix({
|
||
name: 'PATCH /b/organization/hospitals/:id role matrix',
|
||
tokens: ctx.tokens,
|
||
expectedStatusByRole: {
|
||
[Role.SYSTEM_ADMIN]: 404,
|
||
[Role.HOSPITAL_ADMIN]: 404,
|
||
[Role.DIRECTOR]: 403,
|
||
[Role.LEADER]: 403,
|
||
[Role.DOCTOR]: 403,
|
||
[Role.ENGINEER]: 403,
|
||
},
|
||
sendAsRole: async (_role, token) =>
|
||
request(ctx.app.getHttpServer())
|
||
.patch('/b/organization/hospitals/99999999')
|
||
.set('Authorization', `Bearer ${token}`)
|
||
.send({ name: 'matrix-hospital-patch' }),
|
||
sendWithoutToken: async () =>
|
||
request(ctx.app.getHttpServer())
|
||
.patch('/b/organization/hospitals/99999999')
|
||
.send({ name: 'matrix-hospital-patch' }),
|
||
});
|
||
});
|
||
});
|
||
|
||
describe('DELETE /b/organization/hospitals/:id', () => {
|
||
it('成功:SYSTEM_ADMIN 可删除空医院', async () => {
|
||
const createResponse = await request(ctx.app.getHttpServer())
|
||
.post('/b/organization/hospitals')
|
||
.set('Authorization', `Bearer ${ctx.tokens[Role.SYSTEM_ADMIN]}`)
|
||
.send({ name: uniqueSeedValue('医院待删') });
|
||
expectSuccessEnvelope(createResponse, 201);
|
||
|
||
const targetId = createResponse.body.data.id as number;
|
||
const deleteResponse = await request(ctx.app.getHttpServer())
|
||
.delete(`/b/organization/hospitals/${targetId}`)
|
||
.set('Authorization', `Bearer ${ctx.tokens[Role.SYSTEM_ADMIN]}`);
|
||
|
||
expectSuccessEnvelope(deleteResponse, 200);
|
||
expect(deleteResponse.body.data.id).toBe(targetId);
|
||
});
|
||
|
||
it('失败:HOSPITAL_ADMIN 删除医院返回 403', async () => {
|
||
const response = await request(ctx.app.getHttpServer())
|
||
.delete(`/b/organization/hospitals/${ctx.fixtures.hospitalAId}`)
|
||
.set('Authorization', `Bearer ${ctx.tokens[Role.HOSPITAL_ADMIN]}`);
|
||
|
||
expectErrorEnvelope(response, 403, '无权限执行当前操作');
|
||
});
|
||
|
||
it('角色矩阵:仅 SYSTEM_ADMIN 可进入业务,其他角色 403,未登录 401', async () => {
|
||
await assertRoleMatrix({
|
||
name: 'DELETE /b/organization/hospitals/:id role matrix',
|
||
tokens: ctx.tokens,
|
||
expectedStatusByRole: {
|
||
[Role.SYSTEM_ADMIN]: 404,
|
||
[Role.HOSPITAL_ADMIN]: 403,
|
||
[Role.DIRECTOR]: 403,
|
||
[Role.LEADER]: 403,
|
||
[Role.DOCTOR]: 403,
|
||
[Role.ENGINEER]: 403,
|
||
},
|
||
sendAsRole: async (_role, token) =>
|
||
request(ctx.app.getHttpServer())
|
||
.delete('/b/organization/hospitals/99999999')
|
||
.set('Authorization', `Bearer ${token}`),
|
||
sendWithoutToken: async () =>
|
||
request(ctx.app.getHttpServer()).delete(
|
||
'/b/organization/hospitals/99999999',
|
||
),
|
||
});
|
||
});
|
||
});
|
||
});
|
||
|
||
describe('DepartmentsController', () => {
|
||
describe('POST /b/organization/departments', () => {
|
||
it('成功:HOSPITAL_ADMIN 可在本院创建科室', async () => {
|
||
const response = await request(ctx.app.getHttpServer())
|
||
.post('/b/organization/departments')
|
||
.set('Authorization', `Bearer ${ctx.tokens[Role.HOSPITAL_ADMIN]}`)
|
||
.send({
|
||
name: uniqueSeedValue('组织-科室'),
|
||
hospitalId: ctx.fixtures.hospitalAId,
|
||
});
|
||
|
||
expectSuccessEnvelope(response, 201);
|
||
});
|
||
|
||
it('失败:HOSPITAL_ADMIN 跨院创建返回 403', async () => {
|
||
const response = await request(ctx.app.getHttpServer())
|
||
.post('/b/organization/departments')
|
||
.set('Authorization', `Bearer ${ctx.tokens[Role.HOSPITAL_ADMIN]}`)
|
||
.send({
|
||
name: uniqueSeedValue('组织-跨院科室失败'),
|
||
hospitalId: ctx.fixtures.hospitalBId,
|
||
});
|
||
|
||
expectErrorEnvelope(response, 403, '院管仅可操作本院组织数据');
|
||
});
|
||
|
||
it('角色矩阵:SYSTEM_ADMIN/HOSPITAL_ADMIN 可进入业务,其他角色 403,未登录 401', async () => {
|
||
await assertRoleMatrix({
|
||
name: 'POST /b/organization/departments role matrix',
|
||
tokens: ctx.tokens,
|
||
expectedStatusByRole: {
|
||
[Role.SYSTEM_ADMIN]: 400,
|
||
[Role.HOSPITAL_ADMIN]: 400,
|
||
[Role.DIRECTOR]: 403,
|
||
[Role.LEADER]: 403,
|
||
[Role.DOCTOR]: 403,
|
||
[Role.ENGINEER]: 403,
|
||
},
|
||
sendAsRole: async (_role, token) =>
|
||
request(ctx.app.getHttpServer())
|
||
.post('/b/organization/departments')
|
||
.set('Authorization', `Bearer ${token}`)
|
||
.send({}),
|
||
sendWithoutToken: async () =>
|
||
request(ctx.app.getHttpServer())
|
||
.post('/b/organization/departments')
|
||
.send({}),
|
||
});
|
||
});
|
||
});
|
||
|
||
describe('GET /b/organization/departments', () => {
|
||
it('成功:HOSPITAL_ADMIN 可查询本院科室列表', async () => {
|
||
const response = await request(ctx.app.getHttpServer())
|
||
.get('/b/organization/departments')
|
||
.set('Authorization', `Bearer ${ctx.tokens[Role.HOSPITAL_ADMIN]}`);
|
||
|
||
expectSuccessEnvelope(response, 200);
|
||
expect(response.body.data).toHaveProperty('list');
|
||
});
|
||
|
||
it('失败:未登录返回 401', async () => {
|
||
const response = await request(ctx.app.getHttpServer()).get(
|
||
'/b/organization/departments',
|
||
);
|
||
expectErrorEnvelope(response, 401, '缺少 Bearer Token');
|
||
});
|
||
|
||
it('角色矩阵:SYSTEM_ADMIN/HOSPITAL_ADMIN/DIRECTOR/LEADER/DOCTOR 可访问,其余角色 403,未登录 401', async () => {
|
||
await assertRoleMatrix({
|
||
name: 'GET /b/organization/departments role matrix',
|
||
tokens: ctx.tokens,
|
||
expectedStatusByRole: {
|
||
[Role.SYSTEM_ADMIN]: 200,
|
||
[Role.HOSPITAL_ADMIN]: 200,
|
||
[Role.DIRECTOR]: 200,
|
||
[Role.LEADER]: 200,
|
||
[Role.DOCTOR]: 200,
|
||
[Role.ENGINEER]: 403,
|
||
},
|
||
sendAsRole: async (_role, token) =>
|
||
request(ctx.app.getHttpServer())
|
||
.get('/b/organization/departments')
|
||
.set('Authorization', `Bearer ${token}`),
|
||
sendWithoutToken: async () =>
|
||
request(ctx.app.getHttpServer()).get('/b/organization/departments'),
|
||
});
|
||
});
|
||
});
|
||
|
||
describe('GET /b/organization/departments/:id', () => {
|
||
it('成功:SYSTEM_ADMIN 可查询科室详情', async () => {
|
||
const response = await request(ctx.app.getHttpServer())
|
||
.get(`/b/organization/departments/${ctx.fixtures.departmentA1Id}`)
|
||
.set('Authorization', `Bearer ${ctx.tokens[Role.SYSTEM_ADMIN]}`);
|
||
|
||
expectSuccessEnvelope(response, 200);
|
||
expect(response.body.data.id).toBe(ctx.fixtures.departmentA1Id);
|
||
});
|
||
|
||
it('失败:HOSPITAL_ADMIN 查询他院科室返回 403', async () => {
|
||
const response = await request(ctx.app.getHttpServer())
|
||
.get(`/b/organization/departments/${ctx.fixtures.departmentB1Id}`)
|
||
.set('Authorization', `Bearer ${ctx.tokens[Role.HOSPITAL_ADMIN]}`);
|
||
|
||
expectErrorEnvelope(response, 403, '院管仅可操作本院组织数据');
|
||
});
|
||
|
||
it('角色矩阵:SYSTEM_ADMIN/HOSPITAL_ADMIN/DIRECTOR/LEADER/DOCTOR 可访问,其余角色 403,未登录 401', async () => {
|
||
await assertRoleMatrix({
|
||
name: 'GET /b/organization/departments/:id role matrix',
|
||
tokens: ctx.tokens,
|
||
expectedStatusByRole: {
|
||
[Role.SYSTEM_ADMIN]: 200,
|
||
[Role.HOSPITAL_ADMIN]: 200,
|
||
[Role.DIRECTOR]: 200,
|
||
[Role.LEADER]: 200,
|
||
[Role.DOCTOR]: 200,
|
||
[Role.ENGINEER]: 403,
|
||
},
|
||
sendAsRole: async (_role, token) =>
|
||
request(ctx.app.getHttpServer())
|
||
.get(`/b/organization/departments/${ctx.fixtures.departmentA1Id}`)
|
||
.set('Authorization', `Bearer ${token}`),
|
||
sendWithoutToken: async () =>
|
||
request(ctx.app.getHttpServer()).get(
|
||
`/b/organization/departments/${ctx.fixtures.departmentA1Id}`,
|
||
),
|
||
});
|
||
});
|
||
});
|
||
|
||
describe('PATCH /b/organization/departments/:id', () => {
|
||
it('成功:HOSPITAL_ADMIN 可更新本院科室', async () => {
|
||
const originalName = 'Cardiology-A2';
|
||
const nextName = uniqueSeedValue('科室更新');
|
||
|
||
const response = await request(ctx.app.getHttpServer())
|
||
.patch(`/b/organization/departments/${ctx.fixtures.departmentA2Id}`)
|
||
.set('Authorization', `Bearer ${ctx.tokens[Role.HOSPITAL_ADMIN]}`)
|
||
.send({ name: nextName });
|
||
|
||
expectSuccessEnvelope(response, 200);
|
||
|
||
const rollbackResponse = await request(ctx.app.getHttpServer())
|
||
.patch(`/b/organization/departments/${ctx.fixtures.departmentA2Id}`)
|
||
.set('Authorization', `Bearer ${ctx.tokens[Role.HOSPITAL_ADMIN]}`)
|
||
.send({ name: originalName });
|
||
expectSuccessEnvelope(rollbackResponse, 200);
|
||
});
|
||
|
||
it('失败:HOSPITAL_ADMIN 更新他院科室返回 403', async () => {
|
||
const response = await request(ctx.app.getHttpServer())
|
||
.patch(`/b/organization/departments/${ctx.fixtures.departmentB1Id}`)
|
||
.set('Authorization', `Bearer ${ctx.tokens[Role.HOSPITAL_ADMIN]}`)
|
||
.send({ name: uniqueSeedValue('跨院科室更新失败') });
|
||
|
||
expectErrorEnvelope(response, 403, '院管仅可操作本院组织数据');
|
||
});
|
||
|
||
it('角色矩阵:SYSTEM_ADMIN/HOSPITAL_ADMIN 可进入业务,其余角色 403,未登录 401', async () => {
|
||
await assertRoleMatrix({
|
||
name: 'PATCH /b/organization/departments/:id role matrix',
|
||
tokens: ctx.tokens,
|
||
expectedStatusByRole: {
|
||
[Role.SYSTEM_ADMIN]: 404,
|
||
[Role.HOSPITAL_ADMIN]: 404,
|
||
[Role.DIRECTOR]: 403,
|
||
[Role.LEADER]: 403,
|
||
[Role.DOCTOR]: 403,
|
||
[Role.ENGINEER]: 403,
|
||
},
|
||
sendAsRole: async (_role, token) =>
|
||
request(ctx.app.getHttpServer())
|
||
.patch('/b/organization/departments/99999999')
|
||
.set('Authorization', `Bearer ${token}`)
|
||
.send({ name: 'matrix-department-patch' }),
|
||
sendWithoutToken: async () =>
|
||
request(ctx.app.getHttpServer())
|
||
.patch('/b/organization/departments/99999999')
|
||
.send({ name: 'matrix-department-patch' }),
|
||
});
|
||
});
|
||
});
|
||
|
||
describe('DELETE /b/organization/departments/:id', () => {
|
||
it('成功:SYSTEM_ADMIN 可删除无关联科室', async () => {
|
||
const createResponse = await request(ctx.app.getHttpServer())
|
||
.post('/b/organization/departments')
|
||
.set('Authorization', `Bearer ${ctx.tokens[Role.SYSTEM_ADMIN]}`)
|
||
.send({
|
||
name: uniqueSeedValue('科室待删'),
|
||
hospitalId: ctx.fixtures.hospitalAId,
|
||
});
|
||
expectSuccessEnvelope(createResponse, 201);
|
||
|
||
const targetId = createResponse.body.data.id as number;
|
||
const deleteResponse = await request(ctx.app.getHttpServer())
|
||
.delete(`/b/organization/departments/${targetId}`)
|
||
.set('Authorization', `Bearer ${ctx.tokens[Role.SYSTEM_ADMIN]}`);
|
||
|
||
expectSuccessEnvelope(deleteResponse, 200);
|
||
});
|
||
|
||
it('失败:存在关联数据删除返回 409', async () => {
|
||
const response = await request(ctx.app.getHttpServer())
|
||
.delete(`/b/organization/departments/${ctx.fixtures.departmentA1Id}`)
|
||
.set('Authorization', `Bearer ${ctx.tokens[Role.SYSTEM_ADMIN]}`);
|
||
|
||
expectErrorEnvelope(response, 409, '存在关联数据,无法删除');
|
||
});
|
||
|
||
it('角色矩阵:SYSTEM_ADMIN/HOSPITAL_ADMIN 可进入业务,其他角色 403,未登录 401', async () => {
|
||
await assertRoleMatrix({
|
||
name: 'DELETE /b/organization/departments/:id role matrix',
|
||
tokens: ctx.tokens,
|
||
expectedStatusByRole: {
|
||
[Role.SYSTEM_ADMIN]: 404,
|
||
[Role.HOSPITAL_ADMIN]: 404,
|
||
[Role.DIRECTOR]: 403,
|
||
[Role.LEADER]: 403,
|
||
[Role.DOCTOR]: 403,
|
||
[Role.ENGINEER]: 403,
|
||
},
|
||
sendAsRole: async (_role, token) =>
|
||
request(ctx.app.getHttpServer())
|
||
.delete('/b/organization/departments/99999999')
|
||
.set('Authorization', `Bearer ${token}`),
|
||
sendWithoutToken: async () =>
|
||
request(ctx.app.getHttpServer()).delete(
|
||
'/b/organization/departments/99999999',
|
||
),
|
||
});
|
||
});
|
||
});
|
||
});
|
||
|
||
describe('GroupsController', () => {
|
||
describe('POST /b/organization/groups', () => {
|
||
it('成功:HOSPITAL_ADMIN 可创建小组', async () => {
|
||
const response = await request(ctx.app.getHttpServer())
|
||
.post('/b/organization/groups')
|
||
.set('Authorization', `Bearer ${ctx.tokens[Role.HOSPITAL_ADMIN]}`)
|
||
.send({
|
||
name: uniqueSeedValue('组织-小组'),
|
||
departmentId: ctx.fixtures.departmentA1Id,
|
||
});
|
||
|
||
expectSuccessEnvelope(response, 201);
|
||
});
|
||
|
||
it('失败:HOSPITAL_ADMIN 跨院创建小组返回 403', async () => {
|
||
const response = await request(ctx.app.getHttpServer())
|
||
.post('/b/organization/groups')
|
||
.set('Authorization', `Bearer ${ctx.tokens[Role.HOSPITAL_ADMIN]}`)
|
||
.send({
|
||
name: uniqueSeedValue('组织-跨院小组失败'),
|
||
departmentId: ctx.fixtures.departmentB1Id,
|
||
});
|
||
|
||
expectErrorEnvelope(response, 403, '院管仅可操作本院组织数据');
|
||
});
|
||
|
||
it('角色矩阵:SYSTEM_ADMIN/HOSPITAL_ADMIN 可进入业务,其余角色 403,未登录 401', async () => {
|
||
await assertRoleMatrix({
|
||
name: 'POST /b/organization/groups role matrix',
|
||
tokens: ctx.tokens,
|
||
expectedStatusByRole: {
|
||
[Role.SYSTEM_ADMIN]: 400,
|
||
[Role.HOSPITAL_ADMIN]: 400,
|
||
[Role.DIRECTOR]: 403,
|
||
[Role.LEADER]: 403,
|
||
[Role.DOCTOR]: 403,
|
||
[Role.ENGINEER]: 403,
|
||
},
|
||
sendAsRole: async (_role, token) =>
|
||
request(ctx.app.getHttpServer())
|
||
.post('/b/organization/groups')
|
||
.set('Authorization', `Bearer ${token}`)
|
||
.send({}),
|
||
sendWithoutToken: async () =>
|
||
request(ctx.app.getHttpServer())
|
||
.post('/b/organization/groups')
|
||
.send({}),
|
||
});
|
||
});
|
||
});
|
||
|
||
describe('GET /b/organization/groups', () => {
|
||
it('成功:SYSTEM_ADMIN 可查询小组列表', async () => {
|
||
const response = await request(ctx.app.getHttpServer())
|
||
.get('/b/organization/groups')
|
||
.set('Authorization', `Bearer ${ctx.tokens[Role.SYSTEM_ADMIN]}`);
|
||
|
||
expectSuccessEnvelope(response, 200);
|
||
expect(response.body.data).toHaveProperty('list');
|
||
});
|
||
|
||
it('失败:未登录返回 401', async () => {
|
||
const response = await request(ctx.app.getHttpServer()).get(
|
||
'/b/organization/groups',
|
||
);
|
||
expectErrorEnvelope(response, 401, '缺少 Bearer Token');
|
||
});
|
||
|
||
it('角色矩阵:SYSTEM_ADMIN/HOSPITAL_ADMIN/DIRECTOR/LEADER/DOCTOR 可访问,其余角色 403,未登录 401', async () => {
|
||
await assertRoleMatrix({
|
||
name: 'GET /b/organization/groups role matrix',
|
||
tokens: ctx.tokens,
|
||
expectedStatusByRole: {
|
||
[Role.SYSTEM_ADMIN]: 200,
|
||
[Role.HOSPITAL_ADMIN]: 200,
|
||
[Role.DIRECTOR]: 200,
|
||
[Role.LEADER]: 200,
|
||
[Role.DOCTOR]: 200,
|
||
[Role.ENGINEER]: 403,
|
||
},
|
||
sendAsRole: async (_role, token) =>
|
||
request(ctx.app.getHttpServer())
|
||
.get('/b/organization/groups')
|
||
.set('Authorization', `Bearer ${token}`),
|
||
sendWithoutToken: async () =>
|
||
request(ctx.app.getHttpServer()).get('/b/organization/groups'),
|
||
});
|
||
});
|
||
});
|
||
|
||
describe('GET /b/organization/groups/:id', () => {
|
||
it('成功:HOSPITAL_ADMIN 可查询本院小组详情', async () => {
|
||
const response = await request(ctx.app.getHttpServer())
|
||
.get(`/b/organization/groups/${ctx.fixtures.groupA1Id}`)
|
||
.set('Authorization', `Bearer ${ctx.tokens[Role.HOSPITAL_ADMIN]}`);
|
||
|
||
expectSuccessEnvelope(response, 200);
|
||
expect(response.body.data.id).toBe(ctx.fixtures.groupA1Id);
|
||
});
|
||
|
||
it('失败:HOSPITAL_ADMIN 查询他院小组返回 403', async () => {
|
||
const response = await request(ctx.app.getHttpServer())
|
||
.get(`/b/organization/groups/${ctx.fixtures.groupB1Id}`)
|
||
.set('Authorization', `Bearer ${ctx.tokens[Role.HOSPITAL_ADMIN]}`);
|
||
|
||
expectErrorEnvelope(response, 403, '院管仅可操作本院组织数据');
|
||
});
|
||
|
||
it('角色矩阵:SYSTEM_ADMIN/HOSPITAL_ADMIN/DIRECTOR/LEADER/DOCTOR 可访问,其余角色 403,未登录 401', async () => {
|
||
await assertRoleMatrix({
|
||
name: 'GET /b/organization/groups/:id role matrix',
|
||
tokens: ctx.tokens,
|
||
expectedStatusByRole: {
|
||
[Role.SYSTEM_ADMIN]: 200,
|
||
[Role.HOSPITAL_ADMIN]: 200,
|
||
[Role.DIRECTOR]: 200,
|
||
[Role.LEADER]: 200,
|
||
[Role.DOCTOR]: 200,
|
||
[Role.ENGINEER]: 403,
|
||
},
|
||
sendAsRole: async (_role, token) =>
|
||
request(ctx.app.getHttpServer())
|
||
.get(`/b/organization/groups/${ctx.fixtures.groupA1Id}`)
|
||
.set('Authorization', `Bearer ${token}`),
|
||
sendWithoutToken: async () =>
|
||
request(ctx.app.getHttpServer()).get(
|
||
`/b/organization/groups/${ctx.fixtures.groupA1Id}`,
|
||
),
|
||
});
|
||
});
|
||
});
|
||
|
||
describe('PATCH /b/organization/groups/:id', () => {
|
||
it('成功:HOSPITAL_ADMIN 可更新本院小组', async () => {
|
||
const originalName = 'Shift-A2';
|
||
const nextName = uniqueSeedValue('小组更新');
|
||
|
||
const response = await request(ctx.app.getHttpServer())
|
||
.patch(`/b/organization/groups/${ctx.fixtures.groupA2Id}`)
|
||
.set('Authorization', `Bearer ${ctx.tokens[Role.HOSPITAL_ADMIN]}`)
|
||
.send({ name: nextName });
|
||
|
||
expectSuccessEnvelope(response, 200);
|
||
|
||
const rollbackResponse = await request(ctx.app.getHttpServer())
|
||
.patch(`/b/organization/groups/${ctx.fixtures.groupA2Id}`)
|
||
.set('Authorization', `Bearer ${ctx.tokens[Role.HOSPITAL_ADMIN]}`)
|
||
.send({ name: originalName });
|
||
expectSuccessEnvelope(rollbackResponse, 200);
|
||
});
|
||
|
||
it('失败:HOSPITAL_ADMIN 更新他院小组返回 403', async () => {
|
||
const response = await request(ctx.app.getHttpServer())
|
||
.patch(`/b/organization/groups/${ctx.fixtures.groupB1Id}`)
|
||
.set('Authorization', `Bearer ${ctx.tokens[Role.HOSPITAL_ADMIN]}`)
|
||
.send({ name: uniqueSeedValue('跨院小组更新失败') });
|
||
|
||
expectErrorEnvelope(response, 403, '院管仅可操作本院组织数据');
|
||
});
|
||
|
||
it('角色矩阵:SYSTEM_ADMIN/HOSPITAL_ADMIN 可进入业务,其余角色 403,未登录 401', async () => {
|
||
await assertRoleMatrix({
|
||
name: 'PATCH /b/organization/groups/:id role matrix',
|
||
tokens: ctx.tokens,
|
||
expectedStatusByRole: {
|
||
[Role.SYSTEM_ADMIN]: 404,
|
||
[Role.HOSPITAL_ADMIN]: 404,
|
||
[Role.DIRECTOR]: 403,
|
||
[Role.LEADER]: 403,
|
||
[Role.DOCTOR]: 403,
|
||
[Role.ENGINEER]: 403,
|
||
},
|
||
sendAsRole: async (_role, token) =>
|
||
request(ctx.app.getHttpServer())
|
||
.patch('/b/organization/groups/99999999')
|
||
.set('Authorization', `Bearer ${token}`)
|
||
.send({ name: 'matrix-group-patch' }),
|
||
sendWithoutToken: async () =>
|
||
request(ctx.app.getHttpServer())
|
||
.patch('/b/organization/groups/99999999')
|
||
.send({ name: 'matrix-group-patch' }),
|
||
});
|
||
});
|
||
});
|
||
|
||
describe('DELETE /b/organization/groups/:id', () => {
|
||
it('成功:SYSTEM_ADMIN 可删除无关联小组', async () => {
|
||
const createResponse = await request(ctx.app.getHttpServer())
|
||
.post('/b/organization/groups')
|
||
.set('Authorization', `Bearer ${ctx.tokens[Role.SYSTEM_ADMIN]}`)
|
||
.send({
|
||
name: uniqueSeedValue('小组待删'),
|
||
departmentId: ctx.fixtures.departmentA1Id,
|
||
});
|
||
expectSuccessEnvelope(createResponse, 201);
|
||
|
||
const targetId = createResponse.body.data.id as number;
|
||
const deleteResponse = await request(ctx.app.getHttpServer())
|
||
.delete(`/b/organization/groups/${targetId}`)
|
||
.set('Authorization', `Bearer ${ctx.tokens[Role.SYSTEM_ADMIN]}`);
|
||
|
||
expectSuccessEnvelope(deleteResponse, 200);
|
||
});
|
||
|
||
it('失败:HOSPITAL_ADMIN 删除他院小组返回 403', async () => {
|
||
const response = await request(ctx.app.getHttpServer())
|
||
.delete(`/b/organization/groups/${ctx.fixtures.groupB1Id}`)
|
||
.set('Authorization', `Bearer ${ctx.tokens[Role.HOSPITAL_ADMIN]}`);
|
||
|
||
expectErrorEnvelope(response, 403, '院管仅可操作本院组织数据');
|
||
});
|
||
|
||
it('失败:删除有成员的小组返回 409', async () => {
|
||
const response = await request(ctx.app.getHttpServer())
|
||
.delete(`/b/organization/groups/${ctx.fixtures.groupA1Id}`)
|
||
.set('Authorization', `Bearer ${ctx.tokens[Role.SYSTEM_ADMIN]}`);
|
||
|
||
expectErrorEnvelope(response, 409, '小组下仍有成员,无法删除');
|
||
});
|
||
|
||
it('角色矩阵:SYSTEM_ADMIN/HOSPITAL_ADMIN 可进入业务,其余角色 403,未登录 401', async () => {
|
||
await assertRoleMatrix({
|
||
name: 'DELETE /b/organization/groups/:id role matrix',
|
||
tokens: ctx.tokens,
|
||
expectedStatusByRole: {
|
||
[Role.SYSTEM_ADMIN]: 404,
|
||
[Role.HOSPITAL_ADMIN]: 404,
|
||
[Role.DIRECTOR]: 403,
|
||
[Role.LEADER]: 403,
|
||
[Role.DOCTOR]: 403,
|
||
[Role.ENGINEER]: 403,
|
||
},
|
||
sendAsRole: async (_role, token) =>
|
||
request(ctx.app.getHttpServer())
|
||
.delete('/b/organization/groups/99999999')
|
||
.set('Authorization', `Bearer ${token}`),
|
||
sendWithoutToken: async () =>
|
||
request(ctx.app.getHttpServer()).delete(
|
||
'/b/organization/groups/99999999',
|
||
),
|
||
});
|
||
});
|
||
});
|
||
});
|
||
});
|