Skip to content

test: cover runAuthLogin transports, org threading, and cap handling - #561

Merged
ndycode merged 3 commits into
mainfrom
claude/audit-43-login-flow-tests
Jun 11, 2026
Merged

test: cover runAuthLogin transports, org threading, and cap handling#561
ndycode merged 3 commits into
mainfrom
claude/audit-43-login-flow-tests

Conversation

@ndycode

@ndycode ndycode commented Jun 10, 2026

Copy link
Copy Markdown
Owner

Summary

Third suite in the direct-coverage push for the phase-4-extracted login machinery (siblings: #559 login-oauth, #560 login-menu-actions; all independent, based on main). lib/codex-manager/login-flow.ts — the login command's control loop — had only indirect CLI coverage. This adds test/login-flow.test.ts (12 tests) driving runAuthLogin end to end.

The mocking stays at the effectful seams only: storage loads, the sign-in flow, persistence, and the interactive prompts. The real parseAuthLoginArgs and the real isOAuthCancellation predicate run, and a small accountsOnDisk holder makes persistAccountPool actually grow what loadAccounts returns, so the post-persist count logic is exercised honestly. TTY flags are forced false (and restored) so prompts hit their deterministic fallbacks.

What the tests pin

Argument handling: --org without a value fails with the usage message and exit 1 before touching storage; --help exits 0 without starting the flow.

Explicit transports (the script-safety contracts):

  • --device-auth with saved accounts bypasses the dashboard entirely, and a cancellation exits 0 — it must not fall back to the dashboard and trap a script in a sign-in loop.
  • A non-cancellation failure exits 1 with Login failed: <message> and persists nothing.
  • The MAX_ACCOUNTS cap exits 0 without offering another sign-in.

Issue #491/#512 semantics:

  • --org org_team is threaded as an explicit argument into resolveAccountSelection — no process.env mutation.
  • inserted / updated / rebound persist outcomes produce their distinct summary lines (same-email logins don't claim a new slot).

Multi-account flow: answering "add another" runs the second sign-in with forceNewLogin: true so it cannot silently reuse the first account's browser session, and the totals advance.

Onboarding edges: browser-launch suppression promotes the manual transport; named-backup discovery failures warn-and-continue on real errors (EACCES) but stay silent on ENOENT.

Validation

  • vitest run test/login-flow.test.ts — 12/12 passing
  • npm run typecheck — clean
  • npx eslint test/login-flow.test.ts --max-warnings=0 — clean

https://claude.ai/code/session_01XNtnkLbBiXZxfQQYLMpucB


Generated by Claude Code

note: greptile review for oc-chatgpt-multi-auth. cite files like lib/foo.ts:123. confirm regression tests + windows concurrency/token redaction coverage.

Greptile Summary

adds test/login-flow.test.ts — 12 vitest tests driving runAuthLogin end-to-end as the third suite in the phase-4 direct-coverage push. mocking stays at effectful seams only; the real parseAuthLoginArgs and isOAuthCancellation run, and a small accountsOnDisk holder makes persistAccountPool grow what loadAccounts returns.

Confidence Score: 5/5

test-only addition with no production code changes; all 12 tests pass, typecheck is clean, and the suite correctly isolates effectful seams.

the change is a new test file only. mocking strategy is sound, the real predicates (parseAuthLoginArgs, isOAuthCancellation) run against proper fixtures, and the accountsOnDisk holder exercises the post-persist count logic honestly. no production paths are modified.

no files require special attention.

Important Files Changed

Filename Overview
test/login-flow.test.ts new 323-line vitest suite for runAuthLogin; 12 tests covering args, transport routing, org threading, cap handling, and onboarding edges — all passing; previous review concerns (runSignInFlowMock default, promptOAuthSignInMode dependency) are now addressed with a beforeEach default and an explicit comment block

Sequence Diagram

sequenceDiagram
    participant T as test
    participant R as runAuthLogin
    participant P as parseAuthLoginArgs (real)
    participant L as loadAccountsMock
    participant S as runSignInFlowMock
    participant O as isOAuthCancellation (real)
    participant A as resolveAccountSelectionMock
    participant PR as persistAccountPoolMock
    participant SY as syncSelectionToCodexMock

    T->>R: runAuthLogin(args, deps)
    R->>P: parseAuthLoginArgs(args)
    P-->>R: "{ok, options} or {ok:false, reason}"
    alt parse error / --help
        R-->>T: exit 0 or 1 (no storage touched)
    end
    R->>L: loadAccounts() [x3 per loop]
    L-->>R: accountsOnDisk
    R->>S: runSignInFlow(forceNewLogin, mode)
    S-->>R: TokenResult
    R->>O: isOAuthCancellation(result)
    O-->>R: true → exit 0 Cancelled. / false → exit 1 Login failed:
    R->>A: resolveAccountSelection(token, org?)
    A-->>R: RESOLVED
    R->>PR: persistAccountPool([RESOLVED], false)
    PR-->>R: "inserted|updated|rebound"
    R->>SY: syncSelectionToCodex(RESOLVED)
    R-->>T: exit 0 + log outcome message
Loading

Fix All in Codex

Prompt To Fix All With AI
Fix the following 1 code review issue. Work through them one at a time, proposing concise fixes.

---

### Issue 1 of 1
test/login-flow.test.ts:87-90
**Minimal `TOKEN_SUCCESS` fixture silently relies on full mock coverage**

`TOKEN_SUCCESS = { type: "success" }` omits every token field (`access`, `refresh`, `expires`, `idToken`). the real `resolveAccountSelection` calls `getAccountIdCandidates(tokens.access, tokens.idToken)` and `extractAccountEmail(result.access, result.idToken)` — both receive `undefined` if the mock is ever accidentally removed or replaced. since `vi.fn()` doesn't enforce the `TokenSuccess` type at the call site, typescript won't catch this either. if a future test drops the `resolveAccountSelectionMock` or `persistAccountPoolMock` setup, it will crash at a confusing depth rather than at a missing-mock assertion. adding the minimum required fields (`access: ""`, `refresh: ""`, `expires: 0`) or exporting a typed constant would make the fixture self-documenting and safe under partial de-mocking.

Reviews (2): Last reviewed commit: "test: add device-auth conflict case and ..." | Re-trigger Greptile

Direct coverage for the phase-4-extracted login-flow.ts control loop,
exercised through runAuthLogin with the real parseAuthLoginArgs and the
real isOAuthCancellation predicate (only the effectful seams are
mocked: storage loads, sign-in flow, persistence, prompts):

- --org without a value fails with usage; --help exits 0 untouched
- explicit transports (--device-auth/--manual) bypass the dashboard
  and a cancellation exits 0 instead of falling back into a sign-in
  loop; non-cancel failures exit 1 with the message
- --org is threaded as an explicit argument into
  resolveAccountSelection (issue #491, no process.env mutation)
- inserted/updated/rebound persist outcomes produce the right summary
  lines (issue #512 same-email semantics)
- the MAX_ACCOUNTS cap exits without offering another sign-in
- add-another runs the second sign-in with forceNewLogin so it cannot
  reuse the first account's browser session
- browser-suppression promotes the manual transport; named-backup
  discovery failures warn-and-continue on real errors and stay silent
  on ENOENT

https://claude.ai/code/session_01XNtnkLbBiXZxfQQYLMpucB
@chatgpt-codex-connector

Copy link
Copy Markdown

Codex usage limits have been reached for code reviews. Please check with the admins of this repo to increase the limits by adding credits.
Credits must be used to enable repository wide code reviews.

@coderabbitai

coderabbitai Bot commented Jun 10, 2026

Copy link
Copy Markdown
Contributor
📝 Walkthrough

Walkthrough

adds 300 lines of Vitest tests for runAuthLogin with global mocks for storage, CLI prompts, browser suppression, and OAuth modules. tests cover argument validation, explicit transport modes (--device-auth, --manual, --org), account persistence and caps, and default onboarding flows using an in-memory account store and deterministic mock returns.

Changes

Login Flow Test Suite

Layer / File(s) Summary
Mock Infrastructure & Test Fixtures
test/login-flow.test.ts:1–107
global Vitest mocks for loadAccounts, prompt functions, browser suppression, and OAuth login module; test imports runAuthLogin, defines fixed NOW timestamp, builds account/storage helper functions, and maintains an in-memory accountsOnDisk store for persistence assertions.
Test Instrumentation & Lifecycle
test/login-flow.test.ts:108–146
captures original TTY state, declares console spies, provides loggedLines helper to format spy output, and wires beforeEach/afterEach that resets mocks, injects default behaviors (including persistAccountPool growth), forces non-TTY stdin/stdout mode, and restores TTY afterward.
Argument Handling Tests
test/login-flow.test.ts:147–161
validates --org without value fails early with usage error and skips account loading; --help returns 0 without starting sign-in.
Explicit Transport & Account Management
test/login-flow.test.ts:163–264
--device-auth skips mode prompting and exits cleanly on cancellation; --manual fails with exit 1 on non-cancellation without persisting; --manual --org threads org into resolution and persists; parameterized persist outcomes validate rebound/updated messaging; account-cap prevents further prompts; adding another account triggers second sign-in with forced fresh session and validates account-count logging.
Default & Onboarding Flow Tests
test/login-flow.test.ts:265–300
prefers manual transport when browser launch is suppressed; named-backup discovery failure with hard permission errors warns and continues to browser sign-in; missing backup directory (ENOENT) is normal without warning and still proceeds to sign-in.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • ndycode/codex-multi-auth#513: main PR's assertions for Updated/Rebound outcomes and persistAccountPool folding directly target test/login-flow.test.ts:163–264 behavioral changes.
  • ndycode/codex-multi-auth#493: --org parsing and override behavior tested in test/login-flow.test.ts:147–161 and 163–264 match the login parser implementation.
  • ndycode/codex-multi-auth#478: device-auth and manual-flow control paths tested in test/login-flow.test.ts:163–264 correspond to explicit sign-in mode handling added in retrieved PR.

Notes for review

  • no regression gap on --org edge cases across transports: test/login-flow.test.ts:163–264 covers --manual --org threading and --device-auth --org paths; verify both paths tested in same suite or verify device-auth + org combo is actually covered in the parametrized device-auth block.
  • non-TTY stdin behavior untested on windows: test/login-flow.test.ts:114–141 forces non-TTY via stdin.isTTY = false and stdout.isTTY = false; windows terminal behavior with these flags may differ; verify CI covers windows runners or document windows-specific requirements.
  • concurrency risk in in-memory accountsOnDisk: test/login-flow.test.ts:69–107 and 114–141 share mutable accountsOnDisk across all tests via beforeEach reset; no test isolation if suite runs in parallel; confirm Vitest config uses serial mode or test ordering prevents cross-test pollution.
  • missing backup discovery error types: test/login-flow.test.ts:265–300 tests permission error and ENOENT for named-backup discovery; other error categories (e.g., EISDIR, EIO) not explicitly covered; confirm error branching on real implementations matches test assumptions.
🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Description check ⚠️ Warning PR description omits required docs and governance checklist items; validation section lacks checkbox completeness against template. Complete the full template: add docs checklist (README, getting-started.md, features.md, reference pages, upgrade.md, SECURITY.md, CONTRIBUTING.md review), confirm Risk level and Rollback plan, and ensure all validation checkboxes are marked.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed title follows conventional commits format with type=test, uses lowercase imperative summary, and is 68 chars (well under 72-char limit).
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch claude/audit-43-login-flow-tests
✨ Simplify code
  • Create PR with simplified code
  • Commit simplified code in branch claude/audit-43-login-flow-tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Comment thread test/login-flow.test.ts
Comment thread test/login-flow.test.ts

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@test/login-flow.test.ts`:
- Around line 121-124: The default mock persistAccountPoolMock currently always
increments accountsOnDisk and returns "inserted", which can be surprising
because real persistAccountPool may return "rebound" or "updated" without
growing the pool; update the test by adding a clear inline comment at the
persistAccountPoolMock definition explaining that the default behavior
intentionally simulates an insertion-only path (accountsOnDisk +1, return
"inserted") and that individual tests override persistAccountPoolMock to
simulate "rebound"/"updated" outcomes when needed (see tests that override this
behavior), so future authors know why the mock grows the pool and when to
override it; reference persistAccountPoolMock, accountsOnDisk, and storageWith
in the comment.
- Around line 191-212: Add an explicit assertion that
process.env.CODEX_AUTH_ACCOUNT_ID is not set/unchanged before and after calling
runAuthLogin to prove the org is not propagated via env; capture the initial
value (e.g., const original = process.env.CODEX_AUTH_ACCOUNT_ID), call
runAuthLogin([...], deps()), then assert process.env.CODEX_AUTH_ACCOUNT_ID ===
original (or undefined) and keep the existing checks for
resolveAccountSelectionMock and runSignInFlowMock to ensure runAuthLogin,
resolveAccountSelection, and runSignInFlow behavior is unchanged.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: ab6d04a7-7d8d-4939-ae1f-40731bb06fa1

📥 Commits

Reviewing files that changed from the base of the PR and between 6ede089 and 28b9fc3.

📒 Files selected for processing (1)
  • test/login-flow.test.ts
📜 Review details
🧰 Additional context used
📓 Path-based instructions (6)
test/**/*.test.ts

📄 CodeRabbit inference engine (test/AGENTS.md)

test/**/*.test.ts: Vitest globals (describe, it, expect) are enabled and should be used without explicit imports
Maintain 80% coverage threshold across statements, branches, functions, and lines
Use removeWithRetry for Windows filesystem cleanup instead of bare fs.rm to handle EBUSY/EPERM/ENOTEMPTY backoff
Use source files in tests, not compiled dist/ files; test the source directly
Do not skip tests without justification; include rationale if a test must be skipped
Relax ESLint rules for test files as specified in eslint.config.js

Files:

  • test/login-flow.test.ts
**/*.ts

📄 CodeRabbit inference engine (AGENTS.md)

**/*.ts: Do not use as any, @ts-ignore, or @ts-expect-error type assertions
Use ESM only ("type": "module"); target Node >= 18.17

For TypeScript implementation, use strict type safety with explicit null checks, union types for optional values, and no implicit any types. All API responses must be strongly typed.

Files:

  • test/login-flow.test.ts
test/**/*.{ts,js}

📄 CodeRabbit inference engine (AGENTS.md)

Include Windows retry handling in tests that cover Windows filesystem locks and cleanup operations

Files:

  • test/login-flow.test.ts
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (README.md)

**/*.{ts,tsx,js,jsx}: Use OAuth credentials for account management in codex-multi-auth package. Credentials must be stored locally and never transmitted to external services except OpenAI's official API endpoints.
Store account credentials and OAuth tokens in ~/.codex/multi-auth/ directory structure (or custom path via CODEX_MULTI_AUTH_DIR). Never store credentials in environment variables or version control.
Account state files must be stored as JSON: openai-codex-accounts.json, openai-codex-flagged-accounts.json, quota-cache.json, runtime-observability.json, and settings.json under ~/.codex/multi-auth/.
All configuration and account files must validate against the defined schema before read/write operations. Use strict schema validation for settings.json and account JSON files.
Support environment variable overrides for configuration: CODEX_MULTI_AUTH_DIR, CODEX_MULTI_AUTH_CONFIG_PATH, CODEX_MODE, CODEX_MULTI_AUTH_RUNTIME_ROTATION_PROXY, CODEX_MULTI_AUTH_APP_ROTATION_IDLE_MS, CODEX_MULTI_AUTH_APP_BIND_INSTALL, CODEX_MULTI_AUTH_APP_LAUNCHER_INSTALL, CODEX_TUI_V2, CODEX_TUI_COLOR_PROFILE, CODEX_TUI_GLYPHS, CODEX_AUTH_BACKGROUND_RESPONSES, CODEX_AUTH_FETCH_TIMEOUT_MS, CODEX_AUTH_STREAM_STALL_TIMEOUT_MS, and CODEX_MULTI_AUTH_DEBUG.
All usage, quota, and runtime metrics must be logged to the usage ledger at ~/.codex/multi-auth/usage/usage-ledger.jsonl in JSONL format (one JSON object per line).
Implement runtime account rotation as a loopback-only proxy on localhost with bounded outbound request budget and cooldown mechanisms to prevent infinite pool replay and 5xx burst cascades.
All Responses proxy requests must maintain session affinity, live account sync, and proactive quota refresh with staggered background refresh intervals.
Implement account health checks and quota forecasting that surfaces recent runtime request metrics in codex-multi-auth status text output and machine-readable JSON in `codex-multi-auth r...

Files:

  • test/login-flow.test.ts
test/**

⚙️ CodeRabbit configuration file

tests must stay deterministic and use vitest. demand regression cases that reproduce concurrency bugs, token refresh races, and windows filesystem behavior. reject changes that mock real secrets or skip assertions.

Files:

  • test/login-flow.test.ts
**

⚙️ CodeRabbit configuration file

**: # PROJECT KNOWLEDGE BASE

Generated: 2026-04-25
Commit: a87e005
Validated: 2026-06-10 against commit 98d9819 (repo audit; claims re-checked against the tree, content not regenerated)
Branch: main
Package version: 2.3.0-beta.1

OVERVIEW

codex-multi-auth is a Codex CLI-first OAuth account manager and optional forwarding wrapper for the official Codex CLI. The installed codex-multi-auth entrypoint handles account-management commands locally, codex-multi-auth-codex forwards official Codex commands through this package's wrapper when explicitly used, and runtime rotation can route live Responses traffic through a localhost account-rotation proxy by default. The plugin-host entrypoint remains exported for compatibility, but the primary product surface is the account manager, optional wrapper, storage, runtime proxy, and repair tooling.

STRUCTURE

./
├── scripts/
│   ├── codex.js              # codex-multi-auth-codex wrapper, official CLI forwarder, shadow CODEX_HOME/runtime proxy setup
│   ├── codex-multi-auth.js   # standalone package CLI entrypoint
│   ├── codex-routing.js      # auth command and compatibility alias routing
│   ├── codex-bin-resolver.js # official Codex binary discovery
│   ├── codex-app-router.js   # persistent localhost router for packaged Codex app bind
│   └── codex-app-launcher.js # reversible user-level app launcher routing helper
├── index.ts                  # optional plugin-host runtime entry
├── lib/                      # core runtime logic (see lib/AGENTS.md)
│   ├── auth/                 # OAuth flow, PKCE, callback server
│   ├── runtime/              # Codex CLI/app integration helpers, app bind, live sync, runtime observability
│   ├── request/              # request transform, SSE, failover, backoff
│   ├── storage/              # path resolution, migrations, backups, restore, import/export
│   ├── codex-cli/            # Codex CLI state sync and writer helpers
│   ├── codex-manager/        # command modules and...

Files:

  • test/login-flow.test.ts
🧠 Learnings (2)
📚 Learning: 2026-06-04T06:14:18.093Z
Learnt from: ndycode
Repo: ndycode/codex-multi-auth PR: 510
File: test/scheduling-strategy-config.test.ts:1-1
Timestamp: 2026-06-04T06:14:18.093Z
Learning: In ndycode/codex-multi-auth, do not flag explicit imports from "vitest" (e.g., describe, it, expect, beforeEach/afterEach, etc.) in test files as issues—even if the Vitest config sets `globals: true`. The repo’s established convention is to keep these imports for consistency with neighboring tests; removing them would make files outliers.

Applied to files:

  • test/login-flow.test.ts
📚 Learning: 2026-06-04T06:14:24.975Z
Learnt from: ndycode
Repo: ndycode/codex-multi-auth PR: 510
File: test/runtime-rotation-proxy.test.ts:2478-2491
Timestamp: 2026-06-04T06:14:24.975Z
Learning: In ndycode/codex-multi-auth test files (e.g. `test/*.test.ts`), when creating V3 storage fixtures for accounts, it’s an intentional convention to use `as never` for deliberately minimal stored-account objects that only include `refreshToken`, `addedAt`, and `lastUsed`. Do not treat `as never` here as a type-safety problem: optional/other fields are expected to be populated by the runtime during execution, and the cast is used solely to keep the fixture minimal and consistent across existing tests.

Applied to files:

  • test/login-flow.test.ts
🔇 Additional comments (16)
test/login-flow.test.ts (16)

1-4: LGTM!


5-67: LGTM!


71-103: LGTM!


108-141: LGTM!


143-145: LGTM!


147-161: LGTM!


164-176: LGTM!


178-189: LGTM!


214-231: LGTM!


233-246: LGTM!


248-262: LGTM!


266-274: LGTM!


276-289: LGTM!


291-299: LGTM!


163-263: Add regression coverage for --manual + --device-auth login conflict
Ensure there’s a test for the conflict rejection that asserts the expected exit code/log message and verifies runSignInFlow is not started.


1-300: Tighten auth login unit coverage for abort/ordering and concurrency

  • Add at least one regression where mocked persistence/sync dependencies reject/throw in test/login-flow.test.ts to lock in abort/continue semantics (current mocks are always resolved).
  • Add coverage for concurrent runAuthLogin calls in test/login-flow.test.ts (higher-level serialization exists in test/index.test.ts, but this suite doesn’t exercise this file’s local accountsOnDisk + persist/sync ordering).

Comment thread test/login-flow.test.ts
Comment thread test/login-flow.test.ts
claude added 2 commits June 10, 2026 17:40
Give runSignInFlowMock an inert cancellation default so a forgotten
per-test setup exits through the cancel branch instead of crashing on
undefined, and document that the onboarding tests deliberately
exercise the REAL promptOAuthSignInMode non-TTY fallback.

https://claude.ai/code/session_01XNtnkLbBiXZxfQQYLMpucB
Cover the parse-level rejection of --device-auth combined with a
manual-mode flag, assert CODEX_AUTH_ACCOUNT_ID is untouched by the
--org path (closing the loop on the issue #491 no-env-mutation claim),
and document why the default persist mock simulates insertion only.

https://claude.ai/code/session_01XNtnkLbBiXZxfQQYLMpucB
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.

2 participants