1import * as GCP from "@/GCP";
2import * as Test from "@/Test/Alchemy";
3import * as compute from "@distilled.cloud/gcp/compute_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 zone = "us-central1-a";
18
19const runLifecycle = !process.env.FAST;
20
21const waitUntilGone = (
22 projectId: string,
23 reservationZone: string,
24 reservation: string,
25) =>
26 compute
27 .getReservations({
28 project: projectId,
29 zone: reservationZone,
30 reservation,
31 })
32 .pipe(
33 Effect.as("found" as const),
34 Effect.catchTag("NotFound", () => Effect.succeed("gone" as const)),
35 Effect.repeat({
36 schedule: Schedule.spaced("1 second"),
37 until: (status) => status === "gone",
38 times: 10,
39 }),
40 );
41
42test.provider(
43 "getReservations on a missing reservation fails with NotFound",
44 (stack) =>
45 Effect.gen(function* () {
46 const { project } = yield* GcpEnvironment.current;
47 yield* stack.destroy();
48
49 const error = yield* Effect.flip(
50 compute.getReservations({
51 project,
52 zone,
53 reservation: "alchemy-missing-reservation",
54 }),
55 );
56 expect(error._tag).toBe("NotFound");
57
58 yield* stack.destroy();
59 }).pipe(logLevel),
60 { tags: ["provider:gcp", "provider:gcp:compute", "live"], timeout: 300_000 },
61);
62
63test.provider.skipIf(!runLifecycle)(
64 "create, update, and delete a reservation",
65 (stack) =>
66 Effect.gen(function* () {
67 yield* stack.destroy();
68
69 const created = yield* stack.deploy(
70 Effect.gen(function* () {
71 return yield* GCP.Compute.Reservation("Burst", {
72 zone,
73 description: "burst capacity",
74 specificReservation: {
75 count: 1,
76 instanceProperties: { machineType: "n1-standard-1" },
77 },
78 specificReservationRequired: true,
79 });
80 }),
81 );
82
83 expect(created.reservationName).toEqual(expect.any(String));
84 expect(created.zone).toEqual(zone);
85 expect(created.status).toEqual("READY");
86 expect(created.description).toEqual("burst capacity");
87 expect(created.specificReservationRequired).toEqual(true);
88 expect(created.specificReservation?.count).toEqual("1");
89
90 const fetched = yield* compute.getReservations({
91 project: created.project,
92 zone: created.zone,
93 reservation: created.reservationName,
94 });
95 expect(fetched.name).toEqual(created.reservationName);
96 expect(fetched.status).toEqual("READY");
97 expect(fetched.description).toContain("[alchemy ");
98 expect(fetched.description).toContain("burst capacity");
99 expect(fetched.specificReservation?.count).toEqual("1");
100
101 const updated = yield* stack.deploy(
102 Effect.gen(function* () {
103 return yield* GCP.Compute.Reservation("Burst", {
104 reservationName: created.reservationName,
105 zone,
106 description: "burst capacity",
107 specificReservation: {
108 count: 2,
109 instanceProperties: { machineType: "n1-standard-1" },
110 },
111 specificReservationRequired: true,
112 });
113 }),
114 );
115
116 expect(updated.reservationName).toEqual(created.reservationName);
117 expect(updated.specificReservation?.count).toEqual("2");
118
119 const fetchedUpdated = yield* compute.getReservations({
120 project: updated.project,
121 zone: updated.zone,
122 reservation: updated.reservationName,
123 });
124 expect(fetchedUpdated.specificReservation?.count).toEqual("2");
125
126 yield* stack.destroy();
127 const gone = yield* waitUntilGone(
128 created.project,
129 created.zone,
130 created.reservationName,
131 );
132 expect(gone).toEqual("gone");
133 }).pipe(logLevel),
134 { tags: ["provider:gcp", "provider:gcp:compute", "live"], timeout: 300_000 },
135);