2026-08-12 15:20:58 +08:00

283 lines
11 KiB
JavaScript

jest.mock("../../utils/sm4-util", () => ({
encryptDataForSm3: jest.fn((value) => Buffer.from(value).toString("base64")),
decryptDataForSm3: jest.fn((value) => {
const patientValues = {
encryptedName: "张三",
encryptedCertNo: "11010519491231002X",
encryptedMobile: "13800138000",
};
return patientValues[value] || Buffer.from(value, "base64").toString();
}),
}));
jest.mock("../hn-his", () => jest.fn());
const { ObjectId } = require("mongodb");
const Sm4Util = require("../../utils/sm4-util");
const hnHis = require("../hn-his");
const selfAuth = require("./index");
const OWNER_USER_ID = "507f1f77bcf86cd799439011";
const OTHER_USER_ID = "507f191e810c19729de860ea";
const PATIENT_ID = "507f1f77bcf86cd799439012";
const REQUEST_ID = "507f1f77bcf86cd799439013";
const ACCOUNT_ID = "2088000000000000";
function sameValue(left, right) {
if (left instanceof ObjectId || right instanceof ObjectId) {
return String(left) === String(right);
}
return left === right;
}
function matches(record, query) {
return Object.entries(query).every(([key, expected]) => {
if (expected && expected.$in) return expected.$in.includes(record[key]);
return sameValue(record[key], expected);
});
}
function createDb({ requests = [], patient = null } = {}) {
const state = { requests, patient };
const authCollection = {
dropIndex: jest.fn().mockRejectedValue(Object.assign(new Error("index not found"), {
code: 27,
codeName: "IndexNotFound",
})),
createIndex: jest.fn().mockResolvedValue("index"),
insertOne: jest.fn(async (record) => {
if (!record._id) record._id = new ObjectId();
state.requests.push(record);
return { insertedId: record._id };
}),
findOne: jest.fn(async (query) => state.requests.find((record) => matches(record, query)) || null),
findOneAndUpdate: jest.fn(async (query, update) => {
const record = state.requests.find((entry) => matches(entry, query));
if (!record) return null;
Object.assign(record, update.$set || {});
if (update.$inc) {
Object.entries(update.$inc).forEach(([key, value]) => {
record[key] = (record[key] || 0) + value;
});
}
return record;
}),
updateOne: jest.fn(async (query, update) => {
const record = state.requests.find((entry) => matches(entry, query));
if (!record) return { modifiedCount: 0 };
Object.assign(record, update.$set || {});
if (update.$unset) Object.keys(update.$unset).forEach((key) => delete record[key]);
return { modifiedCount: 1 };
}),
};
const patientCollection = {
findOne: jest.fn(async (query) => {
if (!state.patient || !matches(state.patient, query)) return null;
return state.patient;
}),
};
const db = {
collection: jest.fn((name) => {
if (name === "hlw-patient-self-auth") return authCollection;
if (name === "hlw-patient") return patientCollection;
throw new Error(`Unexpected collection: ${name}`);
}),
};
return { db, state, authCollection };
}
function makePatient() {
return {
_id: new ObjectId(PATIENT_ID),
accountId: ACCOUNT_ID,
anotherName: "encryptedName",
anotherIdNo: "encryptedCertNo",
anotherMobile: "encryptedMobile",
address: "测试地址",
disabled: false,
};
}
function makeRequest(status = "pending", timestamp = Date.now()) {
return {
_id: new ObjectId(REQUEST_ID),
patientId: new ObjectId(PATIENT_ID),
ownerAccountId: ACCOUNT_ID,
ownerUserId: OWNER_USER_ID,
requestTimestamp: timestamp,
status,
attempts: 0,
};
}
function requestId(request) {
return request._id.toString();
}
beforeEach(() => {
jest.clearAllMocks();
});
test("rejects malformed authorization ids", async () => {
const { db } = createDb();
await expect(selfAuth({ type: "getSelfAuthPatient", id: "bad-id" }, db, {}))
.resolves.toEqual({ success: false, message: "授权链接无效" });
expect(db.collection).not.toHaveBeenCalled();
});
test("creates a pending request owned by the authenticated user", async () => {
const { db, state } = createDb({ patient: makePatient() });
const result = await selfAuth({
type: "createSelfAuthRequest",
patientId: PATIENT_ID,
accountId: ACCOUNT_ID,
}, db, { userId: OWNER_USER_ID });
expect(result.success).toBe(true);
expect(result.data.id).toMatch(/^[a-f\d]{24}$/i);
expect(result.data.id).toBe(state.requests[0]._id.toString());
expect(state.requests[0]).toEqual(expect.objectContaining({
ownerAccountId: ACCOUNT_ID,
ownerUserId: OWNER_USER_ID,
status: "pending",
}));
expect(state.requests[0]).not.toHaveProperty("authId");
expect(state.requests[0]).not.toHaveProperty("psnToken");
});
test("returns masked patient details and rejects an expired request", async () => {
const active = makeRequest();
const activeDb = createDb({ requests: [active], patient: makePatient() }).db;
const activeResult = await selfAuth({ type: "getSelfAuthPatient", id: requestId(active) }, activeDb, {});
expect(activeResult).toEqual(expect.objectContaining({
success: true,
data: expect.objectContaining({
name: "张三",
certNo: "1101***********02X",
sex: "女",
}),
}));
const expired = makeRequest("pending", Date.now() - 11 * 60 * 1000);
const expiredDb = createDb({ requests: [expired], patient: makePatient() }).db;
const expiredResult = await selfAuth({ type: "getSelfAuthPatient", id: requestId(expired) }, expiredDb, {});
expect(expiredResult).toEqual({ success: false, message: "授权链接已过期,请重新发起授权" });
});
test("rejects missing requests and patients", async () => {
const missingRequest = makeRequest();
const emptyDb = createDb({ patient: makePatient() }).db;
await expect(selfAuth({ type: "getSelfAuthPatient", id: requestId(missingRequest) }, emptyDb, {}))
.resolves.toEqual({ success: false, message: "授权链接不存在或已失效" });
const request = makeRequest();
const noPatientDb = createDb({ requests: [request] }).db;
await expect(selfAuth({ type: "getSelfAuthPatient", id: requestId(request) }, noPatientDb, {}))
.resolves.toEqual({ success: false, message: "就诊人不存在" });
});
test("reports zero years old for a valid infant id", () => {
const now = new Date();
const date = [
now.getFullYear(),
String(now.getMonth() + 1).padStart(2, "0"),
String(now.getDate()).padStart(2, "0"),
].join("");
const body = `110105${date}001`;
const factors = [7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2];
const parity = ["1", "0", "X", "9", "8", "7", "6", "5", "4", "3", "2"];
const sum = body.split("").reduce((total, digit, index) => total + Number(digit) * factors[index], 0);
const certNo = `${body}${parity[sum % 11]}`;
expect(selfAuth.getPersonInfo(certNo, now).age).toBe(0);
});
test("stores only the encrypted matching HIS archive after successful submission", async () => {
const request = makeRequest();
const { db, state } = createDb({ requests: [request], patient: makePatient() });
hnHis.mockResolvedValue({
success: true,
list: [{ socialno: "11010519491231002X", isyb: "1", patientId: "his-patient", blhno: "card-no" }],
tmbxx: [{ code: "11", name: "普通门诊" }],
});
const result = await selfAuth({ type: "submitSelfAuth", id: requestId(request), psnToken: "secret-token" }, db, { userId: OTHER_USER_ID });
expect(result).toEqual({ success: true, message: "授权并建档成功", data: { status: "success" } });
expect(state.requests[0].status).toBe("success");
expect(state.requests[0].encryptedResult).toBeTruthy();
expect(state.requests[0]).not.toHaveProperty("psnToken");
expect(JSON.stringify(state.requests[0])).not.toContain("secret-token");
expect(Sm4Util.encryptDataForSm3).toHaveBeenCalledWith(expect.stringContaining("his-patient"));
});
test("allows a failed request to be retried", async () => {
const request = makeRequest("failed");
request.errorMessage = "previous failure";
const { db, state } = createDb({ requests: [request], patient: makePatient() });
hnHis.mockResolvedValue({
success: true,
list: [{ socialno: "11010519491231002X", isyb: "1" }],
tmbxx: [],
});
const result = await selfAuth({ type: "submitSelfAuth", id: requestId(request), psnToken: "new-token" }, db, { userId: OTHER_USER_ID });
expect(result.success).toBe(true);
expect(state.requests[0].status).toBe("success");
expect(state.requests[0].attempts).toBe(1);
});
test("only one concurrent submission can call HIS", async () => {
const request = makeRequest();
const { db } = createDb({ requests: [request], patient: makePatient() });
let releaseHis;
hnHis.mockImplementation(() => new Promise((resolve) => {
releaseHis = () => resolve({
success: true,
list: [{ socialno: "11010519491231002X", isyb: "1" }],
tmbxx: [],
});
}));
const first = selfAuth({ type: "submitSelfAuth", id: requestId(request), psnToken: "token-1" }, db, { userId: OTHER_USER_ID });
await new Promise((resolve) => setImmediate(resolve));
const second = await selfAuth({ type: "submitSelfAuth", id: requestId(request), psnToken: "token-2" }, db, { userId: OTHER_USER_ID });
expect(second).toEqual({
success: false,
message: "授权正在处理中,请勿重复提交",
data: { status: "processing" },
});
expect(hnHis).toHaveBeenCalledTimes(1);
releaseHis();
await expect(first).resolves.toEqual({ success: true, message: "授权并建档成功", data: { status: "success" } });
});
test.each([
[{ success: true, list: [{ socialno: "330100000000000000", isyb: "1" }] }, "医保建档失败,未找到匹配档案"],
[{ success: true, list: [{ socialno: "11010519491231002X", isyb: "0" }] }, "医保建档失败"],
])("marks invalid HIS results as failed", async (hisResult, message) => {
const request = makeRequest();
const { db, state } = createDb({ requests: [request], patient: makePatient() });
hnHis.mockResolvedValue(hisResult);
const result = await selfAuth({ type: "submitSelfAuth", id: requestId(request), psnToken: "token" }, db, { userId: OTHER_USER_ID });
expect(result.success).toBe(false);
expect(result.message).toBe(message);
expect(state.requests[0].status).toBe("failed");
});
test("is idempotent after success and restricts result access to the owner", async () => {
const archive = { socialno: "11010519491231002X", isyb: "1" };
const request = {
...makeRequest("success"),
encryptedResult: Buffer.from(JSON.stringify(archive)).toString("base64"),
encryption: "SM4",
};
const { db } = createDb({ requests: [request], patient: makePatient() });
const repeated = await selfAuth({ type: "submitSelfAuth", id: requestId(request), psnToken: "token" }, db, { userId: OTHER_USER_ID });
expect(repeated.success).toBe(true);
expect(hnHis).not.toHaveBeenCalled();
const denied = await selfAuth({ type: "getSelfAuthResult", id: requestId(request) }, db, { userId: OTHER_USER_ID });
expect(denied).toEqual({ success: false, message: "授权记录不存在或无权访问" });
const allowed = await selfAuth({ type: "getSelfAuthResult", id: requestId(request) }, db, { userId: OWNER_USER_ID });
expect(allowed).toEqual({ success: true, message: "授权成功", data: { status: "success", hisArchive: archive } });
});