1import * as AWS from "@/AWS";
2import { Datastore } from "@/AWS/MedicalImaging";
3import * as Test from "@/Test/Alchemy";
4import * as medicalimaging from "@distilled.cloud/aws/medical-imaging";
5import { expect } from "alchemy-test";
6import * as Effect from "effect/Effect";
7import * as Schedule from "effect/Schedule";
8
9const { test } = Test.make({ providers: AWS.providers() });
10
11// Ungated typed-error probe: prove the distilled error union carries the
12// not-found tag this provider's read/delete paths depend on. Runs in every
13// CI pass at near-zero cost, unlike the gated lifecycle below.
14test.provider(
15 "getDatastore on a nonexistent id fails with ResourceNotFoundException",
16 () =>
17 Effect.gen(function* () {
18 const error = yield* Effect.flip(
19 // well-formed (32 hex chars) but nonexistent datastore id
20 medicalimaging.getDatastore({
21 datastoreId: "00000000000000000000000000000000",
22 }),
23 );
24 expect(error._tag).toBe("ResourceNotFoundException");
25 }),
26 { tags: ["provider:aws", "provider:aws:medicalimaging", "live"] },
27);
28
29// Deletion is async (DELETING → gone); poll bounded until the store no
30// longer resolves (or reports DELETED).
31const assertDatastoreGone = (datastoreId: string) =>
32 Effect.gen(function* () {
33 const status = yield* medicalimaging.getDatastore({ datastoreId }).pipe(
34 Effect.map(
35 (r) => r.datastoreProperties.datastoreStatus as string | "gone",
36 ),
37 Effect.catchTag("ResourceNotFoundException", () =>
38 Effect.succeed("gone" as const),
39 ),
40 );
41 if (status !== "gone" && status !== "DELETED") {
42 return yield* Effect.fail(
43 new Error(
44 `datastore '${datastoreId}' still exists (status: ${status})`,
45 ),
46 );
47 }
48 }).pipe(
49 Effect.retry({
50 schedule: Schedule.max([
51 Schedule.fixed("10 seconds"),
52 Schedule.recurs(30),
53 ]),
54 }),
55 );
56
57// A HealthImaging data store provisions asynchronously (CREATING → ACTIVE,
58// typically a few minutes) and deletes asynchronously too — the full
59// lifecycle is gated behind AWS_TEST_MEDICAL_IMAGING=1 and always destroys
60// what it created.
61test.provider.skipIf(!process.env.AWS_TEST_MEDICAL_IMAGING)(
62 "create data store, update tags, destroy",
63 (stack) =>
64 Effect.gen(function* () {
65 yield* stack.destroy();
66
67 const { datastore } = yield* stack.deploy(
68 Effect.gen(function* () {
69 const datastore = yield* Datastore("Store", {
70 tags: { fixture: "medical-imaging" },
71 });
72 return { datastore };
73 }),
74 );
75
76 expect(datastore.datastoreId).toBeDefined();
77 expect(datastore.datastoreArn).toContain(":datastore/");
78 expect(datastore.datastoreStatus).toBe("ACTIVE");
79 expect(datastore.tags.fixture).toBe("medical-imaging");
80
81 // Out-of-band verification via distilled.
82 const observed = yield* medicalimaging.getDatastore({
83 datastoreId: datastore.datastoreId,
84 });
85 expect(observed.datastoreProperties.datastoreStatus).toBe("ACTIVE");
86 expect(observed.datastoreProperties.datastoreName).toBe(
87 datastore.datastoreName,
88 );
89
90 // Update path: tags are the only mutable aspect (HealthImaging has no
91 // UpdateDatastore); the same physical store must be reused.
92 const { datastore: updated } = yield* stack.deploy(
93 Effect.gen(function* () {
94 const datastore = yield* Datastore("Store", {
95 tags: { fixture: "medical-imaging", extra: "added" },
96 });
97 return { datastore };
98 }),
99 );
100 expect(updated.datastoreId).toBe(datastore.datastoreId);
101 expect(updated.tags.extra).toBe("added");
102
103 const observedTags = yield* medicalimaging.listTagsForResource({
104 resourceArn: datastore.datastoreArn,
105 });
106 expect(observedTags.tags?.extra).toBe("added");
107
108 // Destroy and verify deletion completes out-of-band (DELETING → gone).
109 yield* stack.destroy();
110 yield* assertDatastoreGone(datastore.datastoreId);
111 }),
112 // async create (a few minutes) + tag update + async delete, one test.
113 {
114 tags: ["provider:aws", "provider:aws:medicalimaging", "live"],
115 timeout: 1_200_000,
116 },
117);