@stardeck-customer-apps/testing 0.17.0 → 0.19.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
@@ -350,6 +350,9 @@ externalId })` helper models a trusted platform/channel link, and
350
350
  platform turning one down. `completeClaim` flips the claimed contact to
351
351
  `verified`, or records `invalidated` when the contact is no longer on that
352
352
  profile and `refused` / `contact_owned` when someone else already proved it.
353
+ A contact the claimant already proved on their own login profile is not
354
+ "someone else": the claim completes and the one verified copy ends up on the
355
+ claimed profile.
353
356
  Completing a finished claim replays the recorded result. Completion attaches
354
357
  the claimant's login to the profile (moving it off any profile it was on), but
355
358
  does not move history — the platform's merge core is not modelled.
@@ -368,7 +371,11 @@ externalId })` helper models a trusted platform/channel link, and
368
371
  back `refused` / `owned_elsewhere`. That refusal is the one a leaked bearer
369
372
  link runs into, so test it. A profile that holds an email or phone is refused
370
373
  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
374
+ the link is used completes as `refused` / `protected`. The same holds for a
375
+ profile another one was merged into (`app.identities.merge(other, target)`):
376
+ `create` is a 409 and an outstanding link completes as `refused` /
377
+ `protected`, because a bearer link hands over one visit's profile, never
378
+ history staff folded into it. A link still
372
379
  outstanding when `.enableBearerLinks(false)` runs completes as `refused` /
373
380
  `expired`. People seeded with only a `dashboard_user` link (team members) are
374
381
  left out of `identities.list()` and `search()`, as on the platform.
package/dist/index.d.mts CHANGED
@@ -185,7 +185,9 @@ interface TestDirectory {
185
185
  * Test setup only: model the customer finishing the platform-hosted claim —
186
186
  * Clerk proof plus their confirmation. Flips the claimed contact to verified,
187
187
  * or records `invalidated` (the contact was removed from that profile) or
188
- * `refused` with `contact_owned` (someone else already proved it). Replaying a
188
+ * `refused` with `contact_owned` (someone else already proved it — the
189
+ * claimant's own login profile does not count; its verified copy is dropped
190
+ * so the claimed profile holds the only one). Replaying a
189
191
  * finished claim returns the recorded result unchanged.
190
192
  *
191
193
  * It does NOT attach a login link or move history: the platform's merge core
package/dist/index.d.ts CHANGED
@@ -185,7 +185,9 @@ interface TestDirectory {
185
185
  * Test setup only: model the customer finishing the platform-hosted claim —
186
186
  * Clerk proof plus their confirmation. Flips the claimed contact to verified,
187
187
  * or records `invalidated` (the contact was removed from that profile) or
188
- * `refused` with `contact_owned` (someone else already proved it). Replaying a
188
+ * `refused` with `contact_owned` (someone else already proved it — the
189
+ * claimant's own login profile does not count; its verified copy is dropped
190
+ * so the claimed profile holds the only one). Replaying a
189
191
  * finished claim returns the recorded result unchanged.
190
192
  *
191
193
  * It does NOT attach a login link or move history: the platform's merge core
package/dist/index.js CHANGED
@@ -867,6 +867,9 @@ function isCurrentTeamMember(identityId) {
867
867
  function hasRecordedContact(identityId) {
868
868
  return linksFor(identityId).some((link) => link.kind === "email" || link.kind === "phone");
869
869
  }
870
+ function hasAbsorbedIdentity(identityId) {
871
+ return [...state.identities.values()].some((identity) => identity.mergedIntoId === identityId);
872
+ }
870
873
  function handleList(request) {
871
874
  const typeParam = new URL(request.url).searchParams.get("type");
872
875
  const type = typeParam === "person" || typeParam === "account" ? typeParam : void 0;
@@ -1217,6 +1220,9 @@ function createBearerClaim(body, returnTo) {
1217
1220
  if (hasRecordedContact(identity.id)) {
1218
1221
  return failure("identity has an email or phone on record; send an invite claim instead", 409);
1219
1222
  }
1223
+ if (hasAbsorbedIdentity(identity.id)) {
1224
+ return failure("another profile was merged into this identity; reception must connect it", 409);
1225
+ }
1220
1226
  return mintClaim({
1221
1227
  flow: "bearer",
1222
1228
  identityId: identity.id,
@@ -1415,8 +1421,12 @@ function completeClaim(claimId, params) {
1415
1421
  claim.state = "invalidated";
1416
1422
  return claim;
1417
1423
  }
1424
+ const claimantIdentityId = state.identityLinks.find(
1425
+ (link) => link.kind === "project_auth_user" && link.externalId === params.clerkUserId
1426
+ )?.identityId;
1418
1427
  const otherVerified = rows.find((link) => link.verified && link.identityId !== claim.identityId);
1419
- if (otherVerified) {
1428
+ const ownProof = otherVerified !== void 0 && otherVerified.identityId === claimantIdentityId;
1429
+ if (otherVerified && !ownProof) {
1420
1430
  claim.state = "refused";
1421
1431
  claim.refusalReason = "contact_owned";
1422
1432
  return claim;
@@ -1426,6 +1436,7 @@ function completeClaim(claimId, params) {
1426
1436
  claim.refusalReason = "owned_elsewhere";
1427
1437
  return claim;
1428
1438
  }
1439
+ if (ownProof) state.identityLinks = state.identityLinks.filter((link) => link !== otherVerified);
1429
1440
  target.verified = true;
1430
1441
  connectLogin(claim, params.clerkUserId);
1431
1442
  claim.state = "completed";
@@ -1458,7 +1469,7 @@ function completeBearerClaim(claim, clerkUserId) {
1458
1469
  claim.state = "invalidated";
1459
1470
  return claim;
1460
1471
  }
1461
- const refusal = !state.identityBearerLinksEnabled ? "expired" : hasRecordedContact(claim.identityId) ? "protected" : ownedByAnotherLogin(claim.identityId, clerkUserId) ? "owned_elsewhere" : null;
1472
+ const refusal = !state.identityBearerLinksEnabled ? "expired" : hasRecordedContact(claim.identityId) || hasAbsorbedIdentity(claim.identityId) ? "protected" : ownedByAnotherLogin(claim.identityId, clerkUserId) ? "owned_elsewhere" : null;
1462
1473
  if (refusal) {
1463
1474
  claim.state = "refused";
1464
1475
  claim.refusalReason = refusal;
package/dist/index.mjs CHANGED
@@ -818,6 +818,9 @@ function isCurrentTeamMember(identityId) {
818
818
  function hasRecordedContact(identityId) {
819
819
  return linksFor(identityId).some((link) => link.kind === "email" || link.kind === "phone");
820
820
  }
821
+ function hasAbsorbedIdentity(identityId) {
822
+ return [...state.identities.values()].some((identity) => identity.mergedIntoId === identityId);
823
+ }
821
824
  function handleList(request) {
822
825
  const typeParam = new URL(request.url).searchParams.get("type");
823
826
  const type = typeParam === "person" || typeParam === "account" ? typeParam : void 0;
@@ -1168,6 +1171,9 @@ function createBearerClaim(body, returnTo) {
1168
1171
  if (hasRecordedContact(identity.id)) {
1169
1172
  return failure("identity has an email or phone on record; send an invite claim instead", 409);
1170
1173
  }
1174
+ if (hasAbsorbedIdentity(identity.id)) {
1175
+ return failure("another profile was merged into this identity; reception must connect it", 409);
1176
+ }
1171
1177
  return mintClaim({
1172
1178
  flow: "bearer",
1173
1179
  identityId: identity.id,
@@ -1366,8 +1372,12 @@ function completeClaim(claimId, params) {
1366
1372
  claim.state = "invalidated";
1367
1373
  return claim;
1368
1374
  }
1375
+ const claimantIdentityId = state.identityLinks.find(
1376
+ (link) => link.kind === "project_auth_user" && link.externalId === params.clerkUserId
1377
+ )?.identityId;
1369
1378
  const otherVerified = rows.find((link) => link.verified && link.identityId !== claim.identityId);
1370
- if (otherVerified) {
1379
+ const ownProof = otherVerified !== void 0 && otherVerified.identityId === claimantIdentityId;
1380
+ if (otherVerified && !ownProof) {
1371
1381
  claim.state = "refused";
1372
1382
  claim.refusalReason = "contact_owned";
1373
1383
  return claim;
@@ -1377,6 +1387,7 @@ function completeClaim(claimId, params) {
1377
1387
  claim.refusalReason = "owned_elsewhere";
1378
1388
  return claim;
1379
1389
  }
1390
+ if (ownProof) state.identityLinks = state.identityLinks.filter((link) => link !== otherVerified);
1380
1391
  target.verified = true;
1381
1392
  connectLogin(claim, params.clerkUserId);
1382
1393
  claim.state = "completed";
@@ -1409,7 +1420,7 @@ function completeBearerClaim(claim, clerkUserId) {
1409
1420
  claim.state = "invalidated";
1410
1421
  return claim;
1411
1422
  }
1412
- const refusal = !state.identityBearerLinksEnabled ? "expired" : hasRecordedContact(claim.identityId) ? "protected" : ownedByAnotherLogin(claim.identityId, clerkUserId) ? "owned_elsewhere" : null;
1423
+ const refusal = !state.identityBearerLinksEnabled ? "expired" : hasRecordedContact(claim.identityId) || hasAbsorbedIdentity(claim.identityId) ? "protected" : ownedByAnotherLogin(claim.identityId, clerkUserId) ? "owned_elsewhere" : null;
1413
1424
  if (refusal) {
1414
1425
  claim.state = "refused";
1415
1426
  claim.refusalReason = refusal;
package/dist/setup.js CHANGED
@@ -769,6 +769,9 @@ function isCurrentTeamMember(identityId) {
769
769
  function hasRecordedContact(identityId) {
770
770
  return linksFor(identityId).some((link) => link.kind === "email" || link.kind === "phone");
771
771
  }
772
+ function hasAbsorbedIdentity(identityId) {
773
+ return [...state.identities.values()].some((identity) => identity.mergedIntoId === identityId);
774
+ }
772
775
  function handleList(request) {
773
776
  const typeParam = new URL(request.url).searchParams.get("type");
774
777
  const type = typeParam === "person" || typeParam === "account" ? typeParam : void 0;
@@ -1119,6 +1122,9 @@ function createBearerClaim(body, returnTo) {
1119
1122
  if (hasRecordedContact(identity.id)) {
1120
1123
  return failure("identity has an email or phone on record; send an invite claim instead", 409);
1121
1124
  }
1125
+ if (hasAbsorbedIdentity(identity.id)) {
1126
+ return failure("another profile was merged into this identity; reception must connect it", 409);
1127
+ }
1122
1128
  return mintClaim({
1123
1129
  flow: "bearer",
1124
1130
  identityId: identity.id,
package/dist/setup.mjs CHANGED
@@ -745,6 +745,9 @@ function isCurrentTeamMember(identityId) {
745
745
  function hasRecordedContact(identityId) {
746
746
  return linksFor(identityId).some((link) => link.kind === "email" || link.kind === "phone");
747
747
  }
748
+ function hasAbsorbedIdentity(identityId) {
749
+ return [...state.identities.values()].some((identity) => identity.mergedIntoId === identityId);
750
+ }
748
751
  function handleList(request) {
749
752
  const typeParam = new URL(request.url).searchParams.get("type");
750
753
  const type = typeParam === "person" || typeParam === "account" ? typeParam : void 0;
@@ -1095,6 +1098,9 @@ function createBearerClaim(body, returnTo) {
1095
1098
  if (hasRecordedContact(identity.id)) {
1096
1099
  return failure("identity has an email or phone on record; send an invite claim instead", 409);
1097
1100
  }
1101
+ if (hasAbsorbedIdentity(identity.id)) {
1102
+ return failure("another profile was merged into this identity; reception must connect it", 409);
1103
+ }
1098
1104
  return mintClaim({
1099
1105
  flow: "bearer",
1100
1106
  identityId: identity.id,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stardeck-customer-apps/testing",
3
- "version": "0.17.0",
3
+ "version": "0.19.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",
@@ -47,9 +47,9 @@
47
47
  "scripts": {
48
48
  "build": "tsup src/index.ts src/config.ts src/setup.ts src/next/headers-shim.ts src/server-only-shim.ts --format cjs,esm --dts --no-splitting",
49
49
  "dev": "tsup src/index.ts src/config.ts src/setup.ts src/next/headers-shim.ts src/server-only-shim.ts --format cjs,esm --dts --no-splitting --watch",
50
- "format": "prettier --write . && eslint . --fix",
50
+ "format": "prettier --write . && oxlint . --fix",
51
51
  "typecheck": "tsc --noEmit && tsc -p tsconfig.test.json",
52
- "lint": "eslint src/",
52
+ "lint": "oxlint src/",
53
53
  "test": "vitest run",
54
54
  "test:watch": "vitest"
55
55
  },
@@ -94,7 +94,6 @@
94
94
  }
95
95
  },
96
96
  "devDependencies": {
97
- "@eslint/js": "^9.0.0",
98
97
  "@neondatabase/serverless": "^1.0.0",
99
98
  "@stardeck-customer-apps/data-store-sdk": "*",
100
99
  "@stardeck-customer-apps/edge-sdk": "*",
@@ -109,7 +108,6 @@
109
108
  "next": "^16.3.0",
110
109
  "tsup": "^8.0.0",
111
110
  "typescript": "^5.0.0",
112
- "typescript-eslint": "^8.0.0",
113
111
  "vitest": "^3.2.4",
114
112
  "@stardeck-customer-apps/starlens": "*",
115
113
  "zod": "^4.3.5"