@stardeck-customer-apps/testing 0.14.0 → 0.16.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
@@ -341,6 +341,37 @@ externalId })` helper models a trusted platform/channel link, and
341
341
  `.merge(sourceId, canonicalId)` models governed merge redirects. They are
342
342
  setup helpers only; deployed app code cannot create verified links or merge
343
343
  identities through the integrations SDK.
344
+ - **Profile connections** (`client.identities.claims`) are driven from the same
345
+ accessor: `.claims()` and `.claim(claimId)` inspect what the app opened;
346
+ `.enableStaffAssist()` turns on the org opt-in that `assist`/`transfer` claims
347
+ need (off by default, so an un-opted project gets the real 403);
348
+ `.completeClaim(claimId, { clerkUserId })` models the customer finishing the
349
+ platform-hosted flow; `.refuseClaim(claimId, reason)` models reception or the
350
+ platform turning one down. `completeClaim` flips the claimed contact to
351
+ `verified`, or records `invalidated` when the contact is no longer on that
352
+ profile and `refused` / `contact_owned` when someone else already proved it.
353
+ Completing a finished claim replays the recorded result. It does not attach a
354
+ login or move history — the platform's merge core is not modelled.
355
+ `.allowReturnOrigin(origin)` widens the allowed `returnTo` origins (the test
356
+ app's own origin is allowed by default); a `returnTo` anywhere else is dropped
357
+ silently, exactly as the platform drops it. `claimUrl` carries a one-time
358
+ opaque handle, so never assert it contains the claim id, and `deliver: true`
359
+ comes back as `delivered`. `.suppressClaimDelivery()` models the platform
360
+ sending nothing (a suppressed quota, a bounced address): `deliver: true` then
361
+ returns `delivered: false` with the `claimUrl` still valid, so the app's own
362
+ fallback delivery can be tested.
363
+
364
+ ```ts
365
+ const claim = await integrations.identities.claims.create({
366
+ flow: "invite",
367
+ identityId: person.id,
368
+ kind: "phone",
369
+ externalId: "+15550100",
370
+ });
371
+ app.identities.completeClaim(claim.claimId, { clerkUserId: "user_1" });
372
+ expect((await integrations.identities.claims.get(claim.claimId)).state).toBe("completed");
373
+ ```
374
+
344
375
  - `app.payments` — checkouts created through payments-sdk:
345
376
  `.checkouts`, `.latest()`, `.setProducts()`, `.markPaid(id)`,
346
377
  `.setSessionStatus(id, status)`, `.setPaymentLinkStatus(id, status)`,
@@ -409,11 +440,16 @@ describe the code, and the owner can't tell from them what is or isn't covered.
409
440
  A–Z→a–z; Turkish İ is left unchanged), requires E.164 phone values, and
410
441
  always writes an unverified link. A guest lookup of a verified link returns
411
442
  `{ identityId: null, reason: "verified_conflict" }`; staff provenance may
412
- resolve either verified or unverified links. `attachLink` accepts only
413
- channel kinds (line/facebook/instagram/email/phone); login keys must be
414
- seeded via `app.identities.seedVerifiedLink`. `attachLink(..., { verified:
443
+ resolve either verified or unverified links. `attachLink` records a contact or
444
+ channel id (line/facebook/instagram/email/phone); login keys must be seeded
445
+ via `app.identities.seedVerifiedLink`. `attachLink(..., { verified:
415
446
  true })` remains source-compatible but the simulator (like the control plane)
416
- ignores that flag and returns `verified: false`. `update` replaces the
447
+ ignores that flag and returns `verified: false`. An **unverified** email or
448
+ phone may be recorded on several identities (a household sharing one number);
449
+ a verified holder makes a further _verified_ attach conflict, but unverified
450
+ copies still succeed, and `resolveOrCreate` prefers the verified owner and
451
+ otherwise the earliest unverified holder. Channel and login kinds stay
452
+ single-owner and 409 on a second holder. `update` replaces the
417
453
  `profile` object (not a merge), like the control plane. Inspect via
418
454
  `app.identities`; merge/archive are privileged setup operations, with only
419
455
  test-only merge modeling exposed above.
@@ -451,6 +487,14 @@ cursor, limit })`; `list()` intentionally keeps its previous array shape.
451
487
  display, test print, `getCapabilities()`) — captured in `app.edge`; seed
452
488
  devices/peripherals for pairing pickers via `app.edge.seedDevices()` /
453
489
  `seedPeripherals()`.
490
+ - `createStarlensClient().extract()` from the starlens SDK — there is no model in
491
+ tests: register what Starlens answers with `app.starlens.mock({ output, issues })`
492
+ (a successful extraction; each issue path comes back `null` in `output`, as in
493
+ production) or `app.starlens.reject("not a receipt")` (the 422 the SDK maps to
494
+ `UnsupportedDocumentError`). An unregistered call fails with a descriptive 502 so a
495
+ test never passes on an invented extraction. `app.starlens.calls` records each
496
+ request's version, input names/media types (never bytes), schema and
497
+ instructions — assert your `.describe()` aliases reached the wire there.
454
498
 
455
499
  ### Receipt layout and printer capabilities
456
500
 
package/dist/index.d.mts CHANGED
@@ -105,6 +105,32 @@ interface CapturedIdentityLink {
105
105
  * the control plane that `client.identities` talks to in tests. Identities and
106
106
  * links created by app code under test land here; assert on them like `inbox`.
107
107
  */
108
+ interface CapturedIdentityClaim {
109
+ claimId: string;
110
+ flow: "invite" | "assist" | "transfer";
111
+ /** The profile the claim connects the customer to. */
112
+ identityId: string;
113
+ kind: "email" | "phone";
114
+ externalId: string;
115
+ returnTo: string | null;
116
+ /** The app's reference for the staff member, for assist/transfer claims. */
117
+ staffActorRef: string | null;
118
+ state: "pending" | "completed" | "expired" | "invalidated" | "refused";
119
+ refusalReason: string | null;
120
+ completedIdentityId: string | null;
121
+ /** Set by completeClaim; models the Clerk account that proved the contact. */
122
+ claimantClerkUserId: string | null;
123
+ /**
124
+ * Hosted claim link. Its path carries a one-time opaque handle, NOT the claim
125
+ * id — the platform never makes the customer's page derivable from an id an
126
+ * app already holds.
127
+ */
128
+ claimUrl: string;
129
+ /** Whether the platform sent the link itself (only when `deliver` was asked). */
130
+ delivered: boolean | null;
131
+ expiresAt: string;
132
+ createdAt: string;
133
+ }
108
134
  interface TestDirectory {
109
135
  /** All identities, oldest first. */
110
136
  all(): CapturedIdentity[];
@@ -126,6 +152,43 @@ interface TestDirectory {
126
152
  * identity. The production integrations SDK does not expose merge operations.
127
153
  */
128
154
  merge(sourceIdentityId: string, canonicalIdentityId: string): void;
155
+ /** Every profile-connection claim the app has opened, oldest first. */
156
+ claims(): CapturedIdentityClaim[];
157
+ /** A single claim by id, or undefined. */
158
+ claim(claimId: string): CapturedIdentityClaim | undefined;
159
+ /**
160
+ * Test setup only: turn on the org's staff-assistance opt-in. Without it,
161
+ * `claims.create` refuses `assist` and `transfer` with 403, like the platform.
162
+ */
163
+ enableStaffAssist(enabled?: boolean): void;
164
+ /**
165
+ * Test setup only: model the platform failing to send a claim link it was
166
+ * asked to deliver (a suppressed quota, a bounced address). `deliver: true`
167
+ * then comes back `delivered: false` with the `claimUrl` still valid, so an
168
+ * app's own fallback delivery can be tested.
169
+ */
170
+ suppressClaimDelivery(enabled?: boolean): void;
171
+ /**
172
+ * Test setup only: allow a claim `returnTo` on this origin. The harness app's
173
+ * own origin is allowed by default; anything else is dropped silently, like
174
+ * the platform's allowed-origins check.
175
+ */
176
+ allowReturnOrigin(origin: string): void;
177
+ /**
178
+ * Test setup only: model the customer finishing the platform-hosted claim —
179
+ * Clerk proof plus their confirmation. Flips the claimed contact to verified,
180
+ * or records `invalidated` (the contact was removed from that profile) or
181
+ * `refused` with `contact_owned` (someone else already proved it). Replaying a
182
+ * finished claim returns the recorded result unchanged.
183
+ *
184
+ * It does NOT attach a login link or move history: the platform's merge core
185
+ * is not modelled here, only the ownership decision the app can observe.
186
+ */
187
+ completeClaim(claimId: string, params: {
188
+ clerkUserId: string;
189
+ }): CapturedIdentityClaim;
190
+ /** Test setup only: model reception or the platform refusing a claim. */
191
+ refuseClaim(claimId: string, reason: string): CapturedIdentityClaim;
129
192
  clear(): void;
130
193
  get count(): number;
131
194
  }
@@ -462,6 +525,44 @@ interface TestEdge {
462
525
  clear(): void;
463
526
  get count(): number;
464
527
  }
528
+ /** One Starlens request the app made, as the simulator saw it (names only, never bytes). */
529
+ interface CapturedStarlensCall {
530
+ version: string;
531
+ inputs: {
532
+ name: string;
533
+ mediaType: string | null;
534
+ source: "url" | "data";
535
+ }[];
536
+ schema: Record<string, unknown>;
537
+ instructions?: string;
538
+ }
539
+ /**
540
+ * What the simulated Starlens answers next. `{ output, issues }` is returned as a
541
+ * successful extraction with every issue path nulled in `output`, exactly as the control
542
+ * plane does; `{ reject }` becomes the 422 `unsupported_document` refusal the SDK maps to
543
+ * `UnsupportedDocumentError`.
544
+ */
545
+ type StarlensReply = {
546
+ output: unknown;
547
+ issues?: {
548
+ path: string;
549
+ reason: string;
550
+ }[];
551
+ } | {
552
+ reject: string;
553
+ };
554
+ interface TestStarlens {
555
+ /** Queue the reply for the next `extract()` call. */
556
+ mock(reply: StarlensReply): void;
557
+ /** Queue a refusal for the next `extract()` call. */
558
+ reject(reason: string): void;
559
+ /** Reply used whenever the queue is empty (null = fail with a descriptive 502). */
560
+ mockDefault(reply: StarlensReply | null): void;
561
+ get calls(): CapturedStarlensCall[];
562
+ latestCall(): CapturedStarlensCall | undefined;
563
+ clear(): void;
564
+ get count(): number;
565
+ }
465
566
  interface TestAppOptions {
466
567
  /** Data stores exposed through the production-shaped STARDECK_DATA_STORES manifest. */
467
568
  dataStores?: TestDataStoreInput[];
@@ -506,6 +607,8 @@ interface TestApp {
506
607
  messages: TestMessages;
507
608
  /** Captured edge print/display ops and peripheral bindings from edge-sdk. */
508
609
  edge: TestEdge;
610
+ /** Scripted replies and captured calls for the Starlens SDK. */
611
+ starlens: TestStarlens;
509
612
  /** Convenience for raw SQL: `app.query("SELECT ...", [param])`. */
510
613
  query<T = Record<string, unknown>>(sql: string, params?: unknown[]): Promise<T[]>;
511
614
  /**
@@ -571,4 +674,4 @@ declare const TEST_ENV_DEFAULTS: {
571
674
  /** Default location of the DDL snapshot written by `generate-types`. */
572
675
  declare const DEFAULT_SCHEMA_PATH = "./src/generated/data-store-schema.sql";
573
676
 
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 };
677
+ export { type BindingInfo, CONTROL_PLANE_TEST_URL, type CallRouteOptions, type CapturedCheckout, type CapturedDisplay, type CapturedEmail, type CapturedIdentity, type CapturedIdentityLink, type CapturedMessage, type CapturedPrint, type CapturedStarlensCall, 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, type StarlensReply, TEST_ENV_DEFAULTS, type TestApp, type TestAppOptions, TestDataStoreInput, type TestDirectory, type TestEdge, type TestInbox, type TestMessages, type TestPayments, type TestStarlens, type TestStorage, type TestUser, WORKFLOW_NAME_PREFIX, callRoute, createTestApp, describeWorkflow, parseWorkflowName };
package/dist/index.d.ts CHANGED
@@ -105,6 +105,32 @@ interface CapturedIdentityLink {
105
105
  * the control plane that `client.identities` talks to in tests. Identities and
106
106
  * links created by app code under test land here; assert on them like `inbox`.
107
107
  */
108
+ interface CapturedIdentityClaim {
109
+ claimId: string;
110
+ flow: "invite" | "assist" | "transfer";
111
+ /** The profile the claim connects the customer to. */
112
+ identityId: string;
113
+ kind: "email" | "phone";
114
+ externalId: string;
115
+ returnTo: string | null;
116
+ /** The app's reference for the staff member, for assist/transfer claims. */
117
+ staffActorRef: string | null;
118
+ state: "pending" | "completed" | "expired" | "invalidated" | "refused";
119
+ refusalReason: string | null;
120
+ completedIdentityId: string | null;
121
+ /** Set by completeClaim; models the Clerk account that proved the contact. */
122
+ claimantClerkUserId: string | null;
123
+ /**
124
+ * Hosted claim link. Its path carries a one-time opaque handle, NOT the claim
125
+ * id — the platform never makes the customer's page derivable from an id an
126
+ * app already holds.
127
+ */
128
+ claimUrl: string;
129
+ /** Whether the platform sent the link itself (only when `deliver` was asked). */
130
+ delivered: boolean | null;
131
+ expiresAt: string;
132
+ createdAt: string;
133
+ }
108
134
  interface TestDirectory {
109
135
  /** All identities, oldest first. */
110
136
  all(): CapturedIdentity[];
@@ -126,6 +152,43 @@ interface TestDirectory {
126
152
  * identity. The production integrations SDK does not expose merge operations.
127
153
  */
128
154
  merge(sourceIdentityId: string, canonicalIdentityId: string): void;
155
+ /** Every profile-connection claim the app has opened, oldest first. */
156
+ claims(): CapturedIdentityClaim[];
157
+ /** A single claim by id, or undefined. */
158
+ claim(claimId: string): CapturedIdentityClaim | undefined;
159
+ /**
160
+ * Test setup only: turn on the org's staff-assistance opt-in. Without it,
161
+ * `claims.create` refuses `assist` and `transfer` with 403, like the platform.
162
+ */
163
+ enableStaffAssist(enabled?: boolean): void;
164
+ /**
165
+ * Test setup only: model the platform failing to send a claim link it was
166
+ * asked to deliver (a suppressed quota, a bounced address). `deliver: true`
167
+ * then comes back `delivered: false` with the `claimUrl` still valid, so an
168
+ * app's own fallback delivery can be tested.
169
+ */
170
+ suppressClaimDelivery(enabled?: boolean): void;
171
+ /**
172
+ * Test setup only: allow a claim `returnTo` on this origin. The harness app's
173
+ * own origin is allowed by default; anything else is dropped silently, like
174
+ * the platform's allowed-origins check.
175
+ */
176
+ allowReturnOrigin(origin: string): void;
177
+ /**
178
+ * Test setup only: model the customer finishing the platform-hosted claim —
179
+ * Clerk proof plus their confirmation. Flips the claimed contact to verified,
180
+ * or records `invalidated` (the contact was removed from that profile) or
181
+ * `refused` with `contact_owned` (someone else already proved it). Replaying a
182
+ * finished claim returns the recorded result unchanged.
183
+ *
184
+ * It does NOT attach a login link or move history: the platform's merge core
185
+ * is not modelled here, only the ownership decision the app can observe.
186
+ */
187
+ completeClaim(claimId: string, params: {
188
+ clerkUserId: string;
189
+ }): CapturedIdentityClaim;
190
+ /** Test setup only: model reception or the platform refusing a claim. */
191
+ refuseClaim(claimId: string, reason: string): CapturedIdentityClaim;
129
192
  clear(): void;
130
193
  get count(): number;
131
194
  }
@@ -462,6 +525,44 @@ interface TestEdge {
462
525
  clear(): void;
463
526
  get count(): number;
464
527
  }
528
+ /** One Starlens request the app made, as the simulator saw it (names only, never bytes). */
529
+ interface CapturedStarlensCall {
530
+ version: string;
531
+ inputs: {
532
+ name: string;
533
+ mediaType: string | null;
534
+ source: "url" | "data";
535
+ }[];
536
+ schema: Record<string, unknown>;
537
+ instructions?: string;
538
+ }
539
+ /**
540
+ * What the simulated Starlens answers next. `{ output, issues }` is returned as a
541
+ * successful extraction with every issue path nulled in `output`, exactly as the control
542
+ * plane does; `{ reject }` becomes the 422 `unsupported_document` refusal the SDK maps to
543
+ * `UnsupportedDocumentError`.
544
+ */
545
+ type StarlensReply = {
546
+ output: unknown;
547
+ issues?: {
548
+ path: string;
549
+ reason: string;
550
+ }[];
551
+ } | {
552
+ reject: string;
553
+ };
554
+ interface TestStarlens {
555
+ /** Queue the reply for the next `extract()` call. */
556
+ mock(reply: StarlensReply): void;
557
+ /** Queue a refusal for the next `extract()` call. */
558
+ reject(reason: string): void;
559
+ /** Reply used whenever the queue is empty (null = fail with a descriptive 502). */
560
+ mockDefault(reply: StarlensReply | null): void;
561
+ get calls(): CapturedStarlensCall[];
562
+ latestCall(): CapturedStarlensCall | undefined;
563
+ clear(): void;
564
+ get count(): number;
565
+ }
465
566
  interface TestAppOptions {
466
567
  /** Data stores exposed through the production-shaped STARDECK_DATA_STORES manifest. */
467
568
  dataStores?: TestDataStoreInput[];
@@ -506,6 +607,8 @@ interface TestApp {
506
607
  messages: TestMessages;
507
608
  /** Captured edge print/display ops and peripheral bindings from edge-sdk. */
508
609
  edge: TestEdge;
610
+ /** Scripted replies and captured calls for the Starlens SDK. */
611
+ starlens: TestStarlens;
509
612
  /** Convenience for raw SQL: `app.query("SELECT ...", [param])`. */
510
613
  query<T = Record<string, unknown>>(sql: string, params?: unknown[]): Promise<T[]>;
511
614
  /**
@@ -571,4 +674,4 @@ declare const TEST_ENV_DEFAULTS: {
571
674
  /** Default location of the DDL snapshot written by `generate-types`. */
572
675
  declare const DEFAULT_SCHEMA_PATH = "./src/generated/data-store-schema.sql";
573
676
 
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 };
677
+ export { type BindingInfo, CONTROL_PLANE_TEST_URL, type CallRouteOptions, type CapturedCheckout, type CapturedDisplay, type CapturedEmail, type CapturedIdentity, type CapturedIdentityLink, type CapturedMessage, type CapturedPrint, type CapturedStarlensCall, 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, type StarlensReply, TEST_ENV_DEFAULTS, type TestApp, type TestAppOptions, TestDataStoreInput, type TestDirectory, type TestEdge, type TestInbox, type TestMessages, type TestPayments, type TestStarlens, type TestStorage, type TestUser, WORKFLOW_NAME_PREFIX, callRoute, createTestApp, describeWorkflow, parseWorkflowName };