Skip to content

test(tests-bypass-email): Use mocked email on testing #318

Draft
christopherferreira9 wants to merge 18 commits into
mainfrom
cferreira/avoid-temp-email-dependency
Draft

test(tests-bypass-email): Use mocked email on testing #318
christopherferreira9 wants to merge 18 commits into
mainfrom
cferreira/avoid-temp-email-dependency

Conversation

@christopherferreira9

@christopherferreira9 christopherferreira9 commented Jul 8, 2026

Copy link
Copy Markdown

Summary

Previously, all E2E and integration tests required hitting Guerrilla Mail and Turnkey to complete OTP flows, making them brittle and slow due to external rate limits. This PR introduces a USE_REAL_EMAIL flag that switches tests between real and mocked backends — PR runs use mocked mode (no external dependencies), and a new daily scheduled workflow runs with real email to catch live-service regressions.

SDK side changes


packages/core/src/core/createZeroDevWalletCore.ts

Added dangerouslyOverrideOtpSignerPublicKey?: string to ZeroDevWalletConfigCore. When present, it's forwarded to encryptOtpAttempt as dangerouslyOverrideSignerPublicKey, bypassing the production-pinned TLS Fetcher key check. The spread conditional (...(config.x && { ... })) ensures the property is only passed when explicitly set — it never appears in a normal integrator's call.

packages/react/src/core/connector.ts

Same option added to ConnectorCoreParams. It's forwarded into createZeroDevWallet() using the same conditional spread — so an integrator who doesn't set it gets no property at all, not undefined.

Both additions are marked @internal with a "Never set this in production" JSDoc comment.


Tests added

packages/core/src/utils/encryptOtpAttempt.test.ts — new test: generates a fresh ephemeral key pair (not the production pinned key), builds a test bundle signed with it, and asserts that calling encryptOtpAttempt without the override throws does not match pinned signing key. This proves the default path enforces the production key.

packages/react/src/connector.test.ts — two new tests in a dangerouslyOverrideOtpSignerPublicKey security guard describe block:

  • A connector created without the option must not forward it to createZeroDevWallet — guards against accidental propagation.
  • A connector explicitly configured with it must forward it correctly — proves the intentional path works.

Workflow changes


ci.yml

  1. Renamed test job from "Test" → "Unit Tests"
  2. Extracted integration and E2E jobs into a reusable workflow (test.yml). The two inline jobs (integration-test, e2e-test) were removed from ci.yml and replaced with a single call-tests job that delegates to test.yml. This also drops the dependency on the build job for parallel execution in favor of faster feedback from tests.
  3. Added all-checks-pass gate job - a job that acts as a single source of truth for what jobs require a passing status in order for the merge to be possible. This reduces management on the repository settings as we can remove and add mandatory jobs through code. The repository then only needs to have all-checks-pass passing.

test.yml - new workflow

Reusable workflow extracted from ci.yml. It contains:

  • Integration Tests — runs pnpm test:integration
  • Browser E2E Tests — builds the SDK, starts the demo app, runs Playwright
    Both of these workflows accept a newly added input called use_real_email with the default value being true. ci.yml then sends use_real_email as false in order to remove the dependency on the email service on PRs and main runs (we may consider using the real email service for main runs - to be discussed).

test.yml - new workflow

Runs test.yml daily at 08:00 UTC with use_real_email: true (real Guerrilla Mail + Turnkey). Also triggerable manually via workflow_dispatch with the option to toggle email mode.

Concerns


1. Mock fidelity
As the payload returned by the backend can change, fixtures can become stale and results would not be reliable. A fixture-shape.test.ts test has been created to catch this drift on real email mode. On a future development, the PR t update the fixture can be made automatically on failure.

2. The rejection test tests the mock, not the logic
"should reject an invalid OTP code" in mock mode stacks setupNodeMocks({ rejectOtpVerify: true }), which unconditionally returns HTTP 400 regardless of what was sent. It doesn't verify that a semantically wrong OTP (wrong code, wrong encryption) is actually detected — it verifies that an HTTP 400 throws an exception. This test is still valued as we have UI checks - we should rename this test if we intend to keep it.

3. dangerouslyOverrideSignerPublicKey is a scattered pattern
Every integration test call site that invokes encryptOtpAttempt directly must manually pass dangerouslyOverrideSignerPublicKey: isRealEmail() ? undefined : MOCK_OTP_SIGNER_PUBLIC_KEY. This is currently in three places. Anyone writing a new integration test that calls encryptOtpAttempt directly will get a cryptic key-mismatch error in mock mode until they discover the pattern. This can be mitigated.

4. Fork PRs have no integration test coverage at all
call-tests is skipped on fork PRs (no secret access). all-checks-pass treats skipped as OK, so external contributors get zero integration or E2E feedback. A fork PR that breaks the OTP flow in a way that unit tests don't catch can pass all required checks. I personally think this is acceptable.

5. otp-init-shape.test.ts is permanently skipped in mock mode
This test exists specifically to validate the shape of the real backend's OTP init response and verify the production pinned key. Skipping it in mock mode means the CI run that runs on every PR never exercises it. It only runs in the daily scheduled run. If the production key rotates or the bundle shape changes, a PR that breaks the integration won't be caught at PR time.

6. Passkeys can't be tested when the mock mode is enabled

@vercel

vercel Bot commented Jul 8, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
zerodev-signer-demo Ready Ready Preview, Comment Jul 9, 2026 1:54pm

Request Review

@pkg-pr-new

pkg-pr-new Bot commented Jul 8, 2026

Copy link
Copy Markdown

Open in StackBlitz

npm i https://pkg.pr.new/zerodevapp/zerodev-wallet-sdk/@zerodev/wallet-core@318
npm i https://pkg.pr.new/zerodevapp/zerodev-wallet-sdk/@zerodev/wallet-react@318
npm i https://pkg.pr.new/zerodevapp/zerodev-wallet-sdk/@zerodev/react-ui@318
npm i https://pkg.pr.new/zerodevapp/zerodev-wallet-sdk/@zerodev/wallet-react-ui@318

commit: 26afb76

@christopherferreira9 christopherferreira9 changed the title Cferreira/avoid temp email dependency test(tests-bypass-email): Use mocked email on testing Jul 9, 2026
Comment thread .github/workflows/ci.yml

test:
name: Test
name: Unit Tests

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Renamed for a clear read on the pipeline since it hasn't been extracted to the dedicated test workflow.

Comment thread .github/workflows/ci.yml
use_real_email: false
secrets: inherit

all-checks-pass:

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

single job to have a mandatory success status. Easier to maintain through code.


on:
schedule:
- cron: '0 8 * * *'

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Currently scheduled to run daily using using real email keeping the original behavior and reducing the amount of flakiness due to rate limiting from the email service probider.

# Pin the bundler/paymaster to staging so the AA stack matches the
# staging KMS above (the SDK default host is prod).
NEXT_PUBLIC_ZERODEV_AA_HOST: https://staging-meta-aa-provider.onrender.com
USE_REAL_EMAIL: ${{ inputs.use_real_email }}

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Demo is built with this env variable which is then passed downstream to the wagmi options.

const otpCode = extractOtpCodeFromMagicLinkUrl(emailContent)
// Step 6: Resolve OTP code — use fixture value in mock mode, poll inbox in real mode
const otpCode =
magicLinkSession.otpCode ??

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

when in mock mode, otpCode comes directly from the fixture. The fixture handles the real vs mocked email logic.

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