1import * as GCP from "@/GCP";
2import * as Test from "@/Test/Alchemy";
3import * as agentidentity from "@distilled.cloud/gcp/agentidentity_v1";
4import { expect } from "alchemy-test";
5import * as Effect from "effect/Effect";
6import { MinimumLogLevel } from "effect/References";
7import * as Schedule from "effect/Schedule";
8import { GcpEnvironment } from "@/GCP/Environment";
9
10const { test } = Test.make({ providers: GCP.providers() });
11
12const logLevel = Effect.provideService(
13 MinimumLogLevel,
14 process.env.DEBUG ? "Debug" : "Info",
15);
16
17const location = "us-central1";
18
19// The Agent Identity API is not enabled in the testing project. Set
20// GCP_TEST_AGENT_IDENTITY=1 on a project where it is.
21const entitled = !!process.env.GCP_TEST_AGENT_IDENTITY;
22
23const waitUntilGone = (name: string) =>
24 agentidentity.getProjectsLocationsAuthProviders({ name }).pipe(
25 Effect.map((item) =>
26 item.deleted === true ? ("gone" as const) : ("found" as const),
27 ),
28 Effect.catchTag("NotFound", () => Effect.succeed("gone" as const)),
29 Effect.repeat({
30 schedule: Schedule.spaced("1 second"),
31 until: (status) => status === "gone",
32 times: 10,
33 }),
34 );
35
36test.provider.skipIf(!entitled)(
37 "getProjectsLocationsAuthProviders on a missing provider fails with a typed tag",
38 (stack) =>
39 Effect.gen(function* () {
40 const { project } = yield* GcpEnvironment.current;
41 yield* stack.destroy();
42
43 const error = yield* Effect.flip(
44 agentidentity.getProjectsLocationsAuthProviders({
45 name: `projects/${project}/locations/${location}/authProviders/alchemy-missing-auth`,
46 }),
47 );
48 expect(error._tag).toEqual("NotFound");
49
50 yield* stack.destroy();
51 }).pipe(logLevel),
52 {
53 tags: ["provider:gcp", "provider:gcp:agentidentity", "live"],
54 timeout: 90_000,
55 },
56);
57
58test.provider.skipIf(entitled)(
59 "getProjectsLocationsAuthProviders fails with ServiceDisabled while the API is disabled",
60 (stack) =>
61 Effect.gen(function* () {
62 const { project } = yield* GcpEnvironment.current;
63 yield* stack.destroy();
64
65 const error = yield* Effect.flip(
66 agentidentity.getProjectsLocationsAuthProviders({
67 name: `projects/${project}/locations/${location}/authProviders/alchemy-missing-auth`,
68 }),
69 );
70 expect(error._tag).toEqual("ServiceDisabled");
71
72 yield* stack.destroy();
73 }).pipe(logLevel),
74 {
75 tags: ["provider:gcp", "provider:gcp:agentidentity", "live"],
76 timeout: 90_000,
77 },
78);
79
80test.provider.skipIf(!entitled)(
81 "create, update, and delete an Agent Identity auth provider",
82 (stack) =>
83 Effect.gen(function* () {
84 yield* stack.destroy();
85
86 const created = yield* stack.deploy(
87 Effect.gen(function* () {
88 return yield* GCP.AgentIdentity.AuthProvider("Maps", {
89 location,
90 description: "alchemy maps key",
91 labels: { env: "test" },
92 allowedScopes: ["https://www.googleapis.com/auth/cloud-platform"],
93 authProviderTypeParams: {
94 apiKey: { apiKey: "alchemy-test-api-key" },
95 },
96 });
97 }),
98 );
99
100 expect(created.name).toContain("/authProviders/");
101 expect(created.authProviderId).toEqual(expect.any(String));
102 expect(created.location).toEqual(location);
103 expect(created.description).toEqual("alchemy maps key");
104 expect(created.labels).toMatchObject({ env: "test" });
105 expect(created.disabled).toEqual(false);
106 expect(created.authProviderTypeParams?.apiKey).toBeDefined();
107
108 const fetched = yield* agentidentity.getProjectsLocationsAuthProviders({
109 name: created.name,
110 });
111 expect(fetched.name).toEqual(created.name);
112 expect(fetched.description).toEqual("alchemy maps key");
113 expect(fetched.labels?.env).toEqual("test");
114 expect(fetched.labels?.["alchemy-id"]).toEqual(expect.any(String));
115
116 const updated = yield* stack.deploy(
117 Effect.gen(function* () {
118 return yield* GCP.AgentIdentity.AuthProvider("Maps", {
119 authProviderId: created.authProviderId,
120 location,
121 description: "alchemy maps key v2",
122 labels: { env: "prod", role: "maps" },
123 allowedScopes: [
124 "https://www.googleapis.com/auth/cloud-platform",
125 "https://www.googleapis.com/auth/userinfo.email",
126 ],
127 authProviderTypeParams: {
128 apiKey: { apiKey: "alchemy-test-api-key" },
129 },
130 });
131 }),
132 );
133
134 expect(updated.name).toEqual(created.name);
135 expect(updated.authProviderId).toEqual(created.authProviderId);
136 expect(updated.description).toEqual("alchemy maps key v2");
137 expect(updated.labels).toMatchObject({ env: "prod", role: "maps" });
138
139 const fetchedUpdate =
140 yield* agentidentity.getProjectsLocationsAuthProviders({
141 name: created.name,
142 });
143 expect(fetchedUpdate.description).toEqual("alchemy maps key v2");
144 expect(fetchedUpdate.labels?.env).toEqual("prod");
145 expect(fetchedUpdate.labels?.role).toEqual("maps");
146 expect(fetchedUpdate.labels?.["alchemy-id"]).toEqual(expect.any(String));
147
148 yield* stack.destroy();
149
150 const gone = yield* waitUntilGone(created.name);
151 expect(gone).toEqual("gone");
152 }).pipe(logLevel),
153 {
154 tags: ["provider:gcp", "provider:gcp:agentidentity", "live"],
155 timeout: 90_000,
156 },
157);