1import { GcpEnvironment } from "@/GCP/Environment";
2import * as GCP from "@/GCP";
3import * as Test from "@/Test/Alchemy";
4import * as apim from "@distilled.cloud/gcp/apim_v1alpha";
5import { expect } from "alchemy-test";
6import * as Effect from "effect/Effect";
7import { MinimumLogLevel } from "effect/References";
8import * as Schedule from "effect/Schedule";
9import { DEFAULT_NETWORK } from "../networkQuota.ts";
10
11const { test } = Test.make({ providers: GCP.providers() });
12
13const logLevel = Effect.provideService(
14 MinimumLogLevel,
15 process.env.DEBUG ? "Debug" : "Info",
16);
17
18// The API Management API is not enabled in the testing project: every call
19// answers 403 SERVICE_DISABLED (typed `ServiceDisabled`). Set GCP_TEST_APIM=1
20// on a project with the API enabled to run the lifecycle.
21const apimEnabled = !!process.env.GCP_TEST_APIM;
22const runLifecycle = !process.env.FAST && apimEnabled;
23const location = "us-central1";
24
25const waitUntilGone = (name: string) =>
26 apim.getProjectsLocationsObservationSources({ name }).pipe(
27 Effect.as("found" as const),
28 Effect.catchTag("NotFound", () => Effect.succeed("gone" as const)),
29 Effect.repeat({
30 schedule: Schedule.spaced("2 seconds"),
31 until: (status) => status === "gone",
32 times: 10,
33 }),
34 );
35
36test.provider(
37 "getProjectsLocationsObservationSources on a missing source fails with ServiceDisabled while the API is disabled",
38 (stack) =>
39 Effect.gen(function* () {
40 const { project } = yield* GcpEnvironment.current;
41 const parent = `projects/${project}/locations/${location}`;
42
43 yield* stack.destroy();
44
45 const error = yield* Effect.flip(
46 apim.getProjectsLocationsObservationSources({
47 name: `${parent}/observationSources/alchemy-missing-src`,
48 }),
49 );
50 expect(error._tag).toEqual(apimEnabled ? "NotFound" : "ServiceDisabled");
51
52 yield* stack.destroy();
53 }).pipe(logLevel),
54 { tags: ["provider:gcp", "provider:gcp:apim", "live"], timeout: 90_000 },
55);
56
57test.provider.skipIf(!runLifecycle)(
58 "create and delete an API Observation source",
59 (stack) =>
60 Effect.gen(function* () {
61 const { project } = yield* GcpEnvironment.current;
62 const parent = `projects/${project}/locations/${location}`;
63
64 yield* stack.destroy();
65
66 const created = yield* stack.deploy(
67 Effect.gen(function* () {
68 const subnet = yield* GCP.Compute.Subnetwork("ApimSrcSubnet", {
69 network: DEFAULT_NETWORK,
70 region: location,
71 ipCidrRange: "172.20.12.0/24",
72 privateIpGoogleAccess: true,
73 });
74 const source = yield* GCP.Apim.ObservationSource("Edge", {
75 location,
76 gclbObservationSource: {
77 pscNetworkConfigs: [
78 {
79 network: DEFAULT_NETWORK,
80 subnetwork: subnet.subnetworkName,
81 },
82 ],
83 },
84 });
85 return { subnet, source };
86 }),
87 );
88
89 expect(created.source.name).toContain("/observationSources/");
90 expect(created.source.observationSourceId).toEqual(expect.any(String));
91 expect(created.source.location).toEqual(location);
92 expect(
93 created.source.gclbObservationSource?.pscNetworkConfigs.length,
94 ).toBeGreaterThan(0);
95
96 const fetched = yield* apim.getProjectsLocationsObservationSources({
97 name: created.source.name,
98 });
99 expect(fetched.name).toEqual(created.source.name);
100
101 yield* stack.destroy();
102 const gone = yield* waitUntilGone(created.source.name);
103 expect(gone).toEqual("gone");
104 }).pipe(logLevel),
105 { tags: ["provider:gcp", "provider:gcp:apim", "live"], timeout: 120_000 },
106);