1import * as GCP from "@/GCP";
2import * as Test from "@/Test/Alchemy";
3import * as spanner from "@distilled.cloud/gcp/spanner_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
17// Partitions need an ENTERPRISE_PLUS instance plus a partition at 1000 PU each
18// (~$10/h) and more node quota than the default testing project has
19// (`Project 457525637530 cannot add 1 nodes in region us-central1.`). Set
20// GCP_TEST_SPANNER_PARTITIONS=1 to opt in.
21const runLifecycle =
22 !!process.env.GCP_TEST_SPANNER_PARTITIONS && !process.env.FAST;
23
24const waitUntilGone = (name: string) =>
25 spanner.getProjectsInstancesInstancePartitions({ name }).pipe(
26 Effect.as("found" as const),
27 Effect.catchTag("NotFound", () => Effect.succeed("gone" as const)),
28 Effect.repeat({
29 schedule: Schedule.spaced("2 seconds"),
30 until: (status) => status === "gone",
31 times: 10,
32 }),
33 );
34
35test.provider(
36 "getProjectsInstancesInstancePartitions on a missing partition fails with NotFound",
37 (stack) =>
38 Effect.gen(function* () {
39 const { project } = yield* GcpEnvironment.current;
40 yield* stack.destroy();
41
42 const error = yield* Effect.flip(
43 spanner.getProjectsInstancesInstancePartitions({
44 name: `projects/${project}/instances/alchemy-spanner-missing/instancePartitions/alchemy-missing`,
45 }),
46 );
47 expect(error._tag).toBe("NotFound");
48
49 yield* stack.destroy();
50 }).pipe(logLevel),
51 { tags: ["provider:gcp", "provider:gcp:spanner", "live"], timeout: 90_000 },
52);
53
54test.provider.skipIf(!runLifecycle)(
55 "create, update, and delete a spanner instance partition",
56 (stack) =>
57 Effect.gen(function* () {
58 yield* stack.destroy();
59
60 const created = yield* stack.deploy(
61 Effect.gen(function* () {
62 const instance = yield* GCP.Spanner.Instance("App", {
63 config: "regional-us-central1",
64 processingUnits: 1000,
65 displayName: "alchemy-pt-spnr",
66 edition: "ENTERPRISE_PLUS",
67 defaultBackupScheduleType: "NONE",
68 });
69 const partition = yield* GCP.Spanner.InstancesInstancePartition(
70 "West",
71 {
72 instance: instance.instanceId,
73 config: "regional-us-west1",
74 displayName: "alchemy-west-pt",
75 processingUnits: 1000,
76 },
77 );
78 return { instance, partition };
79 }),
80 );
81
82 expect(created.partition.name).toContain("/instancePartitions/");
83 expect(created.partition.instancePartitionId).toEqual(expect.any(String));
84 expect(created.partition.instanceId).toEqual(created.instance.instanceId);
85 expect(created.partition.config).toContain("regional-us-west1");
86 expect(created.partition.displayName).toEqual("alchemy-west-pt");
87 expect(created.partition.state).toEqual("READY");
88
89 const fetched = yield* spanner.getProjectsInstancesInstancePartitions({
90 name: created.partition.name,
91 });
92 expect(fetched.name).toEqual(created.partition.name);
93 expect(fetched.displayName).toEqual("alchemy-west-pt");
94
95 const updated = yield* stack.deploy(
96 Effect.gen(function* () {
97 const instance = yield* GCP.Spanner.Instance("App", {
98 instanceId: created.instance.instanceId,
99 config: "regional-us-central1",
100 processingUnits: 1000,
101 displayName: "alchemy-pt-spnr",
102 edition: "ENTERPRISE_PLUS",
103 defaultBackupScheduleType: "NONE",
104 });
105 const partition = yield* GCP.Spanner.InstancesInstancePartition(
106 "West",
107 {
108 instance: instance.instanceId,
109 instancePartitionId: created.partition.instancePartitionId,
110 config: "regional-us-west1",
111 displayName: "alchemy-prod-pt",
112 processingUnits: 1000,
113 },
114 );
115 return { instance, partition };
116 }),
117 );
118
119 expect(updated.partition.name).toEqual(created.partition.name);
120 expect(updated.partition.displayName).toEqual("alchemy-prod-pt");
121
122 const refetched = yield* spanner.getProjectsInstancesInstancePartitions({
123 name: created.partition.name,
124 });
125 expect(refetched.displayName).toEqual("alchemy-prod-pt");
126
127 yield* stack.destroy();
128
129 const gone = yield* waitUntilGone(created.partition.name);
130 expect(gone).toEqual("gone");
131 }).pipe(logLevel),
132 { tags: ["provider:gcp", "provider:gcp:spanner", "live"], timeout: 180_000 },
133);