1import * as GCP from "@/GCP";
2import * as Test from "@/Test/Alchemy";
3import * as analyticshub from "@distilled.cloud/gcp/analyticshub_v1";
4import { expect } from "alchemy-test";
5import * as Effect from "effect/Effect";
6import * as Schedule from "effect/Schedule";
7import { location, logLevel, currentProject, runLifecycle } from "./common.ts";
8
9const { test } = Test.make({ providers: GCP.providers() });
10
11const waitUntilGone = (name: string) =>
12 analyticshub.getProjectsLocationsDataExchanges({ name }).pipe(
13 Effect.as("found" as const),
14 Effect.catchTag("NotFound", () => Effect.succeed("gone" as const)),
15 Effect.catchTag("InternalServerError", () =>
16 Effect.succeed("found" as const),
17 ),
18 Effect.repeat({
19 schedule: Schedule.spaced("2 seconds"),
20 until: (status) => status === "gone",
21 times: 20,
22 }),
23 );
24
25test.provider(
26 "getProjectsLocationsDataExchanges on a missing exchange fails with a typed tag",
27 (stack) =>
28 Effect.gen(function* () {
29 const project = yield* currentProject;
30
31 yield* stack.destroy();
32
33 const error = yield* Effect.flip(
34 analyticshub.getProjectsLocationsDataExchanges({
35 name: `projects/${project}/locations/${location}/dataExchanges/alchemy_missing_exchange`,
36 }),
37 );
38 expect(error._tag).toEqual("NotFound");
39
40 yield* stack.destroy();
41 }).pipe(logLevel),
42 {
43 tags: ["provider:gcp", "provider:gcp:analyticshub", "live"],
44 timeout: 90_000,
45 },
46);
47
48test.provider.skipIf(!runLifecycle)(
49 "create, update, and delete a data exchange",
50 (stack) =>
51 Effect.gen(function* () {
52 yield* stack.destroy();
53
54 const created = yield* stack.deploy(
55 Effect.gen(function* () {
56 return yield* GCP.AnalyticsHub.DataExchange("Marketplace", {
57 location,
58 displayName: "Marketplace",
59 description: "shared datasets",
60 primaryContact: "data@example.com",
61 });
62 }),
63 );
64
65 expect(created.name).toContain("/dataExchanges/");
66 expect(created.location.toLowerCase()).toEqual(location);
67 expect(created.displayName).toEqual("Marketplace");
68 expect(created.description).toEqual("shared datasets");
69 expect(created.primaryContact).toEqual("data@example.com");
70 expect(created.dataExchangeId).toMatch(/^[a-zA-Z][a-zA-Z0-9_]*$/);
71
72 const fetched = yield* analyticshub.getProjectsLocationsDataExchanges({
73 name: created.name,
74 });
75 expect(fetched.name).toEqual(created.name);
76 expect(fetched.displayName).toEqual("Marketplace");
77 expect(fetched.description).toContain("alchemy-id=");
78 expect(fetched.description).toContain("shared datasets");
79 expect(fetched.primaryContact).toEqual("data@example.com");
80
81 const updated = yield* stack.deploy(
82 Effect.gen(function* () {
83 return yield* GCP.AnalyticsHub.DataExchange("Marketplace", {
84 dataExchangeId: created.dataExchangeId,
85 location,
86 displayName: "Marketplace v2",
87 description: "updated catalogs",
88 primaryContact: "catalog@example.com",
89 documentation: "https://example.com/docs",
90 });
91 }),
92 );
93
94 expect(updated.name).toEqual(created.name);
95 expect(updated.displayName).toEqual("Marketplace v2");
96 expect(updated.description).toEqual("updated catalogs");
97 expect(updated.primaryContact).toEqual("catalog@example.com");
98 expect(updated.documentation).toEqual("https://example.com/docs");
99
100 const fetchedUpdate =
101 yield* analyticshub.getProjectsLocationsDataExchanges({
102 name: created.name,
103 });
104 expect(fetchedUpdate.displayName).toEqual("Marketplace v2");
105 expect(fetchedUpdate.description).toContain("updated catalogs");
106 expect(fetchedUpdate.primaryContact).toEqual("catalog@example.com");
107
108 yield* stack.destroy();
109 const gone = yield* waitUntilGone(created.name);
110 expect(gone).toEqual("gone");
111 }).pipe(logLevel),
112 {
113 tags: ["provider:gcp", "provider:gcp:analyticshub", "live"],
114 timeout: 90_000,
115 },
116);