Skip to content

test: mocking harness + $HOME isolation + publish coverage (#447) - #504

Open
0xGaspar wants to merge 1 commit into
mainfrom
chore/mock-harness-and-publish-coverage
Open

test: mocking harness + $HOME isolation + publish coverage (#447)#504
0xGaspar wants to merge 1 commit into
mainfrom
chore/mock-harness-and-publish-coverage

Conversation

@0xGaspar

Copy link
Copy Markdown
Contributor

Addresses the remaining scope of #447 β€” the mocking harness and publish-flow isolation. (The $HOME-isolation subset landed earlier in #463, which auto-closed the issue prematurely; this does not redo that work.)

The harness

Unit tests β€” src/test_support.rs

  • MockHttpServer::start() β€” loopback server on 127.0.0.1:0, thread-per-connection, shuts down on drop. server.on("GET", "/user", MockResponse::json(200, …)), then assert against server.requests() β†’ RecordedRequest { method, path, query, headers, body }.
  • dead_port_url() β€” closed loopback port for connection-refused paths.
  • GithubBaseGuard::api(url) / ::api_and_remote(...) β€” thread-local redirection of the GitHub REST base and git remote base, so parallel tests can't see each other's redirection.
  • GitIdentityGuard β€” git identity plus GIT_CONFIG_GLOBAL/GIT_CONFIG_NOSYSTEM, so commit-producing code never reads ~/.gitconfig.

Integration tests β€” tests/common/mod.rs: TempEnv spawns fledge with fresh HOME/XDG_CONFIG_HOME/FLEDGE_CONFIG_DIR, FLEDGE_NON_INTERACTIVE=1, all ten provider API keys plus GITHUB_TOKEN/GH_TOKEN removed, and OLLAMA_HOST pointed at a closed loopback port.

Note on wiremock: deliberately not used. It would pull in tokio, while all fledge HTTP is blocking ureq. The hand-rolled server adds no dependencies.

Coverage added

  • src/publish.rs (the explicit ask) β€” the tautological tests and the empty #[ignore] publish_live stub are gone, replaced by ~20 real tests: get_authenticated_user, check_repo_exists 200/404/5xx, create_github_repo (body fields, user vs org endpoint, 422, 403), set_repo_topic (additive merge, no duplicate, fetch failure), push_directory against a local bare repo (init+commit+push, re-push, no token leaked into .git/config, git-stderr surfacing), and run_publish orchestration.
  • src/github.rs β€” github_api_get end to end: headers, percent-encoded query, 404/403/generic status mapping, non-JSON body, unreachable host.
  • src/llm.rs β€” OllamaProvider::invoke: request shape, Bearer auth vs none, HTTP-status errors, undecodable body, connection refused, and the OLLAMA_HOST hint (OLLAMA_HOST env var overrides config without clear feedback in error messagesΒ #378).
  • src/doctor.rs + tests/doctor.rs β€” the CLI doctor tests previously probed whatever endpoint the developer's real config named; they now run under TempEnv.

Production seams introduced

Behavior-preserving; release builds are byte-identical.

  • github::api_base() β€” was an inline https://api.github.com literal, now a pub(crate) fn with a #[cfg(test)]-only thread-local override.
  • publish::remote_base() / remote_url(owner, repo) β€” same treatment for the inline remote URL. This is what makes run_publish testable end to end against a local bare repo.

No network, no real $HOME

Every new HTTP call targets 127.0.0.1 (mock server or closed port); git operations use tempdir working trees and local bare remotes with GIT_CONFIG_GLOBAL neutered; config access goes through FLEDGE_CONFIG_DIR/HOME tempdirs. This rests on a full diff audit rather than a network namespace, which the sandbox blocked.

Verification

cargo fmt --check and cargo clippy --all-targets -- -D warnings clean; cargo test passes (999 unit + all integration binaries).

Specs updated: publish v5β†’v6 and github v3β†’v4 (new invariants for the base-URL indirection), plus the publish/github/llm/doctor testing plans β€” the publish plan previously listed tests that did not exist.

fledge spec check is red on main for unrelated reasons (six stale SDD change-record errors β€” CorvidLabs/spec-sync#481); this PR neither adds to nor fixes that.

πŸ€– Generated with Claude Code

https://claude.ai/code/session_01KuhwrJF2XFDVHhX7qzDuyy

Closes the remaining scope of #447 (PR #463 shipped the config/$HOME subset).

Harness (src/test_support.rs, unit tests):
- MockHttpServer: dependency-free loopback HTTP server (127.0.0.1:0,
  thread-per-connection). Register routes with `on(method, path, MockResponse)`,
  point the code under test at `server.url()`, assert on `server.requests()`
  (method, path, query, headers, body). `fallback` covers unregistered routes.
- dead_port_url(): a closed loopback port for connection-refused paths.
- GithubBaseGuard: thread-local redirection of the GitHub REST base and the git
  remote base, so the real publish/API call paths run offline.
- GitIdentityGuard: git author/committer identity plus GIT_CONFIG_GLOBAL /
  GIT_CONFIG_NOSYSTEM, so commit-producing helpers never read ~/.gitconfig.

Harness (tests/common/mod.rs, integration tests):
- TempEnv: spawns fledge with fresh HOME/XDG_CONFIG_HOME/FLEDGE_CONFIG_DIR,
  non-interactive mode, every provider API key stripped, and OLLAMA_HOST
  pointed at a closed loopback port.

Coverage:
- publish.rs: the tautological tests and the empty `#[ignore] publish_live`
  stub are replaced by 20 real tests β€” get_authenticated_user,
  check_repo_exists (200/404/5xx), create_github_repo (body, org vs user URL,
  422, 403), set_repo_topic (additive, no-duplicate, fetch failure),
  push_directory against a local bare repo (init+commit+push, re-push branch,
  no token in .git/config, git-error surfacing), run_publish orchestration
  (create/skip-create/abort), and resolve_owner.
- github.rs: github_api_get against the mock server β€” headers, encoded query,
  404/403/generic status mapping, non-JSON body, unreachable host, and a
  search payload decoded through search::parse_search_response.
- llm.rs: OllamaProvider::invoke β€” request shape, Bearer auth, HTTP-status
  error text, undecodable body, connection refused, OLLAMA_HOST hint.
- doctor.rs: probe_ollama_host true/false paths; tests/doctor.rs now runs
  under TempEnv (it previously probed whatever endpoint the developer's real
  config named) with two added assertions.
- tests/main.rs: the two `review` cases run under TempEnv.

Production seams (behavior-preserving, release builds unchanged):
- github::api_base() resolves the REST base; publish::remote_base()/remote_url()
  resolve the push remote. Both return the previous constants unless a
  #[cfg(test)] thread-local override is set.

No test contacts the network or reads/writes the real ~/.config/fledge.

Specs updated: publish (v6), github (v4), and the publish/github/llm/doctor
testing plans; CONTRIBUTING gains a "no network, no real $HOME" testing section.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KuhwrJF2XFDVHhX7qzDuyy
@0xGaspar
0xGaspar requested a review from a team as a code owner July 31, 2026 16:36
@0xGaspar
0xGaspar requested review from 0xLeif, Kyntrin and tofu-ux July 31, 2026 16:36
@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.
Repo admins can enable using credits for code reviews in their settings.

@github-actions github-actions 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.

❌ Corvin says...

      _
    <(;\  .oO(oh no...)
     |/(\
      \(\\
      " "\\

"Caw... your imports are all over the place."

CI Summary

Check Status
Dependency Audit βœ… Passed
Integration (3 OS) ❌ skipped
Lint (fmt + clippy) βœ… Passed
Spec Validation ❌ failure
Tests (3 OS) ❌ failure

Powered by corvid-pet

@0xLeif 0xLeif 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.

Review β€” REQUEST CHANGES

This is a strong test-infra PR. The harness design (dependency-free loopback MockHttpServer, thread-local GithubBaseGuard, GitIdentityGuard, TempEnv) matches fledge's blocking ureq stack well, and the production seams (github::api_base(), publish::remote_base()) are correctly #[cfg(test)]-gated so release builds stay on the production constants. Specs/testing plans were updated honestly. macOS + Ubuntu tests pass.

Blocker 1: Windows unit test failure (real bug)

test (windows-latest) fails on a single test:

publish::tests::push_directory_initializes_commits_and_pushes
assertion failed: config.contains(remotes.path().to_str().unwrap())
  at src/publish.rs:786

988 passed / 1 failed. The assertion that .git/config contains the raw Windows path from Path::to_str() is brittle: git on Windows typically normalizes remotes to forward slashes (and sometimes a different drive prefix form). The push itself succeeded (the bare-repo log / ls-tree asserts above it would have failed otherwise); only the path-string check is wrong.

Suggested fix: assert via git remote get-url origin (or normalize both sides to / / PathBuf compare) instead of substring-matching the OS path in the config file text. Apply the same care anywhere else you assert path-in-config.

Blocker 2: SpecSync β€” no active change covering the production paths

spec-check / trust fail with:

meaningful changed paths are not covered by an active change:
  src/doctor.rs, src/github.rs, src/llm.rs, src/publish.rs,
  src/test_support.rs, tests/common/mod.rs, tests/doctor.rs, tests/main.rs

Plus stale accepted-change errors from editing CHANGELOG.md and specs/doctor/testing.md after those inputs were locked by CHG-0001 / CHG-0004 / CHG-0006.

Please add an active SpecSync change for this work (or attach it to one), and reopen/succeed the staled accepted changes per the specsync change reopen … guidance in the log.

Non-blocking notes (optional follow-ups)

  • Hand-rolled HTTP mock is the right call over wiremock/tokio here; please keep MockHttpServer documented in CONTRIBUTING (already done β€” good).
  • GithubBaseGuard::api snapshots the current remote override but does not clear it; that is fine with thread-locals + RAII as long as every test that sets remote uses api_and_remote (or a dedicated remote-only guard). Worth a one-line comment if you touch the file again.

Happy to re-review once Windows is green and the SpecSync contract passes.

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