1import * as GCP from "@/GCP";
2import * as Test from "@/Test/Alchemy";
3import * as ml from "@distilled.cloud/gcp/ml_v1";
4import { expect } from "alchemy-test";
5import * as Effect from "effect/Effect";
6import * as Schedule from "effect/Schedule";
7import {
8 logLevel,
9 currentProject,
10 region,
11 runLifecycle,
12 runVersionLifecycle,
13} from "./common.ts";
14
15const { test } = Test.make({ providers: GCP.providers() });
16
17const waitUntilGone = (name: string) =>
18 ml.getProjectsModelsVersions({ name }).pipe(
19 Effect.as("found" as const),
20 Effect.catchTag("NotFound", () => Effect.succeed("gone" as const)),
21 Effect.repeat({
22 schedule: Schedule.spaced("2 seconds"),
23 until: (status) => status === "gone",
24 times: 10,
25 }),
26 );
27
28test.provider(
29 "getProjectsModelsVersions on a missing version fails with a typed tag",
30 (stack) =>
31 Effect.gen(function* () {
32 const project = yield* currentProject;
33
34 yield* stack.destroy();
35
36 const error = yield* Effect.flip(
37 ml.getProjectsModelsVersions({
38 name: `projects/${project}/models/alchemy-missing-model/versions/v1`,
39 }),
40 );
41 expect(error._tag).toEqual("NotFound");
42
43 yield* stack.destroy();
44 }).pipe(logLevel),
45 { tags: ["provider:gcp", "provider:gcp:ml", "live"], timeout: 90_000 },
46);
47
48test.provider.skipIf(runLifecycle)(
49 "createProjectsModelsVersions on a missing model fails with NotFound",
50 (stack) =>
51 Effect.gen(function* () {
52 const project = yield* currentProject;
53
54 yield* stack.destroy();
55
56 const error = yield* Effect.flip(
57 ml.createProjectsModelsVersions({
58 parent: `projects/${project}/models/alchemy-missing-model`,
59 body: {
60 name: "v1",
61 deploymentUri: `gs://${project}-ml-missing/model`,
62 description: "probe",
63 },
64 }),
65 );
66 expect(error._tag).toEqual("NotFound");
67
68 yield* stack.destroy();
69 }).pipe(logLevel),
70 { tags: ["provider:gcp", "provider:gcp:ml", "live"], timeout: 90_000 },
71);
72
73test.provider.skipIf(!runVersionLifecycle)(
74 "create, update, and delete a model version",
75 (stack) =>
76 Effect.gen(function* () {
77 yield* stack.destroy();
78
79 const deploymentUri = process.env.GCP_TEST_ML_DEPLOYMENT_URI ?? "";
80
81 const created = yield* stack.deploy(
82 Effect.gen(function* () {
83 const model = yield* GCP.ML.Model("Classifier", {
84 description: "image classifier",
85 regions: [region],
86 });
87 const version = yield* GCP.ML.ModelsVersion("V1", {
88 model: model.name,
89 deploymentUri,
90 runtimeVersion: "2.11",
91 pythonVersion: "3.7",
92 framework: "TENSORFLOW",
93 description: "first version",
94 labels: { env: "test" },
95 autoScaling: { minNodes: 0 },
96 });
97 return { model, version };
98 }),
99 );
100
101 expect(created.version.name).toContain("/versions/");
102 expect(created.version.model).toEqual(created.model.name);
103 expect(created.version.description).toEqual("first version");
104 expect(created.version.deploymentUri).toEqual(deploymentUri);
105 expect(created.version.labels).toMatchObject({ env: "test" });
106
107 const fetched = yield* ml.getProjectsModelsVersions({
108 name: created.version.name,
109 });
110 expect(fetched.name).toEqual(created.version.name);
111 expect(fetched.description).toContain("[alchemy ");
112 expect(fetched.description).toContain("first version");
113
114 const updated = yield* stack.deploy(
115 Effect.gen(function* () {
116 const model = yield* GCP.ML.Model("Classifier", {
117 modelId: created.model.modelId,
118 description: "image classifier",
119 regions: [region],
120 });
121 const version = yield* GCP.ML.ModelsVersion("V1", {
122 model: model.name,
123 versionId: created.version.versionId,
124 deploymentUri,
125 runtimeVersion: "2.11",
126 pythonVersion: "3.7",
127 framework: "TENSORFLOW",
128 description: "first version v2",
129 autoScaling: { minNodes: 0 },
130 });
131 return { model, version };
132 }),
133 );
134
135 expect(updated.version.name).toEqual(created.version.name);
136 expect(updated.version.description).toEqual("first version v2");
137
138 yield* stack.destroy();
139 const gone = yield* waitUntilGone(created.version.name);
140 expect(gone).toEqual("gone");
141 }).pipe(logLevel),
142 { tags: ["provider:gcp", "provider:gcp:ml", "live"], timeout: 120_000 },
143);