1import * as AWS from "@/AWS";
2import { Registry, Schema } from "@/AWS/Schemas";
3import * as Test from "@/Test/Alchemy";
4import * as schemas from "@distilled.cloud/aws/schemas";
5import { describe, 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
11const contentV1 = JSON.stringify({
12 openapi: "3.0.0",
13 info: { version: "1.0.0", title: "OrderCreated" },
14 paths: {},
15 components: {
16 schemas: {
17 OrderCreated: {
18 type: "object",
19 properties: {
20 orderId: { type: "string" },
21 },
22 },
23 },
24 },
25});
26
27const contentV2 = JSON.stringify({
28 openapi: "3.0.0",
29 info: { version: "1.0.0", title: "OrderCreated" },
30 paths: {},
31 components: {
32 schemas: {
33 OrderCreated: {
34 type: "object",
35 properties: {
36 orderId: { type: "string" },
37 amount: { type: "number" },
38 },
39 },
40 },
41 },
42});
43
44const assertSchemaGone = (registryName: string, schemaName: string) =>
45 schemas
46 .describeSchema({ RegistryName: registryName, SchemaName: schemaName })
47 .pipe(
48 Effect.flatMap(() =>
49 Effect.fail(new Error(`schema ${schemaName} still exists`)),
50 ),
51 Effect.catchTag("NotFoundException", () => Effect.void),
52 Effect.retry({
53 while: (e) => e instanceof Error,
54 schedule: Schedule.max([
55 Schedule.fixed("2 seconds"),
56 Schedule.recurs(10),
57 ]),
58 }),
59 );
60
61describe(
62 "AWS.Schemas.Schema",
63 { tags: ["provider:aws", "provider:aws:schemas", "live"] },
64 () => {
65 test.provider(
66 "creates a schema, publishes a new version on content change, and deletes it",
67 (stack) =>
68 Effect.gen(function* () {
69 yield* stack.destroy();
70
71 // CREATE — registry + schema.
72 const created = yield* stack.deploy(
73 Effect.gen(function* () {
74 const registry = yield* Registry("SchemaTestRegistry", {});
75 const schema = yield* Schema("OrderCreated", {
76 registryName: registry.registryName,
77 type: "OpenApi3",
78 content: contentV1,
79 description: "Order created event",
80 tags: { purpose: "alchemy-test" },
81 });
82 return {
83 registryName: registry.registryName,
84 schemaName: schema.schemaName,
85 schemaArn: schema.schemaArn,
86 schemaVersion: schema.schemaVersion,
87 };
88 }),
89 );
90 expect(created.schemaVersion).toEqual("1");
91
92 // Verify out-of-band via distilled.
93 const observed = yield* schemas.describeSchema({
94 RegistryName: created.registryName,
95 SchemaName: created.schemaName,
96 });
97 expect(observed.SchemaArn).toEqual(created.schemaArn);
98 expect(observed.Type).toEqual("OpenApi3");
99 expect(observed.Description).toEqual("Order created event");
100 expect(JSON.parse(observed.Content!)).toEqual(JSON.parse(contentV1));
101 expect(observed.Tags?.purpose).toEqual("alchemy-test");
102 expect(observed.Tags?.["alchemy::id"]).toEqual("OrderCreated");
103
104 // NO-OP — redeploying identical desired state must not publish a new
105 // schema version (content comparison is canonical).
106 const noop = yield* stack.deploy(
107 Effect.gen(function* () {
108 const registry = yield* Registry("SchemaTestRegistry", {});
109 const schema = yield* Schema("OrderCreated", {
110 registryName: registry.registryName,
111 type: "OpenApi3",
112 content: contentV1,
113 description: "Order created event",
114 tags: { purpose: "alchemy-test" },
115 });
116 return { schemaVersion: schema.schemaVersion };
117 }),
118 );
119 expect(noop.schemaVersion).toEqual("1");
120
121 // UPDATE — new content publishes a new schema version.
122 const updated = yield* stack.deploy(
123 Effect.gen(function* () {
124 const registry = yield* Registry("SchemaTestRegistry", {});
125 const schema = yield* Schema("OrderCreated", {
126 registryName: registry.registryName,
127 type: "OpenApi3",
128 content: contentV2,
129 description: "Order created event",
130 tags: { purpose: "alchemy-test" },
131 });
132 return {
133 schemaName: schema.schemaName,
134 schemaVersion: schema.schemaVersion,
135 };
136 }),
137 );
138 expect(updated.schemaName).toEqual(created.schemaName);
139 expect(updated.schemaVersion).toEqual("2");
140
141 const afterUpdate = yield* schemas.describeSchema({
142 RegistryName: created.registryName,
143 SchemaName: created.schemaName,
144 });
145 expect(JSON.parse(afterUpdate.Content!)).toEqual(
146 JSON.parse(contentV2),
147 );
148
149 // DELETE
150 yield* stack.destroy();
151 yield* assertSchemaGone(created.registryName, created.schemaName);
152 }),
153 { timeout: 120_000 },
154 );
155 },
156);