@fhirust/sdk 0.2.0 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +430 -0
- package/dist/bin/cli.d.ts +1 -0
- package/dist/bin/cli.d.ts.map +1 -1
- package/dist/bin/cli.js +167 -3
- package/dist/bin/cli.js.map +1 -1
- package/dist/plugin.d.ts +5 -2
- package/dist/plugin.d.ts.map +1 -1
- package/dist/plugin.js +15 -7
- package/dist/plugin.js.map +1 -1
- package/dist/r4/allergy-intolerance.d.ts +10 -10
- package/dist/r4/allergy-intolerance.d.ts.map +1 -1
- package/dist/r4/appointment-response.d.ts +16 -0
- package/dist/r4/appointment-response.d.ts.map +1 -0
- package/dist/r4/appointment-response.js +2 -0
- package/dist/r4/appointment-response.js.map +1 -0
- package/dist/r4/appointment.d.ts +37 -0
- package/dist/r4/appointment.d.ts.map +1 -0
- package/dist/r4/appointment.js +2 -0
- package/dist/r4/appointment.js.map +1 -0
- package/dist/r4/care-plan.d.ts +59 -0
- package/dist/r4/care-plan.d.ts.map +1 -0
- package/dist/r4/care-plan.js +2 -0
- package/dist/r4/care-plan.js.map +1 -0
- package/dist/r4/care-team.d.ts +27 -0
- package/dist/r4/care-team.d.ts.map +1 -0
- package/dist/r4/care-team.js +2 -0
- package/dist/r4/care-team.js.map +1 -0
- package/dist/r4/claim-response.d.ts +123 -0
- package/dist/r4/claim-response.d.ts.map +1 -0
- package/dist/r4/claim-response.js +2 -0
- package/dist/r4/claim-response.js.map +1 -0
- package/dist/r4/claim.d.ts +146 -0
- package/dist/r4/claim.d.ts.map +1 -0
- package/dist/r4/claim.js +2 -0
- package/dist/r4/claim.js.map +1 -0
- package/dist/r4/common.d.ts +82 -0
- package/dist/r4/common.d.ts.map +1 -1
- package/dist/r4/communication-request.d.ts +36 -0
- package/dist/r4/communication-request.d.ts.map +1 -0
- package/dist/r4/communication-request.js +2 -0
- package/dist/r4/communication-request.js.map +1 -0
- package/dist/r4/communication.d.ts +36 -0
- package/dist/r4/communication.d.ts.map +1 -0
- package/dist/r4/communication.js +2 -0
- package/dist/r4/communication.js.map +1 -0
- package/dist/r4/composition.d.ts +49 -0
- package/dist/r4/composition.d.ts.map +1 -0
- package/dist/r4/composition.js +2 -0
- package/dist/r4/composition.js.map +1 -0
- package/dist/r4/condition.d.ts +7 -7
- package/dist/r4/condition.d.ts.map +1 -1
- package/dist/r4/coverage.d.ts +39 -0
- package/dist/r4/coverage.d.ts.map +1 -0
- package/dist/r4/coverage.js +2 -0
- package/dist/r4/coverage.js.map +1 -0
- package/dist/r4/device-request.d.ts +42 -0
- package/dist/r4/device-request.d.ts.map +1 -0
- package/dist/r4/device-request.js +2 -0
- package/dist/r4/device-request.js.map +1 -0
- package/dist/r4/device-use-statement.d.ts +23 -0
- package/dist/r4/device-use-statement.d.ts.map +1 -0
- package/dist/r4/device-use-statement.js +2 -0
- package/dist/r4/device-use-statement.js.map +1 -0
- package/dist/r4/device.d.ts +60 -0
- package/dist/r4/device.d.ts.map +1 -0
- package/dist/r4/device.js +2 -0
- package/dist/r4/device.js.map +1 -0
- package/dist/r4/diagnostic-report.d.ts +4 -4
- package/dist/r4/diagnostic-report.d.ts.map +1 -1
- package/dist/r4/document-reference.d.ts +41 -0
- package/dist/r4/document-reference.d.ts.map +1 -0
- package/dist/r4/document-reference.js +2 -0
- package/dist/r4/document-reference.js.map +1 -0
- package/dist/r4/encounter.d.ts +7 -7
- package/dist/r4/encounter.d.ts.map +1 -1
- package/dist/r4/episode-of-care.d.ts +29 -0
- package/dist/r4/episode-of-care.d.ts.map +1 -0
- package/dist/r4/episode-of-care.js +2 -0
- package/dist/r4/episode-of-care.js.map +1 -0
- package/dist/r4/flag.d.ts +16 -0
- package/dist/r4/flag.d.ts.map +1 -0
- package/dist/r4/flag.js +2 -0
- package/dist/r4/flag.js.map +1 -0
- package/dist/r4/goal.d.ts +37 -0
- package/dist/r4/goal.d.ts.map +1 -0
- package/dist/r4/goal.js +2 -0
- package/dist/r4/goal.js.map +1 -0
- package/dist/r4/imaging-study.d.ts +50 -0
- package/dist/r4/imaging-study.d.ts.map +1 -0
- package/dist/r4/imaging-study.js +2 -0
- package/dist/r4/imaging-study.js.map +1 -0
- package/dist/r4/immunization.d.ts +61 -0
- package/dist/r4/immunization.d.ts.map +1 -0
- package/dist/r4/immunization.js +2 -0
- package/dist/r4/immunization.js.map +1 -0
- package/dist/r4/index.d.ts +47 -11
- package/dist/r4/index.d.ts.map +1 -1
- package/dist/r4/index.js +47 -1
- package/dist/r4/index.js.map +1 -1
- package/dist/r4/location.d.ts +36 -0
- package/dist/r4/location.d.ts.map +1 -0
- package/dist/r4/location.js +2 -0
- package/dist/r4/location.js.map +1 -0
- package/dist/r4/media.d.ts +31 -0
- package/dist/r4/media.d.ts.map +1 -0
- package/dist/r4/media.js +2 -0
- package/dist/r4/media.js.map +1 -0
- package/dist/r4/medication-administration.d.ts +42 -0
- package/dist/r4/medication-administration.d.ts.map +1 -0
- package/dist/r4/medication-administration.js +2 -0
- package/dist/r4/medication-administration.js.map +1 -0
- package/dist/r4/medication-dispense.d.ts +44 -0
- package/dist/r4/medication-dispense.d.ts.map +1 -0
- package/dist/r4/medication-dispense.js +2 -0
- package/dist/r4/medication-dispense.js.map +1 -0
- package/dist/r4/medication-request.d.ts +21 -16
- package/dist/r4/medication-request.d.ts.map +1 -1
- package/dist/r4/medication-statement.d.ts +27 -0
- package/dist/r4/medication-statement.d.ts.map +1 -0
- package/dist/r4/medication-statement.js +2 -0
- package/dist/r4/medication-statement.js.map +1 -0
- package/dist/r4/medication.d.ts +26 -0
- package/dist/r4/medication.d.ts.map +1 -0
- package/dist/r4/medication.js +2 -0
- package/dist/r4/medication.js.map +1 -0
- package/dist/r4/observation.d.ts +21 -17
- package/dist/r4/observation.d.ts.map +1 -1
- package/dist/r4/organization.d.ts +1 -1
- package/dist/r4/organization.d.ts.map +1 -1
- package/dist/r4/patient.d.ts +15 -15
- package/dist/r4/patient.d.ts.map +1 -1
- package/dist/r4/person.d.ts +22 -0
- package/dist/r4/person.d.ts.map +1 -0
- package/dist/r4/person.js +2 -0
- package/dist/r4/person.js.map +1 -0
- package/dist/r4/practitioner-role.d.ts +32 -0
- package/dist/r4/practitioner-role.d.ts.map +1 -0
- package/dist/r4/practitioner-role.js +2 -0
- package/dist/r4/practitioner-role.js.map +1 -0
- package/dist/r4/practitioner.d.ts +2 -2
- package/dist/r4/practitioner.d.ts.map +1 -1
- package/dist/r4/procedure.d.ts +14 -7
- package/dist/r4/procedure.d.ts.map +1 -1
- package/dist/r4/related-person.d.ts +24 -0
- package/dist/r4/related-person.d.ts.map +1 -0
- package/dist/r4/related-person.js +2 -0
- package/dist/r4/related-person.js.map +1 -0
- package/dist/r4/risk-assessment.d.ts +36 -0
- package/dist/r4/risk-assessment.d.ts.map +1 -0
- package/dist/r4/risk-assessment.js +2 -0
- package/dist/r4/risk-assessment.js.map +1 -0
- package/dist/r4/schedule.d.ts +16 -0
- package/dist/r4/schedule.d.ts.map +1 -0
- package/dist/r4/schedule.js +2 -0
- package/dist/r4/schedule.js.map +1 -0
- package/dist/r4/service-request.d.ts +46 -0
- package/dist/r4/service-request.d.ts.map +1 -0
- package/dist/r4/service-request.js +2 -0
- package/dist/r4/service-request.js.map +1 -0
- package/dist/r4/slot.d.ts +19 -0
- package/dist/r4/slot.d.ts.map +1 -0
- package/dist/r4/slot.js +2 -0
- package/dist/r4/slot.js.map +1 -0
- package/dist/r4/specimen.d.ts +48 -0
- package/dist/r4/specimen.d.ts.map +1 -0
- package/dist/r4/specimen.js +2 -0
- package/dist/r4/specimen.js.map +1 -0
- package/dist/r4/task.d.ts +150 -0
- package/dist/r4/task.d.ts.map +1 -0
- package/dist/r4/task.js +2 -0
- package/dist/r4/task.js.map +1 -0
- package/dist/testing/__tests__/test-harness.test.d.ts +15 -0
- package/dist/testing/__tests__/test-harness.test.d.ts.map +1 -0
- package/dist/testing/__tests__/test-harness.test.js +416 -0
- package/dist/testing/__tests__/test-harness.test.js.map +1 -0
- package/dist/testing/index.d.ts +37 -0
- package/dist/testing/index.d.ts.map +1 -0
- package/dist/testing/index.js +76 -0
- package/dist/testing/index.js.map +1 -0
- package/dist/testing/mock-fhir.d.ts +38 -0
- package/dist/testing/mock-fhir.d.ts.map +1 -0
- package/dist/testing/mock-fhir.js +113 -0
- package/dist/testing/mock-fhir.js.map +1 -0
- package/dist/testing/mock-http.d.ts +31 -0
- package/dist/testing/mock-http.d.ts.map +1 -0
- package/dist/testing/mock-http.js +52 -0
- package/dist/testing/mock-http.js.map +1 -0
- package/dist/testing/mock-utils.d.ts +15 -0
- package/dist/testing/mock-utils.d.ts.map +1 -0
- package/dist/testing/mock-utils.js +17 -0
- package/dist/testing/mock-utils.js.map +1 -0
- package/dist/testing/simulator.d.ts +21 -0
- package/dist/testing/simulator.d.ts.map +1 -0
- package/dist/testing/simulator.js +74 -0
- package/dist/testing/simulator.js.map +1 -0
- package/dist/testing/types.d.ts +81 -0
- package/dist/testing/types.d.ts.map +1 -0
- package/dist/testing/types.js +8 -0
- package/dist/testing/types.js.map +1 -0
- package/dist/types.d.ts +10 -7
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +0 -22
- package/dist/types.js.map +1 -1
- package/package.json +7 -1
- package/templates/docker-compose.yml +32 -0
- package/templates/gitignore +4 -0
- package/wit/plugin.wit +4 -16
- package/wit/world.wit +1 -1
|
@@ -0,0 +1,416 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Comprehensive tests for @fhirust/sdk/testing — the plugin test harness.
|
|
3
|
+
*
|
|
4
|
+
* Tests cover:
|
|
5
|
+
* - createTestPlugin factory shape
|
|
6
|
+
* - Hook dispatch (beforeCreate with modifications, rejections, pass-through)
|
|
7
|
+
* - HookContext auto-generation and overrides
|
|
8
|
+
* - Mock FHIR backend (read, search, create, not-found errors)
|
|
9
|
+
* - Mock HTTP backend (configured responses, unmatched URL errors)
|
|
10
|
+
* - Log capture at all levels
|
|
11
|
+
* - Event capture
|
|
12
|
+
* - Exception handling in hooks
|
|
13
|
+
*/
|
|
14
|
+
import { describe, it } from "node:test";
|
|
15
|
+
import assert from "node:assert/strict";
|
|
16
|
+
import { createTestPlugin } from "../index.js";
|
|
17
|
+
import { reject } from "../../helpers.js";
|
|
18
|
+
import { FhirClientError } from "../../fhir-client.js";
|
|
19
|
+
import { HttpClientError } from "../../http-client.js";
|
|
20
|
+
// ---------------------------------------------------------------------------
|
|
21
|
+
// 1. createTestPlugin returns all expected properties
|
|
22
|
+
// ---------------------------------------------------------------------------
|
|
23
|
+
describe("createTestPlugin", () => {
|
|
24
|
+
it("returns all expected properties", () => {
|
|
25
|
+
const ctx = createTestPlugin("test-plugin", "1.0.0");
|
|
26
|
+
assert.ok(ctx.plugin, "plugin should be defined");
|
|
27
|
+
assert.ok(ctx.simulate, "simulate should be defined");
|
|
28
|
+
assert.ok(Array.isArray(ctx.logs), "logs should be an array");
|
|
29
|
+
assert.ok(Array.isArray(ctx.events), "events should be an array");
|
|
30
|
+
assert.ok(Array.isArray(ctx.fhirCalls), "fhirCalls should be an array");
|
|
31
|
+
assert.ok(Array.isArray(ctx.httpCalls), "httpCalls should be an array");
|
|
32
|
+
// Verify simulate has the expected methods
|
|
33
|
+
assert.equal(typeof ctx.simulate.beforeCreate, "function");
|
|
34
|
+
assert.equal(typeof ctx.simulate.afterCreate, "function");
|
|
35
|
+
assert.equal(typeof ctx.simulate.beforeRead, "function");
|
|
36
|
+
assert.equal(typeof ctx.simulate.afterRead, "function");
|
|
37
|
+
assert.equal(typeof ctx.simulate.beforeUpdate, "function");
|
|
38
|
+
assert.equal(typeof ctx.simulate.afterUpdate, "function");
|
|
39
|
+
assert.equal(typeof ctx.simulate.beforeDelete, "function");
|
|
40
|
+
assert.equal(typeof ctx.simulate.afterDelete, "function");
|
|
41
|
+
});
|
|
42
|
+
});
|
|
43
|
+
// ---------------------------------------------------------------------------
|
|
44
|
+
// 2. simulate.beforeCreate dispatches to registered hook and returns modified resource
|
|
45
|
+
// ---------------------------------------------------------------------------
|
|
46
|
+
describe("simulate.beforeCreate — modification", () => {
|
|
47
|
+
it("dispatches to registered hook and returns modified resource", () => {
|
|
48
|
+
const { plugin, simulate } = createTestPlugin("tag-plugin", "1.0.0");
|
|
49
|
+
plugin.beforeCreate("Patient", (patient, _ctx) => {
|
|
50
|
+
// Add a meta tag
|
|
51
|
+
const tagged = {
|
|
52
|
+
...patient,
|
|
53
|
+
meta: {
|
|
54
|
+
tag: [
|
|
55
|
+
...(patient.meta?.tag ?? []),
|
|
56
|
+
{ system: "http://example.com/test", code: "validated" },
|
|
57
|
+
],
|
|
58
|
+
},
|
|
59
|
+
};
|
|
60
|
+
return tagged;
|
|
61
|
+
});
|
|
62
|
+
const input = {
|
|
63
|
+
resourceType: "Patient",
|
|
64
|
+
id: "123",
|
|
65
|
+
name: [{ family: "Smith" }],
|
|
66
|
+
};
|
|
67
|
+
const result = simulate.beforeCreate("Patient", input);
|
|
68
|
+
assert.equal(result.ok, true);
|
|
69
|
+
if (result.ok) {
|
|
70
|
+
assert.equal(result.resource.resourceType, "Patient");
|
|
71
|
+
assert.equal(result.resource.id, "123");
|
|
72
|
+
assert.ok(result.resource.meta?.tag, "should have meta.tag");
|
|
73
|
+
assert.equal(result.resource.meta.tag.length, 1);
|
|
74
|
+
assert.equal(result.resource.meta.tag[0].code, "validated");
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
});
|
|
78
|
+
// ---------------------------------------------------------------------------
|
|
79
|
+
// 3. simulate.beforeCreate returns rejection when hook calls reject()
|
|
80
|
+
// ---------------------------------------------------------------------------
|
|
81
|
+
describe("simulate.beforeCreate — rejection", () => {
|
|
82
|
+
it("returns rejection when hook calls reject()", () => {
|
|
83
|
+
const { plugin, simulate } = createTestPlugin("reject-plugin", "1.0.0");
|
|
84
|
+
plugin.beforeCreate("Patient", (patient, _ctx) => {
|
|
85
|
+
if (!patient.name || patient.name.length === 0) {
|
|
86
|
+
return reject("Patient must have at least one name", "required");
|
|
87
|
+
}
|
|
88
|
+
return patient;
|
|
89
|
+
});
|
|
90
|
+
const input = {
|
|
91
|
+
resourceType: "Patient",
|
|
92
|
+
id: "no-name",
|
|
93
|
+
// no name field
|
|
94
|
+
};
|
|
95
|
+
const result = simulate.beforeCreate("Patient", input);
|
|
96
|
+
assert.equal(result.ok, false);
|
|
97
|
+
if (!result.ok) {
|
|
98
|
+
assert.equal(result.outcome.resourceType, "OperationOutcome");
|
|
99
|
+
assert.equal(result.outcome.issue.length, 1);
|
|
100
|
+
assert.equal(result.outcome.issue[0].code, "required");
|
|
101
|
+
assert.equal(result.outcome.issue[0].severity, "error");
|
|
102
|
+
assert.equal(result.outcome.issue[0].diagnostics, "Patient must have at least one name");
|
|
103
|
+
}
|
|
104
|
+
});
|
|
105
|
+
});
|
|
106
|
+
// ---------------------------------------------------------------------------
|
|
107
|
+
// 4. Pass-through when no handler for the resource type
|
|
108
|
+
// ---------------------------------------------------------------------------
|
|
109
|
+
describe("simulate.beforeCreate — pass-through", () => {
|
|
110
|
+
it("passes through when no handler for the resource type", () => {
|
|
111
|
+
const { plugin, simulate } = createTestPlugin("obs-only-plugin", "1.0.0");
|
|
112
|
+
// Register a hook for Observation only
|
|
113
|
+
plugin.beforeCreate("Observation", (obs, _ctx) => {
|
|
114
|
+
return { ...obs, status: "final" };
|
|
115
|
+
});
|
|
116
|
+
// Simulate beforeCreate for Patient (no handler registered)
|
|
117
|
+
const input = {
|
|
118
|
+
resourceType: "Patient",
|
|
119
|
+
id: "456",
|
|
120
|
+
name: [{ family: "Doe" }],
|
|
121
|
+
};
|
|
122
|
+
const result = simulate.beforeCreate("Patient", input);
|
|
123
|
+
assert.equal(result.ok, true);
|
|
124
|
+
if (result.ok) {
|
|
125
|
+
// Resource should be unchanged (pass-through)
|
|
126
|
+
assert.equal(result.resource.resourceType, "Patient");
|
|
127
|
+
assert.equal(result.resource.id, "456");
|
|
128
|
+
assert.deepEqual(result.resource.name, [{ family: "Doe" }]);
|
|
129
|
+
}
|
|
130
|
+
});
|
|
131
|
+
});
|
|
132
|
+
// ---------------------------------------------------------------------------
|
|
133
|
+
// 5. Auto-generates HookContext with requestId and timestamp
|
|
134
|
+
// ---------------------------------------------------------------------------
|
|
135
|
+
describe("HookContext — auto-generation", () => {
|
|
136
|
+
it("auto-generates requestId, timestamp, and operation", () => {
|
|
137
|
+
const { plugin, simulate } = createTestPlugin("ctx-plugin", "1.0.0");
|
|
138
|
+
let capturedCtx = null;
|
|
139
|
+
plugin.beforeCreate("Patient", (patient, ctx) => {
|
|
140
|
+
capturedCtx = ctx;
|
|
141
|
+
return patient;
|
|
142
|
+
});
|
|
143
|
+
simulate.beforeCreate("Patient", {
|
|
144
|
+
resourceType: "Patient",
|
|
145
|
+
id: "ctx-test",
|
|
146
|
+
});
|
|
147
|
+
assert.ok(capturedCtx, "context should have been captured");
|
|
148
|
+
assert.ok(capturedCtx.requestId, "requestId should be auto-generated");
|
|
149
|
+
assert.equal(typeof capturedCtx.requestId, "string");
|
|
150
|
+
assert.ok(capturedCtx.requestId.length > 0, "requestId should not be empty");
|
|
151
|
+
assert.ok(capturedCtx.timestamp, "timestamp should be auto-generated");
|
|
152
|
+
// Verify it's a valid ISO date string
|
|
153
|
+
const parsed = Date.parse(capturedCtx.timestamp);
|
|
154
|
+
assert.ok(!isNaN(parsed), "timestamp should be a valid date string");
|
|
155
|
+
assert.equal(capturedCtx.operation, "test", "default operation should be 'test'");
|
|
156
|
+
});
|
|
157
|
+
});
|
|
158
|
+
// ---------------------------------------------------------------------------
|
|
159
|
+
// 6. Allows overriding HookContext fields
|
|
160
|
+
// ---------------------------------------------------------------------------
|
|
161
|
+
describe("HookContext — overrides", () => {
|
|
162
|
+
it("allows overriding requestId and userId", () => {
|
|
163
|
+
const { plugin, simulate } = createTestPlugin("ctx-override-plugin", "1.0.0");
|
|
164
|
+
let capturedCtx = null;
|
|
165
|
+
plugin.beforeCreate("Patient", (patient, ctx) => {
|
|
166
|
+
capturedCtx = ctx;
|
|
167
|
+
return patient;
|
|
168
|
+
});
|
|
169
|
+
simulate.beforeCreate("Patient", { resourceType: "Patient", id: "override-test" }, { requestId: "custom-req-id-42", userId: "dr-house" });
|
|
170
|
+
assert.ok(capturedCtx, "context should have been captured");
|
|
171
|
+
assert.equal(capturedCtx.requestId, "custom-req-id-42");
|
|
172
|
+
assert.equal(capturedCtx.userId, "dr-house");
|
|
173
|
+
});
|
|
174
|
+
});
|
|
175
|
+
// ---------------------------------------------------------------------------
|
|
176
|
+
// 7. Mock FHIR read returns pre-loaded data
|
|
177
|
+
// ---------------------------------------------------------------------------
|
|
178
|
+
describe("Mock FHIR — read", () => {
|
|
179
|
+
it("returns pre-loaded data and records fhirCalls", () => {
|
|
180
|
+
const patientData = {
|
|
181
|
+
resourceType: "Patient",
|
|
182
|
+
id: "123",
|
|
183
|
+
name: [{ family: "Smith" }],
|
|
184
|
+
active: true,
|
|
185
|
+
};
|
|
186
|
+
const { plugin, simulate, fhirCalls } = createTestPlugin("fhir-read-plugin", "1.0.0", {
|
|
187
|
+
fhir: { "Patient/123": patientData },
|
|
188
|
+
});
|
|
189
|
+
let readResult = null;
|
|
190
|
+
plugin.beforeCreate("Observation", (obs, _ctx) => {
|
|
191
|
+
// Inside hook, use plugin.fhir.read to fetch the patient
|
|
192
|
+
readResult = plugin.fhir.read("Patient", "123");
|
|
193
|
+
return obs;
|
|
194
|
+
});
|
|
195
|
+
simulate.beforeCreate("Observation", {
|
|
196
|
+
resourceType: "Observation",
|
|
197
|
+
id: "obs-1",
|
|
198
|
+
status: "preliminary",
|
|
199
|
+
});
|
|
200
|
+
// Verify the read returned correct data
|
|
201
|
+
assert.ok(readResult, "read result should be defined");
|
|
202
|
+
assert.equal(readResult.resourceType, "Patient");
|
|
203
|
+
assert.equal(readResult.id, "123");
|
|
204
|
+
assert.equal(readResult.active, true);
|
|
205
|
+
assert.deepEqual(readResult.name, [{ family: "Smith" }]);
|
|
206
|
+
// Verify fhirCalls were recorded
|
|
207
|
+
assert.equal(fhirCalls.length, 1);
|
|
208
|
+
assert.equal(fhirCalls[0].method, "read");
|
|
209
|
+
assert.deepEqual(fhirCalls[0].args[0], "Patient");
|
|
210
|
+
assert.deepEqual(fhirCalls[0].args[1], "123");
|
|
211
|
+
});
|
|
212
|
+
});
|
|
213
|
+
// ---------------------------------------------------------------------------
|
|
214
|
+
// 8. Mock FHIR read throws FhirClientError when not found
|
|
215
|
+
// ---------------------------------------------------------------------------
|
|
216
|
+
describe("Mock FHIR — read not found", () => {
|
|
217
|
+
it("throws FhirClientError when resource does not exist", () => {
|
|
218
|
+
const { plugin, simulate } = createTestPlugin("fhir-404-plugin", "1.0.0");
|
|
219
|
+
let caughtError = null;
|
|
220
|
+
plugin.beforeCreate("Observation", (obs, _ctx) => {
|
|
221
|
+
try {
|
|
222
|
+
plugin.fhir.read("Patient", "999");
|
|
223
|
+
}
|
|
224
|
+
catch (err) {
|
|
225
|
+
caughtError = err;
|
|
226
|
+
}
|
|
227
|
+
return obs;
|
|
228
|
+
});
|
|
229
|
+
simulate.beforeCreate("Observation", {
|
|
230
|
+
resourceType: "Observation",
|
|
231
|
+
id: "obs-2",
|
|
232
|
+
});
|
|
233
|
+
assert.ok(caughtError, "should have caught an error");
|
|
234
|
+
assert.ok(caughtError instanceof FhirClientError, "should be a FhirClientError");
|
|
235
|
+
assert.equal(caughtError.name, "FhirClientError");
|
|
236
|
+
assert.ok(caughtError.outcome.issue[0].diagnostics?.includes("not found"), "diagnostics should mention not found");
|
|
237
|
+
});
|
|
238
|
+
});
|
|
239
|
+
// ---------------------------------------------------------------------------
|
|
240
|
+
// 9. Mock FHIR search returns matching resources
|
|
241
|
+
// ---------------------------------------------------------------------------
|
|
242
|
+
describe("Mock FHIR — search", () => {
|
|
243
|
+
it("returns matching resources filtered by params", () => {
|
|
244
|
+
const { plugin, simulate } = createTestPlugin("fhir-search-plugin", "1.0.0", {
|
|
245
|
+
fhir: {
|
|
246
|
+
"Patient/p1": { resourceType: "Patient", id: "p1", gender: "male" },
|
|
247
|
+
"Patient/p2": { resourceType: "Patient", id: "p2", gender: "female" },
|
|
248
|
+
"Patient/p3": { resourceType: "Patient", id: "p3", gender: "male" },
|
|
249
|
+
},
|
|
250
|
+
});
|
|
251
|
+
let searchBundle = null;
|
|
252
|
+
plugin.beforeCreate("Observation", (obs, _ctx) => {
|
|
253
|
+
searchBundle = plugin.fhir.search("Patient", { gender: "male" });
|
|
254
|
+
return obs;
|
|
255
|
+
});
|
|
256
|
+
simulate.beforeCreate("Observation", {
|
|
257
|
+
resourceType: "Observation",
|
|
258
|
+
id: "obs-3",
|
|
259
|
+
});
|
|
260
|
+
assert.ok(searchBundle, "search bundle should be defined");
|
|
261
|
+
assert.equal(searchBundle.resourceType, "Bundle");
|
|
262
|
+
assert.equal(searchBundle.type, "searchset");
|
|
263
|
+
assert.equal(searchBundle.total, 2, "should match 2 male patients");
|
|
264
|
+
assert.equal(searchBundle.entry.length, 2);
|
|
265
|
+
const ids = searchBundle.entry.map((e) => e.resource.id).sort();
|
|
266
|
+
assert.deepEqual(ids, ["p1", "p3"]);
|
|
267
|
+
});
|
|
268
|
+
});
|
|
269
|
+
// ---------------------------------------------------------------------------
|
|
270
|
+
// 10. Mock FHIR create stores and assigns id
|
|
271
|
+
// ---------------------------------------------------------------------------
|
|
272
|
+
describe("Mock FHIR — create", () => {
|
|
273
|
+
it("stores resource and assigns id when missing", () => {
|
|
274
|
+
const { plugin, simulate, fhirCalls } = createTestPlugin("fhir-create-plugin", "1.0.0");
|
|
275
|
+
let created = null;
|
|
276
|
+
plugin.beforeCreate("Patient", (patient, _ctx) => {
|
|
277
|
+
// Create an Observation from inside the hook
|
|
278
|
+
created = plugin.fhir.create("Observation", {
|
|
279
|
+
resourceType: "Observation",
|
|
280
|
+
status: "preliminary",
|
|
281
|
+
});
|
|
282
|
+
return patient;
|
|
283
|
+
});
|
|
284
|
+
simulate.beforeCreate("Patient", {
|
|
285
|
+
resourceType: "Patient",
|
|
286
|
+
id: "trigger",
|
|
287
|
+
});
|
|
288
|
+
assert.ok(created, "created resource should be defined");
|
|
289
|
+
assert.equal(created.resourceType, "Observation");
|
|
290
|
+
assert.ok(created.id, "should have an auto-assigned id");
|
|
291
|
+
assert.equal(typeof created.id, "string");
|
|
292
|
+
assert.ok(created.id.length > 0, "id should not be empty");
|
|
293
|
+
// Verify the create call was recorded
|
|
294
|
+
const createCalls = fhirCalls.filter((c) => c.method === "create");
|
|
295
|
+
assert.equal(createCalls.length, 1);
|
|
296
|
+
});
|
|
297
|
+
});
|
|
298
|
+
// ---------------------------------------------------------------------------
|
|
299
|
+
// 11. Mock HTTP returns configured response
|
|
300
|
+
// ---------------------------------------------------------------------------
|
|
301
|
+
describe("Mock HTTP — configured response", () => {
|
|
302
|
+
it("returns mock response for configured URL", () => {
|
|
303
|
+
const webhookUrl = "https://example.com/webhook";
|
|
304
|
+
const { plugin, simulate, httpCalls } = createTestPlugin("http-plugin", "1.0.0", {
|
|
305
|
+
http: {
|
|
306
|
+
[webhookUrl]: {
|
|
307
|
+
status: 200,
|
|
308
|
+
headers: { "Content-Type": "application/json" },
|
|
309
|
+
body: JSON.stringify({ received: true }),
|
|
310
|
+
},
|
|
311
|
+
},
|
|
312
|
+
});
|
|
313
|
+
let httpResponse = null;
|
|
314
|
+
plugin.beforeCreate("Patient", (patient, _ctx) => {
|
|
315
|
+
httpResponse = plugin.http.post(webhookUrl, {
|
|
316
|
+
body: { event: "patient-created" },
|
|
317
|
+
});
|
|
318
|
+
return patient;
|
|
319
|
+
});
|
|
320
|
+
simulate.beforeCreate("Patient", {
|
|
321
|
+
resourceType: "Patient",
|
|
322
|
+
id: "http-test",
|
|
323
|
+
});
|
|
324
|
+
assert.ok(httpResponse, "http response should be defined");
|
|
325
|
+
assert.equal(httpResponse.status, 200);
|
|
326
|
+
assert.equal(httpResponse.ok, true);
|
|
327
|
+
assert.deepEqual(httpResponse.json(), { received: true });
|
|
328
|
+
// Verify httpCalls were recorded
|
|
329
|
+
assert.equal(httpCalls.length, 1);
|
|
330
|
+
assert.equal(httpCalls[0].method, "fetch");
|
|
331
|
+
});
|
|
332
|
+
});
|
|
333
|
+
// ---------------------------------------------------------------------------
|
|
334
|
+
// 12. Mock HTTP throws for unmatched URL
|
|
335
|
+
// ---------------------------------------------------------------------------
|
|
336
|
+
describe("Mock HTTP — unmatched URL", () => {
|
|
337
|
+
it("throws HttpClientError with helpful message for unknown URL", () => {
|
|
338
|
+
const { plugin, simulate } = createTestPlugin("http-err-plugin", "1.0.0");
|
|
339
|
+
let caughtError = null;
|
|
340
|
+
plugin.beforeCreate("Patient", (patient, _ctx) => {
|
|
341
|
+
try {
|
|
342
|
+
plugin.http.get("https://unknown.com/api");
|
|
343
|
+
}
|
|
344
|
+
catch (err) {
|
|
345
|
+
caughtError = err;
|
|
346
|
+
}
|
|
347
|
+
return patient;
|
|
348
|
+
});
|
|
349
|
+
simulate.beforeCreate("Patient", {
|
|
350
|
+
resourceType: "Patient",
|
|
351
|
+
id: "http-err-test",
|
|
352
|
+
});
|
|
353
|
+
assert.ok(caughtError, "should have caught an error");
|
|
354
|
+
assert.ok(caughtError instanceof HttpClientError, "should be an HttpClientError");
|
|
355
|
+
assert.equal(caughtError.name, "HttpClientError");
|
|
356
|
+
assert.ok(caughtError.message.includes("https://unknown.com/api"), "error should mention the failed URL");
|
|
357
|
+
});
|
|
358
|
+
});
|
|
359
|
+
// ---------------------------------------------------------------------------
|
|
360
|
+
// 13. Log capture at all levels
|
|
361
|
+
// ---------------------------------------------------------------------------
|
|
362
|
+
describe("Log capture", () => {
|
|
363
|
+
it("captures debug, info, warn, and error messages", () => {
|
|
364
|
+
const { plugin, logs } = createTestPlugin("log-plugin", "1.0.0");
|
|
365
|
+
plugin.log.debug("debug message");
|
|
366
|
+
plugin.log.info("info message");
|
|
367
|
+
plugin.log.warn("warn message");
|
|
368
|
+
plugin.log.error("error message");
|
|
369
|
+
assert.equal(logs.length, 4, "should have captured 4 log entries");
|
|
370
|
+
assert.equal(logs[0].level, "debug");
|
|
371
|
+
assert.equal(logs[0].message, "debug message");
|
|
372
|
+
assert.equal(logs[1].level, "info");
|
|
373
|
+
assert.equal(logs[1].message, "info message");
|
|
374
|
+
assert.equal(logs[2].level, "warn");
|
|
375
|
+
assert.equal(logs[2].message, "warn message");
|
|
376
|
+
assert.equal(logs[3].level, "error");
|
|
377
|
+
assert.equal(logs[3].message, "error message");
|
|
378
|
+
});
|
|
379
|
+
});
|
|
380
|
+
// ---------------------------------------------------------------------------
|
|
381
|
+
// 14. Event capture
|
|
382
|
+
// ---------------------------------------------------------------------------
|
|
383
|
+
describe("Event capture", () => {
|
|
384
|
+
it("captures emitted events with type and payload", () => {
|
|
385
|
+
const { plugin, events } = createTestPlugin("event-plugin", "1.0.0");
|
|
386
|
+
plugin.events.emit("patient.validated", { id: "123" });
|
|
387
|
+
assert.equal(events.length, 1, "should have captured 1 event");
|
|
388
|
+
assert.equal(events[0].type, "patient.validated");
|
|
389
|
+
// EventEmitter JSON.stringify's objects
|
|
390
|
+
assert.equal(events[0].payload, JSON.stringify({ id: "123" }));
|
|
391
|
+
});
|
|
392
|
+
});
|
|
393
|
+
// ---------------------------------------------------------------------------
|
|
394
|
+
// 15. Exception handling — hook throws, returns error outcome
|
|
395
|
+
// ---------------------------------------------------------------------------
|
|
396
|
+
describe("Exception handling", () => {
|
|
397
|
+
it("returns error outcome when hook throws an unexpected error", () => {
|
|
398
|
+
const { plugin, simulate } = createTestPlugin("boom-plugin", "1.0.0");
|
|
399
|
+
plugin.beforeCreate("Patient", (_patient, _ctx) => {
|
|
400
|
+
throw new Error("boom");
|
|
401
|
+
});
|
|
402
|
+
const result = simulate.beforeCreate("Patient", {
|
|
403
|
+
resourceType: "Patient",
|
|
404
|
+
id: "boom-test",
|
|
405
|
+
});
|
|
406
|
+
assert.equal(result.ok, false);
|
|
407
|
+
if (!result.ok) {
|
|
408
|
+
assert.equal(result.outcome.resourceType, "OperationOutcome");
|
|
409
|
+
assert.equal(result.outcome.issue.length, 1);
|
|
410
|
+
assert.equal(result.outcome.issue[0].severity, "error");
|
|
411
|
+
assert.equal(result.outcome.issue[0].code, "exception");
|
|
412
|
+
assert.ok(result.outcome.issue[0].diagnostics?.includes("boom"), "diagnostics should contain the error message 'boom'");
|
|
413
|
+
}
|
|
414
|
+
});
|
|
415
|
+
});
|
|
416
|
+
//# sourceMappingURL=test-harness.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"test-harness.test.js","sourceRoot":"","sources":["../../../src/testing/__tests__/test-harness.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fhirust/sdk/testing — Test harness for unit-testing FHIRust plugins.
|
|
3
|
+
*
|
|
4
|
+
* Usage:
|
|
5
|
+
* import { createTestPlugin } from "@fhirust/sdk/testing";
|
|
6
|
+
*
|
|
7
|
+
* const { plugin, simulate, logs, fhirCalls } = createTestPlugin("my-plugin", "1.0.0", {
|
|
8
|
+
* fhir: { "Patient/123": { resourceType: "Patient", id: "123", active: true } },
|
|
9
|
+
* });
|
|
10
|
+
*
|
|
11
|
+
* plugin.beforeCreate<Patient>("Patient", (patient, ctx) => {
|
|
12
|
+
* if (!patient.active) return reject("Patient must be active");
|
|
13
|
+
* return patient;
|
|
14
|
+
* });
|
|
15
|
+
*
|
|
16
|
+
* const result = simulate.beforeCreate("Patient", { resourceType: "Patient", active: false });
|
|
17
|
+
* assert(!result.ok); // rejected
|
|
18
|
+
*/
|
|
19
|
+
import type { MockConfig, TestPluginContext } from "./types.js";
|
|
20
|
+
/**
|
|
21
|
+
* Create a fully-wired test plugin with mock backends and a hook simulator.
|
|
22
|
+
*
|
|
23
|
+
* The returned `simulate` object is lazy — the simulator is created on first
|
|
24
|
+
* property access, so hooks can be registered on the plugin after calling
|
|
25
|
+
* `createTestPlugin()` but before calling any `simulate.*` method.
|
|
26
|
+
*
|
|
27
|
+
* @param name - Plugin name (e.g., "my-plugin")
|
|
28
|
+
* @param version - Plugin version (e.g., "1.0.0")
|
|
29
|
+
* @param mocks - Optional pre-seeded data for FHIR and HTTP mocks
|
|
30
|
+
*/
|
|
31
|
+
export declare function createTestPlugin(name: string, version: string, mocks?: MockConfig): TestPluginContext;
|
|
32
|
+
export type { EventEntry, HookResult, HookSimulator, LogEntry, MockCall, MockConfig, MockHttpResponse, TestPluginContext, } from "./types.js";
|
|
33
|
+
export { MockFhirBackend } from "./mock-fhir.js";
|
|
34
|
+
export { MockHttpBackend } from "./mock-http.js";
|
|
35
|
+
export { MockUtilsBackend } from "./mock-utils.js";
|
|
36
|
+
export { createSimulator } from "./simulator.js";
|
|
37
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/testing/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAOH,OAAO,KAAK,EAAiB,UAAU,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAE/E;;;;;;;;;;GAUG;AACH,wBAAgB,gBAAgB,CAC9B,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,MAAM,EACf,KAAK,CAAC,EAAE,UAAU,GACjB,iBAAiB,CAwCnB;AAGD,YAAY,EACV,UAAU,EACV,UAAU,EACV,aAAa,EACb,QAAQ,EACR,QAAQ,EACR,UAAU,EACV,gBAAgB,EAChB,iBAAiB,GAClB,MAAM,YAAY,CAAC;AAGpB,OAAO,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AACjD,OAAO,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AACjD,OAAO,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AACnD,OAAO,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC"}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fhirust/sdk/testing — Test harness for unit-testing FHIRust plugins.
|
|
3
|
+
*
|
|
4
|
+
* Usage:
|
|
5
|
+
* import { createTestPlugin } from "@fhirust/sdk/testing";
|
|
6
|
+
*
|
|
7
|
+
* const { plugin, simulate, logs, fhirCalls } = createTestPlugin("my-plugin", "1.0.0", {
|
|
8
|
+
* fhir: { "Patient/123": { resourceType: "Patient", id: "123", active: true } },
|
|
9
|
+
* });
|
|
10
|
+
*
|
|
11
|
+
* plugin.beforeCreate<Patient>("Patient", (patient, ctx) => {
|
|
12
|
+
* if (!patient.active) return reject("Patient must be active");
|
|
13
|
+
* return patient;
|
|
14
|
+
* });
|
|
15
|
+
*
|
|
16
|
+
* const result = simulate.beforeCreate("Patient", { resourceType: "Patient", active: false });
|
|
17
|
+
* assert(!result.ok); // rejected
|
|
18
|
+
*/
|
|
19
|
+
import { FhirPlugin } from "../plugin.js";
|
|
20
|
+
import { MockFhirBackend } from "./mock-fhir.js";
|
|
21
|
+
import { MockHttpBackend } from "./mock-http.js";
|
|
22
|
+
import { MockUtilsBackend } from "./mock-utils.js";
|
|
23
|
+
import { createSimulator } from "./simulator.js";
|
|
24
|
+
/**
|
|
25
|
+
* Create a fully-wired test plugin with mock backends and a hook simulator.
|
|
26
|
+
*
|
|
27
|
+
* The returned `simulate` object is lazy — the simulator is created on first
|
|
28
|
+
* property access, so hooks can be registered on the plugin after calling
|
|
29
|
+
* `createTestPlugin()` but before calling any `simulate.*` method.
|
|
30
|
+
*
|
|
31
|
+
* @param name - Plugin name (e.g., "my-plugin")
|
|
32
|
+
* @param version - Plugin version (e.g., "1.0.0")
|
|
33
|
+
* @param mocks - Optional pre-seeded data for FHIR and HTTP mocks
|
|
34
|
+
*/
|
|
35
|
+
export function createTestPlugin(name, version, mocks) {
|
|
36
|
+
const plugin = new FhirPlugin(name, version);
|
|
37
|
+
// Create mock backends
|
|
38
|
+
const fhirBackend = new MockFhirBackend(mocks?.fhir);
|
|
39
|
+
const httpBackend = new MockHttpBackend(mocks?.http);
|
|
40
|
+
const utilsBackend = new MockUtilsBackend();
|
|
41
|
+
// Inject mocks into the plugin's service wrappers.
|
|
42
|
+
// These _bind() calls set the backing implementation before any hook runs.
|
|
43
|
+
// When dispatch() later calls bindIfNeeded(), the try/catch for WASM globals
|
|
44
|
+
// will fail silently (we're in Node.js), setting `bound = true` without
|
|
45
|
+
// overwriting our pre-injected mocks.
|
|
46
|
+
plugin.fhir._bind(fhirBackend);
|
|
47
|
+
plugin.http._bind(httpBackend);
|
|
48
|
+
plugin.log._bind(utilsBackend);
|
|
49
|
+
plugin.events._bind(utilsBackend);
|
|
50
|
+
// Lazy simulator via Proxy: the simulator is created on first property access.
|
|
51
|
+
// This allows the caller to register hooks on `plugin` after createTestPlugin()
|
|
52
|
+
// returns, then call simulate.beforeCreate(...) and have those hooks active.
|
|
53
|
+
let cachedSimulator = null;
|
|
54
|
+
const simulate = new Proxy({}, {
|
|
55
|
+
get(_target, prop, receiver) {
|
|
56
|
+
if (!cachedSimulator) {
|
|
57
|
+
cachedSimulator = createSimulator(plugin);
|
|
58
|
+
}
|
|
59
|
+
return Reflect.get(cachedSimulator, prop, receiver);
|
|
60
|
+
},
|
|
61
|
+
});
|
|
62
|
+
return {
|
|
63
|
+
plugin,
|
|
64
|
+
simulate,
|
|
65
|
+
logs: utilsBackend.logs,
|
|
66
|
+
events: utilsBackend.events,
|
|
67
|
+
fhirCalls: fhirBackend.calls,
|
|
68
|
+
httpCalls: httpBackend.calls,
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
// Re-export mock classes for advanced usage (custom mock configurations)
|
|
72
|
+
export { MockFhirBackend } from "./mock-fhir.js";
|
|
73
|
+
export { MockHttpBackend } from "./mock-http.js";
|
|
74
|
+
export { MockUtilsBackend } from "./mock-utils.js";
|
|
75
|
+
export { createSimulator } from "./simulator.js";
|
|
76
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/testing/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC1C,OAAO,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AACjD,OAAO,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AACjD,OAAO,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AACnD,OAAO,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AAGjD;;;;;;;;;;GAUG;AACH,MAAM,UAAU,gBAAgB,CAC9B,IAAY,EACZ,OAAe,EACf,KAAkB;IAElB,MAAM,MAAM,GAAG,IAAI,UAAU,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;IAE7C,uBAAuB;IACvB,MAAM,WAAW,GAAG,IAAI,eAAe,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC;IACrD,MAAM,WAAW,GAAG,IAAI,eAAe,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC;IACrD,MAAM,YAAY,GAAG,IAAI,gBAAgB,EAAE,CAAC;IAE5C,mDAAmD;IACnD,2EAA2E;IAC3E,6EAA6E;IAC7E,wEAAwE;IACxE,sCAAsC;IACtC,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,WAAW,CAAC,CAAC;IAC/B,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,WAAW,CAAC,CAAC;IAC/B,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC,YAAY,CAAC,CAAC;IAC/B,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,YAAY,CAAC,CAAC;IAElC,+EAA+E;IAC/E,gFAAgF;IAChF,6EAA6E;IAC7E,IAAI,eAAe,GAAyB,IAAI,CAAC;IAEjD,MAAM,QAAQ,GAAkB,IAAI,KAAK,CAAC,EAAmB,EAAE;QAC7D,GAAG,CAAC,OAAO,EAAE,IAAI,EAAE,QAAQ;YACzB,IAAI,CAAC,eAAe,EAAE,CAAC;gBACrB,eAAe,GAAG,eAAe,CAAC,MAAM,CAAC,CAAC;YAC5C,CAAC;YACD,OAAO,OAAO,CAAC,GAAG,CAAC,eAAe,EAAE,IAAI,EAAE,QAAQ,CAAC,CAAC;QACtD,CAAC;KACF,CAAC,CAAC;IAEH,OAAO;QACL,MAAM;QACN,QAAQ;QACR,IAAI,EAAE,YAAY,CAAC,IAAI;QACvB,MAAM,EAAE,YAAY,CAAC,MAAM;QAC3B,SAAS,EAAE,WAAW,CAAC,KAAK;QAC5B,SAAS,EAAE,WAAW,CAAC,KAAK;KAC7B,CAAC;AACJ,CAAC;AAcD,yEAAyE;AACzE,OAAO,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AACjD,OAAO,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AACjD,OAAO,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AACnD,OAAO,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Mock FHIR backend for testing plugins without a real FHIR server.
|
|
3
|
+
*
|
|
4
|
+
* Implements the WitFhir interface with an in-memory resource store.
|
|
5
|
+
* All operations are recorded in the `calls` array for assertion.
|
|
6
|
+
*/
|
|
7
|
+
import type { WitFhir } from "../fhir-client.js";
|
|
8
|
+
import type { MockCall } from "./types.js";
|
|
9
|
+
type WitOkString = {
|
|
10
|
+
tag: "ok";
|
|
11
|
+
val: string;
|
|
12
|
+
};
|
|
13
|
+
type WitOkBool = {
|
|
14
|
+
tag: "ok";
|
|
15
|
+
val: boolean;
|
|
16
|
+
};
|
|
17
|
+
type WitErr = {
|
|
18
|
+
tag: "err";
|
|
19
|
+
val: {
|
|
20
|
+
json: string;
|
|
21
|
+
};
|
|
22
|
+
};
|
|
23
|
+
export declare class MockFhirBackend implements WitFhir {
|
|
24
|
+
private readonly store;
|
|
25
|
+
readonly calls: MockCall[];
|
|
26
|
+
/**
|
|
27
|
+
* Create a mock FHIR backend with optional pre-seeded resources.
|
|
28
|
+
* @param initial - Resources keyed by "ResourceType/id" (e.g., "Patient/123")
|
|
29
|
+
*/
|
|
30
|
+
constructor(initial?: Record<string, any>);
|
|
31
|
+
read(resourceType: string, id: string): WitOkString | WitErr;
|
|
32
|
+
search(resourceType: string, params: [string, string][]): WitOkString | WitErr;
|
|
33
|
+
create(resourceType: string, resourceJson: string): WitOkString | WitErr;
|
|
34
|
+
update(resourceType: string, id: string, resourceJson: string): WitOkString | WitErr;
|
|
35
|
+
delete(resourceType: string, id: string): WitOkBool | WitErr;
|
|
36
|
+
}
|
|
37
|
+
export {};
|
|
38
|
+
//# sourceMappingURL=mock-fhir.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mock-fhir.d.ts","sourceRoot":"","sources":["../../src/testing/mock-fhir.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,mBAAmB,CAAC;AACjD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAE3C,KAAK,WAAW,GAAG;IAAE,GAAG,EAAE,IAAI,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,CAAC;AAC9C,KAAK,SAAS,GAAG;IAAE,GAAG,EAAE,IAAI,CAAC;IAAC,GAAG,EAAE,OAAO,CAAA;CAAE,CAAC;AAC7C,KAAK,MAAM,GAAG;IAAE,GAAG,EAAE,KAAK,CAAC;IAAC,GAAG,EAAE;QAAE,IAAI,EAAE,MAAM,CAAA;KAAE,CAAA;CAAE,CAAC;AAcpD,qBAAa,eAAgB,YAAW,OAAO;IAC7C,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAmB;IACzC,QAAQ,CAAC,KAAK,EAAE,QAAQ,EAAE,CAAM;IAEhC;;;OAGG;gBACS,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC;IASzC,IAAI,CAAC,YAAY,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,WAAW,GAAG,MAAM;IAe5D,MAAM,CACJ,YAAY,EAAE,MAAM,EACpB,MAAM,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,GACzB,WAAW,GAAG,MAAM;IA2CvB,MAAM,CAAC,YAAY,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,GAAG,WAAW,GAAG,MAAM;IAgBxE,MAAM,CACJ,YAAY,EAAE,MAAM,EACpB,EAAE,EAAE,MAAM,EACV,YAAY,EAAE,MAAM,GACnB,WAAW,GAAG,MAAM;IAYvB,MAAM,CAAC,YAAY,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM;CAQ7D"}
|