Skip to content

fix: correct multipart upload boundary mismatch and add real-HTTP-transport tests - #233

Open
morcen wants to merge 1 commit into
mainfrom
fix/issue-87-real-server-integration-test
Open

fix: correct multipart upload boundary mismatch and add real-HTTP-transport tests#233
morcen wants to merge 1 commit into
mainfrom
fix/issue-87-real-server-integration-test

Conversation

@morcen

@morcen morcen commented Sep 10, 2026

Copy link
Copy Markdown
Owner

What was broken

PassageService::callService() forwarded the client's original Content-Type header (including its multipart boundary) even when the multipart branch goes on to build an entirely new multipart body via Guzzle's attach(). Guzzle generates a fresh random boundary for that body and only sets a matching Content-Type header when none is already present on the request, so the forwarded header's boundary silently won instead and never matched the boundary actually used in the body.

Every multipart file upload proxied through Passage was corrupted on the wire as a result — undetected because the existing test suite exclusively uses Http::fake(), which never serializes the outgoing body or validates it against the Content-Type header, so a boundary mismatch of this kind is invisible to it.

What changed

  • src/Services/PassageService.php: the multipart branch now excludes the client's Content-Type header from what gets forwarded, letting Guzzle set the boundary-correct header itself.
  • tests/Feature/PassageRealServerIntegrationTest.php (new): a real-HTTP-transport integration lane that proxies requests through an actual PHP built-in web server (tests/Fixtures/real-server-router.php) instead of Http::fake(), covering JSON/urlencoded/multipart body forwarding, header forwarding, query forwarding, streaming, and the allowed-hosts guard — exactly the class of bug a fake-based HTTP layer can't catch.
  • tests/Unit/PassageServiceTest.php: added a focused unit test asserting the client's Content-Type header is excluded from withHeaders() for a multipart request (verified this test fails without the fix and passes with it).

Notes

Building the real-server test lane requested in #87 is what surfaced this bug in the first place — it's exactly the "riskiest audit findings" scenario that issue describes as invisible to a fake-only suite. The coverage-gate portion of #87 (enabling a coverage driver/floor in CI) is out of scope here and left as follow-up work.

Fixes #87

PassageService forwarded the client's original Content-Type header
(including its multipart boundary) even when the multipart branch goes
on to build an entirely new multipart body via attach(). Guzzle
generates a fresh random boundary for that body and only sets a
matching Content-Type header when none is already present on the
request, so the forwarded header's boundary silently won instead and
never matched the boundary actually used in the body. Every multipart
file upload proxied through Passage was corrupted on the wire as a
result, undetected because the existing Http::fake()-based test suite
never serializes the outgoing body or validates it against the
Content-Type header.

The multipart branch now excludes the client's Content-Type header
from what gets forwarded, letting Guzzle set the boundary-correct one
itself.

Also adds a real-HTTP-transport integration test lane
(tests/Feature/PassageRealServerIntegrationTest.php), proxying through
an actual PHP built-in web server instead of Http::fake(), covering
body types, header forwarding, query forwarding, streaming, and the
allowed-hosts guard — this class of bug is invisible to a fake-based
HTTP layer.
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 a real-server integration test lane and a coverage gate

1 participant