@stardeck-customer-apps/testing 0.15.0 → 0.17.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 +71 -5
- package/dist/index.d.mts +70 -0
- package/dist/index.d.ts +70 -0
- package/dist/index.js +392 -48
- package/dist/index.mjs +392 -48
- package/dist/next/headers-shim.js +10 -0
- package/dist/next/headers-shim.mjs +10 -0
- package/dist/setup.js +265 -30
- package/dist/setup.mjs +265 -30
- package/package.json +2 -2
package/SKILL.md
CHANGED
|
@@ -341,6 +341,64 @@ 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. Completion attaches
|
|
354
|
+
the claimant's login to the profile (moving it off any profile it was on), but
|
|
355
|
+
does not move history — the platform's merge core is not modelled.
|
|
356
|
+
`.allowReturnOrigin(origin)` widens the allowed `returnTo` origins (the test
|
|
357
|
+
app's own origin is allowed by default); a `returnTo` anywhere else is dropped
|
|
358
|
+
silently, exactly as the platform drops it. `claimUrl` carries a one-time
|
|
359
|
+
opaque handle, so never assert it contains the claim id, and `deliver: true`
|
|
360
|
+
comes back as `delivered`. `.suppressClaimDelivery()` models the platform
|
|
361
|
+
sending nothing (a suppressed quota, a bounced address): `deliver: true` then
|
|
362
|
+
returns `delivered: false` with the `claimUrl` still valid, so the app's own
|
|
363
|
+
fallback delivery can be tested. Bearer links are on by default, like the platform;
|
|
364
|
+
`.enableBearerLinks(false)` models a project whose admin turned them off. A bearer claim is raised against a
|
|
365
|
+
profile with no contact at all, so it takes no `kind`/`externalId`/`deliver`
|
|
366
|
+
(sending one is refused, not ignored) and `completeClaim` connects whoever
|
|
367
|
+
completes it — unless another login already holds that profile, which comes
|
|
368
|
+
back `refused` / `owned_elsewhere`. That refusal is the one a leaked bearer
|
|
369
|
+
link runs into, so test it. A profile that holds an email or phone is refused
|
|
370
|
+
at `create` with a 409 (send an `invite`); one that gains a contact before
|
|
371
|
+
the link is used completes as `refused` / `protected`, and a link still
|
|
372
|
+
outstanding when `.enableBearerLinks(false)` runs completes as `refused` /
|
|
373
|
+
`expired`. People seeded with only a `dashboard_user` link (team members) are
|
|
374
|
+
left out of `identities.list()` and `search()`, as on the platform.
|
|
375
|
+
`search({ attributes })` matches profile attributes exactly and by JSON type
|
|
376
|
+
(`5` never matches `"5"`) and rejects nested, array, `null` or more than 10
|
|
377
|
+
keys (or a `__proto__` key) with a `ValidationError` whose message matches the
|
|
378
|
+
platform's, like the platform.
|
|
379
|
+
|
|
380
|
+
```ts
|
|
381
|
+
const claim = await integrations.identities.claims.create({
|
|
382
|
+
flow: "invite",
|
|
383
|
+
identityId: person.id,
|
|
384
|
+
kind: "phone",
|
|
385
|
+
externalId: "+15550100",
|
|
386
|
+
});
|
|
387
|
+
app.identities.completeClaim(claim.claimId, { clerkUserId: "user_1" });
|
|
388
|
+
expect((await integrations.identities.claims.get(claim.claimId)).state).toBe("completed");
|
|
389
|
+
```
|
|
390
|
+
|
|
391
|
+
```ts
|
|
392
|
+
// A name on a card, no email or phone: the link IS the authorisation.
|
|
393
|
+
const bearer = await integrations.identities.claims.create({
|
|
394
|
+
flow: "bearer",
|
|
395
|
+
identityId: walkIn.id,
|
|
396
|
+
});
|
|
397
|
+
expect(app.identities.completeClaim(bearer.claimId, { clerkUserId: "user_2" }).state).toBe(
|
|
398
|
+
"completed"
|
|
399
|
+
);
|
|
400
|
+
```
|
|
401
|
+
|
|
344
402
|
- `app.payments` — checkouts created through payments-sdk:
|
|
345
403
|
`.checkouts`, `.latest()`, `.setProducts()`, `.markPaid(id)`,
|
|
346
404
|
`.setSessionStatus(id, status)`, `.setPaymentLinkStatus(id, status)`,
|
|
@@ -409,12 +467,20 @@ describe the code, and the owner can't tell from them what is or isn't covered.
|
|
|
409
467
|
A–Z→a–z; Turkish İ is left unchanged), requires E.164 phone values, and
|
|
410
468
|
always writes an unverified link. A guest lookup of a verified link returns
|
|
411
469
|
`{ identityId: null, reason: "verified_conflict" }`; staff provenance may
|
|
412
|
-
resolve either verified or unverified links. `attachLink`
|
|
413
|
-
channel
|
|
414
|
-
|
|
470
|
+
resolve either verified or unverified links. `attachLink` records a contact or
|
|
471
|
+
channel id (line/facebook/instagram/email/phone); login keys must be seeded
|
|
472
|
+
via `app.identities.seedVerifiedLink`. `attachLink(..., { verified:
|
|
415
473
|
true })` remains source-compatible but the simulator (like the control plane)
|
|
416
|
-
ignores that flag and returns `verified: false`.
|
|
417
|
-
|
|
474
|
+
ignores that flag and returns `verified: false`. An **unverified** email or
|
|
475
|
+
phone may be recorded on several identities (a household sharing one number);
|
|
476
|
+
a verified holder makes a further _verified_ attach conflict, but unverified
|
|
477
|
+
copies still succeed, and `resolveOrCreate` prefers the verified owner and
|
|
478
|
+
otherwise the earliest unverified holder. Channel and login kinds stay
|
|
479
|
+
single-owner and 409 on a second holder. `update` replaces the
|
|
480
|
+
`profile` object (not a merge), like the control plane. A person with a
|
|
481
|
+
seeded `dashboard_user` link is a team member: changing their `displayName`
|
|
482
|
+
or `profile.email` is refused with a 409 `ValidationError`, and a new
|
|
483
|
+
`profile` without `email` keeps the current one, as on the platform. Inspect via
|
|
418
484
|
`app.identities`; merge/archive are privileged setup operations, with only
|
|
419
485
|
test-only merge modeling exposed above.
|
|
420
486
|
- `session.user.identityId` is the platform-provisioned cross-app customer key.
|
package/dist/index.d.mts
CHANGED
|
@@ -105,6 +105,33 @@ 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" | "bearer";
|
|
111
|
+
/** The profile the claim connects the customer to. */
|
|
112
|
+
identityId: string;
|
|
113
|
+
/** The recorded contact this claim was raised against. Null for `bearer` only. */
|
|
114
|
+
kind: "email" | "phone" | null;
|
|
115
|
+
externalId: string | null;
|
|
116
|
+
returnTo: string | null;
|
|
117
|
+
/** The app's reference for the staff member, for assist/transfer claims. */
|
|
118
|
+
staffActorRef: string | null;
|
|
119
|
+
state: "pending" | "completed" | "expired" | "invalidated" | "refused";
|
|
120
|
+
refusalReason: string | null;
|
|
121
|
+
completedIdentityId: string | null;
|
|
122
|
+
/** Set by completeClaim; models the Clerk account that proved the contact. */
|
|
123
|
+
claimantClerkUserId: string | null;
|
|
124
|
+
/**
|
|
125
|
+
* Hosted claim link. Its path carries a one-time opaque handle, NOT the claim
|
|
126
|
+
* id — the platform never makes the customer's page derivable from an id an
|
|
127
|
+
* app already holds.
|
|
128
|
+
*/
|
|
129
|
+
claimUrl: string;
|
|
130
|
+
/** Whether the platform sent the link itself (only when `deliver` was asked). */
|
|
131
|
+
delivered: boolean | null;
|
|
132
|
+
expiresAt: string;
|
|
133
|
+
createdAt: string;
|
|
134
|
+
}
|
|
108
135
|
interface TestDirectory {
|
|
109
136
|
/** All identities, oldest first. */
|
|
110
137
|
all(): CapturedIdentity[];
|
|
@@ -126,6 +153,49 @@ interface TestDirectory {
|
|
|
126
153
|
* identity. The production integrations SDK does not expose merge operations.
|
|
127
154
|
*/
|
|
128
155
|
merge(sourceIdentityId: string, canonicalIdentityId: string): void;
|
|
156
|
+
/** Every profile-connection claim the app has opened, oldest first. */
|
|
157
|
+
claims(): CapturedIdentityClaim[];
|
|
158
|
+
/** A single claim by id, or undefined. */
|
|
159
|
+
claim(claimId: string): CapturedIdentityClaim | undefined;
|
|
160
|
+
/**
|
|
161
|
+
* Test setup only: turn on the org's staff-assistance opt-in. Without it,
|
|
162
|
+
* `claims.create` refuses `assist` and `transfer` with 403, like the platform.
|
|
163
|
+
*/
|
|
164
|
+
enableStaffAssist(enabled?: boolean): void;
|
|
165
|
+
/**
|
|
166
|
+
* Test setup only: set the project's bearer-link opt-in. It is on by default,
|
|
167
|
+
* like the platform; pass `false` to model an org that switched it off, and
|
|
168
|
+
* `claims.create({ flow: "bearer" })` is then refused with 403.
|
|
169
|
+
*/
|
|
170
|
+
enableBearerLinks(enabled?: boolean): void;
|
|
171
|
+
/**
|
|
172
|
+
* Test setup only: model the platform failing to send a claim link it was
|
|
173
|
+
* asked to deliver (a suppressed quota, a bounced address). `deliver: true`
|
|
174
|
+
* then comes back `delivered: false` with the `claimUrl` still valid, so an
|
|
175
|
+
* app's own fallback delivery can be tested.
|
|
176
|
+
*/
|
|
177
|
+
suppressClaimDelivery(enabled?: boolean): void;
|
|
178
|
+
/**
|
|
179
|
+
* Test setup only: allow a claim `returnTo` on this origin. The harness app's
|
|
180
|
+
* own origin is allowed by default; anything else is dropped silently, like
|
|
181
|
+
* the platform's allowed-origins check.
|
|
182
|
+
*/
|
|
183
|
+
allowReturnOrigin(origin: string): void;
|
|
184
|
+
/**
|
|
185
|
+
* Test setup only: model the customer finishing the platform-hosted claim —
|
|
186
|
+
* Clerk proof plus their confirmation. Flips the claimed contact to verified,
|
|
187
|
+
* or records `invalidated` (the contact was removed from that profile) or
|
|
188
|
+
* `refused` with `contact_owned` (someone else already proved it). Replaying a
|
|
189
|
+
* finished claim returns the recorded result unchanged.
|
|
190
|
+
*
|
|
191
|
+
* It does NOT attach a login link or move history: the platform's merge core
|
|
192
|
+
* is not modelled here, only the ownership decision the app can observe.
|
|
193
|
+
*/
|
|
194
|
+
completeClaim(claimId: string, params: {
|
|
195
|
+
clerkUserId: string;
|
|
196
|
+
}): CapturedIdentityClaim;
|
|
197
|
+
/** Test setup only: model reception or the platform refusing a claim. */
|
|
198
|
+
refuseClaim(claimId: string, reason: string): CapturedIdentityClaim;
|
|
129
199
|
clear(): void;
|
|
130
200
|
get count(): number;
|
|
131
201
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -105,6 +105,33 @@ 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" | "bearer";
|
|
111
|
+
/** The profile the claim connects the customer to. */
|
|
112
|
+
identityId: string;
|
|
113
|
+
/** The recorded contact this claim was raised against. Null for `bearer` only. */
|
|
114
|
+
kind: "email" | "phone" | null;
|
|
115
|
+
externalId: string | null;
|
|
116
|
+
returnTo: string | null;
|
|
117
|
+
/** The app's reference for the staff member, for assist/transfer claims. */
|
|
118
|
+
staffActorRef: string | null;
|
|
119
|
+
state: "pending" | "completed" | "expired" | "invalidated" | "refused";
|
|
120
|
+
refusalReason: string | null;
|
|
121
|
+
completedIdentityId: string | null;
|
|
122
|
+
/** Set by completeClaim; models the Clerk account that proved the contact. */
|
|
123
|
+
claimantClerkUserId: string | null;
|
|
124
|
+
/**
|
|
125
|
+
* Hosted claim link. Its path carries a one-time opaque handle, NOT the claim
|
|
126
|
+
* id — the platform never makes the customer's page derivable from an id an
|
|
127
|
+
* app already holds.
|
|
128
|
+
*/
|
|
129
|
+
claimUrl: string;
|
|
130
|
+
/** Whether the platform sent the link itself (only when `deliver` was asked). */
|
|
131
|
+
delivered: boolean | null;
|
|
132
|
+
expiresAt: string;
|
|
133
|
+
createdAt: string;
|
|
134
|
+
}
|
|
108
135
|
interface TestDirectory {
|
|
109
136
|
/** All identities, oldest first. */
|
|
110
137
|
all(): CapturedIdentity[];
|
|
@@ -126,6 +153,49 @@ interface TestDirectory {
|
|
|
126
153
|
* identity. The production integrations SDK does not expose merge operations.
|
|
127
154
|
*/
|
|
128
155
|
merge(sourceIdentityId: string, canonicalIdentityId: string): void;
|
|
156
|
+
/** Every profile-connection claim the app has opened, oldest first. */
|
|
157
|
+
claims(): CapturedIdentityClaim[];
|
|
158
|
+
/** A single claim by id, or undefined. */
|
|
159
|
+
claim(claimId: string): CapturedIdentityClaim | undefined;
|
|
160
|
+
/**
|
|
161
|
+
* Test setup only: turn on the org's staff-assistance opt-in. Without it,
|
|
162
|
+
* `claims.create` refuses `assist` and `transfer` with 403, like the platform.
|
|
163
|
+
*/
|
|
164
|
+
enableStaffAssist(enabled?: boolean): void;
|
|
165
|
+
/**
|
|
166
|
+
* Test setup only: set the project's bearer-link opt-in. It is on by default,
|
|
167
|
+
* like the platform; pass `false` to model an org that switched it off, and
|
|
168
|
+
* `claims.create({ flow: "bearer" })` is then refused with 403.
|
|
169
|
+
*/
|
|
170
|
+
enableBearerLinks(enabled?: boolean): void;
|
|
171
|
+
/**
|
|
172
|
+
* Test setup only: model the platform failing to send a claim link it was
|
|
173
|
+
* asked to deliver (a suppressed quota, a bounced address). `deliver: true`
|
|
174
|
+
* then comes back `delivered: false` with the `claimUrl` still valid, so an
|
|
175
|
+
* app's own fallback delivery can be tested.
|
|
176
|
+
*/
|
|
177
|
+
suppressClaimDelivery(enabled?: boolean): void;
|
|
178
|
+
/**
|
|
179
|
+
* Test setup only: allow a claim `returnTo` on this origin. The harness app's
|
|
180
|
+
* own origin is allowed by default; anything else is dropped silently, like
|
|
181
|
+
* the platform's allowed-origins check.
|
|
182
|
+
*/
|
|
183
|
+
allowReturnOrigin(origin: string): void;
|
|
184
|
+
/**
|
|
185
|
+
* Test setup only: model the customer finishing the platform-hosted claim —
|
|
186
|
+
* Clerk proof plus their confirmation. Flips the claimed contact to verified,
|
|
187
|
+
* or records `invalidated` (the contact was removed from that profile) or
|
|
188
|
+
* `refused` with `contact_owned` (someone else already proved it). Replaying a
|
|
189
|
+
* finished claim returns the recorded result unchanged.
|
|
190
|
+
*
|
|
191
|
+
* It does NOT attach a login link or move history: the platform's merge core
|
|
192
|
+
* is not modelled here, only the ownership decision the app can observe.
|
|
193
|
+
*/
|
|
194
|
+
completeClaim(claimId: string, params: {
|
|
195
|
+
clerkUserId: string;
|
|
196
|
+
}): CapturedIdentityClaim;
|
|
197
|
+
/** Test setup only: model reception or the platform refusing a claim. */
|
|
198
|
+
refuseClaim(claimId: string, reason: string): CapturedIdentityClaim;
|
|
129
199
|
clear(): void;
|
|
130
200
|
get count(): number;
|
|
131
201
|
}
|