hn-hlw-service/hlw/automation/index.test.js

1080 lines
31 KiB
JavaScript
Raw Normal View History

2026-07-31 16:22:09 +08:00
const { ObjectId } = require("mongodb");
2026-08-03 16:14:45 +08:00
jest.mock("../tencent-im", () => jest.fn());
2026-08-05 18:39:56 +08:00
jest.mock("../diagnostic-record", () => jest.fn());
2026-08-03 16:14:45 +08:00
const tencentIM = require("../tencent-im");
2026-08-05 18:39:56 +08:00
const diagnosticRecord = require("../diagnostic-record");
2026-07-31 16:22:09 +08:00
const {
AUTOMATION_TYPES,
COLLECTION,
2026-08-05 18:39:56 +08:00
ERROR_SOURCES,
2026-08-24 16:41:11 +08:00
MANUAL_OPEN_RX_MESSAGE,
TASK_CANCEL_REASONS,
2026-07-31 16:22:09 +08:00
TASK_STATUS,
2026-08-05 18:39:56 +08:00
AutomationError,
2026-07-31 16:22:09 +08:00
buildAutoPrescriptionParams,
calculateDueAt,
claimNextTask,
enqueueTask,
2026-08-05 18:39:56 +08:00
getFailureNotificationExt,
2026-07-31 16:22:09 +08:00
processClaimedTask,
reconcileAutomationTasks,
2026-08-24 16:41:11 +08:00
scheduleAutoOpenRx,
2026-07-31 16:22:09 +08:00
} = require("./index");
function createCollectionDb(collections) {
return {
collection: jest.fn((name) => {
if (!collections[name]) {
throw new Error(`unexpected collection: ${name}`);
}
return collections[name];
}),
};
}
describe("automation task scheduling", () => {
afterEach(() => {
jest.restoreAllMocks();
2026-08-03 16:14:45 +08:00
jest.clearAllMocks();
2026-07-31 16:22:09 +08:00
});
test("uses the configured delay and respects firstSubmitRxIntervel", () => {
jest.spyOn(Date, "now").mockReturnValue(1_000_000);
const config = {
autoOpenRx: true,
autoOpenRxDelayMinSeconds: 20,
autoOpenRxDelayMaxSeconds: 40,
firstSubmitRxIntervel: 60,
};
expect(
calculateDueAt(AUTOMATION_TYPES.OPEN_RX, config, 1_000_000, () => 0)
).toBe(1_060_000);
expect(
calculateDueAt(
AUTOMATION_TYPES.OPEN_RX,
{ ...config, firstSubmitRxIntervel: 10 },
1_000_000,
() => 0.999999
)
).toBe(1_040_000);
});
test("creates a task idempotently through a unique task key", async () => {
const updateOne = jest
.fn()
.mockResolvedValueOnce({ modifiedCount: 0 })
.mockResolvedValueOnce({ upsertedCount: 1 });
const db = createCollectionDb({
"hlw-config": {
findOne: jest.fn().mockResolvedValue({
autoAcceptOrder: true,
autoAcceptDelayMinSeconds: 2,
autoAcceptDelayMaxSeconds: 5,
}),
},
[COLLECTION]: { updateOne },
});
const result = await enqueueTask(db, {
type: AUTOMATION_TYPES.ACCEPT,
corpId: "corp-1",
orderId: "order-1",
baseTime: Date.now(),
expiresAt: Date.now() + 60_000,
});
expect(result).toMatchObject({
success: true,
created: true,
taskKey: "AUTO_ACCEPT:order-1",
});
expect(updateOne).toHaveBeenLastCalledWith(
{ taskKey: "AUTO_ACCEPT:order-1" },
expect.objectContaining({
$setOnInsert: expect.objectContaining({
status: TASK_STATUS.PENDING,
attempts: 0,
}),
}),
{ upsert: true }
);
});
test("reactivates a task cancelled by a previously disabled switch", async () => {
const updateOne = jest.fn().mockResolvedValue({ modifiedCount: 1 });
const db = createCollectionDb({
"hlw-config": {
findOne: jest.fn().mockResolvedValue({ autoAcceptOrder: true }),
},
[COLLECTION]: { updateOne },
});
const result = await enqueueTask(db, {
type: AUTOMATION_TYPES.ACCEPT,
corpId: "corp-1",
orderId: "order-1",
});
expect(result).toMatchObject({
success: true,
created: true,
reactivated: true,
});
expect(updateOne).toHaveBeenCalledTimes(1);
expect(updateOne).toHaveBeenCalledWith(
{
taskKey: "AUTO_ACCEPT:order-1",
status: TASK_STATUS.CANCELLED,
},
expect.objectContaining({
$set: expect.objectContaining({ status: TASK_STATUS.PENDING }),
2026-08-24 16:41:11 +08:00
$unset: { completedAt: "", result: "", cancelReason: "" },
2026-07-31 16:22:09 +08:00
})
);
});
2026-08-24 16:41:11 +08:00
test("cancels store-drug auto open once when any ordered drug is high risk", async () => {
tencentIM.mockResolvedValue({ success: true });
const drugId = new ObjectId();
const lowRiskDrugId = new ObjectId();
const updateOne = jest
.fn()
.mockResolvedValueOnce({ modifiedCount: 0 })
.mockResolvedValueOnce({ upsertedCount: 1 })
.mockResolvedValueOnce({ modifiedCount: 0 })
.mockResolvedValueOnce({ upsertedCount: 0 });
const db = createCollectionDb({
"consult-order": {
findOne: jest.fn().mockResolvedValue({
_id: new ObjectId(),
orderId: "order-high-risk",
corpId: "corp-1",
doctorCode: "D001",
consultType: "storeMedicinePurchase",
drugs: [
{ _id: lowRiskDrugId.toString() },
{ _id: drugId.toString() },
],
}),
},
"hlw-config": {
findOne: jest.fn().mockResolvedValue({ autoOpenRx: true }),
},
"drug-info": {
findOne: jest.fn().mockResolvedValue({
_id: drugId,
name: "高风险药品",
riskLevel: "高风险",
}),
},
[COLLECTION]: { updateOne },
});
const first = await scheduleAutoOpenRx({
db,
orderId: "order-high-risk",
corpId: "corp-1",
});
const second = await scheduleAutoOpenRx({
db,
orderId: "order-high-risk",
corpId: "corp-1",
});
expect(first).toMatchObject({
success: false,
skipped: true,
manualRequired: true,
message: MANUAL_OPEN_RX_MESSAGE,
});
expect(second).toMatchObject({ manualRequired: true });
expect(updateOne).toHaveBeenNthCalledWith(
2,
{ taskKey: "AUTO_OPEN_RX:order-high-risk" },
{
$setOnInsert: expect.objectContaining({
status: TASK_STATUS.CANCELLED,
cancelReason: TASK_CANCEL_REASONS.HIGH_RISK_DRUG,
lastError: MANUAL_OPEN_RX_MESSAGE,
}),
},
{ upsert: true }
);
expect(tencentIM).toHaveBeenCalledTimes(1);
expect(tencentIM).toHaveBeenCalledWith(
expect.objectContaining({
formAccount: "order-high-risk",
toAccount: "D001",
msgBody: [
expect.objectContaining({
MsgContent: expect.objectContaining({
Data: "AUTORXMANUAL",
Ext: MANUAL_OPEN_RX_MESSAGE,
}),
}),
],
}),
db
);
});
test("checks the online drug collection and allows low-risk tasks to reactivate", async () => {
const drugId = new ObjectId();
const updateOne = jest.fn().mockResolvedValue({ modifiedCount: 1 });
const onlineFindOne = jest.fn().mockResolvedValue(null);
const db = createCollectionDb({
"consult-order": {
findOne: jest.fn().mockResolvedValue({
_id: new ObjectId(),
consultType: "onlineMedicinePurchase",
drugs: [{ _id: drugId.toString() }],
}),
},
"hlw-config": {
findOne: jest.fn().mockResolvedValue({ autoOpenRx: true }),
},
"online-drug-info": { findOne: onlineFindOne },
[COLLECTION]: { updateOne },
});
await expect(
scheduleAutoOpenRx({
db,
orderId: "order-online-low-risk",
corpId: "corp-1",
})
).resolves.toMatchObject({
success: true,
reactivated: true,
});
expect(onlineFindOne).toHaveBeenCalledWith(
{ _id: { $in: [expect.any(ObjectId)] }, riskLevel: "高风险" },
{ projection: { _id: 1, name: 1, riskLevel: 1 } }
);
expect(updateOne).toHaveBeenCalledWith(
{
taskKey: "AUTO_OPEN_RX:order-online-low-risk",
status: TASK_STATUS.CANCELLED,
},
expect.objectContaining({
$set: expect.objectContaining({ status: TASK_STATUS.PENDING }),
$unset: { completedAt: "", result: "", cancelReason: "" },
})
);
});
test("cancels auto open for a high-risk online drug", async () => {
tencentIM.mockResolvedValue({ success: true });
const drugId = new ObjectId();
const updateOne = jest
.fn()
.mockResolvedValueOnce({ modifiedCount: 0 })
.mockResolvedValueOnce({ upsertedCount: 1 });
const db = createCollectionDb({
"consult-order": {
findOne: jest.fn().mockResolvedValue({
_id: new ObjectId(),
doctorCode: "D002",
consultType: "onlineMedicinePurchase",
drugs: [{ _id: drugId.toString() }],
}),
},
"hlw-config": {
findOne: jest.fn().mockResolvedValue({ autoOpenRx: true }),
},
"online-drug-info": {
findOne: jest.fn().mockResolvedValue({
_id: drugId,
riskLevel: "高风险",
}),
},
[COLLECTION]: { updateOne },
});
await expect(
scheduleAutoOpenRx({
db,
orderId: "order-online-high-risk",
corpId: "corp-1",
})
).resolves.toMatchObject({
skipped: true,
manualRequired: true,
});
expect(tencentIM).toHaveBeenCalledTimes(1);
});
2026-07-31 16:22:09 +08:00
test("treats a concurrent unique-key insert as idempotent success", async () => {
const duplicateError = Object.assign(new Error("duplicate key"), {
code: 11000,
});
const updateOne = jest
.fn()
.mockResolvedValueOnce({ modifiedCount: 0 })
.mockRejectedValueOnce(duplicateError);
const db = createCollectionDb({
"hlw-config": {
findOne: jest.fn().mockResolvedValue({ autoPassRx: true }),
},
[COLLECTION]: { updateOne },
});
await expect(
enqueueTask(db, {
type: AUTOMATION_TYPES.PASS_RX,
corpId: "corp-1",
orderId: "order-1",
rxId: new ObjectId().toString(),
})
).resolves.toMatchObject({
success: true,
created: false,
});
});
test("claims due and expired-lease tasks with one atomic update", async () => {
const claimed = {
_id: new ObjectId(),
taskKey: "AUTO_ACCEPT:order-1",
status: TASK_STATUS.RUNNING,
};
const findOneAndUpdate = jest.fn().mockResolvedValue(claimed);
const db = createCollectionDb({
[COLLECTION]: { findOneAndUpdate },
});
await expect(claimNextTask(db, 10_000)).resolves.toBe(claimed);
expect(findOneAndUpdate).toHaveBeenCalledWith(
{
$or: [
{
status: TASK_STATUS.PENDING,
nextRunAt: { $lte: 10_000 },
},
{
status: TASK_STATUS.RUNNING,
leaseUntil: { $lte: 10_000 },
},
],
},
expect.objectContaining({
$set: expect.objectContaining({
status: TASK_STATUS.RUNNING,
leaseOwner: expect.any(String),
}),
$inc: { attempts: 1 },
}),
{
sort: { nextRunAt: 1, createdAt: 1 },
returnDocument: "after",
}
);
});
test("cancels a claimed task when its switch has been turned off", async () => {
const updateOne = jest.fn().mockResolvedValue({ modifiedCount: 1 });
const db = createCollectionDb({
"hlw-config": {
findOne: jest.fn().mockResolvedValue({ autoAcceptOrder: false }),
},
[COLLECTION]: { updateOne },
});
const task = {
_id: new ObjectId(),
type: AUTOMATION_TYPES.ACCEPT,
corpId: "corp-1",
orderId: "order-1",
attempts: 1,
expiresAt: Date.now() + 60_000,
};
await processClaimedTask(db, task);
expect(updateOne).toHaveBeenCalledWith(
expect.objectContaining({
_id: task._id,
status: TASK_STATUS.RUNNING,
leaseOwner: expect.any(String),
}),
expect.objectContaining({
$set: expect.objectContaining({
status: TASK_STATUS.CANCELLED,
completedAt: expect.any(Number),
}),
})
);
});
test("reconciliation recreates a missing task after service restart", async () => {
jest.spyOn(Date, "now").mockReturnValue(2_000_000);
const taskUpdateOne = jest
.fn()
.mockResolvedValueOnce({ modifiedCount: 0 })
.mockResolvedValueOnce({ upsertedCount: 1 });
const order = {
orderId: "order-recovery",
corpId: "corp-1",
orderSource: "ALIPAY_MINI",
orderStatus: "pending",
createTime: 1_990_000,
expireTime: 2_060_000,
};
const db = createCollectionDb({
"consult-order": {
find: jest.fn().mockReturnValue({
toArray: jest.fn().mockResolvedValue([order]),
}),
findOne: jest.fn().mockResolvedValue({ _id: new ObjectId() }),
},
"hlw-config": {
find: jest.fn().mockReturnValue({
toArray: jest
.fn()
.mockResolvedValue([{ corpId: "corp-1", autoAcceptOrder: true }]),
}),
findOne: jest
.fn()
.mockResolvedValue({ corpId: "corp-1", autoAcceptOrder: true }),
},
"diagnostic-record": {
find: jest.fn().mockReturnValue({
toArray: jest.fn().mockResolvedValue([]),
}),
},
[COLLECTION]: { updateOne: taskUpdateOne },
});
await reconcileAutomationTasks(db);
expect(taskUpdateOne).toHaveBeenLastCalledWith(
{ taskKey: "AUTO_ACCEPT:order-recovery" },
expect.objectContaining({
$setOnInsert: expect.objectContaining({
orderId: "order-recovery",
status: TASK_STATUS.PENDING,
}),
}),
{ upsert: true }
);
});
2026-08-24 16:41:11 +08:00
test("cancels a queued auto-open task when a drug becomes high risk before execution", async () => {
tencentIM.mockResolvedValue({ success: true });
const drugId = new ObjectId();
const taskUpdateOne = jest.fn().mockResolvedValue({ modifiedCount: 1 });
const order = {
orderId: "order-risk-changed",
corpId: "corp-1",
orderSource: "ALIPAY_MINI",
orderStatus: "processing",
doctorCode: "D001",
consultType: "storeMedicinePurchase",
drugs: [{ _id: drugId.toString() }],
expireTime: Date.now() + 60_000,
};
const db = createCollectionDb({
"hlw-config": {
findOne: jest.fn().mockResolvedValue({ autoOpenRx: true }),
},
"consult-order": {
findOne: jest.fn().mockResolvedValue(order),
},
"diagnostic-record": {
findOne: jest.fn().mockResolvedValue(null),
},
"hlw-doctor": {
findOne: jest.fn().mockResolvedValue({
doctorNo: "D001",
onlineStatus: "online",
}),
},
"drug-info": {
find: jest.fn().mockReturnValue({
toArray: jest.fn().mockResolvedValue([
{
_id: drugId,
onSale: true,
name: "风险变更药品",
riskLevel: "高风险",
},
]),
}),
},
[COLLECTION]: { updateOne: taskUpdateOne },
});
const task = {
_id: new ObjectId(),
type: AUTOMATION_TYPES.OPEN_RX,
corpId: "corp-1",
orderId: "order-risk-changed",
attempts: 1,
expiresAt: Date.now() + 60_000,
};
await processClaimedTask(db, task);
expect(taskUpdateOne).toHaveBeenCalledWith(
expect.objectContaining({
_id: task._id,
status: TASK_STATUS.RUNNING,
}),
expect.objectContaining({
$set: expect.objectContaining({
status: TASK_STATUS.CANCELLED,
cancelReason: TASK_CANCEL_REASONS.HIGH_RISK_DRUG,
lastError: MANUAL_OPEN_RX_MESSAGE,
}),
})
);
expect(diagnosticRecord).not.toHaveBeenCalled();
expect(tencentIM).toHaveBeenCalledWith(
expect.objectContaining({
toAccount: "D001",
msgBody: [
expect.objectContaining({
MsgContent: expect.objectContaining({
Data: "AUTORXMANUAL",
Ext: MANUAL_OPEN_RX_MESSAGE,
}),
}),
],
}),
db
);
});
2026-08-03 16:14:45 +08:00
test("fails automatic prescription immediately and sends the failure reason", async () => {
tencentIM.mockResolvedValue({ success: true });
const taskUpdateOne = jest.fn().mockResolvedValue({ modifiedCount: 1 });
const order = {
orderId: "order-1",
corpId: "corp-1",
orderSource: "ALIPAY_MINI",
orderStatus: "processing",
doctorCode: "D001",
expireTime: Date.now() + 60_000,
};
const db = createCollectionDb({
"hlw-config": {
findOne: jest.fn().mockResolvedValue({ autoOpenRx: true }),
},
"consult-order": {
findOne: jest.fn().mockResolvedValue(order),
},
"diagnostic-record": {
findOne: jest.fn().mockResolvedValue(null),
},
"hlw-doctor": {
findOne: jest.fn().mockResolvedValue(null),
},
[COLLECTION]: { updateOne: taskUpdateOne },
});
const task = {
_id: new ObjectId(),
type: AUTOMATION_TYPES.OPEN_RX,
corpId: "corp-1",
orderId: "order-1",
attempts: 1,
expiresAt: Date.now() + 60_000,
};
await processClaimedTask(db, task);
expect(taskUpdateOne).toHaveBeenCalledWith(
expect.objectContaining({
_id: task._id,
status: TASK_STATUS.RUNNING,
}),
expect.objectContaining({
$set: expect.objectContaining({
status: TASK_STATUS.FAILED,
completedAt: expect.any(Number),
lastError: "开方医生不存在",
}),
})
);
expect(tencentIM).toHaveBeenCalledWith(
expect.objectContaining({
type: "sendSystemNotification",
formAccount: "order-1",
toAccount: "D001",
SyncOtherMachine: 1,
msgBody: [
expect.objectContaining({
MsgContent: expect.objectContaining({
2026-08-05 18:39:56 +08:00
Data: "AUTORXFAIL",
2026-08-03 16:14:45 +08:00
Desc: "notification",
2026-08-05 18:39:56 +08:00
Ext: JSON.stringify({
errorSource: ERROR_SOURCES.SYSTEM,
reason: "系统错误:开方医生不存在",
}),
2026-08-03 16:14:45 +08:00
}),
}),
],
}),
db
);
expect(taskUpdateOne).not.toHaveBeenCalledWith(
expect.anything(),
expect.objectContaining({
$set: expect.objectContaining({ status: TASK_STATUS.PENDING }),
})
);
});
2026-09-04 09:57:39 +08:00
test("blocks automatic prescription when rational-drug review fails", async () => {
tencentIM.mockResolvedValue({ success: true });
diagnosticRecord.mockResolvedValue({
success: false,
message: "该处方存在不合理用药",
});
const drugId = new ObjectId();
const taskUpdateOne = jest.fn().mockResolvedValue({ modifiedCount: 1 });
const order = {
orderId: "order-review-failed",
corpId: "corp-1",
orderSource: "ALIPAY_MINI",
orderStatus: "processing",
doctorCode: "D001",
patientId: "P001",
name: "患者甲",
description: "鼻塞流涕",
medInfo: { dise_codg: "J00", dise_name: "普通感冒" },
drugs: [
{
_id: drugId.toString(),
dosage: 10,
dosage_unit: "mg",
quantity: 1,
usageName: "口服",
frequencyName: "每日两次",
unit: "盒",
},
],
expireTime: Date.now() + 60_000,
};
const db = createCollectionDb({
"hlw-config": {
findOne: jest.fn().mockResolvedValue({ autoOpenRx: true }),
find: jest.fn().mockReturnValue({
toArray: jest.fn().mockResolvedValue([
{ key: "store-medicine-dosage-unit", list: [{ code: "MG", name: "mg" }] },
{ key: "store-medicine-frequence", list: [{ code: "BID", name: "每日两次" }] },
{ key: "store-medicine-administration", list: [{ code: "PO", name: "口服" }] },
{ key: "medicine-package-unit", list: [{ code: "盒", name: "盒" }] },
]),
}),
},
"consult-order": { findOne: jest.fn().mockResolvedValue(order) },
"diagnostic-record": { findOne: jest.fn().mockResolvedValue(null) },
"hlw-doctor": {
findOne: jest.fn().mockResolvedValue({
doctorNo: "D001",
signatureUrl: "doctor-sign",
onlineStatus: "online",
}),
},
"drug-info": {
find: jest.fn().mockReturnValue({
toArray: jest.fn().mockResolvedValue([
{
_id: drugId,
onSale: true,
name: "测试药品",
erpId: "ERP-1",
insurance_code: "MED-1",
dosage_form: "片剂",
days: 3,
dosage_unit: "mg",
administration_method: "口服",
freq: "每日两次",
unit: "盒",
},
]),
}),
},
[COLLECTION]: { updateOne: taskUpdateOne },
});
const task = {
_id: new ObjectId(),
type: AUTOMATION_TYPES.OPEN_RX,
corpId: "corp-1",
orderId: order.orderId,
attempts: 1,
expiresAt: Date.now() + 60_000,
};
await processClaimedTask(db, task);
expect(diagnosticRecord).toHaveBeenCalledTimes(1);
expect(diagnosticRecord).toHaveBeenCalledWith(
expect.objectContaining({
type: "getYbReviewResult",
corpId: order.corpId,
params: expect.objectContaining({ orderId: order.orderId }),
}),
db
);
expect(taskUpdateOne).toHaveBeenCalledWith(
expect.objectContaining({ _id: task._id, status: TASK_STATUS.RUNNING }),
expect.objectContaining({
$set: expect.objectContaining({
status: TASK_STATUS.FAILED,
lastError: "该处方存在不合理用药",
}),
})
);
});
2026-08-03 16:14:45 +08:00
test("fails automatic audit immediately and sends the failure reason", async () => {
tencentIM.mockResolvedValue({ success: true });
2026-07-31 16:22:09 +08:00
const rxId = new ObjectId();
const taskUpdateOne = jest.fn().mockResolvedValue({ modifiedCount: 1 });
const db = createCollectionDb({
"hlw-config": {
findOne: jest.fn().mockResolvedValue({ autoPassRx: true }),
},
"diagnostic-record": {
findOne: jest.fn().mockResolvedValue({
_id: rxId,
orderId: "order-1",
status: "INIT",
pharmacistNo: "P001",
}),
},
"hlw-doctor": {
findOne: jest.fn().mockResolvedValue({
doctorNo: "P001",
onlineStatus: "offline",
}),
},
2026-08-03 16:14:45 +08:00
"consult-order": {
findOne: jest.fn().mockResolvedValue({ doctorCode: "D001" }),
},
2026-07-31 16:22:09 +08:00
[COLLECTION]: { updateOne: taskUpdateOne },
});
const task = {
_id: new ObjectId(),
type: AUTOMATION_TYPES.PASS_RX,
corpId: "corp-1",
orderId: "order-1",
rxId: rxId.toString(),
attempts: 1,
expiresAt: Date.now() + 60_000,
};
await processClaimedTask(db, task);
expect(taskUpdateOne).toHaveBeenCalledWith(
expect.objectContaining({
_id: task._id,
status: TASK_STATUS.RUNNING,
}),
expect.objectContaining({
$set: expect.objectContaining({
2026-08-03 16:14:45 +08:00
status: TASK_STATUS.FAILED,
completedAt: expect.any(Number),
2026-07-31 16:22:09 +08:00
lastError: "审方药师暂不在线",
}),
})
);
2026-08-03 16:14:45 +08:00
expect(tencentIM).toHaveBeenCalledWith(
expect.objectContaining({
type: "sendSystemNotification",
formAccount: "order-1",
toAccount: "D001",
SyncOtherMachine: 1,
msgBody: [
expect.objectContaining({
MsgContent: expect.objectContaining({
2026-08-05 18:39:56 +08:00
Data: "AUTORXFAIL",
2026-08-03 16:14:45 +08:00
Desc: "notification",
2026-08-05 18:39:56 +08:00
Ext: JSON.stringify({
errorSource: ERROR_SOURCES.SYSTEM,
reason: "系统错误:审方药师暂不在线",
}),
2026-08-03 16:14:45 +08:00
}),
}),
],
}),
db
);
expect(taskUpdateOne).not.toHaveBeenCalledWith(
expect.anything(),
expect.objectContaining({
$set: expect.objectContaining({ status: TASK_STATUS.PENDING }),
})
);
2026-07-31 16:22:09 +08:00
});
2026-08-05 18:39:56 +08:00
test("labels HIS prescription upload failures in the notification", async () => {
tencentIM.mockResolvedValue({ success: true });
diagnosticRecord.mockResolvedValue({
success: false,
failList: [{ message: "[his]处方上传失败: HIS拒绝接收处方" }],
});
const rxId = new ObjectId();
const taskUpdateOne = jest.fn().mockResolvedValue({ modifiedCount: 1 });
const db = createCollectionDb({
"hlw-config": {
findOne: jest.fn().mockResolvedValue({ autoPassRx: true }),
},
"diagnostic-record": {
findOne: jest.fn().mockResolvedValue({
_id: rxId,
orderId: "order-his-failure",
status: "INIT",
pharmacistNo: "P001",
}),
},
"hlw-doctor": {
findOne: jest.fn().mockResolvedValue({
doctorNo: "P001",
onlineStatus: "online",
}),
},
"consult-order": {
findOne: jest.fn().mockResolvedValue({ doctorCode: "D001" }),
},
[COLLECTION]: { updateOne: taskUpdateOne },
});
const task = {
_id: new ObjectId(),
type: AUTOMATION_TYPES.PASS_RX,
corpId: "corp-1",
orderId: "order-his-failure",
rxId: rxId.toString(),
attempts: 1,
expiresAt: Date.now() + 60_000,
};
await processClaimedTask(db, task);
expect(tencentIM).toHaveBeenCalledWith(
expect.objectContaining({
msgBody: [
expect.objectContaining({
MsgContent: expect.objectContaining({
Data: "AUTORXFAIL",
Ext: JSON.stringify({
errorSource: ERROR_SOURCES.HIS,
reason: "HIS上传错误HIS拒绝接收处方",
}),
}),
}),
],
}),
db
);
expect(taskUpdateOne).toHaveBeenCalledWith(
expect.anything(),
expect.objectContaining({
$set: expect.objectContaining({
status: TASK_STATUS.FAILED,
lastError: "[his]处方上传失败: HIS拒绝接收处方",
}),
})
);
});
test("keeps ordinary automatic audit failures classified as system errors", async () => {
tencentIM.mockResolvedValue({ success: true });
diagnosticRecord.mockResolvedValue({
success: false,
failList: [{ message: "处方数据更新失败" }],
});
const rxId = new ObjectId();
const db = createCollectionDb({
"hlw-config": {
findOne: jest.fn().mockResolvedValue({ autoPassRx: true }),
},
"diagnostic-record": {
findOne: jest.fn().mockResolvedValue({
_id: rxId,
orderId: "order-system-failure",
status: "INIT",
pharmacistNo: "P001",
}),
},
"hlw-doctor": {
findOne: jest.fn().mockResolvedValue({
doctorNo: "P001",
onlineStatus: "online",
}),
},
"consult-order": {
findOne: jest.fn().mockResolvedValue({ doctorCode: "D001" }),
},
[COLLECTION]: {
updateOne: jest.fn().mockResolvedValue({ modifiedCount: 1 }),
},
});
const task = {
_id: new ObjectId(),
type: AUTOMATION_TYPES.PASS_RX,
corpId: "corp-1",
orderId: "order-system-failure",
rxId: rxId.toString(),
attempts: 1,
expiresAt: Date.now() + 60_000,
};
await processClaimedTask(db, task);
expect(tencentIM).toHaveBeenCalledWith(
expect.objectContaining({
msgBody: [
expect.objectContaining({
MsgContent: expect.objectContaining({
Ext: JSON.stringify({
errorSource: ERROR_SOURCES.SYSTEM,
reason: "系统错误:处方数据更新失败",
}),
}),
}),
],
}),
db
);
});
test("uses the system unknown-error fallback when no error message exists", () => {
expect(getFailureNotificationExt()).toEqual({
errorSource: ERROR_SOURCES.SYSTEM,
reason: "系统错误:未知错误",
});
expect(
getFailureNotificationExt(
new AutomationError("", { source: ERROR_SOURCES.SYSTEM })
)
).toEqual({
errorSource: ERROR_SOURCES.SYSTEM,
reason: "系统错误:未知错误",
});
});
2026-07-31 16:22:09 +08:00
});
describe("automatic prescription assembly", () => {
test("reloads master data and produces the existing prescription contract", async () => {
const drugId = new ObjectId();
const findConfig = jest.fn((query) => {
if (query.group) {
return {
toArray: jest.fn().mockResolvedValue([
{
key: "store-medicine-dosage-unit",
list: [{ code: "MG", name: "mg" }],
},
{
key: "store-medicine-frequence",
list: [{ code: "BID", name: "每日两次" }],
},
{
key: "store-medicine-administration",
list: [{ code: "PO", name: "口服" }],
},
{
key: "medicine-package-unit",
list: [{ code: "盒", name: "盒" }],
},
]),
};
}
throw new Error("unexpected config find");
});
const db = createCollectionDb({
"hlw-doctor": {
findOne: jest.fn().mockResolvedValue({
doctorNo: "D001",
signatureUrl: "doctor-sign",
onlineStatus: "online",
}),
},
"hlw-diagnosis": {
find: jest.fn().mockReturnValue({
toArray: jest
.fn()
.mockResolvedValue([{ code: "J00", name: "普通感冒" }]),
}),
},
"drug-info": {
find: jest.fn().mockReturnValue({
toArray: jest.fn().mockResolvedValue([
{
_id: drugId,
onSale: true,
name: "测试药品",
erpId: "ERP-1",
insurance_code: "MED-1",
product_id: "P-1",
dosage_form: "片剂",
days: 3,
dosage_unit: "mg",
administration_method: "口服",
freq: "每日两次",
unit: "盒",
specification: "10mg*10片",
package_amount: 10,
recommended_quantity: 1,
},
]),
}),
},
"hlw-config": {
find: findConfig,
findOne: jest
.fn()
.mockResolvedValue({ medicinePurchaseRxDuration: 30 }),
},
});
const order = {
corpId: "corp-1",
orderId: "order-1",
orderSource: "ALIPAY_MINI",
consultType: "storeMedicinePurchase",
doctorCode: "D001",
doctorName: "医生甲",
patientId: "P001",
name: "患者甲",
diseases: ["普通感冒"],
description: "鼻塞流涕",
pastHistoryStr: "两天前起病",
drugs: [
{
_id: drugId.toString(),
dosage: 10,
dosage_unit: "mg",
quantity: 1,
usageName: "口服",
frequencyName: "每日两次",
unit: "盒",
},
],
expireTime: Date.now() + 60_000,
};
const params = await buildAutoPrescriptionParams(db, order);
expect(params).toMatchObject({
complaint: "普通感冒 鼻塞流涕",
presentIllness: "两天前起病",
doctorCAUserId: "doctor-sign",
prescriptionType: "storeMedicinePurchase",
diagnosisList: [{ code: "J00", name: "普通感冒", desc: "" }],
drugs: [
expect.objectContaining({
_id: drugId.toString(),
erpId: "ERP-1",
dosage_unit_code: "MG",
frequencyCode: "BID",
usageCode: "PO",
quantity: 1,
days: 3,
}),
],
});
});
});