1import * as AWS from "@/AWS";
2import { AccountConfiguration } from "@/AWS/ACM/AccountConfiguration.ts";
3import * as Test from "@/Test/Alchemy";
4import { Region as AwsRegion } from "@distilled.cloud/aws/Region";
5import * as acm from "@distilled.cloud/aws/acm";
6import { expect } from "alchemy-test";
7import * as Effect from "effect/Effect";
8import * as Schedule from "effect/Schedule";
9
10// The AccountConfiguration provider pins its calls to us-east-1; every
11// out-of-band ACM call in this file must target the same region.
12const withUsEast1 = <A, E, R>(effect: Effect.Effect<A, E, R>) =>
13 effect.pipe(Effect.provideService(AwsRegion, Effect.succeed("us-east-1")));
14
15const { test } = Test.make({ providers: AWS.providers() });
16
17const getDays = withUsEast1(
18 acm
19 .getAccountConfiguration({})
20 .pipe(
21 Effect.map((response) => response.ExpiryEvents?.DaysBeforeExpiry ?? 45),
22 ),
23);
24
25// PutAccountConfiguration is heavily rate-limited and may report a typed
26// ConflictException while a previous change settles — retry both, bounded.
27const putDays = (days: number, tokenSeed: string) =>
28 withUsEast1(
29 acm
30 .putAccountConfiguration({
31 ExpiryEvents: { DaysBeforeExpiry: days },
32 IdempotencyToken: `${tokenSeed}${days}`.slice(0, 32),
33 })
34 .pipe(
35 Effect.retry({
36 while: (e): boolean =>
37 e._tag === "ConflictException" || e._tag === "ThrottlingException",
38 schedule: Schedule.max([
39 Schedule.exponential("1 second"),
40 Schedule.recurs(8),
41 ]),
42 }),
43 ),
44 );
45
46// Account-global singleton: capture the live threshold up front and restore
47// it on scope close so the test leaves the account exactly as it found it,
48// even if an assertion fails mid-way.
49test.provider(
50 "manages the account expiry-event threshold in place",
51 (stack) =>
52 Effect.gen(function* () {
53 yield* stack.destroy();
54
55 const originalDays = yield* getDays;
56 yield* Effect.addFinalizer(() =>
57 putDays(originalDays, "alchemyacmrestore").pipe(Effect.ignore),
58 );
59
60 // Create — converge the singleton onto a non-default threshold.
61 const created = yield* stack.deploy(
62 AccountConfiguration("ExpiryEvents", {
63 daysBeforeExpiry: "30 days",
64 }),
65 );
66 expect(created.daysBeforeExpiry).toBe(30);
67 expect(yield* getDays).toBe(30);
68
69 // Update in place — same singleton, new threshold (also proves the
70 // provider issues a fresh idempotency token per desired value).
71 const updated = yield* stack.deploy(
72 AccountConfiguration("ExpiryEvents", {
73 daysBeforeExpiry: "31 days",
74 }),
75 );
76 expect(updated.daysBeforeExpiry).toBe(31);
77 expect(yield* getDays).toBe(31);
78
79 // Destroy — resets the threshold to the AWS account default.
80 yield* stack.destroy();
81 expect(yield* getDays).toBe(45);
82 }),
83 { tags: ["provider:aws", "provider:aws:acm", "live"], timeout: 120_000 },
84);