hn-hlw-service/hlw/automation/index.test.js
2026-09-04 09:57:39 +08:00

1080 lines
31 KiB
JavaScript
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.

const { ObjectId } = require("mongodb");
jest.mock("../tencent-im", () => jest.fn());
jest.mock("../diagnostic-record", () => jest.fn());
const tencentIM = require("../tencent-im");
const diagnosticRecord = require("../diagnostic-record");
const {
AUTOMATION_TYPES,
COLLECTION,
ERROR_SOURCES,
MANUAL_OPEN_RX_MESSAGE,
TASK_CANCEL_REASONS,
TASK_STATUS,
AutomationError,
buildAutoPrescriptionParams,
calculateDueAt,
claimNextTask,
enqueueTask,
getFailureNotificationExt,
processClaimedTask,
reconcileAutomationTasks,
scheduleAutoOpenRx,
} = 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();
jest.clearAllMocks();
});
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 }),
$unset: { completedAt: "", result: "", cancelReason: "" },
})
);
});
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);
});
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 }
);
});
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
);
});
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({
Data: "AUTORXFAIL",
Desc: "notification",
Ext: JSON.stringify({
errorSource: ERROR_SOURCES.SYSTEM,
reason: "系统错误:开方医生不存在",
}),
}),
}),
],
}),
db
);
expect(taskUpdateOne).not.toHaveBeenCalledWith(
expect.anything(),
expect.objectContaining({
$set: expect.objectContaining({ status: TASK_STATUS.PENDING }),
})
);
});
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: "该处方存在不合理用药",
}),
})
);
});
test("fails automatic audit immediately and sends the failure reason", async () => {
tencentIM.mockResolvedValue({ success: true });
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",
}),
},
"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-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({
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({
Data: "AUTORXFAIL",
Desc: "notification",
Ext: JSON.stringify({
errorSource: ERROR_SOURCES.SYSTEM,
reason: "系统错误:审方药师暂不在线",
}),
}),
}),
],
}),
db
);
expect(taskUpdateOne).not.toHaveBeenCalledWith(
expect.anything(),
expect.objectContaining({
$set: expect.objectContaining({ status: TASK_STATUS.PENDING }),
})
);
});
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: "系统错误:未知错误",
});
});
});
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,
}),
],
});
});
});