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
17// Lifecycles provision a Bigtable instance; skipped with --fast.
18const runLifecycle = !process.env.FAST;
19
20const waitUntilGone = (name: string) =>
21 bigtable.getProjectsInstancesTablesAuthorizedViews({ name }).pipe(
22 Effect.as("found" as const),
23 Effect.catchTag("NotFound", () => Effect.succeed("gone" as const)),
24 Effect.repeat({
25 schedule: Schedule.spaced("2 seconds"),
26 until: (status) => status === "gone",
27 times: 10,
28 }),
29 );
30
31test.provider(
32 "getProjectsInstancesTablesAuthorizedViews on a missing instance fails with NotFound",
33 (stack) =>
34 Effect.gen(function* () {
35 const { project } = yield* GcpEnvironment.current;
36
37 yield* stack.destroy();
38
39 const error = yield* Effect.flip(
40 bigtable.getProjectsInstancesTablesAuthorizedViews({
41 name: `projects/${project}/instances/alchemybtmissing/tables/missing/authorizedViews/missing`,
42 }),
43 );
44 expect(error._tag).toEqual("NotFound");
45
46 yield* stack.destroy();
47 }).pipe(logLevel),
48 { tags: ["provider:gcp", "provider:gcp:bigtable", "live"], timeout: 90_000 },
49);
50
51test.provider.skipIf(!runLifecycle)(
52 "create, update, and delete a bigtable authorized view",
53 (stack) =>
54 Effect.gen(function* () {
55 yield* stack.destroy();
56
57 const created = yield* stack.deploy(
58 Effect.gen(function* () {
59 const instance = yield* GCP.Bigtable.Instance("Data", {
60 displayName: "alchemy-test-bt-authview",
61 type: "PRODUCTION",
62 clusters: {
63 cluster: {
64 location: "us-central1-b",
65 serveNodes: 1,
66 defaultStorageType: "HDD",
67 },
68 },
69 });
70 const table = yield* GCP.Bigtable.Table("Users", {
71 instance: instance.name,
72 columnFamilies: { cf: { gcRule: { maxNumVersions: 1 } } },
73 });
74 const view = yield* GCP.Bigtable.InstancesTablesAuthorizedView(
75 "Public",
76 {
77 instance: instance.name,
78 table: table.name,
79 subsetView: {
80 rowPrefixes: [""],
81 familySubsets: {
82 cf: { qualifierPrefixes: [""] },
83 },
84 },
85 },
86 );
87 return { instance, table, view };
88 }),
89 );
90
91 expect(created.view.name).toContain("/authorizedViews/");
92 expect(created.view.authorizedViewId).toEqual(expect.any(String));
93 expect(created.view.table).toEqual(created.table.name);
94 expect(created.view.deletionProtection).toEqual(false);
95 expect(created.view.subsetView?.familySubsets?.cf).toBeDefined();
96
97 const fetched = yield* bigtable.getProjectsInstancesTablesAuthorizedViews(
98 {
99 name: created.view.name,
100 view: "FULL",
101 },
102 );
103 expect(fetched.name).toEqual(created.view.name);
104
105 const updated = yield* stack.deploy(
106 Effect.gen(function* () {
107 const instance = yield* GCP.Bigtable.Instance("Data", {
108 instanceId: created.instance.instanceId,
109 displayName: "alchemy-test-bt-authview",
110 type: "PRODUCTION",
111 clusters: {
112 cluster: {
113 location: "us-central1-b",
114 serveNodes: 1,
115 defaultStorageType: "HDD",
116 },
117 },
118 });
119 const table = yield* GCP.Bigtable.Table("Users", {
120 instance: instance.name,
121 tableId: created.table.tableId,
122 columnFamilies: { cf: { gcRule: { maxNumVersions: 1 } } },
123 });
124 const view = yield* GCP.Bigtable.InstancesTablesAuthorizedView(
125 "Public",
126 {
127 instance: instance.name,
128 table: table.name,
129 authorizedViewId: created.view.authorizedViewId,
130 subsetView: {
131 rowPrefixes: [""],
132 familySubsets: {
133 cf: { qualifierPrefixes: [""] },
134 },
135 },
136 deletionProtection: true,
137 },
138 );
139 return { instance, table, view };
140 }),
141 );
142
143 expect(updated.view.name).toEqual(created.view.name);
144 expect(updated.view.deletionProtection).toEqual(true);
145
146 const refetched =
147 yield* bigtable.getProjectsInstancesTablesAuthorizedViews({
148 name: created.view.name,
149 view: "FULL",
150 });
151 expect(refetched.deletionProtection).toEqual(true);
152
153 yield* stack.destroy();
154
155 const gone = yield* waitUntilGone(created.view.name);
156 expect(gone).toEqual("gone");
157 }).pipe(logLevel),
158 { tags: ["provider:gcp", "provider:gcp:bigtable", "live"], timeout: 120_000 },
159);