Skip to content

Add comprehensive integration tests for proxy forwarding#46

Merged
JoshCap20 merged 5 commits into
mainfrom
feat/integration-tests
Feb 26, 2026
Merged

Add comprehensive integration tests for proxy forwarding#46
JoshCap20 merged 5 commits into
mainfrom
feat/integration-tests

Conversation

@JoshCap20
Copy link
Copy Markdown
Owner

@JoshCap20 JoshCap20 commented Feb 26, 2026

Summary

  • Add comprehensive integration tests covering the full proxy TCP lifecycle
  • Split into proxy_integration.rs (SSRF protection, health check) and proxy_forwarding.rs (HTTP/HTTPS forwarding, requires _test-support feature)
  • Add proxy_server.rs for production server behaviors (timeout, connection limiting)
  • Add _test-support cargo feature with SSRF bypass mechanism so forwarding tests can use localhost upstreams
  • Extract handle_connection to lib.rs as a single source of truth — main.rs and all test helpers call the same code path
  • Extract shared read_upstream_body helper for DRY test upstream servers
  • Rewrite malformed_request.rs to use shared test helpers

Closes

Closes #36
Closes #37
Closes #38
Closes #39

Test coverage added

Test What it covers
test_ssrf_bypass_active Verifies SSRF bypass mechanism works
test_ssrf_bypass_not_active_in_integration_tests Guards against accidental bypass leakage
test_http_get_forwarding Full GET → upstream → response
test_http_post_with_body POST with Content-Length body forwarding
test_http_response_headers_forwarded Custom response headers passed through
test_http_hop_by_hop_headers_stripped_and_others_forwarded Proxy-Authorization stripped, X-Keep forwarded
test_health_check_returns_200 Relative /health intercepted by proxy
test_health_check_not_intercepted_for_absolute_url Absolute URL /health forwarded to upstream
test_http_ssrf_block_private_address 403 for 127.0.0.1
test_connect_ssrf_block_private_address 403 for CONNECT to 127.0.0.1
test_http_ssrf_block_localhost 403 for localhost
test_connect_handler_ssrf_blocks_private Handler-level SSRF block
test_http_502_on_closed_port 502 for unreachable upstream (deterministic)
test_connect_502_on_closed_port 502 for CONNECT to closed port
test_connect_tunnel_bidirectional Full CONNECT tunnel with echo server
test_proxy_handles_multiple_sequential_requests 3 sequential requests through one proxy
test_proxy_handles_concurrent_requests 10 simultaneous requests via tokio::spawn
test_http_post_large_body 128 KiB body forwarded correctly
test_http_post_chunked_transfer_encoding Chunked body reassembly end-to-end
test_connection_timeout_closes_idle_connection Partial request triggers EOF after timeout
test_connection_limit_rejects_excess Excess connections get EOF
test_connection_limit_recovers_after_completion Permits released after connection completes

Architecture

lib.rs::handle_connection()     ← single source of truth
    ├── main.rs                 ← thin wrapper (stream split + timeout + semaphore)
    ├── tests/common/mod.rs     ← test proxy calls handle_connection(None)
    │   ├── start_proxy / start_proxy_with_timeout / start_proxy_with_limit
    │   ├── start_upstream / start_looping_upstream
    │   ├── send_raw / read_upstream_body
    ├── tests/proxy_integration.rs  ← SSRF + health check (always runs)
    ├── tests/proxy_forwarding.rs   ← forwarding tests (behind _test-support)
    ├── tests/proxy_server.rs       ← timeout + connection limiting (always runs)
    └── tests/malformed_request.rs  ← uses shared helpers

How to run

# Basic tests (always work)
cargo test

# Full suite including forwarding tests
cargo test --features _test-support

Test plan

  • cargo test --features _test-support passes (92 tests)
  • cargo fmt -- --check clean
  • cargo clippy --features _test-support -- -D warnings clean
  • Zero compiler warnings

🤖 Generated with Claude Code

Add integration tests covering the full proxy TCP lifecycle:
- HTTP GET/POST forwarding with body and headers
- Response header forwarding from upstream
- Hop-by-hop header stripping (Proxy-Authorization not leaked)
- Health check interception vs absolute URL forwarding
- SSRF blocking for HTTP, CONNECT, and localhost
- 502 Bad Gateway on unreachable upstream
- CONNECT tunnel SSRF protection
- Multiple sequential requests through one proxy

Split tests into two files:
- proxy_integration.rs: always runs (SSRF, health check, error handling)
- proxy_forwarding.rs: gated behind _test-support feature (forwarding)

Add _test-support feature flag with SSRF bypass mechanism so
integration tests can use localhost upstreams. The bypass is behind
cfg(feature) and defaults to off — never active in production.

Also fixes pre-existing cargo fmt issues in http.rs and https.rs.
- Extract start_proxy, start_upstream, send_raw into tests/common/mod.rs
- Use std::sync::Once for one-time SSRF bypass setup in forwarding tests
- Add CONNECT tunnel bidirectional data test
- Replace DNS-dependent 502 tests with deterministic bind+drop pattern
- Improve hop-by-hop test to verify both stripping and forwarding
- Remove non-deterministic unreachable host test
- Move connection handling logic from main.rs to lib.rs as public
  generic function, eliminating duplicated code in test helpers
- main.rs becomes a thin wrapper (stream split + delegation)
- tests/common/mod.rs calls rhoxy::handle_connection directly
- tests/malformed_request.rs uses shared helpers instead of own copies
- Add SSRF bypass verification test in proxy_forwarding
- Fix magic number in POST body assertion
Add 6 new integration tests covering previously untested areas:

- Concurrent requests (10 simultaneous via tokio::spawn)
- Connection timeout (partial request triggers EOF after timeout)
- Connection limit rejection (excess connections get EOF)
- Connection limit recovery (permits released after completion)
- Large body forwarding (128 KiB POST)
- Chunked transfer encoding end-to-end reassembly

Add 3 helpers to tests/common: start_proxy_with_timeout,
start_proxy_with_limit, start_looping_upstream.

91 tests passing, zero warnings.
…SRF guard

- Extract duplicated header+body reading logic into
  common::read_upstream_body with case-insensitive Content-Length matching
- Standardize all status assertions to "200 OK" (not bare "200")
- Replace redundant body assertion in health check with ends_with check
- Add negative SSRF bypass test to proxy_integration.rs to catch
  accidental feature flag leakage
- Remove unused AsyncBufReadExt import from proxy_forwarding.rs

92 tests passing, zero warnings.
@JoshCap20 JoshCap20 merged commit e336fdb into main Feb 26, 2026
1 check passed
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.

Add handle_health_check tests Add forward_response tests Add CONNECT/tunnel handler tests Add integration tests with real HTTP server

1 participant