1import * as AWS from "@/AWS";
2import { ProfilePermission, SigningProfile } from "@/AWS/Signer";
3import * as Test from "@/Test/Alchemy";
4import * as signer from "@distilled.cloud/aws/signer";
5import * as sts from "@distilled.cloud/aws/sts";
6import { describe, expect } from "alchemy-test";
7import * as Effect from "effect/Effect";
8
9const { test } = Test.make({ providers: AWS.providers() });
10
11describe(
12 "AWS.Signer.ProfilePermission",
13 { tags: ["provider:aws", "provider:aws:signer", "live"] },
14 () => {
15 test.provider(
16 "adds a cross-account permission, converges action changes in place, removes on destroy",
17 (stack) =>
18 Effect.gen(function* () {
19 yield* stack.destroy();
20
21 const { Account } = yield* sts.getCallerIdentity({});
22 const principal = Account!;
23
24 // CREATE
25 const created = yield* stack.deploy(
26 Effect.gen(function* () {
27 const profile = yield* SigningProfile("PermProfile", {
28 platformId: "AWSLambda-SHA384-ECDSA",
29 });
30 const permission = yield* ProfilePermission("CiCanSign", {
31 profileName: profile.profileName,
32 action: "signer:StartSigningJob",
33 principal,
34 });
35 return {
36 profileName: permission.profileName,
37 statementId: permission.statementId,
38 };
39 }),
40 );
41
42 // Verify out-of-band via distilled
43 const observed = yield* signer.listProfilePermissions({
44 profileName: created.profileName,
45 });
46 const statement = (observed.permissions ?? []).find(
47 (p) => p.statementId === created.statementId,
48 );
49 expect(statement).toBeDefined();
50 expect(statement!.action).toBe("signer:StartSigningJob");
51 expect(statement!.principal).toBe(principal);
52
53 // UPDATE — no update API: the provider removes + re-adds under the
54 // SAME statement id (no replacement of the logical resource).
55 const updated = yield* stack.deploy(
56 Effect.gen(function* () {
57 const profile = yield* SigningProfile("PermProfile", {
58 platformId: "AWSLambda-SHA384-ECDSA",
59 });
60 const permission = yield* ProfilePermission("CiCanSign", {
61 profileName: profile.profileName,
62 action: "signer:GetSigningProfile",
63 principal,
64 });
65 return {
66 profileName: permission.profileName,
67 statementId: permission.statementId,
68 };
69 }),
70 );
71
72 expect(updated.profileName).toEqual(created.profileName);
73 expect(updated.statementId).toEqual(created.statementId);
74
75 const afterUpdate = yield* signer.listProfilePermissions({
76 profileName: created.profileName,
77 });
78 const updatedStatement = (afterUpdate.permissions ?? []).find(
79 (p) => p.statementId === created.statementId,
80 );
81 expect(updatedStatement).toBeDefined();
82 expect(updatedStatement!.action).toBe("signer:GetSigningProfile");
83
84 // DESTROY — the statement is removed (the profile is canceled by its
85 // own delete, but its policy must no longer contain our statement).
86 yield* stack.destroy();
87 const afterDestroy = yield* signer
88 .listProfilePermissions({ profileName: created.profileName })
89 .pipe(
90 Effect.catchTag("ResourceNotFoundException", () =>
91 Effect.succeed({
92 permissions: [] as signer.Permission[],
93 } as signer.ListProfilePermissionsResponse),
94 ),
95 );
96 expect(
97 (afterDestroy.permissions ?? []).find(
98 (p) => p.statementId === created.statementId,
99 ),
100 ).toBeUndefined();
101 }),
102 { timeout: 120_000 },
103 );
104 },
105);