1import * as AWS from "@/AWS";
2import { StorageLensConfiguration } from "@/AWS/S3Control";
3import * as Test from "@/Test/Alchemy";
4import * as s3control from "@distilled.cloud/aws/s3-control";
5import { expect } from "alchemy-test";
6import * as Data from "effect/Data";
7import * as Effect from "effect/Effect";
8import * as Schedule from "effect/Schedule";
9
10const { test } = Test.make({ providers: AWS.providers() });
11
12const ACCOUNT_ID = "391965393224";
13
14const findConfiguration = (configId: string) =>
15 s3control
16 .getStorageLensConfiguration({ AccountId: ACCOUNT_ID, ConfigId: configId })
17 .pipe(
18 Effect.map((r) => r.StorageLensConfiguration),
19 Effect.catchTag("NoSuchConfiguration", () => Effect.succeed(undefined)),
20 );
21
22class ConfigurationStillExists extends Data.TaggedError(
23 "ConfigurationStillExists",
24)<{ readonly configId: string }> {}
25
26const assertConfigurationDeleted = (configId: string) =>
27 findConfiguration(configId).pipe(
28 Effect.flatMap((cfg) =>
29 cfg === undefined
30 ? Effect.void
31 : Effect.fail(new ConfigurationStillExists({ configId })),
32 ),
33 Effect.retry({
34 while: (e) => e._tag === "ConfigurationStillExists",
35 schedule: Schedule.max([Schedule.exponential(500), Schedule.recurs(8)]),
36 }),
37 );
38
39test.provider(
40 "typed NoSuchConfiguration tag on a nonexistent configuration",
41 () =>
42 Effect.gen(function* () {
43 const result = yield* s3control
44 .getStorageLensConfiguration({
45 AccountId: ACCOUNT_ID,
46 ConfigId: "alchemy-does-not-exist-xyz",
47 })
48 .pipe(
49 Effect.map(() => "found" as const),
50 Effect.catchTag("NoSuchConfiguration", () =>
51 Effect.succeed("missing" as const),
52 ),
53 );
54 expect(result).toBe("missing");
55 }),
56 { tags: ["provider:aws", "provider:aws:s3control", "live"], timeout: 60_000 },
57);
58
59test.provider(
60 "create, update, delete storage lens configuration",
61 (stack) =>
62 Effect.gen(function* () {
63 yield* stack.destroy();
64
65 const lens = yield* stack.deploy(
66 Effect.gen(function* () {
67 return yield* StorageLensConfiguration("TestLens", {
68 tags: { Environment: "test" },
69 });
70 }),
71 );
72
73 expect(lens.configId).toBeDefined();
74 expect(lens.storageLensArn).toContain(":storage-lens/");
75
76 // out-of-band verification via distilled
77 const live = yield* findConfiguration(lens.configId);
78 expect(live?.IsEnabled).toBe(true);
79 expect(live?.AccountLevel).toBeDefined();
80
81 const tags = yield* s3control
82 .getStorageLensConfigurationTagging({
83 AccountId: ACCOUNT_ID,
84 ConfigId: lens.configId,
85 })
86 .pipe(
87 Effect.map((r) =>
88 Object.fromEntries((r.Tags ?? []).map((t) => [t.Key, t.Value])),
89 ),
90 );
91 expect(tags.Environment).toBe("test");
92 expect(tags["alchemy::id"]).toBe("TestLens");
93
94 // update: disable the dashboard + enable activity metrics + retag
95 const updated = yield* stack.deploy(
96 Effect.gen(function* () {
97 return yield* StorageLensConfiguration("TestLens", {
98 isEnabled: false,
99 // account-level activity metrics require bucket-level activity
100 // metrics to be enabled as well (MissingBucketLevelActivityMetrics)
101 accountLevel: {
102 ActivityMetrics: { IsEnabled: true },
103 BucketLevel: { ActivityMetrics: { IsEnabled: true } },
104 },
105 tags: { Environment: "production" },
106 });
107 }),
108 );
109 expect(updated.configId).toBe(lens.configId);
110
111 const updatedLive = yield* findConfiguration(lens.configId);
112 expect(updatedLive?.IsEnabled).toBe(false);
113 expect(updatedLive?.AccountLevel?.ActivityMetrics?.IsEnabled).toBe(true);
114
115 const updatedTags = yield* s3control
116 .getStorageLensConfigurationTagging({
117 AccountId: ACCOUNT_ID,
118 ConfigId: lens.configId,
119 })
120 .pipe(
121 Effect.map((r) =>
122 Object.fromEntries((r.Tags ?? []).map((t) => [t.Key, t.Value])),
123 ),
124 );
125 expect(updatedTags.Environment).toBe("production");
126
127 yield* stack.destroy();
128 yield* assertConfigurationDeleted(lens.configId);
129 }),
130 {
131 tags: ["provider:aws", "provider:aws:s3control", "live"],
132 timeout: 120_000,
133 },
134);