@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 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 + Bolt+
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`, and `beam.getPaymentLinkStatus`. HTTP failures support
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.