httpie/cli
Testing is pytest-based with pytest-httpbin providing a real local HTTP/HTTPS server, plus `responses`, `pytest-mock` and a hand-rolled `http_server` fixture for cases httpbin cannot express. Roughly 6,600 lines across ~35 test modules under tests/, exercising the CLI end-to-end through a `MockEnvironment` that swaps stdin/stdout for temp files and disables colors and progress displays. `--doctest-modules` means docstring examples inside httpie/ are also collected. Coverage is strongest on sessions, output, JSON, uploads, and CLI parsing; weakest on the daemon-spawning internals, Rich help/palette rendering, and the manager plugin installer (which is gated behind a `requires_installation` marker). CI runs the suite across 3 OSes x 6 Python versions x pyopenssl on/off, with a separate coverage job and a flake8 style job.
- The suite runs real HTTP against pytest-httpbin: conftest.py auto-uses an `httpbin_add_ca_bundle` fixture that monkeypatches `REQUESTS_CA_BUNDLE` to pytest-httpbin's cert, and adds an `httpbin_secure_untrusted` variant that deliberately removes it for negative TLS tests.
- `tests/utils/__init__.py` defines the `MockEnvironment` harness: colors=0, stdout/stdin as temp files, `show_displays=False`, plus an `Encoder`/`FakeBytesIOBuffer` pair that smuggles undecodable binary output through a text stream as `hash(N)` placeholders.
- Tests globally disable the updater by setting `Config.DEFAULTS['disable_update_warnings'] = True` at import time, explicitly to stop hundreds of subprocesses hitting the network during a run.
- Two pytest markers gate the expensive tests: `requires_installation` (all of tests/test_plugins_cli.py and tests/test_httpie_cli.py, which shell out to a real install) and `requires_external_processes` (parts of test_uploads.py and test_update_warnings.py), with pytest.ini documenting how to deselect them.
- tests/utils/http_server.py provides a decorator-registry `TestHandler` over `BaseHTTPRequestHandler` with `handlers[method][path]`, used for behaviours httpbin cannot produce -- notably cookie-on-redirect semantics.
- The largest test modules by line count are test_sessions.py (852), test_output.py (631), test_json.py (583), test_uploads.py (403), test_cli.py (377) and test_httpie.py (372) -- i.e. persistence, rendering, and the request-item language get the most attention.
- Plugin extension points are covered by executable examples rather than mocks: test_transport_plugin.py registers a `FakeTransportPlugin` with prefix `http+fake`, asserts the fake adapter's response renders correctly, and unregisters it in a finally block.
- The ParserSpec serialization contract has a golden-output test asserting the exact JSON shape (options, is_optional, is_variadic, metavar, choices, python_type_name), which is what protects `httpie cli export-args` consumers.
- A dedicated `tests/utils/matching` package (parsing.py, tokens.py, test_matching.py) exists to assert on tokenized terminal output, letting colorized output be checked structurally instead of by brittle ANSI string comparison.
- `--doctest-modules` in setup.cfg means library docstrings are part of the suite -- e.g. `ensure_path_as_is`, `humanize_bytes`, `KeyValueArgType.tokenize`, and `detect_encoding` all carry assertions that fail the build if behaviour drifts.
- tests.yml runs the full matrix of {ubuntu-latest, macos-13, windows-latest} x Python {3.7..3.12} x pyopenssl {0,1}, with `fail-fast: false`; Windows runs pytest directly while Linux/macOS go through `make install && make test`.
- The pyopenssl matrix dimension is wired through `HTTPIE_TEST_WITH_PYOPENSSL`, which a session-scoped autouse fixture reads to call `urllib3.contrib.pyopenssl.inject_into_urllib3()` and hard-fails if the module is missing.
- Coverage runs only on pull_request via coverage.yml (`make test-cover` then `make codecov-upload` with CODECOV_REPO_TOKEN, followed by `make test-dist` to verify sdist and wheel installs).
- Style is enforced separately by code-style.yml running `make codestyle` -> flake8 with only E501 and W503 ignored, over httpie/, tests/, extras/profiling/ and docs/packaging/brew/.
- Performance regressions have an opt-in path rather than a gate: benchmark.yml only runs when a PR is labeled `benchmark`, executing `extras/profiling/run.py --fresh --complex --min-speed=6` and posting the pyperf results as a PR comment.