1import * as GCP from "@/GCP";
2import * as Test from "@/Test/Alchemy";
3import * as ssm from "@distilled.cloud/gcp/securesourcemanager_v1";
4import { expect } from "alchemy-test";
5import * as Effect from "effect/Effect";
6import * as Schedule from "effect/Schedule";
7import {
8 currentProject,
9 logLevel,
10 missingRepoOf,
11 runLifecycle,
12} from "./common.ts";
13
14const { test } = Test.make({ providers: GCP.providers() });
15
16const waitUntilGone = (name: string) =>
17 ssm.getProjectsLocationsRepositoriesIssues({ 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 "getProjectsLocationsRepositoriesIssues on a missing issue fails with a typed tag",
29 (stack) =>
30 Effect.gen(function* () {
31 const project = yield* currentProject;
32 const missingRepo = missingRepoOf(project);
33 yield* stack.destroy();
34
35 const error = yield* Effect.flip(
36 ssm.getProjectsLocationsRepositoriesIssues({
37 name: `${missingRepo}/issues/12345`,
38 }),
39 );
40 expect(error._tag).toEqual("NotFound");
41
42 yield* stack.destroy();
43 }).pipe(logLevel),
44 {
45 tags: ["provider:gcp", "provider:gcp:securesourcemanager", "live"],
46 timeout: 90_000,
47 },
48);
49
50test.provider(
51 "create against a missing repository is rejected with a typed tag",
52 (stack) =>
53 Effect.gen(function* () {
54 const project = yield* currentProject;
55 const missingRepo = missingRepoOf(project);
56 yield* stack.destroy();
57
58 const error = yield* Effect.flip(
59 stack.deploy(
60 Effect.gen(function* () {
61 return yield* GCP.SecureSourceManager.RepositoriesIssue("Bug", {
62 repository: missingRepo,
63 title: "alchemy probe",
64 body: "should not exist",
65 });
66 }),
67 ),
68 );
69 expect(error._tag).toEqual("NotFound");
70
71 yield* stack.destroy();
72 }).pipe(logLevel),
73 {
74 tags: ["provider:gcp", "provider:gcp:securesourcemanager", "live"],
75 timeout: 90_000,
76 },
77);
78
79test.provider.skipIf(!runLifecycle)(
80 "create, update, and delete an issue",
81 (stack) =>
82 Effect.gen(function* () {
83 const project = yield* currentProject;
84 const missingRepo = missingRepoOf(project);
85 yield* stack.destroy();
86
87 const created = yield* stack.deploy(
88 Effect.gen(function* () {
89 return yield* GCP.SecureSourceManager.RepositoriesIssue("Bug", {
90 repository:
91 process.env.GCP_TEST_SECURE_SOURCE_MANAGER_REPO ?? missingRepo,
92 title: "webhook retries 500s",
93 body: "hooks retry forever on 500",
94 });
95 }),
96 );
97
98 expect(created.name).toContain("/issues/");
99 expect(created.title).toEqual("webhook retries 500s");
100 expect(created.body).toEqual("hooks retry forever on 500");
101
102 const fetched = yield* ssm.getProjectsLocationsRepositoriesIssues({
103 name: created.name,
104 });
105 expect(fetched.name).toEqual(created.name);
106 expect(fetched.body).toEqual("hooks retry forever on 500");
107
108 const updated = yield* stack.deploy(
109 Effect.gen(function* () {
110 return yield* GCP.SecureSourceManager.RepositoriesIssue("Bug", {
111 repository: created.repository,
112 issueId: created.issueId,
113 title: "webhook retries 5xx",
114 body: "cap retries at 8",
115 });
116 }),
117 );
118
119 expect(updated.issueId).toEqual(created.issueId);
120 expect(updated.title).toEqual("webhook retries 5xx");
121 expect(updated.body).toEqual("cap retries at 8");
122
123 yield* stack.destroy();
124
125 const gone = yield* waitUntilGone(created.name);
126 expect(gone).toEqual("gone");
127 }).pipe(logLevel),
128 {
129 tags: ["provider:gcp", "provider:gcp:securesourcemanager", "live"],
130 timeout: 120_000,
131 },
132);