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.getProjectsInstancesMaterializedViews({ 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 "getProjectsInstancesMaterializedViews 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.getProjectsInstancesMaterializedViews({
42 name: `projects/${project}/instances/alchemybtmissing/materializedViews/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 materialized 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-mview",
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("Events", {
72 instance: instance.name,
73 tableId: "events",
74 columnFamilies: { cf: { gcRule: { maxNumVersions: 1 } } },
75 });
76 const view = yield* GCP.Bigtable.InstancesMaterializedView("Counts", {
77 instance: instance.name,
78 query: Output.interpolate`SELECT '*' AS _key, COUNT(*) AS row_count FROM \`${table.tableId}\` GROUP BY _key`,
79 });
80 return { instance, table, view };
81 }),
82 );
83
84 expect(created.view.name).toContain("/materializedViews/");
85 expect(created.view.materializedViewId).toEqual(expect.any(String));
86 expect(created.view.instance).toEqual(created.instance.name);
87 expect(created.view.query).toContain("events");
88 expect(created.view.deletionProtection).toEqual(false);
89
90 const fetched = yield* bigtable.getProjectsInstancesMaterializedViews({
91 name: created.view.name,
92 });
93 expect(fetched.name).toEqual(created.view.name);
94
95 const updated = yield* stack.deploy(
96 Effect.gen(function* () {
97 const instance = yield* GCP.Bigtable.Instance("Data", {
98 instanceId: created.instance.instanceId,
99 displayName: "alchemy-test-bt-mview",
100 type: "PRODUCTION",
101 clusters: {
102 cluster: {
103 location: "us-central1-b",
104 serveNodes: 1,
105 defaultStorageType: "HDD",
106 },
107 },
108 });
109 const table = yield* GCP.Bigtable.Table("Events", {
110 instance: instance.name,
111 tableId: "events",
112 columnFamilies: { cf: { gcRule: { maxNumVersions: 1 } } },
113 });
114 const view = yield* GCP.Bigtable.InstancesMaterializedView("Counts", {
115 instance: instance.name,
116 materializedViewId: created.view.materializedViewId,
117 query: Output.interpolate`SELECT '*' AS _key, COUNT(*) AS row_count FROM \`${table.tableId}\` GROUP BY _key`,
118 deletionProtection: true,
119 });
120 return { instance, table, view };
121 }),
122 );
123
124 expect(updated.view.name).toEqual(created.view.name);
125 expect(updated.view.deletionProtection).toEqual(true);
126
127 const refetched = yield* bigtable.getProjectsInstancesMaterializedViews({
128 name: created.view.name,
129 });
130 expect(refetched.deletionProtection).toEqual(true);
131
132 yield* stack.destroy();
133
134 const gone = yield* waitUntilGone(created.view.name);
135 expect(gone).toEqual("gone");
136 }).pipe(logLevel),
137 { tags: ["provider:gcp", "provider:gcp:bigtable", "live"], timeout: 480_000 },
138);