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