1import * as AWS from "@/AWS";
2import { Profile } from "@/AWS/Route53Profiles";
3import * as Test from "@/Test/Alchemy";
4import * as profiles from "@distilled.cloud/aws/route53profiles";
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// A deleted Profile lingers in `DELETING` for a short while and is still
12// returned by GetProfile — both RNF and DELETING/DELETED count as gone.
13const assertProfileGone = (profileId: string) =>
14 profiles.getProfile({ ProfileId: profileId }).pipe(
15 Effect.flatMap((r) =>
16 r.Profile?.Status === "DELETING" || r.Profile?.Status === "DELETED"
17 ? Effect.void
18 : Effect.fail(new Error(`profile still ${r.Profile?.Status}`)),
19 ),
20 Effect.catchTag("ResourceNotFoundException", () => Effect.void),
21 Effect.retry({
22 while: (e) => e instanceof Error,
23 schedule: Schedule.max([
24 Schedule.fixed("2 seconds"),
25 Schedule.recurs(10),
26 ]),
27 }),
28 );
29
30test.provider(
31 "create, update tags, replace on rename, destroy",
32 (stack) =>
33 Effect.gen(function* () {
34 yield* stack.destroy();
35
36 // Create with a generated name + user tags.
37 const first = yield* stack.deploy(
38 Effect.gen(function* () {
39 const profile = yield* Profile("DnsProfile", {
40 tags: { env: "test" },
41 });
42 return { profile };
43 }),
44 );
45 expect(first.profile.profileId).toMatch(/^rp-/);
46 expect(first.profile.profileArn).toContain(first.profile.profileId);
47
48 // Out-of-band: the profile exists and carries alchemy + user tags.
49 const live = yield* profiles.getProfile({
50 ProfileId: first.profile.profileId,
51 });
52 expect(live.Profile?.Name).toBe(first.profile.profileName);
53 const tags = yield* profiles.listTagsForResource({
54 ResourceArn: first.profile.profileArn,
55 });
56 expect(tags.Tags.env).toBe("test");
57 expect(
58 Object.keys(tags.Tags).some((key) => key.startsWith("alchemy:")),
59 ).toBe(true);
60
61 // Update tags in place — same profile id.
62 const second = yield* stack.deploy(
63 Effect.gen(function* () {
64 const profile = yield* Profile("DnsProfile", {
65 tags: { env: "prod", team: "platform" },
66 });
67 return { profile };
68 }),
69 );
70 expect(second.profile.profileId).toBe(first.profile.profileId);
71 const updatedTags = yield* profiles.listTagsForResource({
72 ResourceArn: second.profile.profileArn,
73 });
74 expect(updatedTags.Tags.env).toBe("prod");
75 expect(updatedTags.Tags.team).toBe("platform");
76
77 // Renaming replaces (profiles have no update API).
78 const third = yield* stack.deploy(
79 Effect.gen(function* () {
80 const profile = yield* Profile("DnsProfile", {
81 name: "alchemy-test-profile-renamed",
82 });
83 return { profile };
84 }),
85 );
86 expect(third.profile.profileName).toBe("alchemy-test-profile-renamed");
87 expect(third.profile.profileId).not.toBe(first.profile.profileId);
88 yield* assertProfileGone(first.profile.profileId);
89
90 yield* stack.destroy();
91 yield* assertProfileGone(third.profile.profileId);
92 }),
93 {
94 tags: ["provider:aws", "provider:aws:route53profiles", "live"],
95 timeout: 120_000,
96 },
97);