@stardeck-customer-apps/testing 0.21.0 → 0.23.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 +55 -4
- package/dist/index.d.mts +51 -1
- package/dist/index.d.ts +51 -1
- package/dist/index.js +372 -2
- package/dist/index.mjs +372 -2
- package/dist/next/headers-shim.js +13 -5
- package/dist/next/headers-shim.mjs +13 -5
- package/dist/setup.js +310 -2
- package/dist/setup.mjs +310 -2
- package/package.json +2 -2
package/SKILL.md
CHANGED
|
@@ -218,6 +218,13 @@ const integrations = createIntegrationsClient();
|
|
|
218
218
|
await integrations.line.push("U123", { type: "text", text: "Your order shipped" });
|
|
219
219
|
expect(app.messages.channel("line").to("U123")[0].body.text).toMatch(/shipped/i);
|
|
220
220
|
|
|
221
|
+
// Signed in through LINE (a shop's LINE MINI App): the session carries the
|
|
222
|
+
// person's shop-provider LINE user ID. Push to the ID on the SESSION.
|
|
223
|
+
app.asUser({ id: "buyer-1", lineUserId: "Ushop-provider-1" });
|
|
224
|
+
// route under test: const s = await getSession(); await integrations.line.push(s.user.lineUserId, ...)
|
|
225
|
+
await callRoute(POST);
|
|
226
|
+
expect(app.messages.channel("line").to("Ushop-provider-1")).toHaveLength(1);
|
|
227
|
+
|
|
221
228
|
// LINE Flex card — the simulator preserves the complete LINE-native container.
|
|
222
229
|
const card = {
|
|
223
230
|
type: "bubble" as const,
|
|
@@ -322,7 +329,18 @@ expect(app.edge.latestDisplay()?.action).toBe("show");
|
|
|
322
329
|
or the platform's test-run deadline.
|
|
323
330
|
|
|
324
331
|
- `app.asUser(partial?)` — sign in a test user; `getSession()` in app code
|
|
325
|
-
resolves to it. `app.signOut()` clears.
|
|
332
|
+
resolves to it. `app.signOut()` clears. Pass `lineUserId` to sign in as a
|
|
333
|
+
person who came through LINE sign-in inside the shop's LINE MINI App; it is
|
|
334
|
+
the same field production sets, so app code reads `session.user.lineUserId`.
|
|
335
|
+
`issueSession({ lineUserId })` carries it through cookie auth and the real
|
|
336
|
+
`/api/auth/me` route too.
|
|
337
|
+
- `app.setLiffId(id | null)` / `createTestApp({ liffId })` — the LIFF ID the
|
|
338
|
+
platform forwards to the app (`null` by default = no LINE MINI App). The real
|
|
339
|
+
project-auth `/api/auth/me` returns it as `liffId` on every response, signed
|
|
340
|
+
in or not, which is what makes `useLiff()` start LIFF. `reset()` restores the
|
|
341
|
+
`createTestApp` value. The simulator does not run LIFF sign-in itself
|
|
342
|
+
(`POST /api/auth/liff` needs a real LINE ID token), so seed the signed-in
|
|
343
|
+
state with `asUser`/`issueSession` instead.
|
|
326
344
|
- `app.issueSession(partial?)` — mint access/refresh tokens for cookie-based
|
|
327
345
|
auth flows (`__stardeck:sess`).
|
|
328
346
|
- `app.inbox` — emails sent through the email-sdk: `.latest(addr?)`,
|
|
@@ -421,6 +439,24 @@ externalId })` helper models a trusted platform/channel link, and
|
|
|
421
439
|
`isVirtual: false`; create/list validation matches the control plane
|
|
422
440
|
(expiry 90–600s, amount positive int, payment-method enum, deploymentId UUID,
|
|
423
441
|
list `limit` 1–100, status filter values).
|
|
442
|
+
Beam direct charges (inline checkout): `.directCharges`, `.getDirectCharge(id)`,
|
|
443
|
+
`.completeDirectCharge(id)` and `.failDirectCharge(id, failureCode?)` settle a
|
|
444
|
+
PENDING charge and return the `charge.succeeded` / `charge.failed` payload to
|
|
445
|
+
pass to `.deliverBeamEvent`. `QR_PROMPT_PAY` returns `ENCODED_IMAGE` (expiry
|
|
446
|
+
honours `expiryDurationInSec`), wallets and mobile banking return `REDIRECT`
|
|
447
|
+
to `https://beam.stardeck.test/pay/<id>?returnUrl=…`, and the same
|
|
448
|
+
`idempotencyKey` returns the same charge. Card tokens: `tokenizeBeamCard`
|
|
449
|
+
against the simulated Beam host returns `ctok_sim_<n>`; the card number picks
|
|
450
|
+
the outcome of a `CARD_TOKEN` charge — `4111111111111111` / `5372074248113841`
|
|
451
|
+
succeed (`NONE`), `4953261730509988` / `4389783280144506` need OTP
|
|
452
|
+
(`REDIRECT`), `4943129900084541` fails `CH_INSUFFICIENT_FUNDS`,
|
|
453
|
+
`4111111111000025` fails `CH_CARD_DECLINED`, `4111111111000033` fails
|
|
454
|
+
`CH_CARD_RESTRICTED`, anything else fails `CH_PROCESSING_FAILED`.
|
|
455
|
+
`.setCardTokenization(false)` models a merchant with no publishable key
|
|
456
|
+
(`getCardTokenizationConfig` throws `CARD_TOKENIZATION_NOT_CONFIGURED`).
|
|
457
|
+
Raw `CARD` / `CARD_INSTALLMENTS`, token methods without a `cardToken`, and
|
|
458
|
+
non-QR methods without a `returnUrl` are
|
|
459
|
+
refused with 400, as on the control plane.
|
|
424
460
|
- `app.storage` — uploads through storage-sdk: `.uploads`, `.latest()`, `.count`, `.clear()`.
|
|
425
461
|
- `app.messages` — outbound Slack/LINE/Facebook sends:
|
|
426
462
|
`.all()`, `.latest()`, `.to(recipient)`, `.channel("line")`, `.count`, `.clear()`. LINE Flex
|
|
@@ -497,8 +533,8 @@ true })` remains source-compatible but the simulator (like the control plane)
|
|
|
497
533
|
- For merge-correct reads, request aliases in batches and include every id in
|
|
498
534
|
the returned canonical set. Page directory UIs through `search({ query,
|
|
499
535
|
cursor, limit })`; `list()` intentionally keeps its previous array shape.
|
|
500
|
-
- `PaymentsServerClient` (Stripe checkout + Beam payment links +
|
|
501
|
-
connections/intents/charges, product list) — captured in `app.payments`;
|
|
536
|
+
- `PaymentsServerClient` (Stripe checkout + Beam payment links + Beam direct
|
|
537
|
+
charges and card tokenization + Bolt+ connections/intents/charges, product list) — captured in `app.payments`;
|
|
502
538
|
fulfill checkout via `markPaid` (poll) or `deliverStripeEvent` /
|
|
503
539
|
`deliverBeamEvent` (webhook push). Drive a terminal without hardware via
|
|
504
540
|
`approveBoltIntent` / `declineBoltIntent` / `expireBoltIntent`. Beam payment-link
|
|
@@ -512,7 +548,8 @@ cursor, limit })`; `list()` intentionally keeps its previous array shape.
|
|
|
512
548
|
one-shot HTTP or network failures during Stripe checkout creation/status polling
|
|
513
549
|
and Beam payment-link creation/status polling. The operation names are
|
|
514
550
|
`stripe.createCheckout`, `stripe.getCheckoutSessionStatus`,
|
|
515
|
-
`beam.createPaymentLink`,
|
|
551
|
+
`beam.createPaymentLink`, `beam.getPaymentLinkStatus`, `beam.createDirectCharge`,
|
|
552
|
+
and `beam.getCharge`. HTTP failures support
|
|
516
553
|
statuses 400, 402, 409, 429, 500, and 503 plus optional `code`, `details`, and
|
|
517
554
|
`retryAfterSeconds`; `{ kind: "network", message }` rejects fetch instead.
|
|
518
555
|
`app.reset()` clears pending failures.
|
|
@@ -600,6 +637,20 @@ per-field validation (unit-test the route), login/logout (platform-provided).
|
|
|
600
637
|
never `waitForTimeout` (web-first assertions auto-wait), every test
|
|
601
638
|
independent of other tests' data and ordering.
|
|
602
639
|
|
|
640
|
+
## Testing LINE flows
|
|
641
|
+
|
|
642
|
+
- Sign in as a LINE person with `app.asUser({ lineUserId: "U..." })` and call
|
|
643
|
+
the route with `callRoute`; assert the push with
|
|
644
|
+
`app.messages.channel("line").to("U...")`. Cover the person with no LINE
|
|
645
|
+
identity too (`asUser()` without `lineUserId`): the route must send nothing.
|
|
646
|
+
- The recipient of a LINE push comes from `session.user.lineUserId` on the
|
|
647
|
+
server, never from a request body, query string or client-side value. A
|
|
648
|
+
`lineUserId` a browser sends can be forged to message any friend of the shop's
|
|
649
|
+
LINE Official Account. Do not write a route that takes one, and do not write a
|
|
650
|
+
test that posts one to make a push happen; sign in with `lineUserId` instead.
|
|
651
|
+
- Whether the person is a friend of the shop's OA is a client concern
|
|
652
|
+
(`useLiff().isFriend`); a server route checks only that `lineUserId` exists.
|
|
653
|
+
|
|
603
654
|
## Rules
|
|
604
655
|
|
|
605
656
|
- Test **business logic and route handlers**, not framework plumbing. Focus on
|
package/dist/index.d.mts
CHANGED
|
@@ -38,6 +38,12 @@ interface TestUser {
|
|
|
38
38
|
projectId?: string | null;
|
|
39
39
|
/** Cross-app customer ownership key returned by the platform identity resolver. */
|
|
40
40
|
identityId?: string | null;
|
|
41
|
+
/**
|
|
42
|
+
* The person's LINE user ID under the shop's own LINE provider. Set it to sign in
|
|
43
|
+
* as someone who came through LINE sign-in inside the shop's LINE MINI App; app
|
|
44
|
+
* code reads it from `session.user.lineUserId` and pushes to it.
|
|
45
|
+
*/
|
|
46
|
+
lineUserId?: string | null;
|
|
41
47
|
}
|
|
42
48
|
interface CapturedEmail {
|
|
43
49
|
resendId: string;
|
|
@@ -210,7 +216,7 @@ interface CapturedCheckout {
|
|
|
210
216
|
metadata?: Record<string, string>;
|
|
211
217
|
createdAt: Date;
|
|
212
218
|
}
|
|
213
|
-
type PaymentSimulatorOperation = "stripe.createCheckout" | "stripe.getCheckoutSessionStatus" | "beam.createPaymentLink" | "beam.getPaymentLinkStatus";
|
|
219
|
+
type PaymentSimulatorOperation = "stripe.createCheckout" | "stripe.getCheckoutSessionStatus" | "beam.createPaymentLink" | "beam.getPaymentLinkStatus" | "beam.createDirectCharge" | "beam.getCharge";
|
|
214
220
|
type PaymentSimulatorFailure = {
|
|
215
221
|
kind?: "http";
|
|
216
222
|
status: 400 | 402 | 409 | 429 | 500 | 503;
|
|
@@ -269,6 +275,26 @@ interface SimBoltCharge {
|
|
|
269
275
|
sourceId?: string;
|
|
270
276
|
createdAt?: string;
|
|
271
277
|
}
|
|
278
|
+
/** A direct charge (`createDirectCharge`) as the simulator holds it. */
|
|
279
|
+
interface SimDirectCharge {
|
|
280
|
+
id: string;
|
|
281
|
+
status: "SUCCEEDED" | "FAILED" | "PENDING";
|
|
282
|
+
amount: number;
|
|
283
|
+
currency: string;
|
|
284
|
+
paymentMethodType: string;
|
|
285
|
+
referenceId: string | null;
|
|
286
|
+
deviceType: string | null;
|
|
287
|
+
returnUrl: string | null;
|
|
288
|
+
actionRequired: "NONE" | "REDIRECT" | "ENCODED_IMAGE";
|
|
289
|
+
redirectUrl: string | null;
|
|
290
|
+
/** When the QR (or the charge) stops being payable. */
|
|
291
|
+
expiresAt: string;
|
|
292
|
+
failureCode: string | null;
|
|
293
|
+
/** Last four digits of a CARD_TOKEN charge's card. */
|
|
294
|
+
cardLast4: string | null;
|
|
295
|
+
idempotencyKey: string | null;
|
|
296
|
+
createdAt: string;
|
|
297
|
+
}
|
|
272
298
|
interface TestPayments {
|
|
273
299
|
get checkouts(): CapturedCheckout[];
|
|
274
300
|
latest(): CapturedCheckout | undefined;
|
|
@@ -317,6 +343,19 @@ interface TestPayments {
|
|
|
317
343
|
expireBoltIntent(intentId: string): void;
|
|
318
344
|
listBoltIntents(): SimBoltIntentRecord[];
|
|
319
345
|
getBoltIntent(intentId: string): SimBoltIntentRecord | undefined;
|
|
346
|
+
/** Every direct charge created so far, oldest first. */
|
|
347
|
+
get directCharges(): SimDirectCharge[];
|
|
348
|
+
getDirectCharge(chargeId: string): SimDirectCharge | undefined;
|
|
349
|
+
/**
|
|
350
|
+
* The shopper paid: mark a PENDING direct charge SUCCEEDED and return the
|
|
351
|
+
* `charge.succeeded` payload. Delivers no webhook; pass the payload to
|
|
352
|
+
* `deliverBeamEvent` the way Beam would.
|
|
353
|
+
*/
|
|
354
|
+
completeDirectCharge(chargeId: string): Record<string, unknown>;
|
|
355
|
+
/** Mark a PENDING direct charge FAILED; returns the `charge.failed` payload. */
|
|
356
|
+
failDirectCharge(chargeId: string, failureCode?: string): Record<string, unknown>;
|
|
357
|
+
/** Turn card tokenization off, as for a merchant with no publishable key. On by default. */
|
|
358
|
+
setCardTokenization(enabled: boolean): void;
|
|
320
359
|
clear(): void;
|
|
321
360
|
get count(): number;
|
|
322
361
|
}
|
|
@@ -582,6 +621,12 @@ interface TestStarlens {
|
|
|
582
621
|
get count(): number;
|
|
583
622
|
}
|
|
584
623
|
interface TestAppOptions {
|
|
624
|
+
/**
|
|
625
|
+
* The LIFF ID the platform forwards to the app for a LINE MINI App shop. It is
|
|
626
|
+
* what `GET /api/auth/me` returns as `liffId` and what makes `useLiff()` start
|
|
627
|
+
* LIFF. Defaults to `null` (the app has no LINE MINI App). Restored by `reset()`.
|
|
628
|
+
*/
|
|
629
|
+
liffId?: string | null;
|
|
585
630
|
/** Data stores exposed through the production-shaped STARDECK_DATA_STORES manifest. */
|
|
586
631
|
dataStores?: TestDataStoreInput[];
|
|
587
632
|
/**
|
|
@@ -636,6 +681,11 @@ interface TestApp {
|
|
|
636
681
|
asUser(user?: Partial<TestUser>): TestUser;
|
|
637
682
|
/** Clear the active test user. */
|
|
638
683
|
signOut(): void;
|
|
684
|
+
/**
|
|
685
|
+
* Set (or clear with `null`) the LIFF ID the platform forwards to the app, for
|
|
686
|
+
* the rest of the test. The real project-auth `me` route returns it as `liffId`.
|
|
687
|
+
*/
|
|
688
|
+
setLiffId(liffId: string | null): void;
|
|
639
689
|
/**
|
|
640
690
|
* Mint access/refresh tokens for cookie-based auth flows. The simulated
|
|
641
691
|
* control-plane verify endpoint resolves these tokens back to the user.
|
package/dist/index.d.ts
CHANGED
|
@@ -38,6 +38,12 @@ interface TestUser {
|
|
|
38
38
|
projectId?: string | null;
|
|
39
39
|
/** Cross-app customer ownership key returned by the platform identity resolver. */
|
|
40
40
|
identityId?: string | null;
|
|
41
|
+
/**
|
|
42
|
+
* The person's LINE user ID under the shop's own LINE provider. Set it to sign in
|
|
43
|
+
* as someone who came through LINE sign-in inside the shop's LINE MINI App; app
|
|
44
|
+
* code reads it from `session.user.lineUserId` and pushes to it.
|
|
45
|
+
*/
|
|
46
|
+
lineUserId?: string | null;
|
|
41
47
|
}
|
|
42
48
|
interface CapturedEmail {
|
|
43
49
|
resendId: string;
|
|
@@ -210,7 +216,7 @@ interface CapturedCheckout {
|
|
|
210
216
|
metadata?: Record<string, string>;
|
|
211
217
|
createdAt: Date;
|
|
212
218
|
}
|
|
213
|
-
type PaymentSimulatorOperation = "stripe.createCheckout" | "stripe.getCheckoutSessionStatus" | "beam.createPaymentLink" | "beam.getPaymentLinkStatus";
|
|
219
|
+
type PaymentSimulatorOperation = "stripe.createCheckout" | "stripe.getCheckoutSessionStatus" | "beam.createPaymentLink" | "beam.getPaymentLinkStatus" | "beam.createDirectCharge" | "beam.getCharge";
|
|
214
220
|
type PaymentSimulatorFailure = {
|
|
215
221
|
kind?: "http";
|
|
216
222
|
status: 400 | 402 | 409 | 429 | 500 | 503;
|
|
@@ -269,6 +275,26 @@ interface SimBoltCharge {
|
|
|
269
275
|
sourceId?: string;
|
|
270
276
|
createdAt?: string;
|
|
271
277
|
}
|
|
278
|
+
/** A direct charge (`createDirectCharge`) as the simulator holds it. */
|
|
279
|
+
interface SimDirectCharge {
|
|
280
|
+
id: string;
|
|
281
|
+
status: "SUCCEEDED" | "FAILED" | "PENDING";
|
|
282
|
+
amount: number;
|
|
283
|
+
currency: string;
|
|
284
|
+
paymentMethodType: string;
|
|
285
|
+
referenceId: string | null;
|
|
286
|
+
deviceType: string | null;
|
|
287
|
+
returnUrl: string | null;
|
|
288
|
+
actionRequired: "NONE" | "REDIRECT" | "ENCODED_IMAGE";
|
|
289
|
+
redirectUrl: string | null;
|
|
290
|
+
/** When the QR (or the charge) stops being payable. */
|
|
291
|
+
expiresAt: string;
|
|
292
|
+
failureCode: string | null;
|
|
293
|
+
/** Last four digits of a CARD_TOKEN charge's card. */
|
|
294
|
+
cardLast4: string | null;
|
|
295
|
+
idempotencyKey: string | null;
|
|
296
|
+
createdAt: string;
|
|
297
|
+
}
|
|
272
298
|
interface TestPayments {
|
|
273
299
|
get checkouts(): CapturedCheckout[];
|
|
274
300
|
latest(): CapturedCheckout | undefined;
|
|
@@ -317,6 +343,19 @@ interface TestPayments {
|
|
|
317
343
|
expireBoltIntent(intentId: string): void;
|
|
318
344
|
listBoltIntents(): SimBoltIntentRecord[];
|
|
319
345
|
getBoltIntent(intentId: string): SimBoltIntentRecord | undefined;
|
|
346
|
+
/** Every direct charge created so far, oldest first. */
|
|
347
|
+
get directCharges(): SimDirectCharge[];
|
|
348
|
+
getDirectCharge(chargeId: string): SimDirectCharge | undefined;
|
|
349
|
+
/**
|
|
350
|
+
* The shopper paid: mark a PENDING direct charge SUCCEEDED and return the
|
|
351
|
+
* `charge.succeeded` payload. Delivers no webhook; pass the payload to
|
|
352
|
+
* `deliverBeamEvent` the way Beam would.
|
|
353
|
+
*/
|
|
354
|
+
completeDirectCharge(chargeId: string): Record<string, unknown>;
|
|
355
|
+
/** Mark a PENDING direct charge FAILED; returns the `charge.failed` payload. */
|
|
356
|
+
failDirectCharge(chargeId: string, failureCode?: string): Record<string, unknown>;
|
|
357
|
+
/** Turn card tokenization off, as for a merchant with no publishable key. On by default. */
|
|
358
|
+
setCardTokenization(enabled: boolean): void;
|
|
320
359
|
clear(): void;
|
|
321
360
|
get count(): number;
|
|
322
361
|
}
|
|
@@ -582,6 +621,12 @@ interface TestStarlens {
|
|
|
582
621
|
get count(): number;
|
|
583
622
|
}
|
|
584
623
|
interface TestAppOptions {
|
|
624
|
+
/**
|
|
625
|
+
* The LIFF ID the platform forwards to the app for a LINE MINI App shop. It is
|
|
626
|
+
* what `GET /api/auth/me` returns as `liffId` and what makes `useLiff()` start
|
|
627
|
+
* LIFF. Defaults to `null` (the app has no LINE MINI App). Restored by `reset()`.
|
|
628
|
+
*/
|
|
629
|
+
liffId?: string | null;
|
|
585
630
|
/** Data stores exposed through the production-shaped STARDECK_DATA_STORES manifest. */
|
|
586
631
|
dataStores?: TestDataStoreInput[];
|
|
587
632
|
/**
|
|
@@ -636,6 +681,11 @@ interface TestApp {
|
|
|
636
681
|
asUser(user?: Partial<TestUser>): TestUser;
|
|
637
682
|
/** Clear the active test user. */
|
|
638
683
|
signOut(): void;
|
|
684
|
+
/**
|
|
685
|
+
* Set (or clear with `null`) the LIFF ID the platform forwards to the app, for
|
|
686
|
+
* the rest of the test. The real project-auth `me` route returns it as `liffId`.
|
|
687
|
+
*/
|
|
688
|
+
setLiffId(liffId: string | null): void;
|
|
639
689
|
/**
|
|
640
690
|
* Mint access/refresh tokens for cookie-based auth flows. The simulated
|
|
641
691
|
* control-plane verify endpoint resolves these tokens back to the user.
|