1import { GcpEnvironment } from "@/GCP/Environment";
2import * as GCP from "@/GCP";
3import * as Test from "@/Test/Alchemy";
4import * as bigtable from "@distilled.cloud/gcp/bigtableadmin_v2";
5import { expect } from "alchemy-test";
6import * as Effect from "effect/Effect";
7import { MinimumLogLevel } from "effect/References";
8import * as Schedule from "effect/Schedule";
9
10const { test } = Test.make({ providers: GCP.providers() });
11
12const logLevel = Effect.provideService(
13 MinimumLogLevel,
14 process.env.DEBUG ? "Debug" : "Info",
15);
16
17const runLifecycle = !process.env.FAST;
18
19const waitUntilGone = (name: string) =>
20 bigtable.getProjectsInstancesTables({ name, view: "NAME_ONLY" }).pipe(
21 Effect.as("found" as const),
22 Effect.catchTag("NotFound", () => Effect.succeed("gone" as const)),
23 Effect.repeat({
24 schedule: Schedule.spaced("2 seconds"),
25 until: (status) => status === "gone",
26 times: 10,
27 }),
28 );
29
30test.provider(
31 "getProjectsInstancesTables on a missing instance fails with a typed tag",
32 (stack) =>
33 Effect.gen(function* () {
34 const { project } = yield* GcpEnvironment.current;
35
36 yield* stack.destroy();
37
38 const error = yield* Effect.flip(
39 bigtable.getProjectsInstancesTables({
40 name: `projects/${project}/instances/alchemybtmissing/tables/missing`,
41 }),
42 );
43 expect(error._tag).toEqual("NotFound");
44
45 yield* stack.destroy();
46 }).pipe(logLevel),
47 { tags: ["provider:gcp", "provider:gcp:bigtable", "live"], timeout: 90_000 },
48);
49
50test.provider.skipIf(!runLifecycle)(
51 "create, update, and delete a bigtable table",
52 (stack) =>
53 Effect.gen(function* () {
54 yield* stack.destroy();
55
56 const created = yield* stack.deploy(
57 Effect.gen(function* () {
58 const instance = yield* GCP.Bigtable.Instance("Data", {
59 displayName: "alchemy-test-bt-table",
60 type: "PRODUCTION",
61 clusters: {
62 cluster: {
63 location: "us-central1-b",
64 serveNodes: 1,
65 defaultStorageType: "HDD",
66 },
67 },
68 });
69 const table = yield* GCP.Bigtable.Table("Users", {
70 instance: instance.name,
71 columnFamilies: {
72 cf: { gcRule: { maxNumVersions: 3 } },
73 },
74 });
75 return { instance, table };
76 }),
77 );
78
79 expect(created.table.name).toContain("/tables/");
80 expect(created.table.tableId).toEqual(expect.any(String));
81 expect(created.table.instance).toEqual(created.instance.name);
82 expect(created.table.columnFamilies.cf?.gcRule?.maxNumVersions).toEqual(
83 3,
84 );
85 expect(created.table.deletionProtection).toEqual(false);
86
87 const fetched = yield* bigtable.getProjectsInstancesTables({
88 name: created.table.name,
89 view: "SCHEMA_VIEW",
90 });
91 expect(fetched.name).toEqual(created.table.name);
92 expect(fetched.columnFamilies?.cf?.gcRule?.maxNumVersions).toEqual(3);
93
94 const updated = yield* stack.deploy(
95 Effect.gen(function* () {
96 const instance = yield* GCP.Bigtable.Instance("Data", {
97 instanceId: created.instance.instanceId,
98 displayName: "alchemy-test-bt-table",
99 type: "PRODUCTION",
100 clusters: {
101 cluster: {
102 location: "us-central1-b",
103 serveNodes: 1,
104 defaultStorageType: "HDD",
105 },
106 },
107 });
108 const table = yield* GCP.Bigtable.Table("Users", {
109 instance: instance.name,
110 tableId: created.table.tableId,
111 columnFamilies: {
112 cf: { gcRule: { maxNumVersions: 5 } },
113 },
114 });
115 return { instance, table };
116 }),
117 );
118
119 expect(updated.table.name).toEqual(created.table.name);
120 expect(updated.table.columnFamilies.cf?.gcRule?.maxNumVersions).toEqual(
121 5,
122 );
123
124 const refetched = yield* bigtable.getProjectsInstancesTables({
125 name: created.table.name,
126 view: "SCHEMA_VIEW",
127 });
128 expect(refetched.columnFamilies?.cf?.gcRule?.maxNumVersions).toEqual(5);
129
130 yield* stack.destroy();
131
132 const gone = yield* waitUntilGone(created.table.name);
133 expect(gone).toEqual("gone");
134 }).pipe(logLevel),
135 { tags: ["provider:gcp", "provider:gcp:bigtable", "live"], timeout: 120_000 },
136);