Skip to content

Make the unit test suite deterministic under ambient configuration - #441

Merged
akolotov merged 8 commits into
mainfrom
claude/issue-436-implementation-cc53e9
Jul 29, 2026
Merged

Make the unit test suite deterministic under ambient configuration#441
akolotov merged 8 commits into
mainfrom
claude/issue-436-implementation-cc53e9

Conversation

@akolotov

@akolotov akolotov commented Jul 29, 2026

Copy link
Copy Markdown
Collaborator

Summary

Closes #436.

The unit suite's verdict depended on the machine it ran on. A developer with a populated .env (or an exported BLOCKSCOUT_* variable) saw failures that CI never reproduced, because ServerConfig picked up ambient operator settings — most visibly BLOCKSCOUT_PRO_API_KEY_REQUIRED_NOTICE, which prepends a note to tool responses and broke assertions in 34 test files.

This PR pins the configuration unit tests see, and adds a CI leg that would catch a regression.

What changed

Autouse pristine_config fixture (tests/conftest.py) — every unit test now runs against a ServerConfig constructed with _env_file=None and the ambient BLOCKSCOUT_* environment closed out, pinned over model_fields. Integration tests are deliberately exempt: they need the real environment. A new contract module, tests/test_pristine_config_fixture.py, covers the fixture's six prescribed scenarios.

.env isolation for the reload tests (tests/test_server.py) — these tests reconstruct the config singleton, so the fixture alone can't protect them. A function-scoped autouse fixture chdirs to a tmp_path (no .env to find) and a _restore_canonical_config helper repairs singleton identity on teardown.

CI ambient-state matrix (.github/workflows/ci.yml) — the unit job gains an ambient: ["clean", "adverse"] dimension. The adverse leg writes a hostile .env that sets every ServerConfig field to a valid non-default value and exports adverse variables (including a lowercase spelling and the unprefixed PORT alias) before running the full suite, so ambient-config leakage fails CI instead of only failing on someone's laptop.

Documentation.cursor/rules/200-development-testing-workflow.mdc gains an "Ambient Configuration Is Pinned" section; .cursor/AGENTS.md narrows the rule-210 trigger to tests/tools/; AGENTS.md records the new test files.

Notes for the reviewer

One pre-existing integration failure was fixed here (4e16df3). test_get_address_info_integration failed on unmodified main too, from the same root cause in a place the fixture deliberately does not reach: the operator notice landed at result.notes[0] and defeated a positional assertion. The fix is a strengthening, not a loosening — the old if result.notes: gate meant the full assertion block only ran when notes was empty, whereas it now runs unless one of the two specific failure notes is present. Skip semantics for the genuine failure modes are unchanged.

CI job rename. The unit job is now named Run Unit Tests (clean) / Run Unit Tests (adverse) instead of Run Unit Tests. Branch protection for main currently defines no required status checks, so the rename does not affect merge requirements. If required checks are ever configured, use the new job names.

No version bump — no functional or server change ships in this PR.

Verification

  • pytest977 passed, 92 deselected, and the identical verdict under an exported BLOCKSCOUT_PRO_API_KEY_REQUIRED_NOTICE and under a hostile .env at the repo root. That determinism is the point of the change.
  • Green for the right reason: neutering the pristine_config fixture reintroduces 36–37 failures, so it is load-bearing rather than vacuously passing. No tests were skipped, xfailed, deleted, or loosened.
  • Full integration suite via the timeout-protected runner → 92 passed, 0 failed, 0 skipped, 0 timed out.
  • ruff check . and ruff format --check . clean across 215 files.
  • Not yet verified: both CI matrix legs actually going green on GitHub Actions — that needs this push.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Bug Fixes

    • Improved integration-test handling for variable API response notes, reducing false failures when messages appear in different orders.
    • Strengthened test isolation so results remain consistent despite local environment configuration or .env files.
  • Tests

    • Added coverage for configuration defaults, environment isolation, and configuration singleton restoration.
    • CI now validates tests under both clean and adverse ambient configuration conditions.
    • Expanded testing guidance and project documentation for configuration-safe unit tests.

akolotov and others added 5 commits July 28, 2026 21:18
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
… operator notice

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

coderabbitai Bot commented Jul 29, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 7f6049b7-a43e-4826-8f1d-c877d11fe612

📥 Commits

Reviewing files that changed from the base of the PR and between 72e765e and 3003b16.

📒 Files selected for processing (8)
  • .cursor/AGENTS.md
  • .cursor/rules/200-development-testing-workflow.mdc
  • .github/workflows/ci.yml
  • AGENTS.md
  • tests/conftest.py
  • tests/integration/address/test_get_address_info_real.py
  • tests/test_pristine_config_fixture.py
  • tests/test_server.py

Walkthrough

The PR isolates unit tests from ambient configuration, adds clean/adverse CI coverage, documents configuration-testing rules, protects the shared config singleton across reloads, and makes live address integration assertions robust to reordered notes.

Changes

Ambient Configuration Isolation

Layer / File(s) Summary
CI matrix and testing guidance
.github/workflows/ci.yml, .cursor/..., AGENTS.md
CI runs tests under clean and adverse environments, with documentation covering pristine configuration and reload isolation practices.
Pristine configuration fixture and contract tests
tests/conftest.py, tests/test_pristine_config_fixture.py
An autouse fixture removes ambient variables and copies code-default values onto the shared configuration singleton; tests verify defaults, overrides, cleanup, and identity.
Reload cleanup and resilient integration assertions
tests/test_server.py, tests/integration/address/test_get_address_info_real.py
Server tests isolate dotenv reads and restore singleton identity after reloads, while address integration tests locate failure notes anywhere in the notes list.

Possibly related issues

Possibly related PRs

  • blockscout/mcp-server#384 — The configuration isolation and PRO-default tests support deterministic behavior for PRO API key–gated configuration changes.
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately captures the main change: making unit tests deterministic by isolating them from ambient configuration.
Docstring Coverage ✅ Passed Docstring coverage is 83.33% which is sufficient. The required threshold is 80.00%.
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.
✨ 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/issue-436-implementation-cc53e9

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.

@akolotov akolotov changed the title Make the unit test suite deterministic under ambient configuration (#436) Make the unit test suite deterministic under ambient configuration Jul 29, 2026
@akolotov akolotov self-assigned this Jul 29, 2026
akolotov and others added 3 commits July 28, 2026 22:13
- tests/integration/address/test_get_address_info_real.py: result.notes is
  None on the fully-healthy path (notes=notes or None in get_address_info,
  preserved by build_tool_response), so iterating it directly raised
  TypeError in a clean environment (no ambient operator notice). Iterate a
  `result.notes or []` local instead. Verified live: the old code fails and
  the fixed code passes with BLOCKSCOUT_PRO_API_KEY_REQUIRED_NOTICE empty,
  and still passes with the notice set.
- tests/conftest.py: pydantic-settings matches environment variables
  case-insensitively, so a lowercase `blockscout_...` (or `port`) variable
  leaked into the pristine instance and got pinned onto the singleton.
  Delete ambient variables by case-insensitive comparison.
- tests/test_pristine_config_fixture.py: make the env-channel contract test
  case-insensitive to enforce the above.
- .github/workflows/ci.yml: export a lowercase `blockscout_bs_timeout` in
  the adverse leg so a case-sensitivity regression fails CI (verified
  load-bearing: the pre-fix fixture fails the contract module under it).
- tests/test_server.py: fix the docstring pointer to the identity contract
  test (it lives in tests/test_pristine_config_fixture.py, not conftest).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…broader adverse leg

- tests/conftest.py: detect the integration exemption via
  request.node.get_closest_marker("integration") instead of request.keywords,
  which also matches node *names*, so a test literally named "integration"
  would silently escape pinning.
- Docstrings: drop references to "Phase 1/Phase 2 of the issue #436
  implementation plan" (not committed to the repo) in favor of naming the
  actual artifacts (pristine_config in tests/conftest.py,
  _isolate_dotenv_and_singleton in tests/test_server.py).
- tests/test_pristine_config_fixture.py: compare spot-checked fields against
  ServerConfig.model_fields[...].default instead of hardcoded literals, so a
  legitimate default change doesn't read as a fixture regression; document the
  collection-order caveat of the singleton-identity canary.
- CI adverse leg: the hostile .env now sets every ServerConfig field to a
  valid non-default value (was: 2 fields), and the exported environment also
  exercises the unprefixed PORT alias channel.

Verified: 977 passed / 92 deselected, identical verdict clean vs. the exact
hostile .env + exported variables from the adverse CI step; lint and format
checks clean.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ementation-cc53e9

# Conflicts:
#	AGENTS.md
#	tests/conftest.py
@akolotov
akolotov marked this pull request as ready for review July 29, 2026 17:36
@akolotov

Copy link
Copy Markdown
Collaborator Author

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Already looking forward to the next diff.

Reviewed commit: 3003b16830

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@akolotov
akolotov merged commit 4b98ff1 into main Jul 29, 2026
10 checks passed
@akolotov
akolotov deleted the claude/issue-436-implementation-cc53e9 branch July 29, 2026 18:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Unit tests fail when BLOCKSCOUT_PRO_API_KEY_REQUIRED_NOTICE is set in the local environment

1 participant