Skip to content

test(settings): split persistence regression coverage#64

Merged
ndycode merged 8 commits intomainfrom
split/pr55-settings-persistence
Mar 12, 2026
Merged

test(settings): split persistence regression coverage#64
ndycode merged 8 commits intomainfrom
split/pr55-settings-persistence

Conversation

@ndycode
Copy link
Copy Markdown
Owner

@ndycode ndycode commented Mar 10, 2026

Summary

  • split the dashboard and unified settings persistence regressions out of the larger settings test branch into a smaller persistence-focused PR
  • keep Windows-safe cleanup, rename-retry coverage, and unrelated-section preservation tests together in one place
  • tighten the test suite by removing a misleading dashboard fallback mock and strengthening retry assertions and spy cleanup

Why

This branch keeps persistence-specific regression coverage separate from the much larger CLI settings harness so reviewers can focus on the file/write-queue behavior by itself.

Verification

  • npm exec vitest run test/dashboard-settings.test.ts test/unified-settings.test.ts
  • npm run typecheck
  • npm run build

Notes

  • split replacement for the persistence test portion of #55
  • this branch stays scoped to persistence, migration, retry, and section-preservation coverage

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Mar 10, 2026

📝 Walkthrough

Walkthrough

new helper module test/helpers/remove-with-retry.ts introduces filesystem removal with transient error retry logic. test suite updated across dashboard-settings and unified-settings to use this helper instead of direct fs.rm, with expanded test coverage for preserving unrelated configuration sections during partial writes.

Changes

Cohort / File(s) Summary
Test Cleanup Infrastructure
test/helpers/remove-with-retry.ts
New module exports removeWithRetry() function. Retries on EBUSY, EPERM, ENOTEMPTY, EACCES with up to 6 attempts and 25ms × attempt backoff. Rethrows on non-transient errors or final failure.
Dashboard Settings Tests
test/dashboard-settings.test.ts
Imports reordered, removeWithRetry integrated into teardown. Adds pattern of preserving/restoring CODEX_MULTI_AUTH_DIR per test. New test validates preservation of unrelated unified settings sections when saving normalized dashboard settings. Lines +516/-454.
Unified Settings Tests
test/unified-settings.test.ts
Import reorganization with removeWithRetry added. Cleanup switches from fs.rm to removeWithRetry. New test verifies partial section writes do not clobber unrelated top-level sections. Test data formatting normalized. Lines +146/-39.

Sequence Diagram(s)

No sequence diagrams generated. Changes are test-only refactoring with straightforward cleanup logic and test expansion; no new features or multi-component interactions introduced.

Flagged Concerns

  • windows edge case: errno handling in test/helpers/remove-with-retry.ts includes EACCES, but validate behavior on windows for junction/symlink scenarios where removal semantics differ.
  • concurrency risk: if parallel test execution targets overlapping directory hierarchies, ENOTEMPTY retry on parent directories could mask genuine permission issues or cause interference between test workers. verify test isolation and temp directory scoping.
  • missing regression validation: no evidence that the retry mechanism was tested against actual transient failures (e.g., simulated EBUSY during cleanup). consider adding a unit test for removeWithRetry itself that verifies backoff and retry count.
  • backoff math: 25ms × attempt means 25ms, 50ms, 75ms, etc. on attempt 6 this is only 150ms total wait. verify this is sufficient for real-world flakiness in ci environments.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~22 minutes

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed Title follows conventional commits format (type: scope/summary), uses 'test' type with 'settings' scope, summary is 53 characters in lowercase imperative.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Description check ✅ Passed the pr description covers summary, rationale, and verification steps; follows the template structure with clear intent and scope boundaries.

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

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch split/pr55-settings-persistence

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.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

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 the current code and only fix it if needed.

Inline comments:
In `@test/dashboard-settings.test.ts`:
- Around line 409-424: The test mutates the exported defaults object
(defaults.actionAutoReturnMs = undefined etc.), which can leak between tests;
instead, before importing normalizeDashboardDisplaySettings, call
vi.resetModules() and use vi.doMock to return a mocked module that spreads the
original export but overrides DEFAULT_DASHBOARD_DISPLAY_SETTINGS (or the
exported defaults object you changed) with the specific fields set to undefined;
then import normalizeDashboardDisplaySettings from the module, run assertions,
and finally vi.doUnmock to restore the real module. Ensure you reference the
exported symbol DEFAULT_DASHBOARD_DISPLAY_SETTINGS (or the actual exported name
used in the module) and the function normalizeDashboardDisplaySettings when
replacing the export via vi.doMock.

In `@test/unified-settings.test.ts`:
- Around line 270-324: The test currently verifies preservation of an unrelated
top-level section only with sequential writes; add a concurrent-write variant to
ensure the serialization/merge logic doesn't clobber other sections under race
conditions. Create a new test that writes an initial file containing
experimentalDraft, then concurrently calls saveUnifiedPluginConfig(...) and
saveUnifiedDashboardSettings(...) (using Promise.all) and finally reads the file
(getUnifiedSettingsPath/readFile) to assert experimentalDraft is unchanged and
both pluginConfig and dashboardDisplaySettings exist; this will exercise the
serialization queue used in the unified-settings read-merge-write functions
(saveUnifiedPluginConfig, saveUnifiedDashboardSettings).

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: d603ed2d-757d-4e9f-ab02-b321e7c9d442

📥 Commits

Reviewing files that changed from the base of the PR and between d36b04f and dcc0432.

📒 Files selected for processing (3)
  • test/dashboard-settings.test.ts
  • test/helpers/remove-with-retry.ts
  • test/unified-settings.test.ts
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: cubic · AI code reviewer
  • GitHub Check: Greptile Review
🧰 Additional context used
📓 Path-based instructions (1)
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/unified-settings.test.ts
  • test/helpers/remove-with-retry.ts
  • test/dashboard-settings.test.ts
🔇 Additional comments (6)
test/helpers/remove-with-retry.ts (1)

3-18: solid retry helper for windows filesystem transients.

the implementation correctly handles the common windows lock errors (EBUSY, EPERM) that cause flaky test teardowns. the incremental backoff (25ms × attempt) is reasonable for test cleanup scenarios.

one minor observation: if you ever need to verify this helper's behavior in isolation, consider adding a dedicated test that mocks fs.rm to confirm retry count and backoff timing. not blocking, but would strengthen confidence in the retry logic itself.

test/unified-settings.test.ts (1)

24-24: good switch to the retry helper for teardown.

using removeWithRetry here aligns with the windows-safe cleanup pattern and matches test/dashboard-settings.test.ts:24. this should reduce flaky test failures on windows CI where antivirus or indexing services hold file handles.

test/dashboard-settings.test.ts (4)

24-24: consistent cleanup pattern with unified-settings tests.

matches the teardown at test/unified-settings.test.ts:24. good for consistency across the test suite.


112-168: good regression coverage for custom sections.

testing with docsParityAnchor (a section outside the known schema) verifies the read-merge-write doesn't strip unknown keys. this is exactly the kind of forward-compatibility test you want.


233-258: solid regression test for windows EBUSY during legacy migration.

mocking a single EBUSY failure then falling through to real reads verifies the retry logic recovers. this is exactly the windows edge case the coding guidelines call for.


276-276: good: retry count assertion keeps test deterministic.

asserting toHaveBeenCalledTimes(4) ties this test to the implementation's retry count (3 retries = 4 total attempts). if the retry count changes in production code, this test will catch the drift.

Comment thread test/dashboard-settings.test.ts Outdated
Comment thread test/unified-settings.test.ts
Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai Bot left a comment

Choose a reason for hiding this comment

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

No issues found across 3 files

Comment thread test/dashboard-settings.test.ts Outdated
Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai Bot left a comment

Choose a reason for hiding this comment

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

1 issue found across 1 file (changes from recent commits).

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="test/dashboard-settings.test.ts">

<violation number="1" location="test/dashboard-settings.test.ts:393">
P2: This mock-based setup no longer verifies the undefined-default fallback path; it only validates normal defaults, so the regression test can pass while fallback literals are broken.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

Comment thread test/dashboard-settings.test.ts Outdated
Comment thread test/dashboard-settings.test.ts Outdated
Comment thread test/dashboard-settings.test.ts Outdated
Comment thread test/dashboard-settings.test.ts Outdated
@ndycode
Copy link
Copy Markdown
Owner Author

ndycode commented Mar 10, 2026

Pushed follow-up 2c7182b on split/pr55-settings-persistence.

I removed the misleading mock-based dashboard fallback test after re-checking the implementation: the mock could not affect normalizeDashboardDisplaySettings() because the function closes over the real module constant, so the test was giving false confidence rather than exercising the intended path.

Validated with npm exec vitest run test/dashboard-settings.test.ts test/unified-settings.test.ts.

Comment thread test/dashboard-settings.test.ts Outdated
const loaded = await loadDashboardDisplaySettings();
expect(loaded.showPerAccountRows).toBe(false);
expect(loaded.menuShowQuotaSummary).toBe(false);
expect(readSpy).toHaveBeenCalledTimes(2);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

The assertion expect(readSpy).toHaveBeenCalledTimes(2) is correct for the happy path, but the spy is never cleaned up if the test fails before readSpy.mockRestore() at line 261. This means the spy continues affecting subsequent tests. The code should wrap the test logic in a try-finally block:

try {
  const loaded = await loadDashboardDisplaySettings();
  expect(loaded.showPerAccountRows).toBe(false);
  expect(loaded.menuShowQuotaSummary).toBe(false);
  expect(readSpy).toHaveBeenCalledTimes(2);
} finally {
  readSpy.mockRestore();
}

This pattern is already used in other tests (lines 278-284) but was missed here.

Spotted by Graphite

Fix in Graphite


Is this helpful? React 👍 or 👎 to let us know.

@ndycode
Copy link
Copy Markdown
Owner Author

ndycode commented Mar 10, 2026

Follow-up pushed on top of the earlier persistence-test cleanup: c78965f on split/pr55-settings-persistence.

This adds the missing toHaveBeenCalledTimes(2) assertions for the rename retry tests and documents the FIFO assumption in the concurrent plugin-write case.

The PR body was also refreshed to a concise human summary.

@ndycode ndycode merged commit 29e9146 into main Mar 12, 2026
4 checks passed
@ndycode ndycode deleted the split/pr55-settings-persistence branch March 15, 2026 12:26
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