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.getProjectsLocationsDependencies({ 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 "getProjectsLocationsDependencies on a missing dependency 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.getProjectsLocationsDependencies({
44 name: `projects/${project}/locations/${location}/dependencies/alchemy-missing-dep`,
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 dependency",
56 (stack) =>
57 Effect.gen(function* () {
58 yield* stack.destroy();
59
60 const created = yield* stack.deploy(
61 Effect.gen(function* () {
62 const consumerApi = yield* GCP.ApiHub.Api("Consumer", {
63 location,
64 displayName: "consumer",
65 });
66 const supplierApi = yield* GCP.ApiHub.Api("Supplier", {
67 location,
68 displayName: "supplier",
69 });
70 const consumerVersion = yield* GCP.ApiHub.ApisVersion("ConsumerV1", {
71 api: consumerApi.name,
72 location,
73 displayName: "v1",
74 });
75 const supplierVersion = yield* GCP.ApiHub.ApisVersion("SupplierV1", {
76 api: supplierApi.name,
77 location,
78 displayName: "v1",
79 });
80 const consumerOp = yield* GCP.ApiHub.ApisVersionsOperation(
81 "ListPets",
82 {
83 version: consumerVersion.name,
84 location,
85 details: {
86 httpOperation: {
87 method: "GET",
88 path: { path: "/pets" },
89 },
90 description: "list pets",
91 },
92 },
93 );
94 const supplierOp = yield* GCP.ApiHub.ApisVersionsOperation("GetPet", {
95 version: supplierVersion.name,
96 location,
97 details: {
98 httpOperation: {
99 method: "GET",
100 path: { path: "/pets/{id}" },
101 },
102 description: "get pet",
103 },
104 });
105 const dependency = yield* GCP.ApiHub.Dependency("Calls", {
106 location,
107 description: "listPets calls getPet",
108 consumer: { operationResourceName: consumerOp.name },
109 supplier: { operationResourceName: supplierOp.name },
110 });
111 return { consumerOp, supplierOp, dependency };
112 }),
113 );
114
115 expect(created.dependency.name).toContain("/dependencies/");
116 expect(created.dependency.consumer?.operationResourceName).toEqual(
117 created.consumerOp.name,
118 );
119 expect(created.dependency.supplier?.operationResourceName).toEqual(
120 created.supplierOp.name,
121 );
122 expect(created.dependency.description).toEqual("listPets calls getPet");
123
124 const fetched = yield* apihub.getProjectsLocationsDependencies({
125 name: created.dependency.name,
126 });
127 expect(fetched.name).toEqual(created.dependency.name);
128 expect(fetched.description).toContain("alchemy-id=");
129
130 const updated = yield* stack.deploy(
131 Effect.gen(function* () {
132 const consumerApi = yield* GCP.ApiHub.Api("Consumer", {
133 location,
134 displayName: "consumer",
135 });
136 const supplierApi = yield* GCP.ApiHub.Api("Supplier", {
137 location,
138 displayName: "supplier",
139 });
140 const consumerVersion = yield* GCP.ApiHub.ApisVersion("ConsumerV1", {
141 api: consumerApi.name,
142 location,
143 displayName: "v1",
144 });
145 const supplierVersion = yield* GCP.ApiHub.ApisVersion("SupplierV1", {
146 api: supplierApi.name,
147 location,
148 displayName: "v1",
149 });
150 const consumerOp = yield* GCP.ApiHub.ApisVersionsOperation(
151 "ListPets",
152 {
153 version: consumerVersion.name,
154 location,
155 details: {
156 httpOperation: {
157 method: "GET",
158 path: { path: "/pets" },
159 },
160 description: "list pets",
161 },
162 },
163 );
164 const supplierOp = yield* GCP.ApiHub.ApisVersionsOperation("GetPet", {
165 version: supplierVersion.name,
166 location,
167 details: {
168 httpOperation: {
169 method: "GET",
170 path: { path: "/pets/{id}" },
171 },
172 description: "get pet",
173 },
174 });
175 const dependency = yield* GCP.ApiHub.Dependency("Calls", {
176 dependencyId: created.dependency.dependencyId,
177 location,
178 description: "listPets still calls getPet",
179 consumer: { operationResourceName: consumerOp.name },
180 supplier: { operationResourceName: supplierOp.name },
181 });
182 return { consumerOp, supplierOp, dependency };
183 }),
184 );
185
186 expect(updated.dependency.name).toEqual(created.dependency.name);
187 expect(updated.dependency.description).toEqual(
188 "listPets still calls getPet",
189 );
190
191 yield* stack.destroy();
192
193 const gone = yield* waitUntilGone(created.dependency.name);
194 expect(gone).toEqual("gone");
195 }).pipe(logLevel),
196 { tags: ["provider:gcp", "provider:gcp:apihub", "live"], timeout: 90_000 },
197);