1import * as GCP from "@/GCP";
2import * as Test from "@/Test/Alchemy";
3import * as gamesConfiguration from "@distilled.cloud/gcp/gamesConfiguration_v1configuration";
4import { expect } from "alchemy-test";
5import * as Effect from "effect/Effect";
6import * as Schedule from "effect/Schedule";
7import {
8 applicationId,
9 logLevel,
10 missingTag,
11 probeApplicationId,
12} from "./common.ts";
13
14const { test } = Test.make({ providers: GCP.providers() });
15
16const waitUntilGone = (leaderboardId: string) =>
17 gamesConfiguration.getLeaderboardConfigurations({ leaderboardId }).pipe(
18 Effect.as("found" as const),
19 Effect.catchTag("NotFound", () => Effect.succeed("gone" as const)),
20 Effect.repeat({
21 schedule: Schedule.spaced("1 second"),
22 until: (status) => status === "gone",
23 times: 10,
24 }),
25 );
26
27test.provider(
28 "getLeaderboardConfigurations on a missing leaderboard fails with a typed tag",
29 (stack) =>
30 Effect.gen(function* () {
31 yield* stack.destroy();
32
33 const error = yield* Effect.flip(
34 gamesConfiguration.getLeaderboardConfigurations({
35 leaderboardId: "alchemy-missing-leaderboard",
36 }),
37 );
38 expect(error._tag).toEqual(missingTag);
39
40 yield* stack.destroy();
41 }).pipe(logLevel),
42 {
43 tags: ["provider:gcp", "provider:gcp:gamesconfiguration", "live"],
44 timeout: 90_000,
45 },
46);
47
48test.provider.skipIf(!!applicationId)(
49 "insertLeaderboardConfigurations without Play Games access fails with a typed tag",
50 (stack) =>
51 Effect.gen(function* () {
52 yield* stack.destroy();
53
54 const error = yield* Effect.flip(
55 gamesConfiguration.insertLeaderboardConfigurations({
56 applicationId: probeApplicationId,
57 body: {
58 scoreOrder: "LARGER_IS_BETTER",
59 draft: {
60 name: {
61 translations: [{ locale: "en-US", value: "Alchemy Probe" }],
62 },
63 scoreFormat: { numberFormatType: "NUMERIC", numDecimalPlaces: 0 },
64 },
65 },
66 }),
67 );
68 expect(error._tag).toEqual("Forbidden");
69
70 yield* stack.destroy();
71 }).pipe(logLevel),
72 {
73 tags: ["provider:gcp", "provider:gcp:gamesconfiguration", "live"],
74 timeout: 90_000,
75 },
76);
77
78// Needs a Play Games Services application the credentials administer; without
79// one the API rejects inserts with Forbidden (probe above).
80// Set GCP_GAMESCONFIGURATION_APPLICATION_ID to run the lifecycle.
81test.provider.skipIf(!applicationId)(
82 "create, update, and delete a leaderboard configuration",
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.GamesConfiguration.LeaderboardConfiguration(
90 "HighScore",
91 {
92 applicationId: applicationId!,
93 name: "High Score",
94 scoreOrder: "LARGER_IS_BETTER",
95 },
96 );
97 }),
98 );
99
100 expect(created.leaderboardId.length).toBeGreaterThan(0);
101 expect(created.applicationId).toEqual(applicationId);
102 expect(created.name).toEqual("High Score");
103
104 const fetched = yield* gamesConfiguration.getLeaderboardConfigurations({
105 leaderboardId: created.leaderboardId,
106 });
107 expect(fetched.id).toEqual(created.leaderboardId);
108 expect(fetched.draft?.name?.translations?.[0]?.value).toEqual(
109 "High Score",
110 );
111
112 const updated = yield* stack.deploy(
113 Effect.gen(function* () {
114 return yield* GCP.GamesConfiguration.LeaderboardConfiguration(
115 "HighScore",
116 {
117 applicationId: created.applicationId,
118 leaderboardId: created.leaderboardId,
119 name: "All-time High Score",
120 scoreOrder: "LARGER_IS_BETTER",
121 },
122 );
123 }),
124 );
125
126 expect(updated.leaderboardId).toEqual(created.leaderboardId);
127 expect(updated.name).toEqual("All-time High Score");
128
129 yield* stack.destroy();
130
131 const gone = yield* waitUntilGone(created.leaderboardId);
132 expect(gone).toEqual("gone");
133 }).pipe(logLevel),
134 {
135 tags: ["provider:gcp", "provider:gcp:gamesconfiguration", "live"],
136 timeout: 90_000,
137 },
138);