744 lines
21 KiB
TypeScript
744 lines
21 KiB
TypeScript
import sensible from "@fastify/sensible";
|
|
import Fastify, { type FastifyInstance } from "fastify";
|
|
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
import { documentationSchemaAjv } from "../utils/documentation-schema-keywords.js";
|
|
|
|
const { fetchMock, requireAuthMock } = vi.hoisted(() => ({
|
|
fetchMock: vi.fn(),
|
|
requireAuthMock: vi.fn(async () => {}),
|
|
}));
|
|
|
|
vi.mock("../plugins/auth.js", () => ({
|
|
requireAuth: requireAuthMock,
|
|
}));
|
|
|
|
function jsonResponse(body: unknown, status = 200): Response {
|
|
return {
|
|
ok: status >= 200 && status < 300,
|
|
status,
|
|
json: async () => body,
|
|
} as Response;
|
|
}
|
|
|
|
function createEmaRow(overrides: Partial<Record<string, unknown>> = {}): Record<string, unknown> {
|
|
return {
|
|
category: "Human",
|
|
medicine_status: "Authorised",
|
|
name_of_medicine: "Aspirin 500 mg tablets",
|
|
international_non_proprietary_name_common_name: "Acetylsalicylic acid",
|
|
active_substance: "Acetylsalicylic acid",
|
|
marketing_authorisation_developer_applicant_holder: "Bayer",
|
|
therapeutic_area_mesh: "Pain",
|
|
therapeutic_indication: "Pain relief",
|
|
atc_code_human: "N02BA01",
|
|
generic_or_hybrid: "No",
|
|
biosimilar: "No",
|
|
marketing_authorisation_date: "01/02/2024",
|
|
ema_product_number: "EMA-ASPIRIN",
|
|
...overrides,
|
|
};
|
|
}
|
|
|
|
async function buildApp(): Promise<FastifyInstance> {
|
|
const { medicationEnrichmentRoutes } = await import("../routes/medication-enrichment.js");
|
|
const app = Fastify({ logger: false, ajv: documentationSchemaAjv });
|
|
await app.register(sensible);
|
|
await app.register(medicationEnrichmentRoutes);
|
|
await app.ready();
|
|
return app;
|
|
}
|
|
|
|
describe("medication enrichment", () => {
|
|
beforeEach(() => {
|
|
vi.resetModules();
|
|
vi.clearAllMocks();
|
|
fetchMock.mockReset();
|
|
requireAuthMock.mockReset();
|
|
requireAuthMock.mockImplementation(async () => {});
|
|
vi.stubGlobal("fetch", fetchMock);
|
|
});
|
|
|
|
it("normalizes German ingredient queries for EMA-backed search results", async () => {
|
|
const { searchMedicationEnrichment } = await import("../services/medication-enrichment.js");
|
|
|
|
fetchMock.mockImplementation((url: string) => {
|
|
if (url.includes("medicines-output-medicines_json-report_en.json")) {
|
|
return Promise.resolve(
|
|
jsonResponse([
|
|
createEmaRow({
|
|
name_of_medicine: "Tylenol 500 mg tablets",
|
|
international_non_proprietary_name_common_name: "Acetaminophen",
|
|
active_substance: "Acetaminophen",
|
|
ema_product_number: "EMA-TYLENOL",
|
|
}),
|
|
createEmaRow({
|
|
name_of_medicine: "Ibuprofen 400 mg tablets",
|
|
international_non_proprietary_name_common_name: "Ibuprofen",
|
|
active_substance: "Ibuprofen",
|
|
ema_product_number: "EMA-IBUPROFEN",
|
|
}),
|
|
])
|
|
);
|
|
}
|
|
if (url.includes("/drugs.json?name=")) {
|
|
return Promise.resolve(jsonResponse({ drugGroup: { conceptGroup: [] } }));
|
|
}
|
|
if (url.includes("api.fda.gov/drug/ndc.json")) {
|
|
return Promise.resolve(jsonResponse({ results: [] }));
|
|
}
|
|
return Promise.reject(new Error(`Unexpected URL: ${url}`));
|
|
});
|
|
|
|
const response = await searchMedicationEnrichment("Paracetamol 500 mg", 5);
|
|
|
|
expect(response.normalizedQuery).toBe("paracetamol 500 mg");
|
|
expect(response.results).toHaveLength(1);
|
|
expect(response.results[0]).toMatchObject({
|
|
code: "EMA-TYLENOL",
|
|
name: "Tylenol 500 mg tablets",
|
|
matchType: "ingredient",
|
|
source: "ema",
|
|
});
|
|
});
|
|
|
|
it("requires auth and returns EMA search results from the route", async () => {
|
|
const app = await buildApp();
|
|
fetchMock.mockImplementation((url: string) => {
|
|
if (url.includes("/drugs.json?name=")) {
|
|
return Promise.resolve(jsonResponse({ drugGroup: { conceptGroup: [] } }));
|
|
}
|
|
if (url.includes("api.fda.gov/drug/ndc.json")) {
|
|
return Promise.resolve(jsonResponse({ results: [] }));
|
|
}
|
|
if (url.includes("medicines-output-medicines_json-report_en.json")) {
|
|
return Promise.resolve(jsonResponse([createEmaRow()]));
|
|
}
|
|
return Promise.reject(new Error(`Unexpected URL: ${url}`));
|
|
});
|
|
|
|
const response = await app.inject({
|
|
method: "GET",
|
|
url: "/medication-enrichment/search?q=aspirin&limit=1",
|
|
});
|
|
|
|
expect(response.statusCode).toBe(200);
|
|
expect(requireAuthMock).toHaveBeenCalledTimes(1);
|
|
expect(response.json()).toMatchObject({
|
|
query: "aspirin",
|
|
normalizedQuery: "aspirin",
|
|
hasMore: false,
|
|
results: [
|
|
{
|
|
code: "EMA-ASPIRIN",
|
|
name: "Aspirin 500 mg tablets",
|
|
source: "ema",
|
|
},
|
|
],
|
|
});
|
|
|
|
await app.close();
|
|
});
|
|
|
|
it("falls back from EMA to RxNorm and openFDA search results when EMA has no match", async () => {
|
|
const { searchMedicationEnrichment } = await import("../services/medication-enrichment.js");
|
|
|
|
fetchMock.mockImplementation((url: string) => {
|
|
if (url.includes("medicines-output-medicines_json-report_en.json")) {
|
|
return Promise.resolve(jsonResponse([createEmaRow()]));
|
|
}
|
|
if (url.includes("/drugs.json?name=semaglutide")) {
|
|
return Promise.resolve(
|
|
jsonResponse({
|
|
drugGroup: {
|
|
conceptGroup: [
|
|
{
|
|
tty: "SBD",
|
|
conceptProperties: [
|
|
{
|
|
rxcui: "12345",
|
|
name: "Semaglutide 0.25 MG Oral Tablet [Wegovy]",
|
|
synonym: "Wegovy 0.25 mg oral tablet",
|
|
},
|
|
],
|
|
},
|
|
],
|
|
},
|
|
})
|
|
);
|
|
}
|
|
if (url.includes("api.fda.gov/drug/ndc.json")) {
|
|
return Promise.resolve(
|
|
jsonResponse({
|
|
results: [
|
|
{
|
|
product_ndc: "00011-1111",
|
|
brand_name: "Ozempic",
|
|
generic_name: "Semaglutide",
|
|
dosage_form: "Tablet",
|
|
marketing_start_date: "20240101",
|
|
packaging: [{ description: "2 blisters in 1 carton / 10 tablets in 1 blister" }],
|
|
},
|
|
],
|
|
})
|
|
);
|
|
}
|
|
return Promise.reject(new Error(`Unexpected URL: ${url}`));
|
|
});
|
|
|
|
const response = await searchMedicationEnrichment("Semaglutide", 3);
|
|
|
|
expect(response.hasMore).toBe(false);
|
|
expect(response.results).toEqual(
|
|
expect.arrayContaining([
|
|
expect.objectContaining({
|
|
code: "12345",
|
|
name: "Wegovy",
|
|
genericName: "Semaglutide",
|
|
source: "rxnorm",
|
|
}),
|
|
expect.objectContaining({
|
|
code: "00011-1111",
|
|
name: "Ozempic",
|
|
genericName: "Semaglutide",
|
|
source: "openfda",
|
|
}),
|
|
])
|
|
);
|
|
expect(response.results.find((result) => result.code === "00011-1111")?.packageOptions).toEqual([
|
|
{
|
|
label: "2 blisters in 1 carton / 10 tablets in 1 blister",
|
|
description: "2 blisters in 1 carton / 10 tablets in 1 blister",
|
|
packageType: "blister",
|
|
packCount: 1,
|
|
blistersPerPack: 2,
|
|
pillsPerBlister: 10,
|
|
totalPills: 20,
|
|
looseTablets: 0,
|
|
packageAmountValue: null,
|
|
packageAmountUnit: null,
|
|
},
|
|
]);
|
|
});
|
|
|
|
it("prioritizes results with package sizes before source-only matches", async () => {
|
|
const { searchMedicationEnrichment } = await import("../services/medication-enrichment.js");
|
|
|
|
fetchMock.mockImplementation((url: string) => {
|
|
if (url.includes("medicines-output-medicines_json-report_en.json")) {
|
|
return Promise.resolve(jsonResponse([createEmaRow()]));
|
|
}
|
|
if (url.includes("/drugs.json?name=")) {
|
|
return Promise.resolve(
|
|
jsonResponse({
|
|
drugGroup: {
|
|
conceptGroup: [
|
|
{
|
|
tty: "SBD",
|
|
conceptProperties: [
|
|
{
|
|
rxcui: "1191",
|
|
name: "Aspirin 500 MG Oral Tablet [Aspirin]",
|
|
synonym: "Aspirin 500 mg oral tablet",
|
|
},
|
|
],
|
|
},
|
|
],
|
|
},
|
|
})
|
|
);
|
|
}
|
|
if (url.includes("api.fda.gov/drug/ndc.json")) {
|
|
return Promise.resolve(
|
|
jsonResponse({
|
|
results: [
|
|
{
|
|
product_ndc: "00011-1111",
|
|
brand_name: "Bayer Aspirin",
|
|
generic_name: "Acetylsalicylic acid",
|
|
dosage_form: "Tablet",
|
|
marketing_start_date: "20240101",
|
|
packaging: [{ description: "2 blisters in 1 carton / 10 tablets in 1 blister" }],
|
|
},
|
|
],
|
|
})
|
|
);
|
|
}
|
|
return Promise.reject(new Error(`Unexpected URL: ${url}`));
|
|
});
|
|
|
|
const response = await searchMedicationEnrichment("Aspirin", 3);
|
|
|
|
expect(response.hasMore).toBe(false);
|
|
expect(response.results).toHaveLength(3);
|
|
expect(response.results[0]).toMatchObject({
|
|
code: "00011-1111",
|
|
source: "openfda",
|
|
});
|
|
expect(response.results[1]).toMatchObject({
|
|
code: "1191",
|
|
source: "rxnorm",
|
|
});
|
|
expect(response.results[2]).toMatchObject({
|
|
code: "EMA-ASPIRIN",
|
|
source: "ema",
|
|
});
|
|
});
|
|
|
|
it("sorts richer package hits ahead of package-bearing results with fewer options", async () => {
|
|
const { searchMedicationEnrichment } = await import("../services/medication-enrichment.js");
|
|
|
|
fetchMock.mockImplementation((url: string) => {
|
|
if (url.includes("medicines-output-medicines_json-report_en.json")) {
|
|
return Promise.resolve(jsonResponse([createEmaRow()]));
|
|
}
|
|
if (url.includes("/drugs.json?name=")) {
|
|
return Promise.resolve(jsonResponse({ drugGroup: { conceptGroup: [] } }));
|
|
}
|
|
if (url.includes("api.fda.gov/drug/ndc.json")) {
|
|
return Promise.resolve(
|
|
jsonResponse({
|
|
results: [
|
|
{
|
|
product_ndc: "00011-1111",
|
|
brand_name: "Ibuprofen Max",
|
|
generic_name: "Ibuprofen",
|
|
dosage_form: "Tablet",
|
|
marketing_start_date: "20240101",
|
|
packaging: [{ description: "60 tablets in 1 bottle" }, { description: "120 tablets in 1 bottle" }],
|
|
},
|
|
{
|
|
product_ndc: "00022-2222",
|
|
brand_name: "Ibuprofen Compact",
|
|
generic_name: "Ibuprofen",
|
|
dosage_form: "Tablet",
|
|
marketing_start_date: "20240101",
|
|
packaging: [{ description: "20 tablets in 1 blister" }],
|
|
},
|
|
],
|
|
})
|
|
);
|
|
}
|
|
return Promise.reject(new Error(`Unexpected URL: ${url}`));
|
|
});
|
|
|
|
const response = await searchMedicationEnrichment("Ibuprofen", 3);
|
|
|
|
expect(response.results.slice(0, 2)).toMatchObject([
|
|
{
|
|
code: "00011-1111",
|
|
source: "openfda",
|
|
},
|
|
{
|
|
code: "00022-2222",
|
|
source: "openfda",
|
|
},
|
|
]);
|
|
expect(response.results[0].packageOptions).toHaveLength(2);
|
|
expect(response.results[1].packageOptions).toHaveLength(1);
|
|
});
|
|
|
|
it("validates malformed search requests", async () => {
|
|
const app = await buildApp();
|
|
|
|
const response = await app.inject({
|
|
method: "GET",
|
|
url: "/medication-enrichment/search?q=",
|
|
});
|
|
|
|
expect(response.statusCode).toBe(400);
|
|
expect(fetchMock).not.toHaveBeenCalled();
|
|
|
|
await app.close();
|
|
});
|
|
|
|
it("returns enrichment suggestions with optional RxNorm strength data", async () => {
|
|
const app = await buildApp();
|
|
fetchMock
|
|
.mockResolvedValueOnce(
|
|
jsonResponse([
|
|
createEmaRow({
|
|
name_of_medicine: "Tylenol 500 mg tablets",
|
|
international_non_proprietary_name_common_name: "Acetaminophen",
|
|
active_substance: "Acetaminophen",
|
|
ema_product_number: "EMA-TYLENOL",
|
|
}),
|
|
])
|
|
)
|
|
.mockResolvedValueOnce(jsonResponse({ idGroup: { rxnormId: ["161"] } }))
|
|
.mockResolvedValueOnce(
|
|
jsonResponse({
|
|
relatedGroup: {
|
|
conceptGroup: [
|
|
{
|
|
conceptProperties: [
|
|
{ name: "Acetaminophen 500 MG Oral Tablet" },
|
|
{ name: "Acetaminophen 650 MG Oral Tablet" },
|
|
],
|
|
},
|
|
],
|
|
},
|
|
})
|
|
);
|
|
|
|
const response = await app.inject({
|
|
method: "POST",
|
|
url: "/medication-enrichment/enrich",
|
|
payload: {
|
|
query: "Paracetamol",
|
|
name: "Tylenol 500 mg tablets",
|
|
genericName: "Acetaminophen",
|
|
},
|
|
});
|
|
|
|
expect(response.statusCode).toBe(200);
|
|
expect(response.json()).toMatchObject({
|
|
selection: {
|
|
name: "Tylenol 500 mg tablets",
|
|
genericName: "Acetaminophen",
|
|
source: "ema+rxnorm",
|
|
},
|
|
suggestions: {
|
|
medicationForm: "tablet",
|
|
strengthOptions: [
|
|
{ label: "500 mg", pillWeightMg: 500, doseUnit: "mg" },
|
|
{ label: "650 mg", pillWeightMg: 650, doseUnit: "mg" },
|
|
],
|
|
},
|
|
meta: {
|
|
rxNormMatched: true,
|
|
openFdaMatched: false,
|
|
partial: false,
|
|
note: null,
|
|
},
|
|
});
|
|
|
|
await app.close();
|
|
});
|
|
|
|
it("includes package suggestions from openFDA fallback in route responses", async () => {
|
|
const app = await buildApp();
|
|
fetchMock.mockImplementation((url: string) => {
|
|
if (url.includes("medicines-output-medicines_json-report_en.json")) {
|
|
return Promise.resolve(
|
|
jsonResponse([
|
|
createEmaRow({
|
|
name_of_medicine: "Tylenol 500 mg tablets",
|
|
international_non_proprietary_name_common_name: "Acetaminophen",
|
|
active_substance: "Acetaminophen",
|
|
ema_product_number: "EMA-TYLENOL",
|
|
}),
|
|
])
|
|
);
|
|
}
|
|
if (url.includes("/rxcui.json?name=acetaminophen&search=2")) {
|
|
return Promise.resolve(jsonResponse({ idGroup: {} }));
|
|
}
|
|
if (url.includes("api.fda.gov/drug/ndc.json")) {
|
|
return Promise.resolve(
|
|
jsonResponse({
|
|
results: [
|
|
{
|
|
product_ndc: "00011-1111",
|
|
brand_name: "Tylenol",
|
|
generic_name: "Acetaminophen",
|
|
dosage_form: "Tablet",
|
|
active_ingredients: [{ name: "Acetaminophen", strength: "500 mg" }],
|
|
packaging: [{ description: "30 tablets in 1 bottle" }],
|
|
},
|
|
],
|
|
})
|
|
);
|
|
}
|
|
return Promise.reject(new Error(`Unexpected URL: ${url}`));
|
|
});
|
|
|
|
const response = await app.inject({
|
|
method: "POST",
|
|
url: "/medication-enrichment/enrich",
|
|
payload: {
|
|
query: "Paracetamol",
|
|
name: "Tylenol 500 mg tablets",
|
|
genericName: "Acetaminophen",
|
|
},
|
|
});
|
|
|
|
expect(response.statusCode).toBe(200);
|
|
expect(response.json()).toMatchObject({
|
|
selection: {
|
|
name: "Tylenol 500 mg tablets",
|
|
genericName: "Acetaminophen",
|
|
source: "ema+openfda",
|
|
},
|
|
suggestions: {
|
|
medicationForm: "tablet",
|
|
strengthOptions: [{ label: "500 mg", pillWeightMg: 500, doseUnit: "mg" }],
|
|
packageOptions: [
|
|
{
|
|
label: "30 tablets in 1 bottle",
|
|
description: "30 tablets in 1 bottle",
|
|
packageType: "bottle",
|
|
packCount: 1,
|
|
blistersPerPack: null,
|
|
pillsPerBlister: null,
|
|
totalPills: 30,
|
|
looseTablets: 30,
|
|
packageAmountValue: null,
|
|
packageAmountUnit: null,
|
|
},
|
|
],
|
|
},
|
|
meta: {
|
|
rxNormMatched: false,
|
|
openFdaMatched: true,
|
|
partial: false,
|
|
note: null,
|
|
},
|
|
});
|
|
|
|
await app.close();
|
|
});
|
|
|
|
it("keeps incomplete-coverage messaging honest when RxNorm enrichment fails", async () => {
|
|
const { enrichMedicationSelection } = await import("../services/medication-enrichment.js");
|
|
|
|
fetchMock.mockImplementation((url: string) => {
|
|
if (url.includes("medicines-output-medicines_json-report_en.json")) {
|
|
return Promise.resolve(
|
|
jsonResponse([
|
|
createEmaRow({
|
|
name_of_medicine: "Tylenol 500 mg tablets",
|
|
international_non_proprietary_name_common_name: "Acetaminophen",
|
|
active_substance: "Acetaminophen",
|
|
ema_product_number: "EMA-TYLENOL",
|
|
}),
|
|
])
|
|
);
|
|
}
|
|
if (url.includes("/rxcui.json?name=acetaminophen&search=2")) {
|
|
return Promise.reject(new Error("rxnorm timeout"));
|
|
}
|
|
if (url.includes("api.fda.gov/drug/ndc.json")) {
|
|
return Promise.resolve(jsonResponse({ results: [] }));
|
|
}
|
|
return Promise.reject(new Error(`Unexpected URL: ${url}`));
|
|
});
|
|
|
|
const response = await enrichMedicationSelection({
|
|
query: "Paracetamol",
|
|
name: "Tylenol 500 mg tablets",
|
|
genericName: "Acetaminophen",
|
|
});
|
|
|
|
expect(response.selection.source).toBe("ema");
|
|
expect(response.suggestions.strengthOptions).toEqual([]);
|
|
expect(response.meta).toEqual({
|
|
rxNormMatched: false,
|
|
openFdaMatched: false,
|
|
partial: true,
|
|
note: "Returned EMA enrichment without RxNorm suggestions.",
|
|
});
|
|
});
|
|
|
|
it("enriches RxNorm selections by code and falls back to openFDA without best-match guessing", async () => {
|
|
const { enrichMedicationSelection } = await import("../services/medication-enrichment.js");
|
|
|
|
fetchMock.mockImplementation((url: string) => {
|
|
if (url.includes("/rxcui/12345/related.json")) {
|
|
return Promise.resolve(
|
|
jsonResponse({
|
|
relatedGroup: {
|
|
conceptGroup: [],
|
|
},
|
|
})
|
|
);
|
|
}
|
|
if (url.includes("api.fda.gov/drug/ndc.json")) {
|
|
return Promise.resolve(
|
|
jsonResponse({
|
|
results: [
|
|
{
|
|
product_ndc: "00011-1111",
|
|
brand_name: "Ozempic",
|
|
generic_name: "Semaglutide",
|
|
dosage_form: "Tablet",
|
|
active_ingredients: [{ name: "Semaglutide", strength: "2 mg" }],
|
|
},
|
|
],
|
|
})
|
|
);
|
|
}
|
|
return Promise.reject(new Error(`Unexpected URL: ${url}`));
|
|
});
|
|
|
|
const response = await enrichMedicationSelection({
|
|
query: "Ozempic",
|
|
name: "Ozempic",
|
|
genericName: "Semaglutide",
|
|
code: "12345",
|
|
source: "rxnorm",
|
|
});
|
|
|
|
expect(response).toMatchObject({
|
|
selection: {
|
|
name: "Ozempic",
|
|
genericName: "Semaglutide",
|
|
source: "rxnorm+openfda",
|
|
},
|
|
suggestions: {
|
|
medicationForm: "tablet",
|
|
strengthOptions: [{ label: "2 mg", pillWeightMg: 2, doseUnit: "mg" }],
|
|
},
|
|
meta: {
|
|
rxNormMatched: false,
|
|
openFdaMatched: true,
|
|
partial: false,
|
|
note: null,
|
|
},
|
|
});
|
|
});
|
|
|
|
it("enriches openFDA selections by code and augments them with RxNorm strength data", async () => {
|
|
const { enrichMedicationSelection } = await import("../services/medication-enrichment.js");
|
|
|
|
fetchMock.mockImplementation((url: string) => {
|
|
if (url.includes("search=product_ndc%3A%2200011-1111%22")) {
|
|
return Promise.resolve(
|
|
jsonResponse({
|
|
results: [
|
|
{
|
|
product_ndc: "00011-1111",
|
|
brand_name: "US Ibuprofen",
|
|
generic_name: "Ibuprofen",
|
|
dosage_form: "Tablet",
|
|
active_ingredients: [{ name: "Ibuprofen", strength: "200 mg" }],
|
|
packaging: [{ description: "100 mL in 1 bottle" }],
|
|
},
|
|
],
|
|
})
|
|
);
|
|
}
|
|
if (url.includes("/rxcui.json?name=ibuprofen&search=2")) {
|
|
return Promise.resolve(jsonResponse({ idGroup: { rxnormId: ["161"] } }));
|
|
}
|
|
if (url.includes("/rxcui/161/related.json")) {
|
|
return Promise.resolve(
|
|
jsonResponse({
|
|
relatedGroup: {
|
|
conceptGroup: [
|
|
{
|
|
conceptProperties: [
|
|
{ name: "Ibuprofen 200 MG Oral Tablet" },
|
|
{ name: "Ibuprofen 400 MG Oral Tablet" },
|
|
],
|
|
},
|
|
],
|
|
},
|
|
})
|
|
);
|
|
}
|
|
return Promise.reject(new Error(`Unexpected URL: ${url}`));
|
|
});
|
|
|
|
const response = await enrichMedicationSelection({
|
|
query: "US Ibuprofen",
|
|
name: "US Ibuprofen",
|
|
genericName: "Ibuprofen",
|
|
code: "00011-1111",
|
|
source: "openfda",
|
|
});
|
|
|
|
expect(response).toMatchObject({
|
|
selection: {
|
|
name: "US Ibuprofen",
|
|
genericName: "Ibuprofen",
|
|
source: "rxnorm+openfda",
|
|
},
|
|
suggestions: {
|
|
medicationForm: "tablet",
|
|
strengthOptions: [
|
|
{ label: "200 mg", pillWeightMg: 200, doseUnit: "mg" },
|
|
{ label: "400 mg", pillWeightMg: 400, doseUnit: "mg" },
|
|
],
|
|
packageOptions: [
|
|
{
|
|
label: "100 mL in 1 bottle",
|
|
description: "100 mL in 1 bottle",
|
|
packageType: "liquid_container",
|
|
packCount: 1,
|
|
blistersPerPack: null,
|
|
pillsPerBlister: null,
|
|
totalPills: 100,
|
|
looseTablets: 100,
|
|
packageAmountValue: 100,
|
|
packageAmountUnit: "ml",
|
|
},
|
|
],
|
|
},
|
|
meta: {
|
|
rxNormMatched: true,
|
|
openFdaMatched: true,
|
|
partial: false,
|
|
note: null,
|
|
},
|
|
});
|
|
});
|
|
|
|
it("returns not found when an explicit selection cannot be resolved", async () => {
|
|
const app = await buildApp();
|
|
fetchMock.mockResolvedValueOnce(jsonResponse([createEmaRow()]));
|
|
|
|
const response = await app.inject({
|
|
method: "POST",
|
|
url: "/medication-enrichment/enrich",
|
|
payload: {
|
|
query: "Unknown",
|
|
name: "Completely Different Medication",
|
|
genericName: "No match",
|
|
},
|
|
});
|
|
|
|
expect(response.statusCode).toBe(404);
|
|
expect(response.json()).toMatchObject({
|
|
code: "MEDICATION_ENRICHMENT_NOT_FOUND",
|
|
error: "Selected medication could not be resolved.",
|
|
});
|
|
|
|
await app.close();
|
|
});
|
|
|
|
it("keeps split module exports aligned with the canonical enrichment service", async () => {
|
|
const indexExports = await import("../services/medication-enrichment/index.js");
|
|
const searchExports = await import("../services/medication-enrichment/search.js");
|
|
const adapterExports = await import("../services/medication-enrichment/adapters.js");
|
|
const canonical = await import("../services/medication-enrichment.js");
|
|
|
|
expect(indexExports.searchMedicationEnrichment).toBe(canonical.searchMedicationEnrichment);
|
|
expect(indexExports.enrichMedicationSelection).toBe(canonical.enrichMedicationSelection);
|
|
expect(searchExports.searchMedicationEnrichment).toBe(canonical.searchMedicationEnrichment);
|
|
expect(adapterExports.MEDICATION_ENRICHMENT_SEARCH_DEFAULT_LIMIT).toBe(
|
|
canonical.MEDICATION_ENRICHMENT_SEARCH_DEFAULT_LIMIT
|
|
);
|
|
expect(adapterExports.MEDICATION_ENRICHMENT_SEARCH_MAX_LIMIT).toBe(
|
|
canonical.MEDICATION_ENRICHMENT_SEARCH_MAX_LIMIT
|
|
);
|
|
});
|
|
|
|
it("returns transport-safe 503 payload when search lookup fails unexpectedly", async () => {
|
|
const app = await buildApp();
|
|
fetchMock.mockRejectedValue(new Error("network unavailable"));
|
|
|
|
const response = await app.inject({
|
|
method: "GET",
|
|
url: "/medication-enrichment/search?q=aspirin&limit=1",
|
|
});
|
|
|
|
expect(response.statusCode).toBe(503);
|
|
expect(response.json()).toEqual({
|
|
error: "Medication enrichment is temporarily unavailable.",
|
|
code: "MEDICATION_ENRICHMENT_UNAVAILABLE",
|
|
});
|
|
|
|
await app.close();
|
|
});
|
|
});
|