Skip to content

test: cover rotation proxy state init and stale-state recovery#572

Merged
ndycode merged 3 commits into
mainfrom
claude/audit-53-rotation-proxy-state-tests
Jun 11, 2026
Merged

test: cover rotation proxy state init and stale-state recovery#572
ndycode merged 3 commits into
mainfrom
claude/audit-53-rotation-proxy-state-tests

Conversation

@ndycode

@ndycode ndycode commented Jun 10, 2026

Copy link
Copy Markdown
Owner

Summary

Twelfth suite in the direct-coverage wave (siblings: #559#561, #563#567, #569#571; all independent, based on main). lib/runtime/rotation-proxy-state.ts holds the proxy's per-instance state container and the pool-exhausted stale-state recovery. This adds test/rotation-proxy-state.test.ts (5 tests).

A real AccountManager backs the state; AccountManager.loadFromDisk is the one stubbed seam (static spy), and the observability reset/reload recorders are mocked so nothing touches disk.

What the tests pin

  • createRotationProxyState: the known-manager set is seeded with the active manager and the status block starts zeroed with startedAt from the injected clock.
  • Recovery happy path: recoverStaleRuntimeState reloads from disk, swaps activeAccountManager, keeps the previous manager in the known set (so in-flight requests can still finish against it), carries the configured routing-mutex mode onto the reloaded pool, stamps lastStaleRuntimeReloadAt, and records both observability markers with pool-exhausted-no-account.
  • Dedupe: a second call inside the 1s window returns the freshly reloaded manager without a second disk read, and two concurrent callers share one in-flight reload (gated loadFromDisk so the window is genuinely open).
  • Failure: a failed reload returns null, surfaces the message in status.lastError, and neither arms the dedupe window nor leaks a stale promise — the very next call retries the reload.

Validation

  • vitest run test/rotation-proxy-state.test.ts — 5/5 passing
  • npm run typecheck — clean
  • npx eslint test/rotation-proxy-state.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/rotation-proxy-state.test.ts — 5 tests covering the init shape, happy-path stale-state recovery, 1-second dedupe window (with vi.useFakeTimers() for determinism), concurrent-caller deduplication via a gated loadFromDisk, and failure-path behaviour.

  • init test uses toStrictEqual to pin the full status object (all 11 fields), knownAccountManagers seeding, lastGlobalAccountIndex, and lastStaleRuntimeReloadAt zero-value.
  • recovery tests verify mutex-mode carry-over, knownAccountManagers accumulation, observability call counts, dedupe window expiry after vi.advanceTimersByTime(1_001), and that a failed reload surfaces status.lastError, leaves lastStaleRuntimeReloadAt at 0, and allows an immediate retry.

Confidence Score: 5/5

test-only change; adds no production code and does not touch disk, tokens, or any live state.

the suite correctly exercises every branch of recoverStaleRuntimeState — happy path, concurrent deduplication, dedupe-window expiry, and failure recovery — using fake timers and a gate promise to keep assertions deterministic. no issues were found that would affect correctness or safety of the production module.

no files require special attention.

Important Files Changed

Filename Overview
test/rotation-proxy-state.test.ts new 5-test suite; correctly uses vi.useFakeTimers() for dedupe-window determinism, toStrictEqual for full status shape, and a gate promise to hold the in-flight reload open for the concurrency test — all major contract points are pinned.

Sequence Diagram

sequenceDiagram
    participant C1 as Caller 1
    participant C2 as Caller 2
    participant R as recoverStaleRuntimeState
    participant S as RotationProxyState
    participant AM as AccountManager

    Note over R,S: dedupe check — Date.now() - lastStaleRuntimeReloadAt <= 1000?
    C1->>R: call
    R->>S: read lastStaleRuntimeReloadAt (0)
    R->>S: read staleRuntimeReloadPromise (null)
    R->>AM: resetVolatileRuntimeState()
    R->>AM: loadFromDisk() [async, gated]
    R->>S: set staleRuntimeReloadPromise

    C2->>R: call (concurrent)
    R->>S: read lastStaleRuntimeReloadAt (still 0)
    R->>S: read staleRuntimeReloadPromise (non-null)
    R-->>C2: return existing promise

    AM-->>R: reloaded AccountManager
    R->>S: "activeAccountManager = reloaded"
    R->>S: knownAccountManagers.add(reloaded)
    R->>S: "lastStaleRuntimeReloadAt = Date.now()"
    R->>S: "staleRuntimeReloadPromise = null (finally)"
    R-->>C1: reloaded
    R-->>C2: reloaded (same promise)

    Note over R,S: within 1s dedupe window
    C1->>R: call again
    R->>S: "read lastStaleRuntimeReloadAt (> 0, within window)"
    R-->>C1: return activeAccountManager (no disk read)
Loading

Reviews (2): Last reviewed commit: "test: pin the full initial status shape ..." | Re-trigger Greptile

Direct coverage for the phase-2-extracted proxy state container:

- createRotationProxyState seeds the known-manager set with the
  active manager and zeroes the status from the injected clock
- recoverStaleRuntimeState reloads from disk, swaps the active
  manager while keeping the previous one known for in-flight
  requests, carries the routing-mutex mode onto the reloaded pool,
  and records the observability reset/reload markers
- the 1s dedupe window returns the freshly reloaded manager without
  a second disk read; concurrent callers share one in-flight reload
- a failed reload reports lastError, returns null, and neither arms
  the dedupe window nor leaks a stale promise, so the next call
  retries

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

Warning

Review limit reached

@ndycode, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 28 minutes and 24 seconds. Learn how PR review limits work.

Your organization has run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans include higher PR review limits than trial, open-source, and free plans. In all cases, reviews become available again over time. During sustained high-volume PR review activity, CodeRabbit may temporarily slow when the next review becomes available.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: bc921616-1fab-4934-9073-9bedd345ebd0

📥 Commits

Reviewing files that changed from the base of the PR and between b566656 and a032a28.

📒 Files selected for processing (1)
  • test/rotation-proxy-state.test.ts
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch claude/audit-53-rotation-proxy-state-tests
✨ Simplify code
  • Create PR with simplified code
  • Commit simplified code in branch claude/audit-53-rotation-proxy-state-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.

ndycode pushed a commit that referenced this pull request Jun 10, 2026
Adds the dashboard-settings-data, rotation-account-selection,
rotation-token-refresh, and rotation-proxy-state suites delivered
after the table was first written.

https://claude.ai/code/session_01XNtnkLbBiXZxfQQYLMpucB
Comment thread test/rotation-proxy-state.test.ts
Comment thread test/rotation-proxy-state.test.ts Outdated
Comment on lines +105 to +117

describe("recoverStaleRuntimeState", () => {
it("reloads from disk, swaps the active manager, and records observability", async () => {
const state = createRotationProxyState(stateInit());
const previousManager = state.activeAccountManager;
const reloaded = new AccountManager(undefined, storageWith(2));
loadFromDisk.mockResolvedValue(reloaded);

const result = await recoverStaleRuntimeState(state);

expect(result).toBe(reloaded);
expect(state.activeAccountManager).toBe(reloaded);
// The previous manager stays known so in-flight requests can finish.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 AccountManager.resetVolatileRuntimeState() runs for real in these tests

only loadFromDisk is spied on; resetVolatileRuntimeState is not mocked. that means resetTrackers() and resetAllCircuitBreakers() execute against the module-level health-tracker and circuit-breaker singletons on every happy-path and concurrent test invocation. within a single vitest worker the state is isolated per file, so this doesn't contaminate sibling test files, but it does silently mutate global state inside this worker. if a future test in this same file relies on a pre-seeded tracker or circuit-breaker state (e.g. to test recovery when an account is already cooling down), the reset will silently erase that setup. adding a vi.spyOn(AccountManager, 'resetVolatileRuntimeState').mockReturnValue(undefined) in beforeEach would make the seam explicit and match the spirit of the PR description ("nothing touches disk").

Prompt To Fix With AI
This is a comment left during a code review.
Path: test/rotation-proxy-state.test.ts
Line: 105-117

Comment:
**`AccountManager.resetVolatileRuntimeState()` runs for real in these tests**

only `loadFromDisk` is spied on; `resetVolatileRuntimeState` is not mocked. that means `resetTrackers()` and `resetAllCircuitBreakers()` execute against the module-level health-tracker and circuit-breaker singletons on every happy-path and concurrent test invocation. within a single vitest worker the state is isolated per file, so this doesn't contaminate sibling test files, but it does silently mutate global state inside this worker. if a future test in this same file relies on a pre-seeded tracker or circuit-breaker state (e.g. to test recovery when an account is already cooling down), the reset will silently erase that setup. adding a `vi.spyOn(AccountManager, 'resetVolatileRuntimeState').mockReturnValue(undefined)` in `beforeEach` would make the seam explicit and match the spirit of the PR description ("nothing touches disk").

How can I resolve this? If you propose a fix, please make it concise.

Fix in Codex

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Declining this one: the resetVolatileRuntimeState() call inside recoverStaleRuntimeState is part of the recovery contract under test (the pool-exhausted reset of trackers and breakers is deliberate product behavior, paired with the recordRuntimeReset marker the test does assert). Stubbing the static would also stub that in-recovery call and hollow out the function being tested. The "nothing touches disk" claim still holds — these singletons are in-memory — and cross-test isolation is already guaranteed by the forks pool plus the explicit beforeEach reset. If a future test in this file needs pre-seeded tracker state, scoping a spy inside that one test is the right tool at that point.


Generated by Claude Code

Comment thread test/rotation-proxy-state.test.ts
claude added 2 commits June 10, 2026 18:31
Run the dedupe test on fake timers (the guard deliberately uses the
real wall clock, not the injected now()), cover the window-expiry
boundary where a fresh reload is allowed again, and assert a failed
reload leaves lastStaleRuntimeReloadAt unarmed.

https://claude.ai/code/session_01XNtnkLbBiXZxfQQYLMpucB
toStrictEqual so the lastAccountLabel/lastAccountId/
lastAccountUpdatedAt initials are asserted too.

https://claude.ai/code/session_01XNtnkLbBiXZxfQQYLMpucB
@ndycode ndycode merged commit 43f0dcc into main Jun 11, 2026
1 of 2 checks passed
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