1import * as GCP from "@/GCP";
2import * as Test from "@/Test/Alchemy";
3import * as reviews from "@distilled.cloud/gcp/merchantapi_reviews_v1beta";
4import { expect } from "alchemy-test";
5import * as Effect from "effect/Effect";
6import * as Schedule from "effect/Schedule";
7import {
8 accountId,
9 logLevel,
10 probeAccount,
11 probeDataSource,
12 productReviewDataSource,
13 runProductReviewLifecycle,
14} from "./common.ts";
15
16const { test } = Test.make({ providers: GCP.providers() });
17
18const waitUntilGone = (name: string) =>
19 reviews.getAccountsProductReviews({ name }).pipe(
20 Effect.as("found" as const),
21 Effect.catchTag("NotFound", () => Effect.succeed("gone" as const)),
22 Effect.repeat({
23 schedule: Schedule.spaced("1 second"),
24 until: (status) => status === "gone",
25 times: 10,
26 }),
27 );
28
29test.provider.skipIf(!runProductReviewLifecycle)(
30 "getAccountsProductReviews on a missing review fails with a typed tag",
31 (stack) =>
32 Effect.gen(function* () {
33 yield* stack.destroy();
34
35 const error = yield* Effect.flip(
36 reviews.getAccountsProductReviews({
37 name: `accounts/${probeAccount}/productReviews/alchemy-missing-product-review`,
38 }),
39 );
40 expect(error._tag).toEqual("NotFound");
41
42 yield* stack.destroy();
43 }).pipe(logLevel),
44 {
45 tags: ["provider:gcp", "provider:gcp:merchantapireviews", "live"],
46 timeout: 90_000,
47 },
48);
49
50test.provider.skipIf(runProductReviewLifecycle)(
51 "insertAccountsProductReviews without Product Reviews access fails with InsufficientScopes",
52 (stack) =>
53 Effect.gen(function* () {
54 yield* stack.destroy();
55
56 const error = yield* Effect.flip(
57 reviews.insertAccountsProductReviews({
58 parent: `accounts/${probeAccount}`,
59 dataSource: probeDataSource(productReviewDataSource),
60 body: {
61 productReviewId: "alchemy-probe-product-review",
62 productReviewAttributes: {
63 reviewTime: "2020-01-01T00:00:00Z",
64 content: "probe",
65 title: "Alchemy Probe Product Review",
66 skus: ["alchemy-probe-sku"],
67 },
68 },
69 }),
70 );
71 expect(error._tag).toEqual("InsufficientScopes");
72
73 yield* stack.destroy();
74 }).pipe(logLevel),
75 {
76 tags: ["provider:gcp", "provider:gcp:merchantapireviews", "live"],
77 timeout: 90_000,
78 },
79);
80
81test.provider.skipIf(!runProductReviewLifecycle)(
82 "create, update, and delete a product review",
83 (stack) =>
84 Effect.gen(function* () {
85 yield* stack.destroy();
86
87 const created = yield* stack.deploy(
88 Effect.gen(function* () {
89 return yield* GCP.MerchantApiReviews.ProductReview("Tote", {
90 account: accountId!,
91 dataSource: productReviewDataSource!,
92 productReviewAttributes: {
93 title: "Sturdy tote",
94 content: "Holds a laptop and lunch",
95 rating: 5,
96 skus: ["tote-navy"],
97 },
98 });
99 }),
100 );
101
102 expect(created.productReviewId.length).toBeGreaterThan(0);
103 expect(created.name).toContain("/productReviews/");
104 expect(created.account).toEqual(accountId);
105 expect(created.productReviewAttributes?.title).toEqual("Sturdy tote");
106 expect(created.productReviewAttributes?.content).toEqual(
107 "Holds a laptop and lunch",
108 );
109
110 const fetched = yield* reviews.getAccountsProductReviews({
111 name: created.name,
112 });
113 expect(fetched.productReviewId).toEqual(created.productReviewId);
114 // Review text is shown to shoppers: no ownership marker.
115 expect(fetched.productReviewAttributes?.content).toEqual(
116 created.productReviewAttributes?.content,
117 );
118 expect(fetched.productReviewAttributes?.title).toEqual("Sturdy tote");
119
120 const updated = yield* stack.deploy(
121 Effect.gen(function* () {
122 return yield* GCP.MerchantApiReviews.ProductReview("Tote", {
123 account: created.account,
124 dataSource: productReviewDataSource!,
125 productReviewId: created.productReviewId,
126 productReviewAttributes: {
127 title: "Roomy tote",
128 content: "Holds a laptop, lunch, and jacket",
129 rating: 4,
130 skus: ["tote-navy"],
131 },
132 });
133 }),
134 );
135
136 expect(updated.productReviewId).toEqual(created.productReviewId);
137 expect(updated.productReviewAttributes?.title).toEqual("Roomy tote");
138 expect(updated.productReviewAttributes?.content).toEqual(
139 "Holds a laptop, lunch, and jacket",
140 );
141 expect(updated.productReviewAttributes?.rating).toEqual(4);
142
143 yield* stack.destroy();
144
145 const gone = yield* waitUntilGone(created.name);
146 expect(gone).toEqual("gone");
147 }).pipe(logLevel),
148 {
149 tags: ["provider:gcp", "provider:gcp:merchantapireviews", "live"],
150 timeout: 90_000,
151 },
152);