1import * as GCP from "@/GCP";
2import * as Test from "@/Test/Alchemy";
3import * as vision from "@distilled.cloud/gcp/vision_v1";
4import { expect } from "alchemy-test";
5import * as Effect from "effect/Effect";
6import * as Schedule from "effect/Schedule";
7import { location, logLevel, currentProject, runLifecycle } from "./common.ts";
8
9const { test } = Test.make({ providers: GCP.providers() });
10
11const waitUntilGone = (name: string) =>
12 vision.getProjectsLocationsProducts({ name }).pipe(
13 Effect.as("found" as const),
14 Effect.catchTag("NotFound", () => Effect.succeed("gone" as const)),
15 Effect.repeat({
16 schedule: Schedule.spaced("1 second"),
17 until: (status) => status === "gone",
18 times: 10,
19 }),
20 );
21
22test.provider(
23 "getProjectsLocationsProducts on a missing product fails with a typed tag",
24 (stack) =>
25 Effect.gen(function* () {
26 const project = yield* currentProject;
27 yield* stack.destroy();
28
29 const error = yield* Effect.flip(
30 vision.getProjectsLocationsProducts({
31 name: `projects/${project}/locations/${location}/products/alchemy-missing-product`,
32 }),
33 );
34 expect(error._tag).toEqual("NotFound");
35
36 yield* stack.destroy();
37 }).pipe(logLevel),
38 { tags: ["provider:gcp", "provider:gcp:vision", "live"], timeout: 90_000 },
39);
40
41test.provider.skipIf(runLifecycle)(
42 "createProjectsLocationsProducts on a new project fails with ProductSearchNotOnboarded",
43 (stack) =>
44 Effect.gen(function* () {
45 const project = yield* currentProject;
46 yield* stack.destroy();
47
48 const error = yield* Effect.flip(
49 vision.createProjectsLocationsProducts({
50 parent: `projects/${project}/locations/${location}`,
51 productId: "alchemy-vision-probe",
52 body: {
53 displayName: "Alchemy probe",
54 productCategory: "homegoods-v2",
55 },
56 }),
57 );
58 expect(error._tag).toEqual("ProductSearchNotOnboarded");
59
60 yield* stack.destroy();
61 }).pipe(logLevel),
62 { tags: ["provider:gcp", "provider:gcp:vision", "live"], timeout: 90_000 },
63);
64
65test.provider.skipIf(!runLifecycle)(
66 "create, update, and delete a product",
67 (stack) =>
68 Effect.gen(function* () {
69 const project = yield* currentProject;
70 yield* stack.destroy();
71
72 const created = yield* stack.deploy(
73 Effect.gen(function* () {
74 return yield* GCP.Vision.Product("Shoe", {
75 location,
76 displayName: "Trail runner",
77 description: "mesh upper",
78 productCategory: "apparel-v2",
79 productLabels: [{ key: "color", value: "blue" }],
80 });
81 }),
82 );
83
84 expect(
85 created.name.startsWith(
86 `projects/${project}/locations/${location}/products/`,
87 ),
88 ).toEqual(true);
89 expect(created.productId.length).toBeGreaterThan(0);
90 expect(created.displayName).toEqual("Trail runner");
91 expect(created.description).toEqual("mesh upper");
92 expect(created.productCategory).toEqual("apparel-v2");
93 expect(created.productLabels).toEqual([{ key: "color", value: "blue" }]);
94
95 const fetched = yield* vision.getProjectsLocationsProducts({
96 name: created.name,
97 });
98 expect(fetched.name).toEqual(created.name);
99 expect(fetched.displayName).toEqual("Trail runner");
100 expect(fetched.description).toEqual("mesh upper");
101 expect(fetched.productCategory).toEqual("apparel-v2");
102 expect(fetched.productLabels).toEqual([{ key: "color", value: "blue" }]);
103
104 const updated = yield* stack.deploy(
105 Effect.gen(function* () {
106 return yield* GCP.Vision.Product("Shoe", {
107 location,
108 productId: created.productId,
109 displayName: "Trail runner v2",
110 description: "knit upper",
111 productCategory: "apparel-v2",
112 productLabels: [
113 { key: "color", value: "green" },
114 { key: "size", value: "10" },
115 ],
116 });
117 }),
118 );
119
120 expect(updated.name).toEqual(created.name);
121 expect(updated.displayName).toEqual("Trail runner v2");
122 expect(updated.description).toEqual("knit upper");
123 expect(updated.productLabels).toEqual([
124 { key: "color", value: "green" },
125 { key: "size", value: "10" },
126 ]);
127
128 const fetchedUpdate = yield* vision.getProjectsLocationsProducts({
129 name: created.name,
130 });
131 expect(fetchedUpdate.displayName).toEqual("Trail runner v2");
132 expect(fetchedUpdate.description).toEqual("knit upper");
133
134 yield* stack.destroy();
135
136 const gone = yield* waitUntilGone(created.name);
137 expect(gone).toEqual("gone");
138 }).pipe(logLevel),
139 { tags: ["provider:gcp", "provider:gcp:vision", "live"], timeout: 90_000 },
140);