1import * as GCP from "@/GCP";
2import * as Test from "@/Test/Alchemy";
3import * as recommendationengine from "@distilled.cloud/gcp/recommendationengine_v1beta1";
4import { expect } from "alchemy-test";
5import * as Effect from "effect/Effect";
6import * as Schedule from "effect/Schedule";
7import { currentProject, entitled, logLevel, missingNameOf } from "./common.ts";
8
9const { test } = Test.make({ providers: GCP.providers() });
10
11const waitUntilGone = (name: string) =>
12 recommendationengine.getProjectsLocationsCatalogsCatalogItems({ 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.skipIf(!entitled)(
23 "getProjectsLocationsCatalogsCatalogItems on a missing item 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 recommendationengine.getProjectsLocationsCatalogsCatalogItems({
31 name: missingNameOf(project),
32 }),
33 );
34 expect(error._tag).toEqual("NotFound");
35
36 yield* stack.destroy();
37 }).pipe(logLevel),
38 {
39 tags: ["provider:gcp", "provider:gcp:recommendationengine", "live"],
40 timeout: 90_000,
41 },
42);
43
44test.provider.skipIf(entitled)(
45 "getProjectsLocationsCatalogsCatalogItems fails with ServiceDisabled while the API is disabled",
46 (stack) =>
47 Effect.gen(function* () {
48 const project = yield* currentProject;
49 yield* stack.destroy();
50
51 const error = yield* Effect.flip(
52 recommendationengine.getProjectsLocationsCatalogsCatalogItems({
53 name: missingNameOf(project),
54 }),
55 );
56 expect(error._tag).toEqual("ServiceDisabled");
57
58 yield* stack.destroy();
59 }).pipe(logLevel),
60 {
61 tags: ["provider:gcp", "provider:gcp:recommendationengine", "live"],
62 timeout: 90_000,
63 },
64);
65
66test.provider.skipIf(!entitled || !!process.env.FAST)(
67 "create, update, and delete a catalog item",
68 (stack) =>
69 Effect.gen(function* () {
70 yield* stack.destroy();
71
72 const created = yield* stack.deploy(
73 Effect.gen(function* () {
74 return yield* GCP.RecommendationEngine.CatalogsCatalogItem("Shirt", {
75 title: "Cotton tee",
76 description: "test tee",
77 categoryHierarchies: [{ categories: ["Apparel", "T-Shirts"] }],
78 productMetadata: {
79 currencyCode: "USD",
80 exactPrice: { displayPrice: 20 },
81 stockState: "IN_STOCK",
82 },
83 });
84 }),
85 );
86
87 expect(created.name).toContain("/catalogItems/");
88 expect(created.title).toEqual("Cotton tee");
89 expect(created.description).toEqual("test tee");
90 expect(created.categoryHierarchies[0]?.categories).toEqual(
91 expect.arrayContaining(["Apparel", "T-Shirts"]),
92 );
93
94 const fetched =
95 yield* recommendationengine.getProjectsLocationsCatalogsCatalogItems({
96 name: created.name,
97 });
98 expect(fetched.id).toEqual(created.catalogItemId);
99 // Description and tags feed recommendations: no ownership marker.
100 expect(fetched.description).toEqual("test tee");
101 expect(fetched.title).toEqual("Cotton tee");
102
103 const updated = yield* stack.deploy(
104 Effect.gen(function* () {
105 return yield* GCP.RecommendationEngine.CatalogsCatalogItem("Shirt", {
106 catalogItemId: created.catalogItemId,
107 catalog: created.catalog,
108 location: created.location,
109 title: "Linen tee",
110 description: "updated tee",
111 categoryHierarchies: [{ categories: ["Apparel", "T-Shirts"] }],
112 productMetadata: {
113 currencyCode: "USD",
114 exactPrice: { displayPrice: 24 },
115 stockState: "IN_STOCK",
116 },
117 });
118 }),
119 );
120
121 expect(updated.name).toEqual(created.name);
122 expect(updated.title).toEqual("Linen tee");
123 expect(updated.productMetadata?.exactPrice?.displayPrice).toEqual(24);
124
125 yield* stack.destroy();
126
127 const gone = yield* waitUntilGone(created.name);
128 expect(gone).toEqual("gone");
129 }).pipe(logLevel),
130 {
131 tags: ["provider:gcp", "provider:gcp:recommendationengine", "live"],
132 timeout: 90_000,
133 },
134);