1import * as AWS from "@/AWS";
2import { ResourcePolicy } from "@/AWS/XRay";
3import * as Test from "@/Test/Alchemy";
4import * as xray from "@distilled.cloud/aws/xray";
5import { expect } from "alchemy-test";
6import * as Data from "effect/Data";
7import * as Effect from "effect/Effect";
8import * as Option from "effect/Option";
9import * as Schedule from "effect/Schedule";
10import * as Stream from "effect/Stream";
11
12const { test } = Test.make({ providers: AWS.providers() });
13
14const findPolicy = (policyName: string) =>
15 xray.listResourcePolicies.items({}).pipe(
16 Stream.filter((policy) => policy.PolicyName === policyName),
17 Stream.runHead,
18 Effect.map(Option.getOrUndefined),
19 );
20
21class PolicyStillExists extends Data.TaggedError("PolicyStillExists")<{
22 readonly policyName: string;
23}> {}
24
25const assertPolicyDeleted = (policyName: string) =>
26 findPolicy(policyName).pipe(
27 Effect.flatMap((policy) =>
28 policy === undefined
29 ? Effect.void
30 : Effect.fail(new PolicyStillExists({ policyName })),
31 ),
32 Effect.retry({
33 while: (e) => e._tag === "PolicyStillExists",
34 schedule: Schedule.max([Schedule.exponential(500), Schedule.recurs(8)]),
35 }),
36 );
37
38const policyDocument = (actions: string[]) =>
39 JSON.stringify({
40 Version: "2012-10-17",
41 Statement: [
42 {
43 Effect: "Allow",
44 Principal: { Service: "sns.amazonaws.com" },
45 Action: actions,
46 Resource: "*",
47 },
48 ],
49 });
50
51test.provider(
52 "create, update document, delete resource policy",
53 (stack) =>
54 Effect.gen(function* () {
55 yield* stack.destroy();
56
57 const created = yield* stack.deploy(
58 Effect.gen(function* () {
59 return yield* ResourcePolicy("TestResourcePolicy", {
60 policyDocument: policyDocument(["xray:PutTraceSegments"]),
61 });
62 }),
63 );
64
65 expect(created.policyName).toBeDefined();
66 expect(created.policyRevisionId).toBeDefined();
67
68 // out-of-band verification via distilled
69 const live = yield* findPolicy(created.policyName);
70 expect(live?.PolicyDocument).toContain("xray:PutTraceSegments");
71
72 // update the document in place (same name, new statement)
73 const updated = yield* stack.deploy(
74 Effect.gen(function* () {
75 return yield* ResourcePolicy("TestResourcePolicy", {
76 policyDocument: policyDocument([
77 "xray:PutTraceSegments",
78 "xray:GetSamplingRules",
79 ]),
80 });
81 }),
82 );
83 expect(updated.policyName).toBe(created.policyName);
84 expect(updated.policyRevisionId).not.toBe(created.policyRevisionId);
85
86 const liveUpdated = yield* findPolicy(created.policyName);
87 expect(liveUpdated?.PolicyDocument).toContain("xray:GetSamplingRules");
88
89 // re-deploy with the same document — a no-op that keeps the revision
90 const noop = yield* stack.deploy(
91 Effect.gen(function* () {
92 return yield* ResourcePolicy("TestResourcePolicy", {
93 policyDocument: policyDocument([
94 "xray:PutTraceSegments",
95 "xray:GetSamplingRules",
96 ]),
97 });
98 }),
99 );
100 expect(noop.policyRevisionId).toBe(updated.policyRevisionId);
101
102 yield* stack.destroy();
103 yield* assertPolicyDeleted(created.policyName);
104 }).pipe(
105 Effect.ensuring(
106 stack.destroy().pipe(Effect.catchCause(() => Effect.void)),
107 ),
108 ),
109 { tags: ["provider:aws", "provider:aws:xray", "live"], timeout: 120_000 },
110);