1import * as AWS from "@/AWS";
2import { AttributeGroup } from "@/AWS/AppRegistry";
3import * as Test from "@/Test/Alchemy";
4import * as appregistry from "@distilled.cloud/aws/service-catalog-appregistry";
5import { expect } from "alchemy-test";
6import * as Data from "effect/Data";
7import * as Effect from "effect/Effect";
8import * as Schedule from "effect/Schedule";
9import { makeAppRegistryTestLease } from "./TestLease.ts";
10
11const { test, beforeAll, afterAll } = Test.make({ providers: AWS.providers() });
12const serviceLease = makeAppRegistryTestLease();
13
14beforeAll(serviceLease.acquire, { timeout: 3_600_000 });
15afterAll(serviceLease.release);
16
17class AttributeGroupStillExists extends Data.TaggedError(
18 "AttributeGroupStillExists",
19)<{ specifier: string }> {}
20
21const assertAttributeGroupGone = (specifier: string) =>
22 appregistry.getAttributeGroup({ attributeGroup: specifier }).pipe(
23 Effect.flatMap(() =>
24 Effect.fail(new AttributeGroupStillExists({ specifier })),
25 ),
26 Effect.catchTag("ResourceNotFoundException", () => Effect.void),
27 Effect.retry({
28 while: (e) => e._tag === "AttributeGroupStillExists",
29 schedule: Schedule.max([
30 Schedule.fixed("2 seconds"),
31 Schedule.recurs(10),
32 ]),
33 }),
34 );
35
36// AppRegistry is in maintenance mode (see Application.test.ts) — lifecycle
37// tests only run on accounts that retain access via AWS_TEST_APPREGISTRY=1.
38test.provider.skipIf(!process.env.AWS_TEST_APPREGISTRY)(
39 "creates, updates, and deletes an attribute group",
40 (stack) =>
41 Effect.gen(function* () {
42 yield* stack.destroy();
43
44 const created = yield* stack.deploy(
45 Effect.gen(function* () {
46 const group = yield* AttributeGroup("TestAttributes", {
47 description: "attribute group lifecycle test",
48 attributes: { owner: "platform-team", costCenter: "1234" },
49 tags: { purpose: "lifecycle" },
50 });
51 return {
52 attributeGroupId: group.attributeGroupId,
53 attributeGroupArn: group.attributeGroupArn,
54 attributeGroupName: group.attributeGroupName,
55 };
56 }),
57 );
58 expect(created.attributeGroupArn).toContain(":servicecatalog:");
59
60 // out-of-band verify via distilled
61 const found = yield* appregistry.getAttributeGroup({
62 attributeGroup: created.attributeGroupId,
63 });
64 expect(found.name).toBe(created.attributeGroupName);
65 expect(found.description).toBe("attribute group lifecycle test");
66 expect(JSON.parse(found.attributes ?? "{}")).toEqual({
67 owner: "platform-team",
68 costCenter: "1234",
69 });
70 expect(found.tags?.purpose).toBe("lifecycle");
71 expect(found.tags?.["alchemy::id"]).toBe("TestAttributes");
72
73 // in-place update of attributes, description, and tags
74 yield* stack.deploy(
75 Effect.gen(function* () {
76 yield* AttributeGroup("TestAttributes", {
77 description: "updated description",
78 attributes: {
79 owner: "commerce-team",
80 costCenter: "5678",
81 tier: 1,
82 },
83 tags: { purpose: "lifecycle-updated" },
84 });
85 }),
86 );
87 const updated = yield* appregistry.getAttributeGroup({
88 attributeGroup: created.attributeGroupId,
89 });
90 expect(updated.id).toBe(created.attributeGroupId);
91 expect(updated.description).toBe("updated description");
92 expect(JSON.parse(updated.attributes ?? "{}")).toEqual({
93 owner: "commerce-team",
94 costCenter: "5678",
95 tier: 1,
96 });
97 expect(updated.tags?.purpose).toBe("lifecycle-updated");
98
99 yield* stack.destroy();
100 yield* assertAttributeGroupGone(created.attributeGroupId);
101 }),
102 {
103 tags: ["provider:aws", "provider:aws:appregistry", "live"],
104 timeout: 180_000,
105 },
106);