Skip to content

Commit ea5166b

Browse files
committed
test: add container isolation tests to verify no state bleed between requests
- testContainerIsolationBetweenRequests: verifies child containers resolve their own request/response independently and parent is not polluted - testContainerIsolationForErrors: verifies error state from one request does not leak into subsequent requests https://claude.ai/code/session_0196tmVsX1KjJKmuHncp7HiV
1 parent 72657e8 commit ea5166b

1 file changed

Lines changed: 111 additions & 0 deletions

File tree

tests/HttpTest.php

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -756,4 +756,115 @@ public function testCallableStringParametersNotExecuted(): void
756756

757757
$this->assertSame('generated: generated-value', $result);
758758
}
759+
760+
public function testContainerIsolationBetweenRequests(): void
761+
{
762+
$this->http
763+
->error()
764+
->inject('error')
765+
->inject('response')
766+
->action(function ($error, $response) {
767+
$response->send('error: ' . $error->getMessage());
768+
});
769+
770+
// Route that echoes request-scoped value
771+
$route = $this->http->addRoute('GET', '/isolation-test');
772+
$route
773+
->inject('request')
774+
->action(function ($request) {
775+
echo $request->getHeader('x-req-id', 'none');
776+
});
777+
778+
// First request
779+
$container1 = new Container($this->context);
780+
$container1->set('request', function () {
781+
$request = new Request([]);
782+
$request->setURI('/isolation-test');
783+
$request->setMethod('GET');
784+
$request->addHeader('x-req-id', 'first');
785+
return $request;
786+
});
787+
$container1->set('response', fn () => new Response());
788+
789+
\ob_start();
790+
$this->http->run($container1);
791+
$result1 = \ob_get_contents();
792+
\ob_end_clean();
793+
794+
// Second request with different header
795+
$container2 = new Container($this->context);
796+
$container2->set('request', function () {
797+
$request = new Request([]);
798+
$request->setURI('/isolation-test');
799+
$request->setMethod('GET');
800+
$request->addHeader('x-req-id', 'second');
801+
return $request;
802+
});
803+
$container2->set('response', fn () => new Response());
804+
805+
\ob_start();
806+
$this->http->run($container2);
807+
$result2 = \ob_get_contents();
808+
\ob_end_clean();
809+
810+
// Each child container should resolve its own request, not bleed across
811+
$this->assertSame('first', $result1);
812+
$this->assertSame('second', $result2, 'Second request must not see first request state');
813+
814+
// Parent container must not be polluted with request-scoped deps
815+
$this->assertFalse($this->context->has('route'));
816+
}
817+
818+
public function testContainerIsolationForErrors(): void
819+
{
820+
$errorMessages = [];
821+
822+
$this->http
823+
->error()
824+
->inject('error')
825+
->action(function ($error) use (&$errorMessages) {
826+
$errorMessages[] = $error->getMessage();
827+
});
828+
829+
// Route that always throws
830+
$route = $this->http->addRoute('GET', '/error-isolation');
831+
$route
832+
->param('msg', '', new Text(200), 'error message')
833+
->action(function ($msg) {
834+
throw new Exception($msg, 500);
835+
});
836+
837+
// First request triggers error "first"
838+
$container1 = new Container($this->context);
839+
$container1->set('request', function () {
840+
$request = new Request(['msg' => 'first']);
841+
$request->setURI('/error-isolation');
842+
$request->setMethod('GET');
843+
return $request;
844+
});
845+
$container1->set('response', fn () => new Response());
846+
847+
\ob_start();
848+
$this->http->run($container1);
849+
\ob_end_clean();
850+
851+
// Second request triggers error "second"
852+
$container2 = new Container($this->context);
853+
$container2->set('request', function () {
854+
$request = new Request(['msg' => 'second']);
855+
$request->setURI('/error-isolation');
856+
$request->setMethod('GET');
857+
return $request;
858+
});
859+
$container2->set('response', fn () => new Response());
860+
861+
\ob_start();
862+
$this->http->run($container2);
863+
\ob_end_clean();
864+
865+
// Each error handler should receive its own error, not a stale one
866+
$this->assertCount(2, $errorMessages);
867+
$this->assertSame('first', $errorMessages[0]);
868+
$this->assertSame('second', $errorMessages[1], 'Second error handler should not receive stale error from first request');
869+
}
759870
}

0 commit comments

Comments
 (0)