@jskit-ai/rewarded-core 0.1.120

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,274 @@
1
+ import assert from "node:assert/strict";
2
+ import test from "node:test";
3
+
4
+ import { requireRewardedUnlock } from "../src/server/support/requireRewardedUnlock.js";
5
+
6
+ function createRewardedService(gateState) {
7
+ const calls = [];
8
+ return {
9
+ calls,
10
+ async getCurrentState(input, options = {}) {
11
+ calls.push({
12
+ input,
13
+ options
14
+ });
15
+ return gateState;
16
+ }
17
+ };
18
+ }
19
+
20
+ const REQUEST_CONTEXT = Object.freeze({
21
+ actor: {
22
+ id: "7"
23
+ }
24
+ });
25
+
26
+ test("requireRewardedUnlock allows an already unlocked gate", async () => {
27
+ const gateState = {
28
+ gateKey: "progress-logging",
29
+ workspaceSlug: "alpha",
30
+ surface: "app",
31
+ enabled: true,
32
+ available: true,
33
+ blocked: false,
34
+ reason: "already-unlocked",
35
+ unlock: {
36
+ id: "31"
37
+ }
38
+ };
39
+ const rewardedService = createRewardedService(gateState);
40
+
41
+ const result = await requireRewardedUnlock(
42
+ rewardedService,
43
+ {
44
+ gateKey: "progress-logging",
45
+ workspaceSlug: "alpha"
46
+ },
47
+ {
48
+ context: REQUEST_CONTEXT
49
+ }
50
+ );
51
+
52
+ assert.equal(result, gateState);
53
+ assert.deepEqual(rewardedService.calls[0], {
54
+ input: {
55
+ gateKey: "progress-logging",
56
+ workspaceSlug: "alpha",
57
+ surface: "app"
58
+ },
59
+ options: {
60
+ context: REQUEST_CONTEXT
61
+ }
62
+ });
63
+ });
64
+
65
+ test("requireRewardedUnlock bypasses missing configuration by default", async () => {
66
+ const gateState = {
67
+ gateKey: "progress-logging",
68
+ workspaceSlug: "alpha",
69
+ surface: "app",
70
+ enabled: false,
71
+ available: false,
72
+ blocked: false,
73
+ reason: "rule-not-configured",
74
+ unlock: null
75
+ };
76
+ const rewardedService = createRewardedService(gateState);
77
+
78
+ const result = await requireRewardedUnlock(
79
+ rewardedService,
80
+ {
81
+ gateKey: "progress-logging",
82
+ workspaceSlug: "alpha"
83
+ },
84
+ {
85
+ context: REQUEST_CONTEXT
86
+ }
87
+ );
88
+
89
+ assert.equal(result, gateState);
90
+ });
91
+
92
+ test("requireRewardedUnlock can fail closed when configuration is required", async () => {
93
+ const gateState = {
94
+ gateKey: "progress-logging",
95
+ workspaceSlug: "alpha",
96
+ surface: "app",
97
+ enabled: false,
98
+ available: false,
99
+ blocked: false,
100
+ reason: "provider-not-configured",
101
+ unlock: null
102
+ };
103
+ const rewardedService = createRewardedService(gateState);
104
+
105
+ await assert.rejects(
106
+ () => requireRewardedUnlock(
107
+ rewardedService,
108
+ {
109
+ gateKey: "progress-logging",
110
+ workspaceSlug: "alpha"
111
+ },
112
+ {
113
+ context: REQUEST_CONTEXT,
114
+ requireConfigured: true
115
+ }
116
+ ),
117
+ (error) => {
118
+ assert.equal(error.statusCode, 503);
119
+ assert.equal(error.code, "rewarded_not_configured");
120
+ assert.equal(error.details.rewardedGate, gateState);
121
+ return true;
122
+ }
123
+ );
124
+ });
125
+
126
+ test("requireRewardedUnlock rejects when a reward is still required", async () => {
127
+ const gateState = {
128
+ gateKey: "progress-logging",
129
+ workspaceSlug: "alpha",
130
+ surface: "app",
131
+ enabled: true,
132
+ available: true,
133
+ blocked: true,
134
+ reason: "reward-required",
135
+ unlock: null
136
+ };
137
+ const rewardedService = createRewardedService(gateState);
138
+
139
+ await assert.rejects(
140
+ () => requireRewardedUnlock(
141
+ rewardedService,
142
+ {
143
+ gateKey: "progress-logging",
144
+ workspaceSlug: "alpha"
145
+ },
146
+ {
147
+ context: REQUEST_CONTEXT,
148
+ errorMessage: "Watch a rewarded ad before logging progress."
149
+ }
150
+ ),
151
+ (error) => {
152
+ assert.equal(error.statusCode, 423);
153
+ assert.equal(error.code, "rewarded_unlock_required");
154
+ assert.equal(error.message, "Watch a rewarded ad before logging progress.");
155
+ assert.equal(error.details.rewardedGate, gateState);
156
+ return true;
157
+ }
158
+ );
159
+ });
160
+
161
+ test("requireRewardedUnlock rejects cooldown and daily-limit states", async () => {
162
+ const cooldownService = createRewardedService({
163
+ gateKey: "progress-logging",
164
+ workspaceSlug: "alpha",
165
+ surface: "app",
166
+ enabled: true,
167
+ available: false,
168
+ blocked: false,
169
+ reason: "cooldown-active",
170
+ unlock: null
171
+ });
172
+ const dailyLimitService = createRewardedService({
173
+ gateKey: "progress-logging",
174
+ workspaceSlug: "alpha",
175
+ surface: "app",
176
+ enabled: true,
177
+ available: false,
178
+ blocked: false,
179
+ reason: "daily-limit-reached",
180
+ unlock: null
181
+ });
182
+
183
+ await assert.rejects(
184
+ () => requireRewardedUnlock(
185
+ cooldownService,
186
+ {
187
+ gateKey: "progress-logging",
188
+ workspaceSlug: "alpha"
189
+ },
190
+ {
191
+ context: REQUEST_CONTEXT
192
+ }
193
+ ),
194
+ (error) => {
195
+ assert.equal(error.statusCode, 423);
196
+ assert.equal(error.code, "rewarded_cooldown_active");
197
+ return true;
198
+ }
199
+ );
200
+
201
+ await assert.rejects(
202
+ () => requireRewardedUnlock(
203
+ dailyLimitService,
204
+ {
205
+ gateKey: "progress-logging",
206
+ workspaceSlug: "alpha"
207
+ },
208
+ {
209
+ context: REQUEST_CONTEXT
210
+ }
211
+ ),
212
+ (error) => {
213
+ assert.equal(error.statusCode, 423);
214
+ assert.equal(error.code, "rewarded_daily_limit_reached");
215
+ return true;
216
+ }
217
+ );
218
+ });
219
+
220
+ test("requireRewardedUnlock fails closed when the rewarded service returns an invalid gate state", async () => {
221
+ const rewardedService = createRewardedService({
222
+ gateKey: "progress-logging",
223
+ workspaceSlug: "alpha"
224
+ });
225
+
226
+ await assert.rejects(
227
+ () => requireRewardedUnlock(
228
+ rewardedService,
229
+ {
230
+ gateKey: "progress-logging",
231
+ workspaceSlug: "alpha"
232
+ },
233
+ {
234
+ context: REQUEST_CONTEXT
235
+ }
236
+ ),
237
+ (error) => {
238
+ assert.equal(error.statusCode, 503);
239
+ assert.equal(error.code, "rewarded_gate_state_invalid");
240
+ assert.deepEqual(error.details.rewardedGate, {
241
+ gateKey: "progress-logging",
242
+ workspaceSlug: "alpha"
243
+ });
244
+ return true;
245
+ }
246
+ );
247
+ });
248
+
249
+ test("requireRewardedUnlock fails closed when a non-blocking gate state has no explicit reason", async () => {
250
+ const rewardedService = createRewardedService({
251
+ gateKey: "progress-logging",
252
+ workspaceSlug: "alpha",
253
+ enabled: false,
254
+ blocked: false
255
+ });
256
+
257
+ await assert.rejects(
258
+ () => requireRewardedUnlock(
259
+ rewardedService,
260
+ {
261
+ gateKey: "progress-logging",
262
+ workspaceSlug: "alpha"
263
+ },
264
+ {
265
+ context: REQUEST_CONTEXT
266
+ }
267
+ ),
268
+ (error) => {
269
+ assert.equal(error.statusCode, 503);
270
+ assert.equal(error.code, "rewarded_gate_state_invalid");
271
+ return true;
272
+ }
273
+ );
274
+ });
@@ -0,0 +1,239 @@
1
+ import assert from "node:assert/strict";
2
+ import test from "node:test";
3
+
4
+ import { registerRoutes } from "../src/server/registerRoutes.js";
5
+
6
+ function createReplyDouble() {
7
+ return {
8
+ statusCode: 200,
9
+ payload: null,
10
+ code(value) {
11
+ this.statusCode = value;
12
+ return this;
13
+ },
14
+ send(value) {
15
+ this.payload = value;
16
+ return this;
17
+ }
18
+ };
19
+ }
20
+
21
+ function findRoute(routes, { method, path }) {
22
+ return routes.find((route) => route.method === method && route.path === path) || null;
23
+ }
24
+
25
+ function mountRoutes() {
26
+ const registeredRoutes = [];
27
+ const router = {
28
+ register(method, path, route, handler) {
29
+ registeredRoutes.push({
30
+ ...route,
31
+ method,
32
+ path,
33
+ handler
34
+ });
35
+ }
36
+ };
37
+
38
+ registerRoutes(router);
39
+ return registeredRoutes;
40
+ }
41
+
42
+ function createActionRequest({ input = {}, executeAction }) {
43
+ return {
44
+ input,
45
+ executeAction
46
+ };
47
+ }
48
+
49
+ test("google rewarded workflow routes mount with explicit request and response contracts", () => {
50
+ const routes = mountRoutes();
51
+ const currentRoute = findRoute(routes, {
52
+ method: "GET",
53
+ path: "/api/w/:workspaceSlug/rewarded/current"
54
+ });
55
+ const startRoute = findRoute(routes, {
56
+ method: "POST",
57
+ path: "/api/w/:workspaceSlug/rewarded/start"
58
+ });
59
+ const grantRoute = findRoute(routes, {
60
+ method: "POST",
61
+ path: "/api/w/:workspaceSlug/rewarded/grant"
62
+ });
63
+ const closeRoute = findRoute(routes, {
64
+ method: "POST",
65
+ path: "/api/w/:workspaceSlug/rewarded/close"
66
+ });
67
+
68
+ assert.equal(currentRoute?.auth, "required");
69
+ assert.equal(currentRoute?.visibility, "workspace_user");
70
+ assert.equal(currentRoute?.surface, "app");
71
+ assert.equal(typeof currentRoute?.query?.schema, "object");
72
+ assert.equal(currentRoute?.responses?.[200]?.transportSchema?.properties?.blocked?.["x-json-rest-schema"]?.castType, "boolean");
73
+
74
+ assert.equal(startRoute?.auth, "required");
75
+ assert.equal(startRoute?.visibility, "workspace_user");
76
+ assert.equal(typeof startRoute?.body?.schema, "object");
77
+ assert.equal(startRoute?.responses?.[200]?.transportSchema?.properties?.session?.anyOf?.[1]?.type, "null");
78
+
79
+ assert.equal(grantRoute?.responses?.[200]?.transportSchema?.properties?.unlocked?.["x-json-rest-schema"]?.castType, "boolean");
80
+ assert.equal(closeRoute?.responses?.[200]?.transportSchema?.properties?.closed?.["x-json-rest-schema"]?.castType, "boolean");
81
+ });
82
+
83
+ test("google rewarded workflow route handlers build action input from request.input", async () => {
84
+ const routes = mountRoutes();
85
+ const calls = [];
86
+ const executeAction = async (payload) => {
87
+ calls.push(payload);
88
+ if (payload.actionId === "rewarded.current.read") {
89
+ return {
90
+ gateKey: "progress-logging",
91
+ workspaceSlug: "alpha",
92
+ surface: "app",
93
+ enabled: true,
94
+ available: true,
95
+ blocked: true,
96
+ reason: "reward-required",
97
+ rule: null,
98
+ providerConfig: null,
99
+ unlock: null,
100
+ cooldownUntil: null,
101
+ dailyLimitRemaining: null
102
+ };
103
+ }
104
+ if (payload.actionId === "rewarded.start") {
105
+ return {
106
+ gateKey: "progress-logging",
107
+ workspaceSlug: "alpha",
108
+ surface: "app",
109
+ enabled: true,
110
+ available: true,
111
+ blocked: true,
112
+ reason: "reward-required",
113
+ rule: null,
114
+ providerConfig: null,
115
+ unlock: null,
116
+ cooldownUntil: null,
117
+ dailyLimitRemaining: null,
118
+ session: null
119
+ };
120
+ }
121
+ if (payload.actionId === "rewarded.grant") {
122
+ return {
123
+ unlocked: true,
124
+ workspaceSlug: "alpha",
125
+ gateKey: "progress-logging",
126
+ unlock: null,
127
+ session: null
128
+ };
129
+ }
130
+ return {
131
+ closed: true,
132
+ workspaceSlug: "alpha",
133
+ gateKey: "progress-logging",
134
+ session: null,
135
+ reason: null
136
+ };
137
+ };
138
+
139
+ await findRoute(routes, {
140
+ method: "GET",
141
+ path: "/api/w/:workspaceSlug/rewarded/current"
142
+ }).handler(
143
+ createActionRequest({
144
+ input: {
145
+ params: {
146
+ workspaceSlug: "Alpha"
147
+ },
148
+ query: {
149
+ gateKey: "progress-logging"
150
+ }
151
+ },
152
+ executeAction
153
+ }),
154
+ createReplyDouble()
155
+ );
156
+
157
+ await findRoute(routes, {
158
+ method: "POST",
159
+ path: "/api/w/:workspaceSlug/rewarded/start"
160
+ }).handler(
161
+ createActionRequest({
162
+ input: {
163
+ params: {
164
+ workspaceSlug: "Alpha"
165
+ },
166
+ body: {
167
+ gateKey: "progress-logging"
168
+ }
169
+ },
170
+ executeAction
171
+ }),
172
+ createReplyDouble()
173
+ );
174
+
175
+ await findRoute(routes, {
176
+ method: "POST",
177
+ path: "/api/w/:workspaceSlug/rewarded/grant"
178
+ }).handler(
179
+ createActionRequest({
180
+ input: {
181
+ params: {
182
+ workspaceSlug: "Alpha"
183
+ },
184
+ body: {
185
+ sessionId: "41"
186
+ }
187
+ },
188
+ executeAction
189
+ }),
190
+ createReplyDouble()
191
+ );
192
+
193
+ await findRoute(routes, {
194
+ method: "POST",
195
+ path: "/api/w/:workspaceSlug/rewarded/close"
196
+ }).handler(
197
+ createActionRequest({
198
+ input: {
199
+ params: {
200
+ workspaceSlug: "Alpha"
201
+ },
202
+ body: {
203
+ sessionId: "41"
204
+ }
205
+ },
206
+ executeAction
207
+ }),
208
+ createReplyDouble()
209
+ );
210
+
211
+ assert.deepEqual(calls[0], {
212
+ actionId: "rewarded.current.read",
213
+ input: {
214
+ workspaceSlug: "alpha",
215
+ gateKey: "progress-logging"
216
+ }
217
+ });
218
+ assert.deepEqual(calls[1], {
219
+ actionId: "rewarded.start",
220
+ input: {
221
+ workspaceSlug: "alpha",
222
+ gateKey: "progress-logging"
223
+ }
224
+ });
225
+ assert.deepEqual(calls[2], {
226
+ actionId: "rewarded.grant",
227
+ input: {
228
+ workspaceSlug: "alpha",
229
+ sessionId: "41"
230
+ }
231
+ });
232
+ assert.deepEqual(calls[3], {
233
+ actionId: "rewarded.close",
234
+ input: {
235
+ workspaceSlug: "alpha",
236
+ sessionId: "41"
237
+ }
238
+ });
239
+ });