tyt-api-nest/test/e2e/specs/organization.e2e-spec.ts
EL 6ec2d0b0e0 新增 B 端设备模块(后端 CRUD、分页筛选、权限隔离)并接入前端设备管理页面与路由菜单
鉴权改为登录态回库校验,新增 tokenValidAfter 失效时间,支持密码变更与 seed 重置后旧 token 立即失效
患者字段由 idCardHash 统一迁移为 idCard,新增身份证标准化逻辑并同步 C 端生命周期查询参数
组织模块增加小组删除限制(有成员时返回 409)并补充中文错误消息
任务取消接口支持可选 reason 字段(先透传事件层)
补齐 Prisma 迁移、文档说明和 E2E 用例(含设备模块与 token 失效场景)
2026-03-18 20:23:55 +08:00

738 lines
29 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 } 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 可访问,其余角色 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]: 403,
[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 可访问,其余角色 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]: 403,
[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/DIRECTOR/LEADER 可进入业务,其余角色 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]: 404,
[Role.LEADER]: 404,
[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/DIRECTOR 可进入业务,其余角色 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]: 400,
[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 可访问,其余角色 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]: 403,
[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 可访问,其余角色 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]: 403,
[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/DIRECTOR/LEADER 可进入业务,其余角色 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]: 404,
[Role.LEADER]: 404,
[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/DIRECTOR 可进入业务,其余角色 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]: 404,
[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',
),
});
});
});
});
});