222 lines
8.0 KiB
TypeScript
222 lines
8.0 KiB
TypeScript
import { describe, expect, it, vi } from 'vitest';
|
||
import { DocumentService, EXPORT_NOTICE } from './document.service.js';
|
||
import type { PrismaService } from '../prisma/prisma.service.js';
|
||
import { ErrorCodes } from '../common/error-codes.js';
|
||
import { encrypt } from '../common/id-card-cipher.js';
|
||
import { runWithRequestContext } from '../common/context/request-context.js';
|
||
import type { RequestContext } from '../common/context/request-context.js';
|
||
|
||
const ctx: RequestContext = {
|
||
tenantCode: 'T001',
|
||
userId: 'u1',
|
||
username: 'admin',
|
||
super: false,
|
||
dataScope: 'self',
|
||
orgCode: null,
|
||
orgCodes: [],
|
||
};
|
||
|
||
function makeService(
|
||
overrides: {
|
||
elderFindUnique?: ReturnType<typeof vi.fn>;
|
||
agencyFindUnique?: ReturnType<typeof vi.fn>;
|
||
planFindMany?: ReturnType<typeof vi.fn>;
|
||
planItemFindMany?: ReturnType<typeof vi.fn>;
|
||
recordFindMany?: ReturnType<typeof vi.fn>;
|
||
recordItemFindMany?: ReturnType<typeof vi.fn>;
|
||
recordAbnormalFindMany?: ReturnType<typeof vi.fn>;
|
||
} = {},
|
||
) {
|
||
const elderFindUnique = overrides.elderFindUnique ?? vi.fn().mockResolvedValue(null);
|
||
const agencyFindUnique = overrides.agencyFindUnique ?? vi.fn().mockResolvedValue({ code: 'T001', orgName: '测试机构' });
|
||
const planFindMany = overrides.planFindMany ?? vi.fn().mockResolvedValue([]);
|
||
const planItemFindMany = overrides.planItemFindMany ?? vi.fn().mockResolvedValue([]);
|
||
const recordFindMany = overrides.recordFindMany ?? vi.fn().mockResolvedValue([]);
|
||
const recordItemFindMany = overrides.recordItemFindMany ?? vi.fn().mockResolvedValue([]);
|
||
const recordAbnormalFindMany = overrides.recordAbnormalFindMany ?? vi.fn().mockResolvedValue([]);
|
||
|
||
const prisma = {
|
||
client: {
|
||
elder: { findUnique: elderFindUnique },
|
||
agency: { findUnique: agencyFindUnique },
|
||
plan: { findMany: planFindMany },
|
||
planItem: { findMany: planItemFindMany },
|
||
record: { findMany: recordFindMany },
|
||
recordItem: { findMany: recordItemFindMany },
|
||
recordAbnormal: { findMany: recordAbnormalFindMany },
|
||
},
|
||
};
|
||
const service = new DocumentService(prisma as unknown as PrismaService);
|
||
return {
|
||
service,
|
||
elderFindUnique,
|
||
agencyFindUnique,
|
||
planFindMany,
|
||
planItemFindMany,
|
||
recordFindMany,
|
||
recordItemFindMany,
|
||
recordAbnormalFindMany,
|
||
};
|
||
}
|
||
|
||
describe('DocumentService.export', () => {
|
||
it('老人不存在 → ELDER_NOT_FOUND', async () => {
|
||
const { service, recordFindMany } = makeService();
|
||
|
||
await expect(
|
||
runWithRequestContext(ctx, () => service.export('missing')),
|
||
).rejects.toMatchObject({ response: { code: ErrorCodes.ELDER_NOT_FOUND } });
|
||
|
||
expect(recordFindMany).not.toHaveBeenCalled();
|
||
});
|
||
|
||
it('组装一人一档:掩码身份证 + 日期转 YYYY-MM-DD + 计划/记录/异常 + 水印', async () => {
|
||
const elder = {
|
||
id: 'e1',
|
||
name: '张三',
|
||
gender: '1',
|
||
birthDate: new Date('1950-01-01T00:00:00Z'),
|
||
idCardNoEnc: encrypt('530102195001010011'),
|
||
phone: '13800000000',
|
||
address: '昆明市',
|
||
insurNo: 'I001',
|
||
careLevel: '重度',
|
||
treatStatus: 'active',
|
||
benefitStart: new Date('2026-01-01T00:00:00Z'),
|
||
benefitEnd: null,
|
||
};
|
||
const plans = [
|
||
{
|
||
id: 'p1',
|
||
elderId: 'e1',
|
||
planPeriodStart: new Date('2026-10-01T00:00:00Z'),
|
||
planPeriodEnd: new Date('2026-10-31T00:00:00Z'),
|
||
careLevel: '重度',
|
||
careMode: 'home_org',
|
||
planStatus: 'active',
|
||
},
|
||
];
|
||
const planItems = [
|
||
{ id: 'pi1', planId: 'p1', svcId: 'svc1', svcNameSnapshot: '协助翻身', freqType: 'weekly', freqValue: 3, durationMin: 30, photoTier: 'must' },
|
||
];
|
||
const records = [
|
||
{
|
||
id: 'r1',
|
||
elderId: 'e1',
|
||
checkinTime: new Date('2026-10-15T02:00:00Z'),
|
||
checkoutTime: new Date('2026-10-15T03:00:00Z'),
|
||
durationMin: 60,
|
||
recordStatus: 'submitted',
|
||
},
|
||
];
|
||
const recordItems = [
|
||
{ id: 'ri1', recordId: 'r1', svcId: 'svc1', svcNameSnapshot: '协助翻身', execResult: 'done', undoReason: null, extraFlag: false },
|
||
];
|
||
const abnormals = [
|
||
{ id: 'a1', elderId: 'e1', abnormalType: '跌倒', abnormalDesc: '轻微', handleStatus: 'open' },
|
||
];
|
||
|
||
const {
|
||
service,
|
||
elderFindUnique,
|
||
agencyFindUnique,
|
||
planFindMany,
|
||
planItemFindMany,
|
||
recordFindMany,
|
||
recordItemFindMany,
|
||
recordAbnormalFindMany,
|
||
} = makeService({
|
||
elderFindUnique: vi.fn().mockResolvedValue(elder),
|
||
planFindMany: vi.fn().mockResolvedValue(plans),
|
||
planItemFindMany: vi.fn().mockResolvedValue(planItems),
|
||
recordFindMany: vi.fn().mockResolvedValue(records),
|
||
recordItemFindMany: vi.fn().mockResolvedValue(recordItems),
|
||
recordAbnormalFindMany: vi.fn().mockResolvedValue(abnormals),
|
||
});
|
||
|
||
const res = await runWithRequestContext(ctx, () => service.export('e1', '2026-10-01', '2026-10-31'));
|
||
|
||
// 定位与范围
|
||
expect(elderFindUnique).toHaveBeenCalledWith({ where: { id: 'e1' } });
|
||
expect(agencyFindUnique).toHaveBeenCalledWith({ where: { code: 'T001' } });
|
||
expect(planFindMany).toHaveBeenCalledWith({ where: { elderId: 'e1' }, orderBy: { createdAt: 'desc' } });
|
||
expect(planItemFindMany).toHaveBeenCalledWith({ where: { planId: { in: ['p1'] } } });
|
||
expect(recordFindMany).toHaveBeenCalledWith({
|
||
where: {
|
||
elderId: 'e1',
|
||
checkinTime: {
|
||
gte: new Date('2026-10-01T00:00:00+08:00'),
|
||
lte: new Date('2026-10-31T23:59:59.999+08:00'),
|
||
},
|
||
},
|
||
orderBy: { checkinTime: 'desc' },
|
||
});
|
||
expect(recordItemFindMany).toHaveBeenCalledWith({ where: { recordId: { in: ['r1'] } } });
|
||
expect(recordAbnormalFindMany).toHaveBeenCalledWith({ where: { elderId: 'e1' } });
|
||
|
||
// 水印:机构名 + 时间 + 操作人(username)+ R4 文案
|
||
expect(res.watermark).toMatchObject({
|
||
orgName: '测试机构',
|
||
operator: 'admin',
|
||
notice: EXPORT_NOTICE,
|
||
});
|
||
expect(res.watermark.exportTime).toEqual(expect.any(String));
|
||
|
||
// 老人信息:身份证掩码 + 日期转 YYYY-MM-DD
|
||
expect(res.elder).toEqual({
|
||
name: '张三',
|
||
gender: '1',
|
||
birthDate: '1950-01-01',
|
||
idCardNo: '5301**********0011',
|
||
phone: '13800000000',
|
||
address: '昆明市',
|
||
insurNo: 'I001',
|
||
careLevel: '重度',
|
||
treatStatus: 'active',
|
||
benefitStart: '2026-01-01',
|
||
benefitEnd: null,
|
||
});
|
||
|
||
// 计划 + 计划项
|
||
expect(res.plans).toEqual([
|
||
{
|
||
id: 'p1',
|
||
planPeriodStart: '2026-10-01',
|
||
planPeriodEnd: '2026-10-31',
|
||
careLevel: '重度',
|
||
careMode: 'home_org',
|
||
planStatus: 'active',
|
||
items: [{ svcId: 'svc1', svcName: '协助翻身', freqType: 'weekly', freqValue: 3, durationMin: 30, photoTier: 'must' }],
|
||
},
|
||
]);
|
||
|
||
// 记录 + 记录项(checkin/checkout 转 ISO)
|
||
expect(res.records).toEqual([
|
||
{
|
||
id: 'r1',
|
||
checkinTime: new Date('2026-10-15T02:00:00Z').toISOString(),
|
||
checkoutTime: new Date('2026-10-15T03:00:00Z').toISOString(),
|
||
durationMin: 60,
|
||
recordStatus: 'submitted',
|
||
items: [{ svcId: 'svc1', svcName: '协助翻身', execResult: 'done', undoReason: null, extraFlag: false }],
|
||
},
|
||
]);
|
||
|
||
// 异常
|
||
expect(res.abnormals).toEqual([{ id: 'a1', type: '跌倒', desc: '轻微', handleStatus: 'open' }]);
|
||
});
|
||
|
||
it('无日期范围时不加 checkinTime 条件', async () => {
|
||
const elder = { id: 'e1', name: '张三', gender: '1', birthDate: new Date('1950-01-01T00:00:00Z'), idCardNoEnc: encrypt('530102195001010011'), phone: null, address: '昆明市', insurNo: 'I001', careLevel: '重度', treatStatus: 'active', benefitStart: null, benefitEnd: null };
|
||
const recordFindMany = vi.fn().mockResolvedValue([]);
|
||
const { service } = makeService({
|
||
elderFindUnique: vi.fn().mockResolvedValue(elder),
|
||
recordFindMany,
|
||
});
|
||
|
||
await runWithRequestContext(ctx, () => service.export('e1'));
|
||
|
||
expect(recordFindMany).toHaveBeenCalledWith({ where: { elderId: 'e1' }, orderBy: { checkinTime: 'desc' } });
|
||
});
|
||
});
|