1import * as AWS from "@/AWS";
2import { AggregationAuthorization } from "@/AWS/Config";
3import * as Test from "@/Test/Alchemy";
4import * as config from "@distilled.cloud/aws/config-service";
5import { expect } from "alchemy-test";
6import * as Data from "effect/Data";
7import * as Effect from "effect/Effect";
8import * as Schedule from "effect/Schedule";
9import * as Stream from "effect/Stream";
10
11const { test } = Test.make({ providers: AWS.providers() });
12
13// A fixed, syntactically valid aggregator identity. The authorization is a
14// pure grant — the target account does not need to exist.
15const AGGREGATOR_ACCOUNT = "123456789012";
16
17const findAuthorization = (accountId: string, region: string) =>
18 config.describeAggregationAuthorizations.items({}).pipe(
19 Stream.filter(
20 (auth) =>
21 auth.AuthorizedAccountId === accountId &&
22 auth.AuthorizedAwsRegion === region,
23 ),
24 Stream.runHead,
25 Effect.map((head) => (head._tag === "Some" ? head.value : undefined)),
26 );
27
28class AuthorizationStillExists extends Data.TaggedError(
29 "AuthorizationStillExists",
30)<{ readonly region: string }> {}
31
32const assertAuthorizationDeleted = (accountId: string, region: string) =>
33 findAuthorization(accountId, region).pipe(
34 Effect.flatMap((auth) =>
35 auth === undefined
36 ? Effect.void
37 : Effect.fail(new AuthorizationStillExists({ region })),
38 ),
39 Effect.retry({
40 while: (e) => e._tag === "AuthorizationStillExists",
41 schedule: Schedule.max([Schedule.exponential(500), Schedule.recurs(8)]),
42 }),
43 );
44
45test.provider(
46 "create, update tags, replace on region change, delete",
47 (stack) =>
48 Effect.gen(function* () {
49 yield* stack.destroy();
50
51 const auth = yield* stack.deploy(
52 Effect.gen(function* () {
53 return yield* AggregationAuthorization("TestAuth", {
54 authorizedAccountId: AGGREGATOR_ACCOUNT,
55 authorizedAwsRegion: "us-east-1",
56 tags: { Environment: "test" },
57 });
58 }),
59 );
60
61 expect(auth.authorizedAccountId).toBe(AGGREGATOR_ACCOUNT);
62 expect(auth.authorizedAwsRegion).toBe("us-east-1");
63 expect(auth.aggregationAuthorizationArn).toContain(
64 ":aggregation-authorization/",
65 );
66
67 // Out-of-band verification via distilled.
68 const created = yield* findAuthorization(AGGREGATOR_ACCOUNT, "us-east-1");
69 expect(created?.AggregationAuthorizationArn).toBe(
70 auth.aggregationAuthorizationArn,
71 );
72 const tags = yield* config
73 .listTagsForResource({
74 ResourceArn: auth.aggregationAuthorizationArn,
75 })
76 .pipe(
77 Effect.map((r) =>
78 Object.fromEntries((r.Tags ?? []).map((t) => [t.Key, t.Value])),
79 ),
80 );
81 expect(tags.Environment).toBe("test");
82 expect(tags["alchemy::id"]).toBe("TestAuth");
83
84 // Tags update in place (Put ignores tags on an existing authorization,
85 // so this exercises the TagResource sync path).
86 const updated = yield* stack.deploy(
87 Effect.gen(function* () {
88 return yield* AggregationAuthorization("TestAuth", {
89 authorizedAccountId: AGGREGATOR_ACCOUNT,
90 authorizedAwsRegion: "us-east-1",
91 tags: { Environment: "prod" },
92 });
93 }),
94 );
95 expect(updated.aggregationAuthorizationArn).toBe(
96 auth.aggregationAuthorizationArn,
97 );
98 const updatedTags = yield* config
99 .listTagsForResource({
100 ResourceArn: auth.aggregationAuthorizationArn,
101 })
102 .pipe(
103 Effect.map((r) =>
104 Object.fromEntries((r.Tags ?? []).map((t) => [t.Key, t.Value])),
105 ),
106 );
107 expect(updatedTags.Environment).toBe("prod");
108
109 // Changing the authorized region replaces the authorization.
110 const replaced = yield* stack.deploy(
111 Effect.gen(function* () {
112 return yield* AggregationAuthorization("TestAuth", {
113 authorizedAccountId: AGGREGATOR_ACCOUNT,
114 authorizedAwsRegion: "us-west-2",
115 tags: { Environment: "prod" },
116 });
117 }),
118 );
119 expect(replaced.authorizedAwsRegion).toBe("us-west-2");
120 expect(replaced.aggregationAuthorizationArn).not.toBe(
121 auth.aggregationAuthorizationArn,
122 );
123 yield* assertAuthorizationDeleted(AGGREGATOR_ACCOUNT, "us-east-1");
124
125 yield* stack.destroy();
126 yield* assertAuthorizationDeleted(AGGREGATOR_ACCOUNT, "us-west-2");
127 }),
128 { tags: ["provider:aws", "provider:aws:config", "live"], timeout: 120_000 },
129);