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 { logLevel, currentProject, runLifecycle } from "./common.ts";
8
9const { test } = Test.make({ providers: GCP.providers() });
10
11const waitUntilGone = (name: string) =>
12 beyondcorp.getProjectsLocationsSecurityGatewaysApplications({ name }).pipe(
13 Effect.as("found" as const),
14 Effect.catchTag("NotFound", () => Effect.succeed("gone" as const)),
15 Effect.repeat({
16 schedule: Schedule.spaced("2 seconds"),
17 until: (status) => status === "gone",
18 times: 10,
19 }),
20 );
21
22test.provider(
23 "getProjectsLocationsSecurityGatewaysApplications on a missing application fails with a typed tag",
24 (stack) =>
25 Effect.gen(function* () {
26 const project = yield* currentProject;
27 yield* stack.destroy();
28
29 const error = yield* Effect.flip(
30 beyondcorp.getProjectsLocationsSecurityGatewaysApplications({
31 name: `projects/${project}/locations/global/securityGateways/alchemy-missing-sg/applications/alchemy-missing-app`,
32 }),
33 );
34 expect(error._tag).toEqual("NotFound");
35
36 yield* stack.destroy();
37 }).pipe(logLevel),
38 {
39 tags: ["provider:gcp", "provider:gcp:beyondcorp", "live"],
40 timeout: 90_000,
41 },
42);
43
44test.provider.skipIf(runLifecycle)(
45 "createProjectsLocationsSecurityGatewaysApplications without entitlement fails with a typed tag",
46 (stack) =>
47 Effect.gen(function* () {
48 const project = yield* currentProject;
49 yield* stack.destroy();
50
51 const error = yield* Effect.flip(
52 beyondcorp.createProjectsLocationsSecurityGatewaysApplications({
53 parent: `projects/${project}/locations/global/securityGateways/alchemy-missing-sg`,
54 applicationId: "alch-probe-app",
55 body: {
56 displayName: "probe",
57 endpointMatchers: [{ hostname: "example.com", ports: [443] }],
58 },
59 }),
60 );
61 expect(error._tag).toEqual("NotFound");
62
63 yield* stack.destroy();
64 }).pipe(logLevel),
65 {
66 tags: ["provider:gcp", "provider:gcp:beyondcorp", "live"],
67 timeout: 90_000,
68 },
69);
70
71test.provider.skipIf(!runLifecycle)(
72 "create, update, and delete a security gateway application",
73 (stack) =>
74 Effect.gen(function* () {
75 yield* stack.destroy();
76
77 const created = yield* stack.deploy(
78 Effect.gen(function* () {
79 const gateway = yield* GCP.BeyondCorp.SecurityGateway("Pep", {
80 location: "global",
81 displayName: "app parent",
82 });
83 return yield* GCP.BeyondCorp.SecurityGatewaysApplication("Web", {
84 securityGateway: gateway.name,
85 endpointMatchers: [{ hostname: "app.example.com", ports: [443] }],
86 displayName: "app a",
87 });
88 }),
89 );
90
91 expect(created.name).toContain("/applications/");
92 expect(created.location).toEqual("global");
93 expect(created.endpointMatchers[0]?.hostname).toEqual("app.example.com");
94 expect(created.displayName).toEqual("app a");
95
96 const fetched =
97 yield* beyondcorp.getProjectsLocationsSecurityGatewaysApplications({
98 name: created.name,
99 });
100 expect(fetched.name).toEqual(created.name);
101 expect(fetched.displayName).toContain("app a");
102
103 const updated = yield* stack.deploy(
104 Effect.gen(function* () {
105 const gateway = yield* GCP.BeyondCorp.SecurityGateway("Pep", {
106 securityGatewayId: created.securityGateway.split("/").pop(),
107 location: "global",
108 displayName: "app parent",
109 });
110 return yield* GCP.BeyondCorp.SecurityGatewaysApplication("Web", {
111 securityGateway: gateway.name,
112 applicationId: created.applicationId,
113 endpointMatchers: [
114 { hostname: "app.example.com", ports: [80, 443] },
115 ],
116 displayName: "app b",
117 });
118 }),
119 );
120
121 expect(updated.name).toEqual(created.name);
122 expect(updated.displayName).toEqual("app b");
123 expect(updated.endpointMatchers[0]?.ports).toEqual(
124 expect.arrayContaining([80, 443]),
125 );
126
127 yield* stack.destroy();
128
129 const gone = yield* waitUntilGone(created.name);
130 expect(gone).toEqual("gone");
131 }).pipe(logLevel),
132 {
133 tags: ["provider:gcp", "provider:gcp:beyondcorp", "live"],
134 timeout: 120_000,
135 },
136);