341 lines
12 KiB
TypeScript
341 lines
12 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,
|
||
uniquePhone,
|
||
uniqueSeedValue,
|
||
} from '../helpers/e2e-http.helper.js';
|
||
|
||
describe('UsersController + BUsersController (e2e)', () => {
|
||
let ctx: E2EContext;
|
||
|
||
beforeAll(async () => {
|
||
ctx = await createE2EContext();
|
||
});
|
||
|
||
afterAll(async () => {
|
||
await closeE2EContext(ctx);
|
||
});
|
||
|
||
async function createDoctorUser(token: string) {
|
||
const response = await request(ctx.app.getHttpServer())
|
||
.post('/users')
|
||
.set('Authorization', `Bearer ${token}`)
|
||
.send({
|
||
name: uniqueSeedValue('用户-医生'),
|
||
phone: uniquePhone(),
|
||
password: 'Seed@1234',
|
||
role: Role.DOCTOR,
|
||
hospitalId: ctx.fixtures.hospitalAId,
|
||
departmentId: ctx.fixtures.departmentA1Id,
|
||
groupId: ctx.fixtures.groupA1Id,
|
||
openId: uniqueSeedValue('users-doctor-openid'),
|
||
});
|
||
|
||
expectSuccessEnvelope(response, 201);
|
||
return response.body.data as { id: number; name: string };
|
||
}
|
||
|
||
async function createEngineerUser(token: string) {
|
||
const response = await request(ctx.app.getHttpServer())
|
||
.post('/users')
|
||
.set('Authorization', `Bearer ${token}`)
|
||
.send({
|
||
name: uniqueSeedValue('用户-工程师'),
|
||
phone: uniquePhone(),
|
||
password: 'Seed@1234',
|
||
role: Role.ENGINEER,
|
||
hospitalId: ctx.fixtures.hospitalAId,
|
||
openId: uniqueSeedValue('users-engineer-openid'),
|
||
});
|
||
|
||
expectSuccessEnvelope(response, 201);
|
||
return response.body.data as { id: number; name: string };
|
||
}
|
||
|
||
describe('POST /users', () => {
|
||
it('成功:SYSTEM_ADMIN 可创建用户', async () => {
|
||
await createDoctorUser(ctx.tokens[Role.SYSTEM_ADMIN]);
|
||
});
|
||
|
||
it('失败:参数校验失败返回 400', async () => {
|
||
const response = await request(ctx.app.getHttpServer())
|
||
.post('/users')
|
||
.set('Authorization', `Bearer ${ctx.tokens[Role.SYSTEM_ADMIN]}`)
|
||
.send({
|
||
name: 'bad-user',
|
||
phone: '123',
|
||
password: 'short',
|
||
role: Role.DOCTOR,
|
||
hospitalId: ctx.fixtures.hospitalAId,
|
||
departmentId: ctx.fixtures.departmentA1Id,
|
||
groupId: ctx.fixtures.groupA1Id,
|
||
});
|
||
|
||
expectErrorEnvelope(response, 400, 'phone 必须是合法手机号');
|
||
});
|
||
|
||
it('角色矩阵:SYSTEM_ADMIN/HOSPITAL_ADMIN 可进入业务,其他角色 403,未登录 401', async () => {
|
||
await assertRoleMatrix({
|
||
name: 'POST /users 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('/users')
|
||
.set('Authorization', `Bearer ${token}`)
|
||
.send({}),
|
||
sendWithoutToken: async () =>
|
||
request(ctx.app.getHttpServer()).post('/users').send({}),
|
||
});
|
||
});
|
||
});
|
||
|
||
describe('GET /users', () => {
|
||
it('成功:SYSTEM_ADMIN 可查询用户列表', async () => {
|
||
const response = await request(ctx.app.getHttpServer())
|
||
.get('/users')
|
||
.set('Authorization', `Bearer ${ctx.tokens[Role.SYSTEM_ADMIN]}`);
|
||
|
||
expectSuccessEnvelope(response, 200);
|
||
expect(Array.isArray(response.body.data)).toBe(true);
|
||
});
|
||
|
||
it('失败:未登录返回 401', async () => {
|
||
const response = await request(ctx.app.getHttpServer()).get('/users');
|
||
expectErrorEnvelope(response, 401, '缺少 Bearer Token');
|
||
});
|
||
|
||
it('角色矩阵:SYSTEM_ADMIN/HOSPITAL_ADMIN 可访问,其他角色 403,未登录 401', async () => {
|
||
await assertRoleMatrix({
|
||
name: 'GET /users 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('/users')
|
||
.set('Authorization', `Bearer ${token}`),
|
||
sendWithoutToken: async () =>
|
||
request(ctx.app.getHttpServer()).get('/users'),
|
||
});
|
||
});
|
||
});
|
||
|
||
describe('GET /users/:id', () => {
|
||
it('成功:SYSTEM_ADMIN 可查询用户详情', async () => {
|
||
const response = await request(ctx.app.getHttpServer())
|
||
.get(`/users/${ctx.fixtures.users.doctorAId}`)
|
||
.set('Authorization', `Bearer ${ctx.tokens[Role.SYSTEM_ADMIN]}`);
|
||
|
||
expectSuccessEnvelope(response, 200);
|
||
expect(response.body.data.id).toBe(ctx.fixtures.users.doctorAId);
|
||
});
|
||
|
||
it('失败:查询不存在用户返回 404', async () => {
|
||
const response = await request(ctx.app.getHttpServer())
|
||
.get('/users/99999999')
|
||
.set('Authorization', `Bearer ${ctx.tokens[Role.SYSTEM_ADMIN]}`);
|
||
|
||
expectErrorEnvelope(response, 404, '用户不存在');
|
||
});
|
||
|
||
it('角色矩阵:SYSTEM_ADMIN/HOSPITAL_ADMIN 可访问,其他角色 403,未登录 401', async () => {
|
||
await assertRoleMatrix({
|
||
name: 'GET /users/:id 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(`/users/${ctx.fixtures.users.doctorAId}`)
|
||
.set('Authorization', `Bearer ${token}`),
|
||
sendWithoutToken: async () =>
|
||
request(ctx.app.getHttpServer()).get(
|
||
`/users/${ctx.fixtures.users.doctorAId}`,
|
||
),
|
||
});
|
||
});
|
||
});
|
||
|
||
describe('PATCH /users/:id', () => {
|
||
it('成功:SYSTEM_ADMIN 可更新用户姓名', async () => {
|
||
const created = await createDoctorUser(ctx.tokens[Role.SYSTEM_ADMIN]);
|
||
const nextName = uniqueSeedValue('更新后医生名');
|
||
|
||
const response = await request(ctx.app.getHttpServer())
|
||
.patch(`/users/${created.id}`)
|
||
.set('Authorization', `Bearer ${ctx.tokens[Role.SYSTEM_ADMIN]}`)
|
||
.send({ name: nextName });
|
||
|
||
expectSuccessEnvelope(response, 200);
|
||
expect(response.body.data.name).toBe(nextName);
|
||
});
|
||
|
||
it('失败:非医生调整科室/小组返回 400', async () => {
|
||
const response = await request(ctx.app.getHttpServer())
|
||
.patch(`/users/${ctx.fixtures.users.engineerAId}`)
|
||
.set('Authorization', `Bearer ${ctx.tokens[Role.SYSTEM_ADMIN]}`)
|
||
.send({
|
||
departmentId: ctx.fixtures.departmentA1Id,
|
||
groupId: ctx.fixtures.groupA1Id,
|
||
});
|
||
|
||
expectErrorEnvelope(response, 400, '仅医生/主任/组长允许调整科室/小组归属');
|
||
});
|
||
|
||
it('角色矩阵:SYSTEM_ADMIN/HOSPITAL_ADMIN 可进入业务,其他角色 403,未登录 401', async () => {
|
||
await assertRoleMatrix({
|
||
name: 'PATCH /users/: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('/users/99999999')
|
||
.set('Authorization', `Bearer ${token}`)
|
||
.send({ name: 'matrix-patch' }),
|
||
sendWithoutToken: async () =>
|
||
request(ctx.app.getHttpServer())
|
||
.patch('/users/99999999')
|
||
.send({ name: 'matrix-patch' }),
|
||
});
|
||
});
|
||
});
|
||
|
||
describe('DELETE /users/:id', () => {
|
||
it('成功:SYSTEM_ADMIN 可删除用户', async () => {
|
||
const created = await createEngineerUser(ctx.tokens[Role.SYSTEM_ADMIN]);
|
||
const response = await request(ctx.app.getHttpServer())
|
||
.delete(`/users/${created.id}`)
|
||
.set('Authorization', `Bearer ${ctx.tokens[Role.SYSTEM_ADMIN]}`);
|
||
|
||
expectSuccessEnvelope(response, 200);
|
||
expect(response.body.data.id).toBe(created.id);
|
||
});
|
||
|
||
it('失败:存在关联患者/任务时返回 409', async () => {
|
||
const response = await request(ctx.app.getHttpServer())
|
||
.delete(`/users/${ctx.fixtures.users.doctorAId}`)
|
||
.set('Authorization', `Bearer ${ctx.tokens[Role.SYSTEM_ADMIN]}`);
|
||
|
||
expectErrorEnvelope(response, 409, '用户存在关联患者或任务,无法删除');
|
||
});
|
||
|
||
it('失败:HOSPITAL_ADMIN 无法删除返回 403', async () => {
|
||
const response = await request(ctx.app.getHttpServer())
|
||
.delete(`/users/${ctx.fixtures.users.doctorAId}`)
|
||
.set('Authorization', `Bearer ${ctx.tokens[Role.HOSPITAL_ADMIN]}`);
|
||
|
||
expectErrorEnvelope(response, 403, '无权限执行当前操作');
|
||
});
|
||
|
||
it('角色矩阵:仅 SYSTEM_ADMIN 可进入业务,其他角色 403,未登录 401', async () => {
|
||
await assertRoleMatrix({
|
||
name: 'DELETE /users/: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('/users/99999999')
|
||
.set('Authorization', `Bearer ${token}`),
|
||
sendWithoutToken: async () =>
|
||
request(ctx.app.getHttpServer()).delete('/users/99999999'),
|
||
});
|
||
});
|
||
});
|
||
|
||
describe('PATCH /b/users/:id/assign-engineer-hospital', () => {
|
||
it('成功:SYSTEM_ADMIN 可绑定工程师医院', async () => {
|
||
const response = await request(ctx.app.getHttpServer())
|
||
.patch(
|
||
`/b/users/${ctx.fixtures.users.engineerAId}/assign-engineer-hospital`,
|
||
)
|
||
.set('Authorization', `Bearer ${ctx.tokens[Role.SYSTEM_ADMIN]}`)
|
||
.send({ hospitalId: ctx.fixtures.hospitalAId });
|
||
|
||
expectSuccessEnvelope(response, 200);
|
||
expect(response.body.data.hospitalId).toBe(ctx.fixtures.hospitalAId);
|
||
expect(response.body.data.role).toBe(Role.ENGINEER);
|
||
});
|
||
|
||
it('失败:目标用户不是工程师返回 400', async () => {
|
||
const response = await request(ctx.app.getHttpServer())
|
||
.patch(
|
||
`/b/users/${ctx.fixtures.users.doctorAId}/assign-engineer-hospital`,
|
||
)
|
||
.set('Authorization', `Bearer ${ctx.tokens[Role.SYSTEM_ADMIN]}`)
|
||
.send({ hospitalId: ctx.fixtures.hospitalAId });
|
||
|
||
expectErrorEnvelope(response, 400, '目标用户不是工程师');
|
||
});
|
||
|
||
it('角色矩阵:仅 SYSTEM_ADMIN 可进入业务,其他角色 403,未登录 401', async () => {
|
||
await assertRoleMatrix({
|
||
name: 'PATCH /b/users/:id/assign-engineer-hospital 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())
|
||
.patch(
|
||
`/b/users/${ctx.fixtures.users.engineerAId}/assign-engineer-hospital`,
|
||
)
|
||
.set('Authorization', `Bearer ${token}`)
|
||
.send({}),
|
||
sendWithoutToken: async () =>
|
||
request(ctx.app.getHttpServer())
|
||
.patch(
|
||
`/b/users/${ctx.fixtures.users.engineerAId}/assign-engineer-hospital`,
|
||
)
|
||
.send({}),
|
||
});
|
||
});
|
||
});
|
||
});
|