Skip to content

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

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

test: mocking harness + $HOME isolation + publish coverage (#447)#504
0xGaspar wants to merge 8 commits 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.

`push_directory_initializes_commits_and_pushes` substring-matched the raw
`Path::to_str()` of the temp remote directory against the text of
`.git/config`. That is not portable: git's config writer escapes `\` as
`\\`, so on Windows the file never literally contains the path handed to
git (and git may also normalise separators in a remote URL). The push
itself was fine β€” only the path-string check was wrong.

Read the remote back through `git remote get-url origin` instead and
compare separator-insensitively. Also assert the token is absent from the
origin URL, not just from the config text.

`git_out` now takes the `.git` directory (unchanged for the bare-repo call
sites, where the repo path *is* the git dir).

Documents why `GithubBaseGuard::api` may leave the remote-base thread-local
untouched: it is only ever written by `api_and_remote`, whose guard restores
it on drop, so an `api`-only test cannot observe a leaked override.
@github-actions
github-actions Bot dismissed their stale review August 7, 2026 17:38

Superseded by updated review.

@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...)
     |/(\
      \(\\
      " "\\

"I'm pecking through the errors..."

CI Summary

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

Powered by corvid-pet

Addresses the review blocker on #504: the source and spec paths touched
by this PR had no active SpecSync change, so trust/spec-check failed.

Recorded through the normal lifecycle (draft, interview, definition
approval, implementation, verify-native, closing approval) with semantic
deltas for the four affected modules -- github, publish, llm, doctor --
and requirement evidence for REQ-github-020, REQ-publish-020,
REQ-llm-020 and REQ-doctor-020.

Also documents `api_base` in the github spec's Public API, which the
export-coverage gate correctly flagged as undocumented once the
test-only base-URL seam was introduced.

Allocated as CHG-0008 rather than CHG-0007: #503 already claims 0007 on
its branch, and the sequence ledger on main is still at 6, so the tool
would otherwise hand both PRs the same identity.

Verification passed (1 command, 4 requirements); `specsync check` exits 0.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KuhwrJF2XFDVHhX7qzDuyy
@github-actions
github-actions Bot dismissed their stale review August 8, 2026 02:25

Superseded by updated review.

github-actions[bot]
github-actions Bot previously approved these changes Aug 8, 2026

@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(Caw! ^v^)
     |/(\
      \(\\
      " "\\

"That's a nice looking export you've got there."

CI Summary

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

Powered by corvid-pet

0xLeif
0xLeif previously approved these changes Aug 8, 2026

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

Approved. The test harness and isolation changes are now covered across CI, including Windows; CI and trust are green.

0xGaspar added a commit that referenced this pull request Aug 11, 2026
Addresses the review blocker on #505: CHG-0007 existed but sat in draft,
and draft does not count as an active change for path coverage, so the
new lint surface was uncovered for the trust gate.

Renumbered CHG-0007 -> CHG-0009. #503 already claims 0007 and #504 claims
0008 on their branches while the sequence ledger on main is still at 6,
so the tool would otherwise hand several open PRs the same identity. The
rename was done while the record was still draft, before any approval
digest covered its id.

Taken through the full lifecycle: semantic deltas for the two affected
modules (spec, main), requirement evidence for REQ-spec-030/031/032 and
REQ-main-010, definition approval, verify-native, closing approval.

Also documents three lint exports the coverage gate flagged --
ACCEPTANCE_SECTION and REJECTION_SECTION were sharing one table row, and
Finding::error was missing entirely.

The two open maintainer decisions (lane wiring, layer-2 default) moved
out of the task list into a deferred-decisions section: they are
questions for the reviewer, not work items of this change, and both ship
with the conservative default.

Verification passed (1 command, 4 requirements); `specsync check` exits 0.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KuhwrJF2XFDVHhX7qzDuyy
0xGaspar and others added 2 commits August 11, 2026 17:49
…d-publish-coverage

# Conflicts:
#	.specsync/change-sequence.json
Merging main (which landed CHG-0007 via #503) advanced
`.specsync/change-sequence.json`, which is an exact-match delivery input
in every acceptance manifest. That staled both accepted records even
though no real delivery input changed -- the cascade documented in
CorvidLabs/spec-sync#481.

Both were healed through the supported lifecycle rather than by hand:
reopen -> verify -> accept, with the verify-native lane passing for each
(CHG-0008 re-evidencing all four requirements). No approval digests were
rewritten.

Note `reopen` succeeded here where it deadlocked on the pre-existing
records fixed in #506, because these two carry consistent
closing-approval digests from their original acceptance.

`specsync check` exits 0.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KuhwrJF2XFDVHhX7qzDuyy
@0xGaspar
0xGaspar dismissed stale reviews from 0xLeif and github-actions[bot] via ce978f4 August 11, 2026 18:09
github-actions[bot]
github-actions Bot previously approved these changes Aug 11, 2026

@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(Caw! ^v^)
     |/(\
      \(\\
      " "\\

"Caw! Found a shiny new spec!"

CI Summary

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

Powered by corvid-pet

0xGaspar added a commit that referenced this pull request Aug 12, 2026
Records the #507 work through the normal lifecycle: interview, semantic
delta for the run module, requirement evidence for REQ-run-020/021/022,
definition approval, verify-native, closing approval.

Allocated as CHG-0010 rather than the CHG-0008 the tool offered: #504
already claims 0008 and #505 claims 0009 on their branches while the
sequence ledger on main is at 7, so the tool hands every open PR a
colliding identity. Renumbered while still draft, before any approval
digest covered the id.

Also re-verified CHG-0007, which the sequence-ledger bump staled even
though no real delivery input changed (CorvidLabs/spec-sync#481). Healed
via reopen -> verify -> accept; no approval digests were rewritten.

`specsync check` exits 0.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KuhwrJF2XFDVHhX7qzDuyy

@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

Automated review via Claude Code (/code-review), covering this PR alongside #509 and #505.

CI is green on the current commit, but my earlier approval was on a prior commit that got superseded by a later push, so this HEAD hasn't had a human/AI review yet. Re-reviewing surfaced that the isolation harness doesn't fully deliver on its own stated guarantees, plus two plausible flakiness/leak issues under parallel test execution.

Blocker 1: TempEnv's isolation doc oversells itself β€” GitHub calls from the spawned subprocess aren't actually mocked

tests/common/mod.rs:45 β€” the doc comment claims a wrapped command can "neither read nor write ~/.config/fledge/, nor reach any network endpoint," but GithubBaseGuard's override is #[cfg(test)]-only in the library crate and never reaches the real fledge binary that TempEnv spawns as a subprocess via cargo_bin().

Impact: any TempEnv-wrapped integration test that runs a GitHub-touching command (fledge templates search, fledge plugins search, fledge github ...) makes a real unauthenticated HTTPS request to api.github.com β€” flaky/rate-limited in CI and a genuine network egress despite the stated hermetic design.

Blocker 2: remote template git clone bypasses the mock harness entirely

src/test_support.rs:211 β€” the mocking-harness module doc claims "remote template fetch" is covered by the ureq-based mock, but src/remote.rs::clone_repo shells out to a real git clone subprocess against a hardcoded https://github.com/... URL that's never routed through GithubBaseGuard/remote_base().

Impact: a test exercising fledge templates init <owner>/<repo> for a remote template still performs a real git clone against github.com no matter how it's wrapped.

Blocker 3: two pre-existing doctor e2e tests weren't migrated to TempEnv

tests/main.rs:231 β€” e2e_rust_project_lifecycle and e2e_tsbun_project_lifecycle (which invoke doctor/doctor --json) were left on the old unisolated run_fledge_in helper instead of being retrofitted, unlike sibling tests this same PR did convert.

Impact: these tests still read the developer's real ~/.config/fledge/config.toml; doctor's check_ai() calls probe_ollama_host against whatever host is configured there, so on a machine/CI runner with a real provider key set, cargo test triggers a live network probe β€” the exact class of leak issue #447 (which this PR claims to close) remains latent here.

Plausible, worth a look

  • TOCTOU race in dead_port_url()/closed_loopback_addr() (src/test_support.rs:491) β€” both synthesize a "known-closed" port via bind-then-drop, which races under cargo test's default parallel execution: between the drop and the caller's connect attempt, the OS can hand the freed port to a different concurrently-running test's own bind(0) call. Could cause intermittent CI flakes.
  • MockHttpServer leaks per-connection handler threads (src/test_support.rs:334) β€” the accept loop spawns an untracked thread per connection; Drop only joins the top-level accept-loop thread. A blocked handler (bad Content-Length, slow/partial write) stays running detached after the test drops the server.

Non-blocking note

api_base()/remote_base() (src/github.rs:17, duplicated in src/publish.rs) embed the #[cfg(test)] thread-local override lookup inline rather than as an injected parameter, and the pattern is independently duplicated across the two files β€” nothing at the type level stops a test from forgetting the guard and silently hitting the real endpoint.

Verdict

The harness design itself (dependency-free MockHttpServer, GithubBaseGuard, GitIdentityGuard, TempEnv) is the right shape and Windows is green now β€” good progress since the last round. But three real gaps mean the isolation guarantee this PR sets out to deliver isn't fully true yet for GitHub-touching and legacy-doctor test paths. Please close those before merge.

…aries

Review blocker 1: `TempEnv`'s doc claimed a wrapped command could not "reach
any network endpoint", but `GithubBaseGuard` is a `#[cfg(test)]` thread-local
that cannot cross into the `fledge` binary `TempEnv` spawns. Any GitHub-touching
command under it would have gone to api.github.com for real.

Give the binary a runtime hook instead of narrowing the claim:
`github::api_base()` and a new `github::remote_base()`/`remote_url()` accept
`FLEDGE_TEST_GITHUB_API_BASE` / `FLEDGE_TEST_GITHUB_REMOTE_BASE`. It is a test
hook, not configuration, and is doubly gated β€” compiled out of release builds
(every shipped binary), and in debug builds honoured only for a loopback
`http://` host or an existing local directory, with userinfo forms such as
`http://127.0.0.1@evil.example` rejected. So no environment can redirect an
`Authorization: Bearer <token>` request off the machine; rejected values fall
back to the production constant with a warning. `TempEnv` points both at a
closed loopback port by default, so a stray GitHub call now fails locally.

Review blocker 2: remote template fetch shells out to `git clone`, which no
HTTP mock can intercept. `remote::clone_repo` now builds its URL through
`github::remote_url`, so a test can clone from a local bare repo β€” the real
clone path, offline. `publish` drops its duplicate `remote_base` and shares the
same helper.

Also isolate the four `doctor` invocations in tests/main.rs that were still
using the unwrapped helpers: they read the developer's real config and probed
whatever AI host it names, the last live-network path left in the suite.

New tests: `tests/isolation.rs` (mock reached through the spawned binary,
default dead-port behaviour, remote-template clone from a local bare repo) and
unit tests in `src/github.rs` covering unset/loopback/hostile values and the
release-build fallback.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KuhwrJF2XFDVHhX7qzDuyy

@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...)
     |/(\
      \(\\
      " "\\

"I'm pecking through the errors..."

CI Summary

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

Powered by corvid-pet

The review fixes changed src/ under an already-accepted record, staling
CHG-0008. Healed via reopen -> verify -> accept (verify-native green,
4 requirements re-evidenced).

The fixes also introduced src/remote.rs and tests/isolation.rs, which
CHG-0008 did not cover. spec-sync refuses to widen the definition of an
already-applied change ("perform further spec changes in a new change
workspace"), so those land as CHG-0011 with its own remote delta and
REQ-remote-010 rather than by editing the accepted definition.

Allocated 0011 because 0008 (this PR and #511), 0009 (#505) and 0010
(#509) are all claimed on open branches.

Also documents four github exports the coverage gate flagged: remote_base
and remote_url were sharing one table row, and API_BASE_ENV /
REMOTE_BASE_ENV were undocumented.

`specsync check` exits 0.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KuhwrJF2XFDVHhX7qzDuyy

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

Request changes β€” two small fixes, one of which is red on head right now

Everything the 2026-08-13 review asked for is done, and I verified each rather than trusting the commit message: the env hook now crosses the process boundary with an honest "what it does not guarantee" doc section; src/remote.rs:78 builds its URL via crate::github::remote_url and tests/isolation.rs:117-152 clones through the unmodified production path (I ran it β€” passes); the two doctor e2e tests were migrated plus three more sites. Locally on head: 1005 unit tests pass, tests/isolation.rs and tests/doctor.rs pass.

The threat model on the env hook is unusually well reasoned. Two independent gates (#[cfg(debug_assertions)] with a None-returning release stub, and no custom [profile.release] in Cargo.toml), and is_loopback_http_url correctly rejects the http://127.0.0.1@evil.example userinfo trick by refusing any authority containing @. The hostile-value test table walks six forms including suffix-confusion and scheme-relative. Replacing tautological tests like topics_include_fledge_template β€” which asserted a copy of the production logic β€” with real ones is the right trade.

πŸ”΄ Blocking: Windows clippy is red on this PR's own new file

tests/isolation.rs:17-18:

use std::path::Path;
use std::process::Command;

Both are unconditional, but every consumer is #[cfg(not(windows))]. cargo clippy --all-targets -- -D warnings promotes unused_imports to an error, so the test target fails to compile:

error: could not compile fledge (test "isolation") due to 2 previous errors
Lane 'check' failed at step 1

Confirmed on run 32036644470, job 95408935980, at headSha 0be2268. Introduced by f8fd97a β€” the very commit that fixed Blocker 1.

It's invisible to the required checks because ci.yml's lint job runs cargo clippy without --all-targets, on ubuntu only. Note test (windows-latest) is SUCCESS β€” that's cargo test, which doesn't run clippy, so "Windows tests pass" and "Windows lint is broken" are both true. Merging as-is lands a permanently red Windows job on main.

Fix: gate both imports with #[cfg(not(windows))].

πŸ”΄ Blocking: the new env override makes two pre-existing tests racy

build_api_url used to interpolate a literal; it now reads process-global FLEDGE_TEST_GITHUB_API_BASE via api_base(). But build_api_url_without_query and build_api_url_encodes_and_joins_query still hard-code https://api.github.com and do not take env_lock(), while endpoint_env_override_redirects_only_in_debug_builds sets that var under the lock.

Reproduced independently on a fresh worktree: 60 runs at --test-threads 16 β†’ one failure with both tests failing together (left: "http://127.0.0.1:9/repos/CorvidLabs/fledge"), and a plain cargo test github at default parallelism β†’ 4 failures in 40 runs, ~10%. Full-suite runs at default and --test-threads 4 were clean, which is why CI is green β€” the window is narrow, not absent.

This PR's own module doc states the rule being broken (src/test_support.rs:56-63), and CONTRIBUTING.md now says "Env-mutating tests must hold env_lock()". The gap is that build_api_url became an env reader without being brought under the lock. One line per test: let _lock = env_lock(); plus EnvVarGuard::set(API_BASE_ENV, None).

Minor

  • CONTRIBUTING.md:140 now states "No test may touch the network or your real ~/.config/fledge/", which ~10 integration sites still violate β€” all five tests/config.rs tests, plugin list Γ—2, templates list Γ—2, ai status --json. plugin list is worse than described: src/plugin/mod.rs:374-390 resolves from dirs::config_dir() and doesn't honour FLEDGE_CONFIG_DIR at all, so cli_plugin_list_empty reads the developer's real plugins.toml and, asserting only status.success(), provides no coverage of the "empty" case it names. No test writes the real config β€” I checked. Read-only leak, developer-machine only, zero CI impact.
  • dead_port_url() bind-then-drop is a TOCTOU; raised on 08-13 and untouched. I didn't hit it in ~70 runs, but it's load-bearing for default_temp_env_points_github_at_a_dead_port, whose entire assertion is that the connection fails.
  • The MockHttpServer thread-leak fix landed only in the integration-test copy; the two servers have now diverged with no note saying why.
  • TestRepo::init() doesn't neuter the global gitconfig, unlike every other git helper here β€” a developer with commit.gpgsign = true or a global core.hooksPath gets failures or hooks running under test.
  • PR body says specs went publish v5β†’v6 and github v3β†’v4; head is actually v7 and v5, and remote v3β†’v4 isn't mentioned.

Both blockers are a handful of lines. This should go green quickly and I'll re-review straight away.

πŸ€– Reviewed via Claude Code

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