1import * as GCP from "@/GCP";
2import * as Test from "@/Test/Alchemy";
3import * as beyondcorp from "@distilled.cloud/gcp/beyondcorp_v1";
4import { expect } from "alchemy-test";
5import * as Effect from "effect/Effect";
6import * as Schedule from "effect/Schedule";
7import {
8 logLevel,
9 currentProject,
10 runLifecycle,
11 serviceAccountEmailOf,
12} from "./common.ts";
13
14const { test } = Test.make({ providers: GCP.providers() });
15
16const waitUntilGone = (name: string) =>
17 beyondcorp.getProjectsLocationsAppConnections({ name }).pipe(
18 Effect.as("found" as const),
19 Effect.catchTag("NotFound", () => Effect.succeed("gone" as const)),
20 Effect.repeat({
21 schedule: Schedule.spaced("2 seconds"),
22 until: (status) => status === "gone",
23 times: 10,
24 }),
25 );
26
27test.provider(
28 "getProjectsLocationsAppConnections on a missing connection fails with a typed tag",
29 (stack) =>
30 Effect.gen(function* () {
31 const project = yield* currentProject;
32 yield* stack.destroy();
33
34 const error = yield* Effect.flip(
35 beyondcorp.getProjectsLocationsAppConnections({
36 name: `projects/${project}/locations/us-central1/appConnections/alchemy-missing-ac`,
37 }),
38 );
39 expect(error._tag).toEqual("NotFound");
40
41 const page = yield* beyondcorp.listProjectsLocationsAppConnections({
42 parent: `projects/${project}/locations/us-central1`,
43 pageSize: 10,
44 });
45 expect(
46 (page.appConnections ?? []).map((item) => item.name),
47 ).not.toContain(
48 `projects/${project}/locations/us-central1/appConnections/alchemy-missing-ac`,
49 );
50
51 yield* stack.destroy();
52 }).pipe(logLevel),
53 {
54 tags: ["provider:gcp", "provider:gcp:beyondcorp", "live"],
55 timeout: 90_000,
56 },
57);
58
59test.provider.skipIf(runLifecycle)(
60 "createProjectsLocationsAppConnections without entitlement fails with a typed tag",
61 (stack) =>
62 Effect.gen(function* () {
63 const project = yield* currentProject;
64 yield* stack.destroy();
65
66 const error = yield* Effect.flip(
67 beyondcorp.createProjectsLocationsAppConnections({
68 parent: `projects/${project}/locations/us-central1`,
69 appConnectionId: "alch-probe-ac",
70 validateOnly: true,
71 body: {
72 type: "TCP_PROXY",
73 applicationEndpoint: { host: "10.0.0.4", port: 8080 },
74 },
75 }),
76 );
77 expect(error._tag).toEqual("AppConnectorsNotImplemented");
78
79 yield* stack.destroy();
80 }).pipe(logLevel),
81 {
82 tags: ["provider:gcp", "provider:gcp:beyondcorp", "live"],
83 timeout: 90_000,
84 },
85);
86
87test.provider.skipIf(!runLifecycle)(
88 "create, update, and delete an app connection",
89 (stack) =>
90 Effect.gen(function* () {
91 const project = yield* currentProject;
92 const serviceAccountEmail = serviceAccountEmailOf(project);
93 yield* stack.destroy();
94
95 const created = yield* stack.deploy(
96 Effect.gen(function* () {
97 const gateway = yield* GCP.BeyondCorp.AppGateway("Edge", {
98 location: "us-central1",
99 });
100 const connector = yield* GCP.BeyondCorp.AppConnector("Agent", {
101 location: "us-central1",
102 serviceAccountEmail,
103 });
104 return yield* GCP.BeyondCorp.AppConnection("App", {
105 location: "us-central1",
106 type: "TCP_PROXY",
107 applicationEndpoint: { host: "10.0.0.4", port: 8080 },
108 gateway: { appGateway: gateway.name },
109 connectors: [connector.name],
110 displayName: "connection a",
111 labels: { env: "test" },
112 });
113 }),
114 );
115
116 expect(created.name).toContain("/appConnections/");
117 expect(created.location).toEqual("us-central1");
118 expect(created.applicationEndpoint?.host).toEqual("10.0.0.4");
119 expect(created.applicationEndpoint?.port).toEqual(8080);
120 expect(created.labels).toMatchObject({ env: "test" });
121
122 const fetched = yield* beyondcorp.getProjectsLocationsAppConnections({
123 name: created.name,
124 });
125 expect(fetched.name).toEqual(created.name);
126 expect(fetched.applicationEndpoint?.port).toEqual(8080);
127
128 const updated = yield* stack.deploy(
129 Effect.gen(function* () {
130 const gateway = yield* GCP.BeyondCorp.AppGateway("Edge", {
131 location: "us-central1",
132 });
133 const connector = yield* GCP.BeyondCorp.AppConnector("Agent", {
134 location: "us-central1",
135 serviceAccountEmail,
136 });
137 return yield* GCP.BeyondCorp.AppConnection("App", {
138 appConnectionId: created.appConnectionId,
139 location: "us-central1",
140 type: "TCP_PROXY",
141 applicationEndpoint: { host: "10.0.0.8", port: 8443 },
142 gateway: { appGateway: gateway.name },
143 connectors: [connector.name],
144 displayName: "connection b",
145 labels: { env: "prod" },
146 });
147 }),
148 );
149
150 expect(updated.name).toEqual(created.name);
151 expect(updated.applicationEndpoint?.host).toEqual("10.0.0.8");
152 expect(updated.applicationEndpoint?.port).toEqual(8443);
153 expect(updated.displayName).toEqual("connection b");
154 expect(updated.labels).toMatchObject({ env: "prod" });
155
156 yield* stack.destroy();
157
158 const gone = yield* waitUntilGone(created.name);
159 expect(gone).toEqual("gone");
160 }).pipe(logLevel),
161 {
162 tags: ["provider:gcp", "provider:gcp:beyondcorp", "live"],
163 timeout: 120_000,
164 },
165);