Skip to content

Commit db9fd31

Browse files
committed
add tests for FHIR HTTP methods
1 parent 37877d9 commit db9fd31

File tree

6 files changed

+275
-10
lines changed

6 files changed

+275
-10
lines changed
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
volumes:
2+
postgres_data: {}
3+
services:
4+
postgres:
5+
image: docker.io/library/postgres:18
6+
volumes:
7+
- postgres_data:/var/lib/postgresql/18/docker:delegated
8+
command:
9+
- postgres
10+
- -c
11+
- shared_preload_libraries=pg_stat_statements
12+
environment:
13+
POSTGRES_USER: aidbox
14+
POSTGRES_PORT: '5432'
15+
POSTGRES_DB: aidbox
16+
POSTGRES_PASSWORD: postgres
17+
aidbox:
18+
image: docker.io/healthsamurai/aidboxone:edge
19+
pull_policy: always
20+
depends_on:
21+
- postgres
22+
ports:
23+
- 8080:8080
24+
volumes:
25+
- "./resources/bundle.json:/tmp/bundle.json:z"
26+
environment:
27+
BOX_ADMIN_PASSWORD: password
28+
BOX_BOOTSTRAP_FHIR_PACKAGES: hl7.fhir.r4.core#4.0.1
29+
BOX_COMPATIBILITY_VALIDATION_JSON__SCHEMA_REGEX: '#{:fhir-datetime}'
30+
BOX_DB_DATABASE: aidbox
31+
BOX_DB_HOST: postgres
32+
BOX_DB_PASSWORD: postgres
33+
BOX_DB_PORT: '5432'
34+
BOX_DB_USER: aidbox
35+
BOX_FHIR_BUNDLE_EXECUTION_VALIDATION_MODE: limited
36+
BOX_FHIR_COMPLIANT_MODE: 'true'
37+
BOX_FHIR_CORRECT_AIDBOX_FORMAT: 'true'
38+
BOX_FHIR_SCHEMA_VALIDATION: 'true'
39+
BOX_FHIR_SEARCH_AUTHORIZE_INLINE_REQUESTS: 'true'
40+
BOX_FHIR_SEARCH_CHAIN_SUBSELECT: 'true'
41+
BOX_FHIR_SEARCH_COMPARISONS: 'true'
42+
BOX_FHIR_TERMINOLOGY_ENGINE: hybrid
43+
BOX_FHIR_TERMINOLOGY_ENGINE_HYBRID_EXTERNAL_TX_SERVER: https://tx.health-samurai.io/fhir
44+
BOX_FHIR_TERMINOLOGY_SERVICE_BASE_URL: https://tx.health-samurai.io/fhir
45+
BOX_MODULE_SDC_STRICT_ACCESS_CONTROL: 'true'
46+
BOX_SEARCH_INCLUDE_CONFORMANT: 'true'
47+
BOX_SECURITY_AUDIT_LOG_ENABLED: 'true'
48+
BOX_SECURITY_DEV_MODE: 'true'
49+
BOX_SETTINGS_MODE: read-write
50+
BOX_WEB_BASE_URL: http://localhost:8080
51+
BOX_WEB_PORT: 8080
52+
BOX_INIT_BUNDLE: file:///tmp/bundle.json
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
{
2+
"resourceType": "Bundle",
3+
"type": "transaction",
4+
"entry": [
5+
{
6+
"request": {
7+
"method": "POST",
8+
"url": "/AccessPolicy"
9+
},
10+
"resource": {
11+
"engine": "allow",
12+
"resourceType": "AccessPolicy"
13+
}
14+
}
15+
]
16+
}

packages/aidbox-client/src/client.ts

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import type {
22
BatchOptions,
33
CapabilitiesOptions,
44
ConditionalCreateOptions,
5+
ConditionalDeleteOptions,
56
ConditionalPatchOptions,
67
ConditionalUpdateOptions,
78
CreateOptions,
@@ -358,7 +359,6 @@ export function makeClient<TBundle, TOperationOutcome, TUser>({
358359
return await request<T>({
359360
url: `/fhir/${opts.type}/${opts.id}`,
360361
method: "DELETE",
361-
params: opts.searchParameters ?? [],
362362
});
363363
};
364364

@@ -385,25 +385,19 @@ export function makeClient<TBundle, TOperationOutcome, TUser>({
385385
};
386386

387387
const conditionalDelete = async <T>(
388-
opts: DeleteOptions,
388+
opts: ConditionalDeleteOptions,
389389
): Promise<
390390
Result<ResourceResponse<T>, ResourceResponse<TOperationOutcome>>
391391
> => {
392392
const url = ["/fhir"];
393393
if (opts.type) url.push(opts.type);
394-
if (opts.id) url.push(opts.id);
395394

396395
const requestParams: RequestParams = {
397396
url: url.join("/"),
398397
method: "DELETE",
399398
params: opts.searchParameters,
400399
};
401400

402-
if (opts.id && !opts.type)
403-
throw new RequestError(
404-
"resource type must be specified if ID is provided",
405-
{ request: requestParams },
406-
);
407401
return await request<T>(requestParams);
408402
};
409403

packages/aidbox-client/src/fhir-http.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,11 @@ export type ConditionalPatchOptions = PatchOptions & {
4141
searchParameters: Parameters;
4242
};
4343

44-
export type DeleteOptions = Omit<UpdateOptions, "resource"> & {
44+
export type DeleteOptions = Omit<CreateOptions, "resource"> & {
45+
id: string;
46+
};
47+
48+
export type ConditionalDeleteOptions = Omit<DeleteOptions, "id"> & {
4549
searchParameters: Parameters;
4650
};
4751

packages/aidbox-client/src/types.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import type {
22
BatchOptions,
33
CapabilitiesOptions,
44
ConditionalCreateOptions,
5+
ConditionalDeleteOptions,
56
ConditionalPatchOptions,
67
ConditionalUpdateOptions,
78
CreateOptions,
@@ -190,7 +191,7 @@ export type FhirServerClient<
190191
Result<ResourceResponse<T>, ResourceResponse<TOperationOutcome>>
191192
>;
192193
conditionalDelete: <T>(
193-
opts: DeleteOptions,
194+
opts: ConditionalDeleteOptions,
194195
) => Promise<
195196
Result<ResourceResponse<T>, ResourceResponse<TOperationOutcome>>
196197
>;
Lines changed: 198 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,198 @@
1+
import { describe, it, expect } from "vitest";
2+
import { makeClient } from "src/client.js";
3+
// import type { Bundle, OperationOutcome } from "src/fhir-types/hl7-fhir-r4-core";
4+
// import type { User, ResourceResponse } from "src/types";
5+
// import type { Result } from "src/result";
6+
7+
const client = makeClient({ baseurl: "http://localhost:8080" });
8+
const patientId = "test-id";
9+
10+
describe("Type Level Interaction", () => {
11+
describe("create", () => {
12+
it("should create a Patient", async () => {
13+
const result = await client.create({
14+
type: "Patient",
15+
resource: {
16+
id: `pt-${patientId}`,
17+
}
18+
});
19+
expect(result.isOk()).toBe(true);
20+
if (result.isOk())
21+
expect(result.value.resource).toMatchObject({
22+
id: `pt-${patientId}`,
23+
resourceType: "Patient",
24+
});
25+
});
26+
});
27+
describe("conditionalCreate", () => {
28+
it("should create new Patient", async () => {
29+
const result = await client.conditionalCreate({
30+
type: "Patient",
31+
searchParameters: [["given", "John"]],
32+
resource: {
33+
resourceType: "Patient",
34+
name: [{
35+
family: "Doe",
36+
given: ["John"]
37+
}],
38+
}
39+
});
40+
expect(result.isOk()).toBe(true);
41+
if (result.isOk())
42+
expect(result.value.resource).toMatchObject({
43+
resourceType: "Patient",
44+
});
45+
});
46+
it("should not create a new Patient", async () => {
47+
const result = await client.conditionalCreate({
48+
type: "Patient",
49+
searchParameters: [["family", "Doe"]],
50+
resource: {
51+
resourceType: "Patient",
52+
name: [{
53+
family: "Smith",
54+
given: ["John"]
55+
}],
56+
}
57+
});
58+
expect(result.isOk()).toBe(true);
59+
if (result.isOk())
60+
expect(result.value.resource).toMatchObject({
61+
resourceType: "Patient",
62+
name: [{
63+
family: "Doe",
64+
given: ["John"]
65+
}],
66+
});
67+
});
68+
});
69+
describe("search", () => {
70+
it("should find a Patient", async () => {
71+
const result = await client.search({query: [["family", "Doe"]], type: "Patient"});
72+
expect(result.isOk()).toBe(true);
73+
if (result.isOk())
74+
expect(result.value.resource).toMatchObject({
75+
resourceType: "Bundle",
76+
entry: [{
77+
resource: {
78+
name: [{
79+
family: "Doe",
80+
given: [
81+
"John",
82+
],
83+
}],
84+
resourceType: "Patient",
85+
},
86+
search: {
87+
mode: "match",
88+
},
89+
}],
90+
});
91+
});
92+
it("should not find a Patient", async () => {
93+
const result = await client.search({query: [["family", "Smith"]], type: "Patient"});
94+
expect(result.isOk()).toBe(true);
95+
if (result.isOk())
96+
expect(result.value.resource).toMatchObject({
97+
resourceType: "Bundle",
98+
total: 0,
99+
type: "searchset",
100+
});
101+
});
102+
});
103+
describe("conditionalDelete", () => {
104+
it("should delete a Patient", async () => {
105+
const result = await client.conditionalDelete({
106+
type: "Patient",
107+
searchParameters: [["family", "Doe"]],
108+
});
109+
expect(result.isOk()).toBe(true);
110+
if (result.isOk())
111+
expect(result.value.resource).toMatchObject({
112+
name: [{
113+
family: "Doe",
114+
given: [
115+
"John",
116+
],
117+
}],
118+
resourceType: "Patient",
119+
});
120+
});
121+
});
122+
describe("history", () => {
123+
it("should retrieve type-level history", async () => {
124+
const result = await client.history({type: "Patient"});
125+
expect(result.isOk()).toBe(true);
126+
if (result.isOk())
127+
expect(result.value.resource).toMatchObject({
128+
resourceType: "Bundle",
129+
type: "history",
130+
});
131+
});
132+
});
133+
});
134+
135+
describe("Instance Level Interaction", () => {
136+
describe("read", () => {
137+
it("should read Patient", async () => {
138+
const result = await client.read({type: "Patient", id: `pt-${patientId}`});
139+
expect(result.isOk()).toBe(true);
140+
if (result.isOk())
141+
expect(result.value.resource).toMatchObject({
142+
id: `pt-${patientId}`,
143+
resourceType: "Patient",
144+
});
145+
})
146+
});
147+
// describe("vread", () => {
148+
// it("should", async () => { const result = await client.vread(); });
149+
// });
150+
// describe("update", () => {
151+
// it("should", async () => { const result = await client.update(); });
152+
// });
153+
// describe("conditionalUpdate", () => {
154+
// it("should", async () => { const result = await client.conditionalUpdate(); });
155+
// });
156+
// describe("patch", () => {
157+
// it("should", async () => { const result = await client.patch(); });
158+
// });
159+
// describe("conditionalPatch", () => {
160+
// it("should", async () => { const result = await client.conditionalPatch(); });
161+
// });
162+
// describe("delete", () => {
163+
// it("should", async () => { const result = await client.delete(); });
164+
// });
165+
// describe("deleteHistory", () => {
166+
// it("should", async () => { const result = await client.deleteHistory(); });
167+
// });
168+
// describe("deleteHistoryVersion", () => {
169+
// it("should", async () => { const result = await client.deleteHistoryVersion(); });
170+
// });
171+
// describe("history", () => {
172+
// it("should", async () => { const result = await client.history(); });
173+
// });
174+
});
175+
176+
// describe("Whole System Interaction", () => {
177+
// describe("capabilities", () => {
178+
// it("should", async () => { const result = await client.capabilities(); });
179+
// });
180+
// describe("batch", () => {
181+
// it("should", async () => { const result = await client.batch(); });
182+
// });
183+
// describe("delete", () => {
184+
// it("should", async () => { const result = await client.delete(); });
185+
// });
186+
// describe("history", () => {
187+
// it("should", async () => { const result = await client.history(); });
188+
// });
189+
// describe("search", () => {
190+
// it("should", async () => { const result = await client.search(); });
191+
// });
192+
// })
193+
194+
// describe("Compartment Interaction", () => {
195+
// describe("search", () => {
196+
// it("should", async () => { const result = await client.search(); });
197+
// });
198+
// });

0 commit comments

Comments
 (0)