1import * as GCP from "@/GCP";
2import * as Test from "@/Test/Alchemy";
3import * as apihub from "@distilled.cloud/gcp/apihub_v1";
4import { expect } from "alchemy-test";
5import * as Effect from "effect/Effect";
6import { MinimumLogLevel } from "effect/References";
7import * as Schedule from "effect/Schedule";
8import { GcpEnvironment } from "@/GCP/Environment";
9
10const { test } = Test.make({ providers: GCP.providers() });
11
12const logLevel = Effect.provideService(
13 MinimumLogLevel,
14 process.env.DEBUG ? "Debug" : "Info",
15);
16
17// Needs a provisioned API Hub instance in us-central1 (one per project,
18// behind a host project registration); without one writes fail with
19// BadRequest ("Invalid resource state … API Hub instance …"). Set
20// GCP_TEST_APIHUB_INSTANCE=1 when the hub exists.
21const runLifecycle = !!process.env.GCP_TEST_APIHUB_INSTANCE;
22const location = "us-central1";
23
24const waitUntilGone = (name: string) =>
25 apihub.getProjectsLocationsApisVersions({ name }).pipe(
26 Effect.as("found" as const),
27 Effect.catchTag("NotFound", () => Effect.succeed("gone" as const)),
28 Effect.repeat({
29 schedule: Schedule.spaced("1 second"),
30 until: (status) => status === "gone",
31 times: 10,
32 }),
33 );
34
35test.provider(
36 "getProjectsLocationsApisVersions on a missing version fails with a typed tag",
37 (stack) =>
38 Effect.gen(function* () {
39 const { project } = yield* GcpEnvironment.current;
40 yield* stack.destroy();
41
42 const error = yield* Effect.flip(
43 apihub.getProjectsLocationsApisVersions({
44 name: `projects/${project}/locations/${location}/apis/alchemy-missing/versions/v1`,
45 }),
46 );
47 expect(error._tag).toEqual("NotFound");
48
49 yield* stack.destroy();
50 }).pipe(logLevel),
51 { tags: ["provider:gcp", "provider:gcp:apihub", "live"], timeout: 90_000 },
52);
53
54test.provider.skipIf(!runLifecycle)(
55 "create, update, and delete an API Hub version",
56 (stack) =>
57 Effect.gen(function* () {
58 yield* stack.destroy();
59
60 const created = yield* stack.deploy(
61 Effect.gen(function* () {
62 const api = yield* GCP.ApiHub.Api("Pets", {
63 location,
64 displayName: "pets",
65 });
66 const version = yield* GCP.ApiHub.ApisVersion("V1", {
67 api: api.name,
68 location,
69 displayName: "v1",
70 description: "first cut",
71 });
72 return { api, version };
73 }),
74 );
75
76 expect(created.version.name).toContain("/versions/");
77 expect(created.version.api).toEqual(created.api.name);
78 expect(created.version.displayName).toEqual("v1");
79 expect(created.version.description).toEqual("first cut");
80
81 const fetched = yield* apihub.getProjectsLocationsApisVersions({
82 name: created.version.name,
83 });
84 expect(fetched.name).toEqual(created.version.name);
85 expect(fetched.description).toContain("alchemy-id=");
86 expect(fetched.description).toContain("first cut");
87
88 const updated = yield* stack.deploy(
89 Effect.gen(function* () {
90 const api = yield* GCP.ApiHub.Api("Pets", {
91 apiId: created.api.apiId,
92 location,
93 displayName: "pets",
94 });
95 const version = yield* GCP.ApiHub.ApisVersion("V1", {
96 api: api.name,
97 versionId: created.version.versionId,
98 location,
99 displayName: "v1-prod",
100 description: "stable",
101 documentation: { externalUri: "https://example.com/v1" },
102 });
103 return { api, version };
104 }),
105 );
106
107 expect(updated.version.name).toEqual(created.version.name);
108 expect(updated.version.displayName).toEqual("v1-prod");
109 expect(updated.version.description).toEqual("stable");
110
111 yield* stack.destroy();
112
113 const gone = yield* waitUntilGone(created.version.name);
114 expect(gone).toEqual("gone");
115 }).pipe(logLevel),
116 { tags: ["provider:gcp", "provider:gcp:apihub", "live"], timeout: 90_000 },
117);