1import * as GCP from "@/GCP";
2import * as Test from "@/Test/Alchemy";
3import * as androidpublisher from "@distilled.cloud/gcp/androidpublisher_v3";
4import { expect } from "alchemy-test";
5import * as Effect from "effect/Effect";
6import * as Schedule from "effect/Schedule";
7import {
8 logLevel,
9 packageName,
10 probePackageName,
11 missingTag,
12 runLifecycle,
13} from "./common.ts";
14
15const { test } = Test.make({ providers: GCP.providers() });
16
17const waitUntilGone = (appId: string, productId: string) =>
18 androidpublisher
19 .getMonetizationSubscriptions({
20 packageName: appId,
21 productId,
22 })
23 .pipe(
24 Effect.as("found" as const),
25 Effect.catchTag("NotFound", () => Effect.succeed("gone" as const)),
26 Effect.repeat({
27 schedule: Schedule.spaced("1 second"),
28 until: (status) => status === "gone",
29 times: 10,
30 }),
31 );
32
33test.provider(
34 "getMonetizationSubscriptions on a missing subscription fails with a typed tag",
35 (stack) =>
36 Effect.gen(function* () {
37 yield* stack.destroy();
38
39 const error = yield* Effect.flip(
40 androidpublisher.getMonetizationSubscriptions({
41 packageName: probePackageName,
42 productId: "alchemy_missing_sub",
43 }),
44 );
45 expect(error._tag).toEqual(missingTag);
46
47 yield* stack.destroy();
48 }).pipe(logLevel),
49 {
50 tags: ["provider:gcp", "provider:gcp:androidpublisher", "live"],
51 timeout: 90_000,
52 },
53);
54
55test.provider.skipIf(runLifecycle)(
56 "createMonetizationSubscriptions without Play access fails with a typed tag",
57 (stack) =>
58 Effect.gen(function* () {
59 yield* stack.destroy();
60
61 const error = yield* Effect.flip(
62 androidpublisher.createMonetizationSubscriptions({
63 packageName: probePackageName,
64 productId: "alchemy_probe_sub",
65 "regionsVersion.version": "2025/01",
66 body: {
67 packageName: probePackageName,
68 productId: "alchemy_probe_sub",
69 listings: [{ languageCode: "en-US", title: "Alchemy Probe" }],
70 },
71 }),
72 );
73 expect(error._tag).toEqual("Forbidden");
74
75 yield* stack.destroy();
76 }).pipe(logLevel),
77 {
78 tags: ["provider:gcp", "provider:gcp:androidpublisher", "live"],
79 timeout: 90_000,
80 },
81);
82
83test.provider.skipIf(!runLifecycle)(
84 "create, update, and delete a subscription",
85 (stack) =>
86 Effect.gen(function* () {
87 yield* stack.destroy();
88
89 const created = yield* stack.deploy(
90 Effect.gen(function* () {
91 return yield* GCP.AndroidPublisher.MonetizationSubscription(
92 "Premium",
93 {
94 packageName: packageName!,
95 listings: [{ languageCode: "en-US", title: "Premium" }],
96 },
97 );
98 }),
99 );
100
101 expect(created.productId.length).toBeGreaterThan(0);
102 expect(created.packageName).toEqual(packageName);
103 expect(created.listings?.[0]?.title).toEqual("Premium");
104
105 const fetched = yield* androidpublisher.getMonetizationSubscriptions({
106 packageName: created.packageName,
107 productId: created.productId,
108 });
109 expect(fetched.productId).toEqual(created.productId);
110 expect(fetched.listings?.[0]?.title).toEqual("Premium");
111
112 const updated = yield* stack.deploy(
113 Effect.gen(function* () {
114 return yield* GCP.AndroidPublisher.MonetizationSubscription(
115 "Premium",
116 {
117 packageName: created.packageName,
118 productId: created.productId,
119 listings: [{ languageCode: "en-US", title: "Premium Plus" }],
120 },
121 );
122 }),
123 );
124
125 expect(updated.productId).toEqual(created.productId);
126 expect(updated.listings?.[0]?.title).toEqual("Premium Plus");
127
128 yield* stack.destroy();
129
130 const gone = yield* waitUntilGone(created.packageName, created.productId);
131 expect(gone).toEqual("gone");
132 }).pipe(logLevel),
133 {
134 tags: ["provider:gcp", "provider:gcp:androidpublisher", "live"],
135 timeout: 90_000,
136 },
137);