1import * as AWS from "@/AWS";
2import { Workspace } from "@/AWS/Grafana";
3import * as Test from "@/Test/Alchemy";
4import * as grafana from "@distilled.cloud/aws/grafana";
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
12// Ungated typed-error probe: proves the distilled error union carries the
13// not-found tag the read/delete paths depend on.
14test.provider(
15 "describeWorkspace on a nonexistent id fails with ResourceNotFoundException",
16 () =>
17 Effect.gen(function* () {
18 const error = yield* Effect.flip(
19 grafana.describeWorkspace({ workspaceId: "g-0000000000" }),
20 );
21 expect(error._tag).toBe("ResourceNotFoundException");
22 }),
23 { tags: ["provider:aws", "provider:aws:grafana", "live"] },
24);
25
26class WorkspaceStillExists extends Data.TaggedError("WorkspaceStillExists")<{
27 readonly workspaceId: string;
28}> {}
29
30const assertWorkspaceDeleted = (workspaceId: string) =>
31 grafana.describeWorkspace({ workspaceId }).pipe(
32 Effect.flatMap((r) =>
33 r.workspace.status === "DELETING"
34 ? Effect.fail(new WorkspaceStillExists({ workspaceId }))
35 : Effect.succeed(undefined),
36 ),
37 Effect.catchTag("ResourceNotFoundException", () =>
38 Effect.succeed(undefined),
39 ),
40 Effect.retry({
41 while: (e) => e._tag === "WorkspaceStillExists",
42 schedule: Schedule.max([
43 Schedule.spaced("5 seconds"),
44 Schedule.recurs(24),
45 ]),
46 }),
47 );
48
49// GATED: Grafana workspace provisioning is asynchronous (a few minutes) and
50// `AWS_SSO` authentication requires IAM Identity Center to be enabled in the
51// account. Run with AWS_TEST_GRAFANA=1 in an entitled account.
52test.provider.skipIf(!process.env.AWS_TEST_GRAFANA)(
53 "create, update, and destroy a Grafana workspace",
54 (stack) =>
55 Effect.gen(function* () {
56 yield* stack.destroy();
57
58 const created = yield* stack.deploy(
59 Effect.gen(function* () {
60 return yield* Workspace("Dashboards", {
61 name: "alchemy-test-grafana",
62 description: "initial",
63 accountAccessType: "CURRENT_ACCOUNT",
64 authenticationProviders: ["AWS_SSO"],
65 permissionType: "SERVICE_MANAGED",
66 dataSources: ["PROMETHEUS", "CLOUDWATCH"],
67 tags: { Environment: "test" },
68 });
69 }),
70 );
71
72 expect(created.workspaceId).toMatch(/^g-/);
73 expect(created.workspaceArn).toContain(":/workspaces/");
74 expect(created.status).toBe("ACTIVE");
75 expect(created.endpoint).toContain("grafana-workspace");
76
77 const describedWs = yield* grafana.describeWorkspace({
78 workspaceId: created.workspaceId,
79 });
80 expect(describedWs.workspace.tags?.["alchemy::id"]).toBe("Dashboards");
81
82 // Update the description in place.
83 const updated = yield* stack.deploy(
84 Effect.gen(function* () {
85 return yield* Workspace("Dashboards", {
86 name: "alchemy-test-grafana",
87 description: "updated",
88 accountAccessType: "CURRENT_ACCOUNT",
89 authenticationProviders: ["AWS_SSO"],
90 permissionType: "SERVICE_MANAGED",
91 dataSources: ["PROMETHEUS", "CLOUDWATCH"],
92 tags: { Environment: "test", Extra: "yes" },
93 });
94 }),
95 );
96 expect(updated.workspaceId).toBe(created.workspaceId);
97
98 yield* stack.destroy();
99 yield* assertWorkspaceDeleted(created.workspaceId);
100 }),
101 { tags: ["provider:aws", "provider:aws:grafana", "live"], timeout: 600_000 },
102);