1import * as GCP from "@/GCP";
2import * as Test from "@/Test/Alchemy";
3import * as workloadmanager from "@distilled.cloud/gcp/workloadmanager_v1";
4import { expect } from "alchemy-test";
5import * as Effect from "effect/Effect";
6import { MinimumLogLevel } from "effect/References";
7import * as Schedule from "effect/Schedule";
8import { GcpEnvironment } from "@/GCP/Environment";
9
10const { test } = Test.make({ providers: GCP.providers() });
11
12const logLevel = Effect.provideService(
13 MinimumLogLevel,
14 process.env.DEBUG ? "Debug" : "Info",
15);
16
17const parentOf = (project: string) =>
18 `projects/${project}/locations/us-central1`;
19
20// Workload Manager API is entitlement-gated on the default testing project
21// (`ServiceDisabled`: "Workload Manager API has not been used in project
22// alchemy-gcp-testing-83661 before or it is disabled."). Set
23// GCP_TEST_WORKLOADMANAGER=1 on an entitled project to run the lifecycle.
24const entitled = process.env.GCP_TEST_WORKLOADMANAGER === "1";
25const runLifecycle = entitled && !process.env.FAST;
26
27const waitUntilGone = (name: string) =>
28 workloadmanager.getProjectsLocationsEvaluations({ name }).pipe(
29 Effect.as("found" as const),
30 Effect.catchTag("NotFound", () => Effect.succeed("gone" as const)),
31 Effect.repeat({
32 schedule: Schedule.spaced("1 second"),
33 until: (status) => status === "gone",
34 times: 10,
35 }),
36 );
37
38const firstRuleName = Effect.gen(function* () {
39 const { project } = yield* GcpEnvironment.current;
40 const parent = parentOf(project);
41 const page = yield* workloadmanager
42 .listProjectsLocationsRules({
43 parent,
44 evaluationType: "SAP",
45 pageSize: 20,
46 })
47 .pipe(
48 Effect.catchTag("NotFound", () => Effect.succeed({ rules: [] as const })),
49 );
50 const named = (page.rules ?? []).find(
51 (rule) => typeof rule.name === "string" && rule.name.length > 0,
52 );
53 return named?.name ?? "sap-hana";
54});
55
56test.provider(
57 "getProjectsLocationsEvaluations fails with ServiceDisabled while the API is disabled",
58 (stack) =>
59 Effect.gen(function* () {
60 const { project } = yield* GcpEnvironment.current;
61 const parent = parentOf(project);
62 yield* stack.destroy();
63
64 const error = yield* Effect.flip(
65 workloadmanager.getProjectsLocationsEvaluations({
66 name: `${parent}/evaluations/alchemy-missing-evaluation`,
67 }),
68 );
69 expect(error._tag).toEqual("ServiceDisabled");
70
71 yield* stack.destroy();
72 }).pipe(logLevel),
73 {
74 tags: ["provider:gcp", "provider:gcp:workloadmanager", "live"],
75 timeout: 90_000,
76 },
77);
78
79test.provider.skipIf(entitled)(
80 "createProjectsLocationsEvaluations is rejected with ServiceDisabled while Workload Manager is disabled",
81 (stack) =>
82 Effect.gen(function* () {
83 const { project } = yield* GcpEnvironment.current;
84 const parent = parentOf(project);
85 yield* stack.destroy();
86
87 const error = yield* Effect.flip(
88 workloadmanager.createProjectsLocationsEvaluations({
89 parent,
90 evaluationId: "alchemy-evaluation-probe",
91 body: {
92 evaluationType: "SAP",
93 ruleNames: ["sap-hana"],
94 resourceFilter: { scopes: [`projects/${project}`] },
95 description: "alchemy-probe",
96 },
97 }),
98 );
99 expect(error._tag).toEqual("ServiceDisabled");
100
101 yield* stack.destroy();
102 }).pipe(logLevel),
103 {
104 tags: ["provider:gcp", "provider:gcp:workloadmanager", "live"],
105 timeout: 90_000,
106 },
107);
108
109test.provider.skipIf(!runLifecycle)(
110 "create, update, and delete an evaluation",
111 (stack) =>
112 Effect.gen(function* () {
113 const { project } = yield* GcpEnvironment.current;
114 yield* stack.destroy();
115
116 const ruleName = yield* firstRuleName;
117
118 const created = yield* stack.deploy(
119 Effect.gen(function* () {
120 return yield* GCP.WorkloadManager.Evaluation("SapBest", {
121 evaluationType: "SAP",
122 ruleNames: [ruleName],
123 resourceFilter: { scopes: [`projects/${project}`] },
124 description: "alchemy-test-evaluation",
125 labels: { env: "test" },
126 });
127 }),
128 );
129
130 expect(created.name).toContain("/evaluations/");
131 expect(created.evaluationId).toEqual(expect.any(String));
132 expect(created.location).toEqual("us-central1");
133 expect(created.project).toEqual(project);
134 expect(created.labels).toMatchObject({ env: "test" });
135 expect(created.description).toEqual("alchemy-test-evaluation");
136 expect(created.ruleNames.length).toBeGreaterThan(0);
137
138 const fetched = yield* workloadmanager.getProjectsLocationsEvaluations({
139 name: created.name,
140 });
141 expect(fetched.name).toEqual(created.name);
142 expect(fetched.labels?.env).toEqual("test");
143 expect(fetched.labels?.["alchemy-id"]).toEqual(expect.any(String));
144
145 const updated = yield* stack.deploy(
146 Effect.gen(function* () {
147 return yield* GCP.WorkloadManager.Evaluation("SapBest", {
148 evaluationId: created.evaluationId,
149 evaluationType: "SAP",
150 ruleNames: [ruleName],
151 resourceFilter: { scopes: [`projects/${project}`] },
152 description: "alchemy-test-evaluation-v2",
153 labels: { env: "prod", role: "eval" },
154 });
155 }),
156 );
157
158 expect(updated.name).toEqual(created.name);
159 expect(updated.evaluationId).toEqual(created.evaluationId);
160 expect(updated.description).toEqual("alchemy-test-evaluation-v2");
161 expect(updated.labels).toMatchObject({ env: "prod", role: "eval" });
162
163 yield* stack.destroy();
164
165 const gone = yield* waitUntilGone(created.name);
166 expect(gone).toEqual("gone");
167 }).pipe(logLevel),
168 {
169 tags: ["provider:gcp", "provider:gcp:workloadmanager", "live"],
170 timeout: 120_000,
171 },
172);