1import * as GCP from "@/GCP";
2import * as Test from "@/Test/Alchemy";
3import * as containeranalysis from "@distilled.cloud/gcp/containeranalysis_v1";
4import { expect } from "alchemy-test";
5import * as Effect from "effect/Effect";
6import * as Schedule from "effect/Schedule";
7import {
8 logLevel,
9 currentProject,
10 TEST_ATTESTATION,
11 TEST_RESOURCE_URI,
12} from "./common.ts";
13
14const { test } = Test.make({ providers: GCP.providers() });
15
16const waitUntilGone = (name: string) =>
17 containeranalysis.getProjectsOccurrences({ name }).pipe(
18 Effect.as("found" as const),
19 Effect.catchTag("NotFound", () => Effect.succeed("gone" as const)),
20 Effect.repeat({
21 schedule: Schedule.spaced("1 second"),
22 until: (status) => status === "gone",
23 times: 10,
24 }),
25 );
26
27test.provider(
28 "getProjectsOccurrences on a missing occurrence 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 containeranalysis.getProjectsOccurrences({
36 name: `projects/${project}/occurrences/alchemy-missing-occurrence`,
37 }),
38 );
39 expect(error._tag).toEqual("NotFound");
40
41 yield* stack.destroy();
42 }).pipe(logLevel),
43 {
44 tags: ["provider:gcp", "provider:gcp:containeranalysis", "live"],
45 timeout: 90_000,
46 },
47);
48
49test.provider(
50 "create, update, and delete an occurrence",
51 (stack) =>
52 Effect.gen(function* () {
53 yield* stack.destroy();
54
55 const created = yield* stack.deploy(
56 Effect.gen(function* () {
57 const note = yield* GCP.ContainerAnalysis.Note("Authority", {
58 shortDescription: "occurrence attestor",
59 attestation: { hint: { humanReadableName: "Alchemy QA" } },
60 });
61 const occurrence = yield* GCP.ContainerAnalysis.Occurrence("Signed", {
62 noteName: note.name,
63 resourceUri: TEST_RESOURCE_URI,
64 remediation: "initial",
65 attestation: TEST_ATTESTATION,
66 });
67 return { note, occurrence };
68 }),
69 );
70
71 expect(created.occurrence.name).toContain("/occurrences/");
72 expect(created.occurrence.noteName).toEqual(created.note.name);
73 expect(created.occurrence.resourceUri).toEqual(TEST_RESOURCE_URI);
74 expect(created.occurrence.remediation).toEqual("initial");
75 expect(created.occurrence.kind).toEqual("ATTESTATION");
76
77 const fetched = yield* containeranalysis.getProjectsOccurrences({
78 name: created.occurrence.name,
79 });
80 expect(fetched.name).toEqual(created.occurrence.name);
81 expect(fetched.noteName).toEqual(created.note.name);
82 expect(fetched.resourceUri).toEqual(TEST_RESOURCE_URI);
83 expect(fetched.remediation).toContain("[alchemy ");
84 expect(fetched.kind).toEqual("ATTESTATION");
85
86 const updated = yield* stack.deploy(
87 Effect.gen(function* () {
88 const note = yield* GCP.ContainerAnalysis.Note("Authority", {
89 noteId: created.note.noteId,
90 shortDescription: "occurrence attestor",
91 attestation: { hint: { humanReadableName: "Alchemy QA" } },
92 });
93 const occurrence = yield* GCP.ContainerAnalysis.Occurrence("Signed", {
94 noteName: note.name,
95 resourceUri: TEST_RESOURCE_URI,
96 remediation: "rebuild from a patched base",
97 attestation: TEST_ATTESTATION,
98 });
99 return { note, occurrence };
100 }),
101 );
102
103 expect(updated.occurrence.name).toEqual(created.occurrence.name);
104 expect(updated.occurrence.remediation).toEqual(
105 "rebuild from a patched base",
106 );
107
108 yield* stack.destroy();
109 const gone = yield* waitUntilGone(created.occurrence.name);
110 expect(gone).toEqual("gone");
111 }).pipe(logLevel),
112 {
113 tags: ["provider:gcp", "provider:gcp:containeranalysis", "live"],
114 timeout: 90_000,
115 },
116);