1import * as AWS from "@/AWS";
2import { SigningProfile } from "@/AWS/Signer";
3import * as Test from "@/Test/Alchemy";
4import * as signer from "@distilled.cloud/aws/signer";
5import { describe, 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/**
12 * Signing profiles cannot be deleted — destroy cancels them. Wait (bounded)
13 * for the profile to observe as Canceled.
14 */
15const assertProfileCanceled = (profileName: string) =>
16 signer.getSigningProfile({ profileName }).pipe(
17 Effect.repeat({
18 schedule: Schedule.fixed("2 seconds"),
19 until: (r) => r.status === "Canceled",
20 times: 10,
21 }),
22 Effect.map((r) => {
23 expect(r.status).toBe("Canceled");
24 }),
25 );
26
27describe(
28 "AWS.Signer.SigningProfile",
29 { tags: ["provider:aws", "provider:aws:signer", "live"] },
30 () => {
31 test.provider(
32 "creates a Lambda code-signing profile, syncs tags, replaces on validity change, cancels on destroy",
33 (stack) =>
34 Effect.gen(function* () {
35 yield* stack.destroy();
36
37 // CREATE
38 const created = yield* stack.deploy(
39 Effect.gen(function* () {
40 const profile = yield* SigningProfile("ReleaseProfile", {
41 platformId: "AWSLambda-SHA384-ECDSA",
42 signatureValidityPeriod: { value: 12, type: "MONTHS" },
43 tags: { purpose: "alchemy-test" },
44 });
45 return {
46 profileName: profile.profileName,
47 arn: profile.arn,
48 profileVersion: profile.profileVersion,
49 profileVersionArn: profile.profileVersionArn,
50 platformId: profile.platformId,
51 status: profile.status,
52 };
53 }),
54 );
55
56 expect(created.platformId).toBe("AWSLambda-SHA384-ECDSA");
57 expect(created.status).toBe("Active");
58 expect(created.profileVersionArn).toContain(created.profileName);
59
60 // Verify out-of-band via distilled
61 const observed = yield* signer.getSigningProfile({
62 profileName: created.profileName,
63 });
64 expect(observed.arn).toEqual(created.arn);
65 expect(observed.profileVersion).toEqual(created.profileVersion);
66 expect(observed.status).toBe("Active");
67 expect(observed.platformId).toBe("AWSLambda-SHA384-ECDSA");
68 expect(observed.signatureValidityPeriod).toEqual({
69 value: 12,
70 type: "MONTHS",
71 });
72 expect(observed.tags?.purpose).toBe("alchemy-test");
73 // internal alchemy tags are branded on the profile
74 expect(
75 Object.keys(observed.tags ?? {}).some((k) =>
76 k.startsWith("alchemy::"),
77 ),
78 ).toBe(true);
79
80 // UPDATE — tags are the only mutable aspect; the profile (name,
81 // arn, version) must be untouched
82 const updated = yield* stack.deploy(
83 Effect.gen(function* () {
84 const profile = yield* SigningProfile("ReleaseProfile", {
85 platformId: "AWSLambda-SHA384-ECDSA",
86 signatureValidityPeriod: { value: 12, type: "MONTHS" },
87 tags: { purpose: "alchemy-test", stage: "two" },
88 });
89 return {
90 profileName: profile.profileName,
91 arn: profile.arn,
92 profileVersion: profile.profileVersion,
93 };
94 }),
95 );
96
97 expect(updated.profileName).toEqual(created.profileName);
98 expect(updated.arn).toEqual(created.arn);
99 expect(updated.profileVersion).toEqual(created.profileVersion);
100
101 const afterUpdate = yield* signer.getSigningProfile({
102 profileName: created.profileName,
103 });
104 expect(afterUpdate.tags?.stage).toBe("two");
105 expect(afterUpdate.tags?.purpose).toBe("alchemy-test");
106
107 // REPLACE — PutSigningProfile is create-only, so changing the
108 // validity period replaces the profile under a new generated name
109 const replaced = yield* stack.deploy(
110 Effect.gen(function* () {
111 const profile = yield* SigningProfile("ReleaseProfile", {
112 platformId: "AWSLambda-SHA384-ECDSA",
113 signatureValidityPeriod: { value: 24, type: "MONTHS" },
114 tags: { purpose: "alchemy-test", stage: "two" },
115 });
116 return {
117 profileName: profile.profileName,
118 arn: profile.arn,
119 status: profile.status,
120 };
121 }),
122 );
123
124 expect(replaced.profileName).not.toEqual(created.profileName);
125 expect(replaced.status).toBe("Active");
126
127 const afterReplace = yield* signer.getSigningProfile({
128 profileName: replaced.profileName,
129 });
130 expect(afterReplace.status).toBe("Active");
131 expect(afterReplace.signatureValidityPeriod).toEqual({
132 value: 24,
133 type: "MONTHS",
134 });
135 // the replaced (old) profile is canceled by the engine's delete
136 yield* assertProfileCanceled(created.profileName);
137
138 // DESTROY — the profile is canceled (Signer has no hard delete)
139 yield* stack.destroy();
140 yield* assertProfileCanceled(replaced.profileName);
141 }),
142 { timeout: 120_000 },
143 );
144 },
145);