1import * as artifactregistry from "@distilled.cloud/gcp/artifactregistry_v1";
2import * as cloudrun from "@distilled.cloud/gcp/run_v2";
3import { expect } from "alchemy-test";
4import * as Effect from "effect/Effect";
5import { MinimumLogLevel } from "effect/References";
6import * as Schedule from "effect/Schedule";
7import * as pathe from "pathe";
8
9/**
10 * Shared helpers for the `GCP.Website.*` live tests. Each test deploys a
11 * framework fixture (reused from `test/AWS/Website/fixtures` or the
12 * Cloudflare website fixtures), GETs the Cloud Run URL, then destroys and
13 * verifies out-of-band that the service and its image repository are gone.
14 */
15
16export const logLevel = Effect.provideService(
17 MinimumLogLevel,
18 process.env.DEBUG ? "Debug" : "Info",
19);
20
21export const tempRoot = pathe.resolve(import.meta.dirname, "../../../.tmp");
22
23export const awsFixture = (name: string) =>
24 pathe.resolve(import.meta.dirname, "../../AWS/Website/fixtures", name);
25
26export const cloudflareFixture = (name: string) =>
27 pathe.resolve(import.meta.dirname, "../../Cloudflare/Website", name);
28
29/** Live-test options: image build + push + Cloud Run rollout + destroy. */
30export const liveOptions = (timeout = 900_000) => ({
31 timeout,
32 retry: 0,
33 tags: ["provider:gcp", "provider:gcp:website", "live"],
34});
35
36/** The deployed service identity a test needs to verify teardown. */
37export interface DeployedService {
38 readonly name: string;
39 readonly serviceId: string;
40 readonly project: string;
41 readonly location: string;
42}
43
44export const cloudRunUrl = /^https:\/\/[a-z0-9-]+.*\.run\.app\/?$/;
45
46const serviceStatus = (name: string) =>
47 cloudrun.getProjectsLocationsServices({ name }).pipe(
48 Effect.as("found" as const),
49 Effect.catchTag("NotFound", () => Effect.succeed("gone" as const)),
50 );
51
52const repositoryStatus = (name: string) =>
53 artifactregistry.getProjectsLocationsRepositories({ name }).pipe(
54 Effect.as("found" as const),
55 Effect.catchTag("NotFound", () => Effect.succeed("gone" as const)),
56 );
57
58/** Out-of-band: the Cloud Run service and its `{serviceId}-src` image repository are gone. */
59export const assertSiteGone = (service: DeployedService) =>
60 Effect.gen(function* () {
61 const service_ = yield* serviceStatus(service.name).pipe(
62 Effect.repeat({
63 schedule: Schedule.spaced("2 seconds"),
64 until: (status) => status === "gone",
65 times: 15,
66 }),
67 );
68 expect(service_).toEqual("gone");
69 const repositoryId = `${service.serviceId}-src`
70 .slice(0, 49)
71 .replace(/-+$/g, "");
72 const repository = yield* repositoryStatus(
73 `projects/${service.project}/locations/${service.location}/repositories/${repositoryId}`,
74 ).pipe(
75 Effect.repeat({
76 schedule: Schedule.spaced("2 seconds"),
77 until: (status) => status === "gone",
78 times: 15,
79 }),
80 );
81 expect(repository).toEqual("gone");
82 });
83
84/** Project the deployed `GCP.Run.Service` attributes a test verifies. */
85export const serviceIdentity = (service: {
86 name: string;
87 serviceId: string;
88 project: string;
89 location: string;
90}): DeployedService => ({
91 name: service.name,
92 serviceId: service.serviceId,
93 project: service.project,
94 location: service.location,
95});