Skip to content

test(wallets): cover the SIGNER_LIMIT_EXCEEDED regression in the smoke suite - #2133

Open
luans-qa wants to merge 10 commits into
mainfrom
luan/qa-91-signer-limit-regression
Open

luans-qa wants to merge 10 commits into
mainfrom
luan/qa-91-signer-limit-regression

Conversation

@luans-qa

@luans-qa luans-qa commented Oct 1, 2026

Copy link
Copy Markdown
Contributor

Description

PR #2049 removed the SDK's client-side signer-count pre-check and relied entirely on the backend's enforcement. That exposed that the long-lived smoke-test wallet had silently accumulated well over the signer cap (root cause: DeviceRecoveryService#resolveStaleDeviceSignerLocator only prunes a stale device signer when exactly one candidate is unambiguous — with 2+ it prunes nothing), which broke Wallet Smoke › transfers funds with SIGNER_LIMIT_EXCEEDED.

PR #2056 fixed CI by rotating the wallet identity every run (TESTS_WALLET_EMAIL_SUFFIX: smoke-${{ github.run_id }}), but that only masked the issue — a fresh wallet per run never reaches the cap, so a real enforcement regression would go unnoticed again.

This PR adds the two regression scenarios instead:

  • signer-limit-regression.spec.ts re-authenticates into the original smoke wallet (the one that regressed, now well past the cap) via a new emailOverride on TestConfiguration, and transfers funds — asserting the backend keeps grandfathering wallets that were already over the cap before it existed.
  • signer-limit-cap.spec.ts creates a brand-new wallet and adds external-wallet signers one at a time (via a server key, @crossmint/wallets-sdk's WalletsApiClient directly) until the backend rejects one — asserting that rejection is specifically SIGNER_LIMIT_EXCEEDED, so the cap itself stays enforced on new wallets.

Together they cover both directions of the regression: old wallets must keep working, new wallets must still be capped.

Needs the TESTS_CROSSMINT_SERVER_API_KEY_SMOKE_TESTS secret (now set) — a staging server key, since adding a signer after wallet creation needs server-only usage, unlike the suite's existing client key.

Linear: QA-91.

Test plan

  • signer-limit-regression.spec.ts: full Playwright UI run against the existing over-the-cap wallet; passes if the transfer completes and no response ever carries SIGNER_LIMIT_EXCEEDED.
  • signer-limit-cap.spec.ts: no browser — direct SDK/API calls against staging; passes once a registerSigner call returns code: "SIGNER_LIMIT_EXCEEDED" (discovered dynamically, not hardcoded to today's cap value).
  • Typechecked (tsc --noEmit) and biome check clean against all changed/added files.
  • Verification is CI — this worktree has no node_modules installed (pnpm install needs a token this environment doesn't have), so there is no local Playwright run backing this.

Package updates

None — tests only, no package changes.

…e suite (QA-91)

PR #2049 removed the SDK's client-side signer-count pre-check, relying on the
backend's enforcement, which exposed that the long-lived smoke wallet had
accumulated well over the signer cap (prior device-signer accumulation bug).
PR #2056 worked around it by rotating the wallet identity every run, masking
the regression instead of covering it.

Add two scenarios instead of masking it further:
- signer-limit-regression.spec.ts re-authenticates into the original
  over-the-cap wallet (fixed "e2e" suffix, from before the per-run rotation)
  and transfers funds, asserting the backend keeps grandfathering it.
- signer-limit-cap.spec.ts seeds a brand new wallet with external-wallet
  signers via a server key until the backend rejects one, asserting that
  rejection is SIGNER_LIMIT_EXCEEDED, so the cap itself stays enforced.

TestConfiguration gains an optional emailOverride so a test can authenticate
into a specific wallet instead of the suite's rotating-per-run identity.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@changeset-bot

changeset-bot Bot commented Oct 1, 2026 •

Copy link
Copy Markdown

⚠️ No Changeset found

Latest commit: 606c5c2

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@greptile-apps

greptile-apps Bot commented Oct 1, 2026 •

Copy link
Copy Markdown
Contributor

RetriggerConfidence Score: 3/5

[Medium risk] Adds regression tests for signer limit enforcement.

The PR is not ready to merge because the legacy regression test targets the wrong wallet and can fail during signer confirmation.

Reviews (1) · Last reviewed commit: "test(wallets): cover the SIGNER_LIMIT_EX..."

Comment thread apps/wallets/quickstart-devkit/tests/shared/constants/globalConstants.ts Outdated
Comment thread apps/wallets/quickstart-devkit/tests/smoke/specs/wallets/signer-limit-cap.spec.ts Outdated
@luans-qa luans-qa self-assigned this Oct 1, 2026
@github-actions

github-actions Bot commented Oct 1, 2026 •

Copy link
Copy Markdown
Contributor

🔥 Smoke Test Results

❌ Status: Failed

Statistics

  • Total Tests: 7
  • Passed: 5 ✅
  • Failed: 2 ❌
  • Skipped: 0
  • Duration: 5.95 min

Test Details


This is a non-blocking smoke test. Full regression tests run separately.

luans-qa and others added 9 commits October 1, 2026 08:07
…ation email (QA-91)

handleEmailPhoneSignerFlow resolved the OTP email via getEmailForSigner directly,
ignoring testConfig.emailOverride, so signer-limit-regression.spec.ts logged into
the legacy wallet correctly but then searched for the transaction-confirmation
OTP under the rotating per-run email and timed out. Thread emailOverride through
handleSignerConfirmation/transferFunds so the confirmation step checks the same
inbox as the login.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…QA-91)

It only used validateAPITestConfig to check TESTS_CROSSMINT_API_KEY, which
this test never reads — it creates its own wallet entirely through the
server key. That check tied it to whichever project the client key
belongs to for no reason, which matters now that scenario A and scenario B
are meant to run against two different projects.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Scenario A's project is staying grandfathered (its cap gets raised in
ConfigCat); scenario B now creates its wallet against a separate project
so it tests the cap-enforcement mechanism itself without depending on, or
contributing load to, the project behind the legacy wallet.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
One-off debug step to answer "how many signers does it have right now"
without putting any key in chat. Will revert once read.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…nt in CI"

Debug step used the wrong key type (client key; the GET /:walletLocator
route needs a server key) and printed a bogus count. The real number came
from the actual production error instead.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…cap-test wallet (QA-91)

Greptile review on #2133:
- getLegacySmokeWalletEmail used the "e2e" suffix, but smoke-tests.yml
  pinned TESTS_WALLET_EMAIL_SUFFIX to "smoke" before PR #2056 introduced
  per-run rotation (see smoke-tests.yml's git history). The regression test
  was authenticating into a different, unrelated wallet.
- signer-limit-cap.spec.ts created a wallet with a Date.now()-based owner
  every run, leaving an ever-growing set of abandoned staging wallets.
  owner already guarantees idempotency server-side (getOrCreateWallet), so
  a fixed owner reuses the same wallet and plateaus at the project's cap
  instead of growing forever.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…run (QA-91)

Confirmed the removal can't be scripted via a plain API/SDK call: both
addSigner and removeSigner route through withRecoverySigner, and for an
email recovery method the approval is a TEE handshake (ncs-signer.ts
encrypts the OTP client-side and posts it to the signer iframe) — there is
no REST endpoint to replicate that from Node.

Adds a Remove button to the devkit's existing Permissions component (it
already lists delegated signers and holds a live wallet instance; it was
missing only the remove affordance). The regression test captures the
locator of whichever device signer the transfer step silently registers,
reloads to force a fresh signer list, and removes that one signer through
the same UI + OTP confirmation flow already used elsewhere in the suite.

Net effect: this wallet's signer count stays flat across runs instead of
growing by one every time, so a modest cap raise stays correct going
forward instead of needing to be re-raised periodically.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
The legacy-wallet regression test now does a second OTP round-trip to
clean up the signer it adds, pushing the suite past the previous 10-minute
budget — the last run was killed by the job timeout mid-test rather than
by an actual failure.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…llet (QA-91)

Placed under tests/sdk, a Playwright project defined in playwright.config.ts
but never invoked by any workflow, so it never runs as part of the regular
smoke suite and never re-seeds on a normal CI run.

Logs into test-email-cap-fixture@<mailosaurServerId>.mailosaur.net and adds
external-wallet delegated signers through the existing Permissions UI (the
same one the QA-91 cleanup step uses) until it holds 1 admin + 15 delegated
signers. Each add needs its own OTP confirmation (no server-key shortcut —
approval for an email recovery method is a TEE handshake, not a scriptable
HTTP call), so this is meant to be run once via
`pnpm exec playwright test --project=sdk --grep "cap-fixture"`, not as a
recurring test. signer-limit-regression.spec.ts will be pointed at this
wallet once it is seeded.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant