Add black-box e2e suite pinning the CLI config contract - #57
Merged
Merged
Conversation
Raises coverage from 39.0% to 99.3% and, more importantly, makes the configuration behaviour executable so that removing viper (#54) can be verified instead of hoped. Every test drives the compiled binary as a subprocess and asserts only on exit code, stdout and stderr. Nothing touches package internals, so the suite must keep passing verbatim across the planned rearchitecture (#54 viper removal, #55 run() extraction, #56 assertion constructors) -- that property is the point, not a side effect. TestE2EConfigContract is the centrepiece: all 19 options x {unset, command line, environment}. The unset and command-line cases together act as a mutation check, since a vacuous predicate fails one of them. EnvSupported records that only 6 of the 19 options read the environment today; when #54 makes this uniform, flipping those 13 booleans is the diff that proves it. Previously the coverage number could not include main.go at all, because die() calls os.Exit and every error path terminated the process. Now the binary is built with -cover and its counters are merged with the unit-test profile, so `just test-cover` reports both. The only statements left uncovered are logWarn and logError, which no caller reaches. Two behaviours are pinned that would otherwise be lost silently: - HTTP_ASSERT_MAPHOST separates values on whitespace, not commas. A reimplementation reaching for strings.Split(v, ",") breaks every multi-mapping user; the decoy mapping in TestE2EConfigEnvSliceSeparator is listed first so a pass proves both entries parsed. - A command-line flag always wins over the environment, asserted in both directions so a build that ignored the environment entirely cannot pass. e2e_known_issues_test.go characterises 14 filed defects. These are deliberately not skipped: when an issue is fixed the corresponding test fails, which is the signal to update the expectation in the same commit. Writing them surfaced that #33 was wrong -- `-H 'BareHeader'` sends an empty-valued header rather than dropping it -- and that issue has been corrected. Out of scope: fixing any characterised defect, and deleting the dead logWarn/logError helpers that account for the remaining 0.7%. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019EMMhgmTkbzAsmeNy97PrP
The environment-variable section claimed the HTTP_ASSERT_ prefix works generally. It does not: 6 of the 19 options honour it and 13 silently ignore it, so a reader following the docs could set HTTP_ASSERT_REQUEST=POST and watch the tool keep sending GET. Previously three examples were listed with no statement of scope, which read as illustrative rather than exhaustive. Now the six supported variables are tabulated, the command-line-only options are named, and two rules that were undocumented are stated: a flag beats the environment, and HTTP_ASSERT_MAPHOST separates multiple mappings with whitespace rather than commas -- the comma form parses as one malformed mapping and exits 71. Every claim here is asserted by TestE2EConfigContract, so the docs and the binary cannot drift apart without a test failing. Scope: documents current behaviour only. Making the environment uniform across all 19 options is #54, and this section will need revisiting when that lands. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019EMMhgmTkbzAsmeNy97PrP
korya
marked this pull request as ready for review
August 7, 2026 20:26
This was referenced Aug 7, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
Removing viper (#54) would be a leap of faith: nothing tests how
http-assertresolves configuration, andmain.gohas zero coverage.The 39% figure was not a testing-effort problem — it was one architectural fact showing up as a number.
assertions.goandutils.gowere already at 100%; every single function at 0.0% lived inmain.go, unreachable becausedie()callsos.Exitand terminates the process on every error path. So the code that resolves flags, reads the environment, builds the request and renders failures was entirely unverified, and a refactor of it had nothing to check itself against.Solution
Add a black-box suite that drives the compiled binary and pins all 19 config options across the command line and the environment.
Every test asserts only on exit code, stdout and stderr. Nothing references package internals, so the suite must keep passing verbatim across #54, #55 and #56 — that property is the deliverable, not a side effect.
Coverage:
main.gofunctions at 0.0%logWarn,logError— dead, no callers)The binary is built with
-cover; its counters merge with the unit-test profile viago tool covdata, so subprocess execution counts. Coverage is now literally 100% minus the dead code.The contract
TestE2EConfigContractruns all 19 options × {unset, command line, environment}. Theunsetandclicases are a built-in mutation check — a vacuous predicate fails one of them, so the matrix cannot pass by being empty.verbosesilentlog-levelinsecuremax-timemaphostrequestheaderdata+ all 10assert-*envSupportedrecords what the tool does today, not what it should do. When #54 makes this uniform, flipping those 13 booleans is the diff that proves the change worked.Two behaviours that would otherwise have been lost silently
HTTP_ASSERT_MAPHOSTsplits on whitespace, not commas. This is undocumented and counter-intuitive, and a reimplementation reaching forstrings.Split(v, ",")— the obvious choice — breaks every multi-mapping user with nothing to catch it:The decoy in
TestE2EConfigEnvSliceSeparatoris listed first and points at a dead port, so a pass proves both entries parsed rather than just the leading one.A flag always beats the environment, asserted in both directions — a build that ignored the environment entirely would pass a one-directional test.
Characterised defects
e2e_known_issues_test.gopins 14 filed bugs (#17 #18 #19 #20 #22 #23 #25 #26 #27 #28 #31 #32 #33 #34). They are deliberately nott.Skip-able: when an issue is fixed the test fails, and that failure is the signal to update the expectation in the same commit. Each carries a greppable// Characterizes #NNcomment and acharacterizes(t, NN, …)tag, sogo test -run TestKnownselects the set.Writing them caught an error in my own earlier report: #33 was wrong.
-H 'BareHeader'does not drop the header — it sendsBareheader:with an empty value. The original finding came from a case-sensitive grep against a canonicalised name. The issue now carries a dated correction and the real curl comparison.201 leaf assertions across 36 tests. The suite adds ~10.7s to
go test(0.5s unit-only → 11.3s total, measured with-count=1); most of that is the deliberate sleeps the--max-timeand precedence cases need, since--max-timeis integer-seconds. CI runs it three times —test,test-race,test-cover— and the job completes in 1m10s.No visual change — this is a headless CLI.
Other Changes
Justfile—test-coverandtest-coveragenow merge unit and end-to-end profiles; without this the e2e runs would report as uncovered. Addstest-cover-funcfor a per-function breakdown.README.md— the environment-variable section claimed theHTTP_ASSERT_prefix works generally. Corrected to tabulate the six supported variables, name the command-line-only options, and state the precedence and whitespace-separator rules. Every claim is now asserted by the matrix, so docs and binary cannot drift apart without a test failing.Related:
🤖 Generated with Claude Code