1import * as GCP from "@/GCP";
2import * as Test from "@/Test/Alchemy";
3import * as bigqueryreservation from "@distilled.cloud/gcp/bigqueryreservation_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 = (name: string) => name.replace(/\/assignments\/[^/]+$/, "");
18
19const waitUntilGone = (name: string) =>
20 bigqueryreservation
21 .listProjectsLocationsReservationsAssignments({
22 parent: parentOf(name),
23 pageSize: 100,
24 })
25 .pipe(
26 Effect.map((page) =>
27 (page.assignments ?? []).some((item) => item.name === name)
28 ? ("found" as const)
29 : ("gone" as const),
30 ),
31 Effect.catchTag("NotFound", () => Effect.succeed("gone" as const)),
32 Effect.repeat({
33 schedule: Schedule.spaced("1 second"),
34 until: (status) => status === "gone",
35 times: 10,
36 }),
37 );
38
39test.provider(
40 "listProjectsLocationsReservationsAssignments on a missing reservation is empty",
41 (stack) =>
42 Effect.gen(function* () {
43 const { project } = yield* GcpEnvironment.current;
44 yield* stack.destroy();
45
46 // Listing under a missing reservation answers an empty page.
47 const page =
48 yield* bigqueryreservation.listProjectsLocationsReservationsAssignments(
49 {
50 parent: `projects/${project}/locations/us-central1/reservations/alchemy-bq-assignment-missing`,
51 },
52 );
53 expect(page.assignments).toBeUndefined();
54
55 yield* stack.destroy();
56 }).pipe(logLevel),
57 {
58 tags: ["provider:gcp", "provider:gcp:bigqueryreservation", "live"],
59 timeout: 90_000,
60 },
61);
62
63test.provider.skipIf(!!process.env.FAST)(
64 "create, update, and delete a reservation assignment",
65 (stack) =>
66 Effect.gen(function* () {
67 const { project } = yield* GcpEnvironment.current;
68 yield* stack.destroy();
69
70 const created = yield* stack.deploy(
71 Effect.gen(function* () {
72 const reservation = yield* GCP.BigQueryReservation.Reservation(
73 "Slots",
74 {
75 location: "us-central1",
76 edition: "ENTERPRISE",
77 slotCapacity: "0",
78 },
79 );
80 const assignment = yield* GCP.BigQueryReservation.Assignment(
81 "Query",
82 {
83 reservation: reservation.name,
84 jobType: "QUERY",
85 },
86 );
87 return { reservation, assignment };
88 }),
89 );
90
91 expect(created.assignment.name).toContain("/assignments/");
92 expect(created.assignment.reservation).toEqual(created.reservation.name);
93 expect(created.assignment.jobType).toEqual("QUERY");
94 expect(created.assignment.assignee).toContain(`projects/${project}`);
95
96 const listed =
97 yield* bigqueryreservation.listProjectsLocationsReservationsAssignments(
98 {
99 parent: created.reservation.name,
100 },
101 );
102 expect(
103 (listed.assignments ?? []).some(
104 (item) => item.name === created.assignment.name,
105 ),
106 ).toEqual(true);
107
108 const updated = yield* stack.deploy(
109 Effect.gen(function* () {
110 const reservation = yield* GCP.BigQueryReservation.Reservation(
111 "Slots",
112 {
113 reservationId: created.reservation.reservationId,
114 location: "us-central1",
115 edition: "ENTERPRISE",
116 slotCapacity: "0",
117 },
118 );
119 const assignment = yield* GCP.BigQueryReservation.Assignment(
120 "Query",
121 {
122 assignmentId: created.assignment.assignmentId,
123 reservation: reservation.name,
124 jobType: "QUERY",
125 principal: created.assignment.principal,
126 },
127 );
128 return { reservation, assignment };
129 }),
130 );
131
132 expect(updated.assignment.name).toEqual(created.assignment.name);
133
134 yield* stack.destroy();
135
136 const gone = yield* waitUntilGone(created.assignment.name);
137 expect(gone).toEqual("gone");
138 }).pipe(logLevel),
139 {
140 tags: ["provider:gcp", "provider:gcp:bigqueryreservation", "live"],
141 timeout: 90_000,
142 },
143);