@stardeck-customer-apps/testing 0.12.0 → 0.14.0
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.
- package/SKILL.md +58 -1
- package/dist/index.d.mts +24 -3
- package/dist/index.d.ts +24 -3
- package/dist/index.js +65 -3
- package/dist/index.mjs +65 -3
- package/dist/next/headers-shim.js +1 -0
- package/dist/next/headers-shim.mjs +1 -0
- package/dist/setup.js +40 -0
- package/dist/setup.mjs +40 -0
- package/package.json +1 -1
package/SKILL.md
CHANGED
|
@@ -193,6 +193,21 @@ await app.payments.deliverBeamEvent(beamPost, {
|
|
|
193
193
|
// charge-failure or expiry event for bolt payments.
|
|
194
194
|
// EXPIRED is derived from expiresAt on read, never stored.
|
|
195
195
|
|
|
196
|
+
// Fail exactly one real SDK call, then let the next poll recover normally.
|
|
197
|
+
app.payments.failNext("beam.getPaymentLinkStatus", {
|
|
198
|
+
status: 503,
|
|
199
|
+
message: "Beam is temporarily unavailable",
|
|
200
|
+
});
|
|
201
|
+
await expect(payments.getPaymentLinkStatus("plink_test_1")).rejects.toMatchObject({
|
|
202
|
+
statusCode: 503,
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
// Network failures reject fetch instead of returning a PaymentError response.
|
|
206
|
+
app.payments.failNext("stripe.getCheckoutSessionStatus", {
|
|
207
|
+
kind: "network",
|
|
208
|
+
message: "connection reset",
|
|
209
|
+
});
|
|
210
|
+
|
|
196
211
|
// File upload
|
|
197
212
|
const storage = new StorageClient();
|
|
198
213
|
await storage.upload(new File(["hi"], "note.txt", { type: "text/plain" }));
|
|
@@ -280,6 +295,32 @@ expect(app.edge.latestDisplay()?.action).toBe("show");
|
|
|
280
295
|
- `allowNetwork` — permit non-Stardeck outbound fetches (off by default).
|
|
281
296
|
- `app.reset()` — wipe + re-apply schema + re-seed + clear inbox/sessions.
|
|
282
297
|
Call in `beforeEach`.
|
|
298
|
+
- `app.reset({ mode: "data" })` — available in testing SDK **0.14.0+**. Faster
|
|
299
|
+
opt-in for stable schemas: truncate ordinary tables in `public`, restart their
|
|
300
|
+
owned serial/identity sequences, re-run the data-only seed, and clear the same
|
|
301
|
+
simulator/user/session state as the default. It preserves tables and other
|
|
302
|
+
schema objects instead of rebuilding them. The default remains full schema
|
|
303
|
+
reset; `app.reset({ mode: "schema" })` explicitly selects that default.
|
|
304
|
+
|
|
305
|
+
```ts
|
|
306
|
+
// Only after verifying this suite keeps its schema fixed and uses a data-only seed:
|
|
307
|
+
beforeEach(() => app.reset({ mode: "data" }));
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
Keep `app.reset()` for tests that change schema or depend on INSERTs in the
|
|
311
|
+
schema script. Also keep full reset for independent sequences, materialized
|
|
312
|
+
views, partitioned/foreign tables, temporary tables, and custom triggers or
|
|
313
|
+
cross-schema dependencies. Data mode does not restore those states or detect
|
|
314
|
+
eligibility; inspect both the tests and seed before opting in. All required
|
|
315
|
+
baseline rows must come from the data-only seed callback, not schema SQL.
|
|
316
|
+
Data mode refuses foreign keys requiring a cross-schema cascade. Database and
|
|
317
|
+
seed errors reject the reset; fix them before running the next test.
|
|
318
|
+
|
|
319
|
+
Do not use `it.concurrent` or `test.concurrent` with a shared test app: user,
|
|
320
|
+
database, and simulator state are shared within the file. Keep file isolation
|
|
321
|
+
enabled and let Vitest parallelize files. This option does not change workers
|
|
322
|
+
or the platform's test-run deadline.
|
|
323
|
+
|
|
283
324
|
- `app.asUser(partial?)` — sign in a test user; `getSession()` in app code
|
|
284
325
|
resolves to it. `app.signOut()` clears.
|
|
285
326
|
- `app.issueSession(partial?)` — mint access/refresh tokens for cookie-based
|
|
@@ -302,6 +343,8 @@ externalId })` helper models a trusted platform/channel link, and
|
|
|
302
343
|
identities through the integrations SDK.
|
|
303
344
|
- `app.payments` — checkouts created through payments-sdk:
|
|
304
345
|
`.checkouts`, `.latest()`, `.setProducts()`, `.markPaid(id)`,
|
|
346
|
+
`.setSessionStatus(id, status)`, `.setPaymentLinkStatus(id, status)`,
|
|
347
|
+
`.failNext(operation, failure)`,
|
|
305
348
|
`.deliverStripeEvent(handler, event)`, `.deliverBeamEvent(handler, event)`,
|
|
306
349
|
Bolt+ controls: `.approveBoltIntent(id)`, `.declineBoltIntent(id)`,
|
|
307
350
|
`.expireBoltIntent(id)`, `.listBoltIntents()`, `.getBoltIntent(id)`,
|
|
@@ -385,7 +428,21 @@ cursor, limit })`; `list()` intentionally keeps its previous array shape.
|
|
|
385
428
|
connections/intents/charges, product list) — captured in `app.payments`;
|
|
386
429
|
fulfill checkout via `markPaid` (poll) or `deliverStripeEvent` /
|
|
387
430
|
`deliverBeamEvent` (webhook push). Drive a terminal without hardware via
|
|
388
|
-
`approveBoltIntent` / `declineBoltIntent` / `expireBoltIntent`.
|
|
431
|
+
`approveBoltIntent` / `declineBoltIntent` / `expireBoltIntent`. Beam payment-link
|
|
432
|
+
polling returns the app's original `order.referenceId`; playground project
|
|
433
|
+
scoping is an internal control-plane detail and must not be handled in app code.
|
|
434
|
+
- Payment outcomes are controllable without bypassing the SDK: Stripe sessions
|
|
435
|
+
support `open`, `complete`, and `expired` with `paid`, `unpaid`, or
|
|
436
|
+
`no_payment_required`; Beam links support `ACTIVE`, `DISABLED`, `EXPIRED`,
|
|
437
|
+
`PAID`, `VOIDED`, and `REFUNDED`; Bolt+ supports approve, decline, expiry, and
|
|
438
|
+
cancellation through the real SDK call. Use `app.payments.failNext()` for
|
|
439
|
+
one-shot HTTP or network failures during Stripe checkout creation/status polling
|
|
440
|
+
and Beam payment-link creation/status polling. The operation names are
|
|
441
|
+
`stripe.createCheckout`, `stripe.getCheckoutSessionStatus`,
|
|
442
|
+
`beam.createPaymentLink`, and `beam.getPaymentLinkStatus`. HTTP failures support
|
|
443
|
+
statuses 400, 402, 409, 429, 500, and 503 plus optional `code`, `details`, and
|
|
444
|
+
`retryAfterSeconds`; `{ kind: "network", message }` rejects fetch instead.
|
|
445
|
+
`app.reset()` clears pending failures.
|
|
389
446
|
- `StorageClient` (upload/list/get/patch/delete, presigned upload) — captured
|
|
390
447
|
in `app.storage` against the simulated `STORAGE_URL` host.
|
|
391
448
|
- `client.slack` / `client.line` / `client.facebook` send endpoints — captured
|
package/dist/index.d.mts
CHANGED
|
@@ -138,6 +138,18 @@ interface CapturedCheckout {
|
|
|
138
138
|
metadata?: Record<string, string>;
|
|
139
139
|
createdAt: Date;
|
|
140
140
|
}
|
|
141
|
+
type PaymentSimulatorOperation = "stripe.createCheckout" | "stripe.getCheckoutSessionStatus" | "beam.createPaymentLink" | "beam.getPaymentLinkStatus";
|
|
142
|
+
type PaymentSimulatorFailure = {
|
|
143
|
+
kind?: "http";
|
|
144
|
+
status: 400 | 402 | 409 | 429 | 500 | 503;
|
|
145
|
+
message: string;
|
|
146
|
+
code?: string;
|
|
147
|
+
details?: unknown;
|
|
148
|
+
retryAfterSeconds?: number;
|
|
149
|
+
} | {
|
|
150
|
+
kind: "network";
|
|
151
|
+
message: string;
|
|
152
|
+
};
|
|
141
153
|
/** Simulated Bolt+ connection as returned by the control-plane store API. */
|
|
142
154
|
interface SimBoltConnection {
|
|
143
155
|
id: string;
|
|
@@ -189,6 +201,8 @@ interface TestPayments {
|
|
|
189
201
|
get checkouts(): CapturedCheckout[];
|
|
190
202
|
latest(): CapturedCheckout | undefined;
|
|
191
203
|
setProducts(products: _stardeck_customer_apps_payments_sdk.Product[]): void;
|
|
204
|
+
/** Fail the next matching SDK request once, then resume normal simulator behavior. */
|
|
205
|
+
failNext(operation: PaymentSimulatorOperation, failure: PaymentSimulatorFailure): void;
|
|
192
206
|
markPaid(id: string): void;
|
|
193
207
|
setSessionStatus(id: string, status: Partial<_stardeck_customer_apps_payments_sdk.CheckoutSessionStatus>): void;
|
|
194
208
|
setPaymentLinkStatus(id: string, status: _stardeck_customer_apps_payments_sdk.BeamPaymentLinkStatus): void;
|
|
@@ -506,8 +520,15 @@ interface TestApp {
|
|
|
506
520
|
* control-plane verify endpoint resolves these tokens back to the user.
|
|
507
521
|
*/
|
|
508
522
|
issueSession(user?: Partial<TestUser>): SessionTokens;
|
|
509
|
-
/**
|
|
510
|
-
|
|
523
|
+
/**
|
|
524
|
+
* Rebuild the schema (default), re-run the seed, and clear simulator/user state.
|
|
525
|
+
* `mode: "data"` instead truncates ordinary public tables and restarts owned
|
|
526
|
+
* sequences. Use only with a stable schema and data-only seed; it does not
|
|
527
|
+
* restore DDL changes, schema-script data, or independent sequences.
|
|
528
|
+
*/
|
|
529
|
+
reset(options?: {
|
|
530
|
+
mode: "schema" | "data";
|
|
531
|
+
}): Promise<void>;
|
|
511
532
|
/** Tear down the app and restore global fetch. */
|
|
512
533
|
close(): Promise<void>;
|
|
513
534
|
}
|
|
@@ -550,4 +571,4 @@ declare const TEST_ENV_DEFAULTS: {
|
|
|
550
571
|
/** Default location of the DDL snapshot written by `generate-types`. */
|
|
551
572
|
declare const DEFAULT_SCHEMA_PATH = "./src/generated/data-store-schema.sql";
|
|
552
573
|
|
|
553
|
-
export { type BindingInfo, CONTROL_PLANE_TEST_URL, type CallRouteOptions, type CapturedCheckout, type CapturedDisplay, type CapturedEmail, type CapturedIdentity, type CapturedIdentityLink, type CapturedMessage, type CapturedPrint, type CapturedTestPrint, type CapturedUpload, DATA_STORE_TEST_HOST, DATA_STORE_TEST_URL, DEFAULT_SCHEMA_PATH, type DeviceInfo, type PeripheralInfo, STORAGE_TEST_HOST, STORAGE_TEST_URL, type SessionTokens, type SimBoltCharge, type SimBoltConnection, type SimBoltIntentRecord, type SimBoltIntentStatus, TEST_ENV_DEFAULTS, type TestApp, type TestAppOptions, TestDataStoreInput, type TestDirectory, type TestEdge, type TestInbox, type TestMessages, type TestPayments, type TestStorage, type TestUser, WORKFLOW_NAME_PREFIX, callRoute, createTestApp, describeWorkflow, parseWorkflowName };
|
|
574
|
+
export { type BindingInfo, CONTROL_PLANE_TEST_URL, type CallRouteOptions, type CapturedCheckout, type CapturedDisplay, type CapturedEmail, type CapturedIdentity, type CapturedIdentityLink, type CapturedMessage, type CapturedPrint, type CapturedTestPrint, type CapturedUpload, DATA_STORE_TEST_HOST, DATA_STORE_TEST_URL, DEFAULT_SCHEMA_PATH, type DeviceInfo, type PaymentSimulatorFailure, type PaymentSimulatorOperation, type PeripheralInfo, STORAGE_TEST_HOST, STORAGE_TEST_URL, type SessionTokens, type SimBoltCharge, type SimBoltConnection, type SimBoltIntentRecord, type SimBoltIntentStatus, TEST_ENV_DEFAULTS, type TestApp, type TestAppOptions, TestDataStoreInput, type TestDirectory, type TestEdge, type TestInbox, type TestMessages, type TestPayments, type TestStorage, type TestUser, WORKFLOW_NAME_PREFIX, callRoute, createTestApp, describeWorkflow, parseWorkflowName };
|
package/dist/index.d.ts
CHANGED
|
@@ -138,6 +138,18 @@ interface CapturedCheckout {
|
|
|
138
138
|
metadata?: Record<string, string>;
|
|
139
139
|
createdAt: Date;
|
|
140
140
|
}
|
|
141
|
+
type PaymentSimulatorOperation = "stripe.createCheckout" | "stripe.getCheckoutSessionStatus" | "beam.createPaymentLink" | "beam.getPaymentLinkStatus";
|
|
142
|
+
type PaymentSimulatorFailure = {
|
|
143
|
+
kind?: "http";
|
|
144
|
+
status: 400 | 402 | 409 | 429 | 500 | 503;
|
|
145
|
+
message: string;
|
|
146
|
+
code?: string;
|
|
147
|
+
details?: unknown;
|
|
148
|
+
retryAfterSeconds?: number;
|
|
149
|
+
} | {
|
|
150
|
+
kind: "network";
|
|
151
|
+
message: string;
|
|
152
|
+
};
|
|
141
153
|
/** Simulated Bolt+ connection as returned by the control-plane store API. */
|
|
142
154
|
interface SimBoltConnection {
|
|
143
155
|
id: string;
|
|
@@ -189,6 +201,8 @@ interface TestPayments {
|
|
|
189
201
|
get checkouts(): CapturedCheckout[];
|
|
190
202
|
latest(): CapturedCheckout | undefined;
|
|
191
203
|
setProducts(products: _stardeck_customer_apps_payments_sdk.Product[]): void;
|
|
204
|
+
/** Fail the next matching SDK request once, then resume normal simulator behavior. */
|
|
205
|
+
failNext(operation: PaymentSimulatorOperation, failure: PaymentSimulatorFailure): void;
|
|
192
206
|
markPaid(id: string): void;
|
|
193
207
|
setSessionStatus(id: string, status: Partial<_stardeck_customer_apps_payments_sdk.CheckoutSessionStatus>): void;
|
|
194
208
|
setPaymentLinkStatus(id: string, status: _stardeck_customer_apps_payments_sdk.BeamPaymentLinkStatus): void;
|
|
@@ -506,8 +520,15 @@ interface TestApp {
|
|
|
506
520
|
* control-plane verify endpoint resolves these tokens back to the user.
|
|
507
521
|
*/
|
|
508
522
|
issueSession(user?: Partial<TestUser>): SessionTokens;
|
|
509
|
-
/**
|
|
510
|
-
|
|
523
|
+
/**
|
|
524
|
+
* Rebuild the schema (default), re-run the seed, and clear simulator/user state.
|
|
525
|
+
* `mode: "data"` instead truncates ordinary public tables and restarts owned
|
|
526
|
+
* sequences. Use only with a stable schema and data-only seed; it does not
|
|
527
|
+
* restore DDL changes, schema-script data, or independent sequences.
|
|
528
|
+
*/
|
|
529
|
+
reset(options?: {
|
|
530
|
+
mode: "schema" | "data";
|
|
531
|
+
}): Promise<void>;
|
|
511
532
|
/** Tear down the app and restore global fetch. */
|
|
512
533
|
close(): Promise<void>;
|
|
513
534
|
}
|
|
@@ -550,4 +571,4 @@ declare const TEST_ENV_DEFAULTS: {
|
|
|
550
571
|
/** Default location of the DDL snapshot written by `generate-types`. */
|
|
551
572
|
declare const DEFAULT_SCHEMA_PATH = "./src/generated/data-store-schema.sql";
|
|
552
573
|
|
|
553
|
-
export { type BindingInfo, CONTROL_PLANE_TEST_URL, type CallRouteOptions, type CapturedCheckout, type CapturedDisplay, type CapturedEmail, type CapturedIdentity, type CapturedIdentityLink, type CapturedMessage, type CapturedPrint, type CapturedTestPrint, type CapturedUpload, DATA_STORE_TEST_HOST, DATA_STORE_TEST_URL, DEFAULT_SCHEMA_PATH, type DeviceInfo, type PeripheralInfo, STORAGE_TEST_HOST, STORAGE_TEST_URL, type SessionTokens, type SimBoltCharge, type SimBoltConnection, type SimBoltIntentRecord, type SimBoltIntentStatus, TEST_ENV_DEFAULTS, type TestApp, type TestAppOptions, TestDataStoreInput, type TestDirectory, type TestEdge, type TestInbox, type TestMessages, type TestPayments, type TestStorage, type TestUser, WORKFLOW_NAME_PREFIX, callRoute, createTestApp, describeWorkflow, parseWorkflowName };
|
|
574
|
+
export { type BindingInfo, CONTROL_PLANE_TEST_URL, type CallRouteOptions, type CapturedCheckout, type CapturedDisplay, type CapturedEmail, type CapturedIdentity, type CapturedIdentityLink, type CapturedMessage, type CapturedPrint, type CapturedTestPrint, type CapturedUpload, DATA_STORE_TEST_HOST, DATA_STORE_TEST_URL, DEFAULT_SCHEMA_PATH, type DeviceInfo, type PaymentSimulatorFailure, type PaymentSimulatorOperation, type PeripheralInfo, STORAGE_TEST_HOST, STORAGE_TEST_URL, type SessionTokens, type SimBoltCharge, type SimBoltConnection, type SimBoltIntentRecord, type SimBoltIntentStatus, TEST_ENV_DEFAULTS, type TestApp, type TestAppOptions, TestDataStoreInput, type TestDirectory, type TestEdge, type TestInbox, type TestMessages, type TestPayments, type TestStorage, type TestUser, WORKFLOW_NAME_PREFIX, callRoute, createTestApp, describeWorkflow, parseWorkflowName };
|
package/dist/index.js
CHANGED
|
@@ -73,6 +73,19 @@ async function applySchema(db, schemaSql) {
|
|
|
73
73
|
async function dropAllTables(db) {
|
|
74
74
|
await db.exec(`DROP SCHEMA public CASCADE; CREATE SCHEMA public;`);
|
|
75
75
|
}
|
|
76
|
+
async function listPublicTables(db) {
|
|
77
|
+
const { rows } = await db.query(`
|
|
78
|
+
SELECT format('%I.%I', n.nspname, c.relname) AS name
|
|
79
|
+
FROM pg_class c JOIN pg_namespace n ON n.oid = c.relnamespace
|
|
80
|
+
WHERE n.nspname = 'public' AND c.relkind = 'r'
|
|
81
|
+
ORDER BY c.relname
|
|
82
|
+
`);
|
|
83
|
+
return rows.map(({ name }) => name);
|
|
84
|
+
}
|
|
85
|
+
async function truncateAllTables(db) {
|
|
86
|
+
const tables = await listPublicTables(db);
|
|
87
|
+
if (tables.length) await db.exec(`TRUNCATE TABLE ${tables.join(", ")} RESTART IDENTITY`);
|
|
88
|
+
}
|
|
76
89
|
var identityParsers = null;
|
|
77
90
|
function getIdentityParsers() {
|
|
78
91
|
if (!identityParsers) {
|
|
@@ -108,6 +121,7 @@ var state = globalSingleton("state", () => ({
|
|
|
108
121
|
checkoutCounter: 0,
|
|
109
122
|
sessionStatuses: /* @__PURE__ */ new Map(),
|
|
110
123
|
paymentLinks: /* @__PURE__ */ new Map(),
|
|
124
|
+
paymentFailures: [],
|
|
111
125
|
products: [],
|
|
112
126
|
boltConnections: /* @__PURE__ */ new Map(),
|
|
113
127
|
boltUsedPairingCodes: /* @__PURE__ */ new Set(),
|
|
@@ -1274,6 +1288,43 @@ function payErr(error, status = 400, code, details) {
|
|
|
1274
1288
|
if (details !== void 0) body.details = details;
|
|
1275
1289
|
return json(body, status);
|
|
1276
1290
|
}
|
|
1291
|
+
function paymentOperationForRequest(request, pathname) {
|
|
1292
|
+
if (request.method === "POST" && /^\/api\/store\/beam\/[^/]+\/payment-links$/.test(pathname)) {
|
|
1293
|
+
return "beam.createPaymentLink";
|
|
1294
|
+
}
|
|
1295
|
+
if (request.method === "GET" && /^\/api\/store\/beam\/[^/]+\/payment-links\/[^/]+$/.test(pathname)) {
|
|
1296
|
+
return "beam.getPaymentLinkStatus";
|
|
1297
|
+
}
|
|
1298
|
+
if (request.method === "POST" && /^\/api\/store\/(?!beam\/)[^/]+\/checkout$/.test(pathname)) {
|
|
1299
|
+
return "stripe.createCheckout";
|
|
1300
|
+
}
|
|
1301
|
+
if (request.method === "GET" && /^\/api\/store\/(?!beam\/)[^/]+\/checkout-sessions\/[^/]+$/.test(pathname)) {
|
|
1302
|
+
return "stripe.getCheckoutSessionStatus";
|
|
1303
|
+
}
|
|
1304
|
+
return null;
|
|
1305
|
+
}
|
|
1306
|
+
function consumePaymentFailure(request, pathname) {
|
|
1307
|
+
const operation = paymentOperationForRequest(request, pathname);
|
|
1308
|
+
if (!operation) return null;
|
|
1309
|
+
const index = state.paymentFailures.findIndex((entry) => entry.operation === operation);
|
|
1310
|
+
if (index === -1) return null;
|
|
1311
|
+
const [{ failure: failure2 }] = state.paymentFailures.splice(index, 1);
|
|
1312
|
+
if (failure2.kind === "network") {
|
|
1313
|
+
throw new TypeError(failure2.message);
|
|
1314
|
+
}
|
|
1315
|
+
const headers = new Headers({ "Content-Type": "application/json" });
|
|
1316
|
+
if (failure2.retryAfterSeconds !== void 0) {
|
|
1317
|
+
headers.set("Retry-After", String(failure2.retryAfterSeconds));
|
|
1318
|
+
}
|
|
1319
|
+
return new Response(
|
|
1320
|
+
JSON.stringify({
|
|
1321
|
+
error: failure2.message,
|
|
1322
|
+
code: failure2.code ?? "REQUEST_ERROR",
|
|
1323
|
+
...failure2.details !== void 0 ? { details: failure2.details } : {}
|
|
1324
|
+
}),
|
|
1325
|
+
{ status: failure2.status, headers }
|
|
1326
|
+
);
|
|
1327
|
+
}
|
|
1277
1328
|
function isBoltPaymentMethod(value) {
|
|
1278
1329
|
return typeof value === "string" && BEAM_BOLT_PAYMENT_METHODS.includes(value);
|
|
1279
1330
|
}
|
|
@@ -1417,6 +1468,8 @@ async function deliverEvent(envelope, handler, options) {
|
|
|
1417
1468
|
}
|
|
1418
1469
|
async function handlePaymentsRequest(request, url) {
|
|
1419
1470
|
const pathname = url.pathname;
|
|
1471
|
+
const injectedFailure = consumePaymentFailure(request, pathname);
|
|
1472
|
+
if (injectedFailure) return injectedFailure;
|
|
1420
1473
|
if (/\/billing-portal$/.test(pathname)) {
|
|
1421
1474
|
return payErr("Not found", 404, "NOT_FOUND");
|
|
1422
1475
|
}
|
|
@@ -1711,6 +1764,9 @@ function createPayments() {
|
|
|
1711
1764
|
setProducts(products) {
|
|
1712
1765
|
state.products = products;
|
|
1713
1766
|
},
|
|
1767
|
+
failNext(operation, failure2) {
|
|
1768
|
+
state.paymentFailures.push({ operation, failure: failure2 });
|
|
1769
|
+
},
|
|
1714
1770
|
markPaid(id) {
|
|
1715
1771
|
const session = state.sessionStatuses.get(id);
|
|
1716
1772
|
if (session) {
|
|
@@ -1820,6 +1876,7 @@ function createPayments() {
|
|
|
1820
1876
|
state.checkoutCounter = 0;
|
|
1821
1877
|
state.sessionStatuses.clear();
|
|
1822
1878
|
state.paymentLinks.clear();
|
|
1879
|
+
state.paymentFailures = [];
|
|
1823
1880
|
state.products = [];
|
|
1824
1881
|
state.boltConnections.clear();
|
|
1825
1882
|
state.boltUsedPairingCodes.clear();
|
|
@@ -2797,9 +2854,14 @@ async function createTestApp(options = {}) {
|
|
|
2797
2854
|
state.refreshSessions.set(tokens.refreshToken, fullUser);
|
|
2798
2855
|
return tokens;
|
|
2799
2856
|
},
|
|
2800
|
-
async reset() {
|
|
2801
|
-
|
|
2802
|
-
|
|
2857
|
+
async reset(resetOptions) {
|
|
2858
|
+
if (resetOptions?.mode === "data") {
|
|
2859
|
+
await truncateAllTables(db);
|
|
2860
|
+
if (options.seed) await options.seed(db);
|
|
2861
|
+
} else {
|
|
2862
|
+
await dropAllTables(db);
|
|
2863
|
+
await initialize();
|
|
2864
|
+
}
|
|
2803
2865
|
state.currentUser = null;
|
|
2804
2866
|
state.sessions.clear();
|
|
2805
2867
|
state.refreshSessions.clear();
|
package/dist/index.mjs
CHANGED
|
@@ -24,6 +24,19 @@ async function applySchema(db, schemaSql) {
|
|
|
24
24
|
async function dropAllTables(db) {
|
|
25
25
|
await db.exec(`DROP SCHEMA public CASCADE; CREATE SCHEMA public;`);
|
|
26
26
|
}
|
|
27
|
+
async function listPublicTables(db) {
|
|
28
|
+
const { rows } = await db.query(`
|
|
29
|
+
SELECT format('%I.%I', n.nspname, c.relname) AS name
|
|
30
|
+
FROM pg_class c JOIN pg_namespace n ON n.oid = c.relnamespace
|
|
31
|
+
WHERE n.nspname = 'public' AND c.relkind = 'r'
|
|
32
|
+
ORDER BY c.relname
|
|
33
|
+
`);
|
|
34
|
+
return rows.map(({ name }) => name);
|
|
35
|
+
}
|
|
36
|
+
async function truncateAllTables(db) {
|
|
37
|
+
const tables = await listPublicTables(db);
|
|
38
|
+
if (tables.length) await db.exec(`TRUNCATE TABLE ${tables.join(", ")} RESTART IDENTITY`);
|
|
39
|
+
}
|
|
27
40
|
var identityParsers = null;
|
|
28
41
|
function getIdentityParsers() {
|
|
29
42
|
if (!identityParsers) {
|
|
@@ -59,6 +72,7 @@ var state = globalSingleton("state", () => ({
|
|
|
59
72
|
checkoutCounter: 0,
|
|
60
73
|
sessionStatuses: /* @__PURE__ */ new Map(),
|
|
61
74
|
paymentLinks: /* @__PURE__ */ new Map(),
|
|
75
|
+
paymentFailures: [],
|
|
62
76
|
products: [],
|
|
63
77
|
boltConnections: /* @__PURE__ */ new Map(),
|
|
64
78
|
boltUsedPairingCodes: /* @__PURE__ */ new Set(),
|
|
@@ -1225,6 +1239,43 @@ function payErr(error, status = 400, code, details) {
|
|
|
1225
1239
|
if (details !== void 0) body.details = details;
|
|
1226
1240
|
return json(body, status);
|
|
1227
1241
|
}
|
|
1242
|
+
function paymentOperationForRequest(request, pathname) {
|
|
1243
|
+
if (request.method === "POST" && /^\/api\/store\/beam\/[^/]+\/payment-links$/.test(pathname)) {
|
|
1244
|
+
return "beam.createPaymentLink";
|
|
1245
|
+
}
|
|
1246
|
+
if (request.method === "GET" && /^\/api\/store\/beam\/[^/]+\/payment-links\/[^/]+$/.test(pathname)) {
|
|
1247
|
+
return "beam.getPaymentLinkStatus";
|
|
1248
|
+
}
|
|
1249
|
+
if (request.method === "POST" && /^\/api\/store\/(?!beam\/)[^/]+\/checkout$/.test(pathname)) {
|
|
1250
|
+
return "stripe.createCheckout";
|
|
1251
|
+
}
|
|
1252
|
+
if (request.method === "GET" && /^\/api\/store\/(?!beam\/)[^/]+\/checkout-sessions\/[^/]+$/.test(pathname)) {
|
|
1253
|
+
return "stripe.getCheckoutSessionStatus";
|
|
1254
|
+
}
|
|
1255
|
+
return null;
|
|
1256
|
+
}
|
|
1257
|
+
function consumePaymentFailure(request, pathname) {
|
|
1258
|
+
const operation = paymentOperationForRequest(request, pathname);
|
|
1259
|
+
if (!operation) return null;
|
|
1260
|
+
const index = state.paymentFailures.findIndex((entry) => entry.operation === operation);
|
|
1261
|
+
if (index === -1) return null;
|
|
1262
|
+
const [{ failure: failure2 }] = state.paymentFailures.splice(index, 1);
|
|
1263
|
+
if (failure2.kind === "network") {
|
|
1264
|
+
throw new TypeError(failure2.message);
|
|
1265
|
+
}
|
|
1266
|
+
const headers = new Headers({ "Content-Type": "application/json" });
|
|
1267
|
+
if (failure2.retryAfterSeconds !== void 0) {
|
|
1268
|
+
headers.set("Retry-After", String(failure2.retryAfterSeconds));
|
|
1269
|
+
}
|
|
1270
|
+
return new Response(
|
|
1271
|
+
JSON.stringify({
|
|
1272
|
+
error: failure2.message,
|
|
1273
|
+
code: failure2.code ?? "REQUEST_ERROR",
|
|
1274
|
+
...failure2.details !== void 0 ? { details: failure2.details } : {}
|
|
1275
|
+
}),
|
|
1276
|
+
{ status: failure2.status, headers }
|
|
1277
|
+
);
|
|
1278
|
+
}
|
|
1228
1279
|
function isBoltPaymentMethod(value) {
|
|
1229
1280
|
return typeof value === "string" && BEAM_BOLT_PAYMENT_METHODS.includes(value);
|
|
1230
1281
|
}
|
|
@@ -1368,6 +1419,8 @@ async function deliverEvent(envelope, handler, options) {
|
|
|
1368
1419
|
}
|
|
1369
1420
|
async function handlePaymentsRequest(request, url) {
|
|
1370
1421
|
const pathname = url.pathname;
|
|
1422
|
+
const injectedFailure = consumePaymentFailure(request, pathname);
|
|
1423
|
+
if (injectedFailure) return injectedFailure;
|
|
1371
1424
|
if (/\/billing-portal$/.test(pathname)) {
|
|
1372
1425
|
return payErr("Not found", 404, "NOT_FOUND");
|
|
1373
1426
|
}
|
|
@@ -1662,6 +1715,9 @@ function createPayments() {
|
|
|
1662
1715
|
setProducts(products) {
|
|
1663
1716
|
state.products = products;
|
|
1664
1717
|
},
|
|
1718
|
+
failNext(operation, failure2) {
|
|
1719
|
+
state.paymentFailures.push({ operation, failure: failure2 });
|
|
1720
|
+
},
|
|
1665
1721
|
markPaid(id) {
|
|
1666
1722
|
const session = state.sessionStatuses.get(id);
|
|
1667
1723
|
if (session) {
|
|
@@ -1771,6 +1827,7 @@ function createPayments() {
|
|
|
1771
1827
|
state.checkoutCounter = 0;
|
|
1772
1828
|
state.sessionStatuses.clear();
|
|
1773
1829
|
state.paymentLinks.clear();
|
|
1830
|
+
state.paymentFailures = [];
|
|
1774
1831
|
state.products = [];
|
|
1775
1832
|
state.boltConnections.clear();
|
|
1776
1833
|
state.boltUsedPairingCodes.clear();
|
|
@@ -2748,9 +2805,14 @@ async function createTestApp(options = {}) {
|
|
|
2748
2805
|
state.refreshSessions.set(tokens.refreshToken, fullUser);
|
|
2749
2806
|
return tokens;
|
|
2750
2807
|
},
|
|
2751
|
-
async reset() {
|
|
2752
|
-
|
|
2753
|
-
|
|
2808
|
+
async reset(resetOptions) {
|
|
2809
|
+
if (resetOptions?.mode === "data") {
|
|
2810
|
+
await truncateAllTables(db);
|
|
2811
|
+
if (options.seed) await options.seed(db);
|
|
2812
|
+
} else {
|
|
2813
|
+
await dropAllTables(db);
|
|
2814
|
+
await initialize();
|
|
2815
|
+
}
|
|
2754
2816
|
state.currentUser = null;
|
|
2755
2817
|
state.sessions.clear();
|
|
2756
2818
|
state.refreshSessions.clear();
|
|
@@ -51,6 +51,7 @@ var state = globalSingleton("state", () => ({
|
|
|
51
51
|
checkoutCounter: 0,
|
|
52
52
|
sessionStatuses: /* @__PURE__ */ new Map(),
|
|
53
53
|
paymentLinks: /* @__PURE__ */ new Map(),
|
|
54
|
+
paymentFailures: [],
|
|
54
55
|
products: [],
|
|
55
56
|
boltConnections: /* @__PURE__ */ new Map(),
|
|
56
57
|
boltUsedPairingCodes: /* @__PURE__ */ new Set(),
|
|
@@ -24,6 +24,7 @@ var state = globalSingleton("state", () => ({
|
|
|
24
24
|
checkoutCounter: 0,
|
|
25
25
|
sessionStatuses: /* @__PURE__ */ new Map(),
|
|
26
26
|
paymentLinks: /* @__PURE__ */ new Map(),
|
|
27
|
+
paymentFailures: [],
|
|
27
28
|
products: [],
|
|
28
29
|
boltConnections: /* @__PURE__ */ new Map(),
|
|
29
30
|
boltUsedPairingCodes: /* @__PURE__ */ new Set(),
|
package/dist/setup.js
CHANGED
|
@@ -45,6 +45,7 @@ var state = globalSingleton("state", () => ({
|
|
|
45
45
|
checkoutCounter: 0,
|
|
46
46
|
sessionStatuses: /* @__PURE__ */ new Map(),
|
|
47
47
|
paymentLinks: /* @__PURE__ */ new Map(),
|
|
48
|
+
paymentFailures: [],
|
|
48
49
|
products: [],
|
|
49
50
|
boltConnections: /* @__PURE__ */ new Map(),
|
|
50
51
|
boltUsedPairingCodes: /* @__PURE__ */ new Set(),
|
|
@@ -1012,6 +1013,43 @@ function payErr(error, status = 400, code, details) {
|
|
|
1012
1013
|
if (details !== void 0) body.details = details;
|
|
1013
1014
|
return json(body, status);
|
|
1014
1015
|
}
|
|
1016
|
+
function paymentOperationForRequest(request, pathname) {
|
|
1017
|
+
if (request.method === "POST" && /^\/api\/store\/beam\/[^/]+\/payment-links$/.test(pathname)) {
|
|
1018
|
+
return "beam.createPaymentLink";
|
|
1019
|
+
}
|
|
1020
|
+
if (request.method === "GET" && /^\/api\/store\/beam\/[^/]+\/payment-links\/[^/]+$/.test(pathname)) {
|
|
1021
|
+
return "beam.getPaymentLinkStatus";
|
|
1022
|
+
}
|
|
1023
|
+
if (request.method === "POST" && /^\/api\/store\/(?!beam\/)[^/]+\/checkout$/.test(pathname)) {
|
|
1024
|
+
return "stripe.createCheckout";
|
|
1025
|
+
}
|
|
1026
|
+
if (request.method === "GET" && /^\/api\/store\/(?!beam\/)[^/]+\/checkout-sessions\/[^/]+$/.test(pathname)) {
|
|
1027
|
+
return "stripe.getCheckoutSessionStatus";
|
|
1028
|
+
}
|
|
1029
|
+
return null;
|
|
1030
|
+
}
|
|
1031
|
+
function consumePaymentFailure(request, pathname) {
|
|
1032
|
+
const operation = paymentOperationForRequest(request, pathname);
|
|
1033
|
+
if (!operation) return null;
|
|
1034
|
+
const index = state.paymentFailures.findIndex((entry) => entry.operation === operation);
|
|
1035
|
+
if (index === -1) return null;
|
|
1036
|
+
const [{ failure: failure2 }] = state.paymentFailures.splice(index, 1);
|
|
1037
|
+
if (failure2.kind === "network") {
|
|
1038
|
+
throw new TypeError(failure2.message);
|
|
1039
|
+
}
|
|
1040
|
+
const headers = new Headers({ "Content-Type": "application/json" });
|
|
1041
|
+
if (failure2.retryAfterSeconds !== void 0) {
|
|
1042
|
+
headers.set("Retry-After", String(failure2.retryAfterSeconds));
|
|
1043
|
+
}
|
|
1044
|
+
return new Response(
|
|
1045
|
+
JSON.stringify({
|
|
1046
|
+
error: failure2.message,
|
|
1047
|
+
code: failure2.code ?? "REQUEST_ERROR",
|
|
1048
|
+
...failure2.details !== void 0 ? { details: failure2.details } : {}
|
|
1049
|
+
}),
|
|
1050
|
+
{ status: failure2.status, headers }
|
|
1051
|
+
);
|
|
1052
|
+
}
|
|
1015
1053
|
function isBoltPaymentMethod(value) {
|
|
1016
1054
|
return typeof value === "string" && BEAM_BOLT_PAYMENT_METHODS.includes(value);
|
|
1017
1055
|
}
|
|
@@ -1120,6 +1158,8 @@ function storedStatusesForFilter(statuses) {
|
|
|
1120
1158
|
}
|
|
1121
1159
|
async function handlePaymentsRequest(request, url) {
|
|
1122
1160
|
const pathname = url.pathname;
|
|
1161
|
+
const injectedFailure = consumePaymentFailure(request, pathname);
|
|
1162
|
+
if (injectedFailure) return injectedFailure;
|
|
1123
1163
|
if (/\/billing-portal$/.test(pathname)) {
|
|
1124
1164
|
return payErr("Not found", 404, "NOT_FOUND");
|
|
1125
1165
|
}
|
package/dist/setup.mjs
CHANGED
|
@@ -21,6 +21,7 @@ var state = globalSingleton("state", () => ({
|
|
|
21
21
|
checkoutCounter: 0,
|
|
22
22
|
sessionStatuses: /* @__PURE__ */ new Map(),
|
|
23
23
|
paymentLinks: /* @__PURE__ */ new Map(),
|
|
24
|
+
paymentFailures: [],
|
|
24
25
|
products: [],
|
|
25
26
|
boltConnections: /* @__PURE__ */ new Map(),
|
|
26
27
|
boltUsedPairingCodes: /* @__PURE__ */ new Set(),
|
|
@@ -988,6 +989,43 @@ function payErr(error, status = 400, code, details) {
|
|
|
988
989
|
if (details !== void 0) body.details = details;
|
|
989
990
|
return json(body, status);
|
|
990
991
|
}
|
|
992
|
+
function paymentOperationForRequest(request, pathname) {
|
|
993
|
+
if (request.method === "POST" && /^\/api\/store\/beam\/[^/]+\/payment-links$/.test(pathname)) {
|
|
994
|
+
return "beam.createPaymentLink";
|
|
995
|
+
}
|
|
996
|
+
if (request.method === "GET" && /^\/api\/store\/beam\/[^/]+\/payment-links\/[^/]+$/.test(pathname)) {
|
|
997
|
+
return "beam.getPaymentLinkStatus";
|
|
998
|
+
}
|
|
999
|
+
if (request.method === "POST" && /^\/api\/store\/(?!beam\/)[^/]+\/checkout$/.test(pathname)) {
|
|
1000
|
+
return "stripe.createCheckout";
|
|
1001
|
+
}
|
|
1002
|
+
if (request.method === "GET" && /^\/api\/store\/(?!beam\/)[^/]+\/checkout-sessions\/[^/]+$/.test(pathname)) {
|
|
1003
|
+
return "stripe.getCheckoutSessionStatus";
|
|
1004
|
+
}
|
|
1005
|
+
return null;
|
|
1006
|
+
}
|
|
1007
|
+
function consumePaymentFailure(request, pathname) {
|
|
1008
|
+
const operation = paymentOperationForRequest(request, pathname);
|
|
1009
|
+
if (!operation) return null;
|
|
1010
|
+
const index = state.paymentFailures.findIndex((entry) => entry.operation === operation);
|
|
1011
|
+
if (index === -1) return null;
|
|
1012
|
+
const [{ failure: failure2 }] = state.paymentFailures.splice(index, 1);
|
|
1013
|
+
if (failure2.kind === "network") {
|
|
1014
|
+
throw new TypeError(failure2.message);
|
|
1015
|
+
}
|
|
1016
|
+
const headers = new Headers({ "Content-Type": "application/json" });
|
|
1017
|
+
if (failure2.retryAfterSeconds !== void 0) {
|
|
1018
|
+
headers.set("Retry-After", String(failure2.retryAfterSeconds));
|
|
1019
|
+
}
|
|
1020
|
+
return new Response(
|
|
1021
|
+
JSON.stringify({
|
|
1022
|
+
error: failure2.message,
|
|
1023
|
+
code: failure2.code ?? "REQUEST_ERROR",
|
|
1024
|
+
...failure2.details !== void 0 ? { details: failure2.details } : {}
|
|
1025
|
+
}),
|
|
1026
|
+
{ status: failure2.status, headers }
|
|
1027
|
+
);
|
|
1028
|
+
}
|
|
991
1029
|
function isBoltPaymentMethod(value) {
|
|
992
1030
|
return typeof value === "string" && BEAM_BOLT_PAYMENT_METHODS.includes(value);
|
|
993
1031
|
}
|
|
@@ -1096,6 +1134,8 @@ function storedStatusesForFilter(statuses) {
|
|
|
1096
1134
|
}
|
|
1097
1135
|
async function handlePaymentsRequest(request, url) {
|
|
1098
1136
|
const pathname = url.pathname;
|
|
1137
|
+
const injectedFailure = consumePaymentFailure(request, pathname);
|
|
1138
|
+
if (injectedFailure) return injectedFailure;
|
|
1099
1139
|
if (/\/billing-portal$/.test(pathname)) {
|
|
1100
1140
|
return payErr("Not found", 404, "NOT_FOUND");
|
|
1101
1141
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stardeck-customer-apps/testing",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.14.0",
|
|
4
4
|
"description": "Vitest test harness for Stardeck customer apps — in-process Postgres (PGlite) plus a control-plane simulator so the real Stardeck SDKs run unmodified in tests",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"module": "dist/index.mjs",
|