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