diff --git a/src/Illuminate/Foundation/Precognition.php b/src/Illuminate/Foundation/Precognition.php index 8ea2a675d09b..679b35edb1a3 100644 --- a/src/Illuminate/Foundation/Precognition.php +++ b/src/Illuminate/Foundation/Precognition.php @@ -14,7 +14,7 @@ public static function afterValidationHook($request) { return function ($validator) use ($request) { if ($validator->messages()->isEmpty() && $request->headers->has('Precognition-Validate-Only')) { - abort(204); + abort(204, headers: ['Precognition-Success' => 'true']); } }; } diff --git a/src/Illuminate/Foundation/Routing/PrecognitionCallableDispatcher.php b/src/Illuminate/Foundation/Routing/PrecognitionCallableDispatcher.php index 012dd3f13796..66456a192d84 100644 --- a/src/Illuminate/Foundation/Routing/PrecognitionCallableDispatcher.php +++ b/src/Illuminate/Foundation/Routing/PrecognitionCallableDispatcher.php @@ -18,6 +18,6 @@ public function dispatch(Route $route, $callable) { $this->resolveParameters($route, $callable); - abort(204); + abort(204, headers: ['Precognition-Success' => 'true']); } } diff --git a/src/Illuminate/Foundation/Routing/PrecognitionControllerDispatcher.php b/src/Illuminate/Foundation/Routing/PrecognitionControllerDispatcher.php index 0edcb4cef899..7ed860560f4d 100644 --- a/src/Illuminate/Foundation/Routing/PrecognitionControllerDispatcher.php +++ b/src/Illuminate/Foundation/Routing/PrecognitionControllerDispatcher.php @@ -22,7 +22,7 @@ public function dispatch(Route $route, $controller, $method) $this->resolveParameters($route, $controller, $method); - abort(204); + abort(204, headers: ['Precognition-Success' => 'true']); } /** diff --git a/src/Illuminate/Foundation/helpers.php b/src/Illuminate/Foundation/helpers.php index 844e91b86582..4d1c1a2f5997 100644 --- a/src/Illuminate/Foundation/helpers.php +++ b/src/Illuminate/Foundation/helpers.php @@ -612,7 +612,7 @@ function precognitive($callable = null) }); if (request()->isPrecognitive()) { - abort(204); + abort(204, headers: ['Precognition-Success' => 'true']); } return $payload; diff --git a/tests/Integration/Routing/PrecognitionTest.php b/tests/Integration/Routing/PrecognitionTest.php index 7c592dcaad50..21862425d39b 100644 --- a/tests/Integration/Routing/PrecognitionTest.php +++ b/tests/Integration/Routing/PrecognitionTest.php @@ -33,6 +33,7 @@ public function testItDoesntInvokeControllerMethodByDefault() $response = $this->get('test-route', ['Precognition' => 'true']); $response->assertNoContent(); + $response->assertHeader('Precognition-Success', 'true'); $this->assertTrue($this->app['ClassWasInstantiated']); } @@ -45,6 +46,7 @@ public function testItDoesntInvokeCallableControllerByDefault() $response = $this->get('test-route', ['Precognition' => 'true']); $response->assertNoContent(); + $response->assertHeader('Precognition-Success', 'true'); $this->assertTrue($this->app['ClassWasInstantiated']); } @@ -75,6 +77,7 @@ public function testItReturnsTheEmptyResponseWhenNotBailing() $response = $this->get('test-route', ['Precognition' => 'true']); $response->assertNoContent(); + $response->assertHeader('Precognition-Success', 'true'); $response->assertHeader('Precognition', 'true'); $response->assertHeader('Vary', 'Precognition'); } @@ -94,6 +97,7 @@ public function testItCanBailDuringPrecognitionRequest() $response->assertOk(); $response->assertJson(['expected' => 'response']); $response->assertHeader('Precognition', 'true'); + $response->assertHeaderMissing('Precognition-Success'); } public function testItCanExcludeValidationRulesWhenPrecognitiveWithFormRequest() @@ -109,6 +113,7 @@ public function testItCanExcludeValidationRulesWhenPrecognitiveWithFormRequest() ]); $response->assertStatus(422); + $response->assertHeaderMissing('Precognition-Success'); $response->assertJsonPath('errors', [ 'required_integer' => [ 'The required integer field must be an integer.', @@ -127,6 +132,7 @@ public function testItRunsExcludedRulesWhenNotPrecognitiveForFormRequest() ]); $response->assertStatus(422); + $response->assertHeaderMissing('Precognition-Success'); $response->assertJsonPath('errors', [ 'required_integer' => [ 'The required integer field must be an integer.', @@ -152,6 +158,7 @@ public function testClientCanSpecifyInputToValidate() ]); $response->assertStatus(422); + $response->assertHeaderMissing('Precognition-Success'); $response->assertJsonPath('errors', [ 'optional_integer_1' => [ 'The optional integer 1 field must be an integer.', @@ -178,6 +185,7 @@ public function testClientCanSpecifyNoInputsToValidate() ]); $response->assertNoContent(); + $response->assertHeader('Precognition-Success', 'true'); } public function testItAppliesHeadersWhenExceptionThrownInPrecognition() @@ -194,6 +202,7 @@ public function testItAppliesHeadersWhenExceptionThrownInPrecognition() $response->assertNotFound(); $response->assertHeader('Precognition', 'true'); $response->assertHeader('Vary', 'Precognition'); + $response->assertHeaderMissing('Precognition-Success'); } public function testItAppliesHeadersWhenFlowControlExceptionIsThrown() @@ -208,6 +217,7 @@ public function testItAppliesHeadersWhenFlowControlExceptionIsThrown() $response->assertForbidden(); $response->assertHeader('Precognition', 'true'); $response->assertHeader('Vary', 'Precognition'); + $response->assertHeaderMissing('Precognition-Success'); // Check with Authorize middleware last... Route::get('test-route-after', fn () => fail()) @@ -218,6 +228,7 @@ public function testItAppliesHeadersWhenFlowControlExceptionIsThrown() $response->assertForbidden(); $response->assertHeader('Precognition', 'true'); $response->assertHeader('Vary', 'Precognition'); + $response->assertHeaderMissing('Precognition-Success'); } public function testItCanReturnValuesFromPrecognitionClosure() @@ -237,6 +248,7 @@ public function testItCanReturnValuesFromPrecognitionClosure() $response = $this->get('test-route'); $response->assertOk(); + $response->assertHeaderMissing('Precognition-Success'); $response->assertExactJson([ 'first' => 'expected', 'second' => 'values', @@ -261,6 +273,7 @@ public function testItCanBailWithResponseDuringNormalRequest() $response->assertOk(); $response->assertJson(['expected' => 'response']); $response->assertHeaderMissing('Precognition'); + $response->assertHeaderMissing('Precognition-Success'); } public function testArbitraryBailResponseIsParsedToResponse() @@ -282,6 +295,7 @@ public function testArbitraryBailResponseIsParsedToResponse() $response->assertJson(['expected' => 'response']); $response->assertHeader('Precognition', 'true'); $response->assertHeader('Vary', 'Precognition'); + $response->assertHeaderMissing('Precognition-Success'); } public function testClientCanSpecifyInputsToValidateWhenUsingControllerValidate() @@ -300,6 +314,7 @@ public function testClientCanSpecifyInputsToValidateWhenUsingControllerValidate( ]); $response->assertStatus(422); + $response->assertHeaderMissing('Precognition-Success'); $response->assertJsonPath('errors', [ 'optional_integer_1' => [ 'The optional integer 1 field must be an integer.', @@ -326,6 +341,7 @@ public function testClientCanSpecifyInputsToValidateWhenUsingControllerValidateW ]); $response->assertStatus(422); + $response->assertHeaderMissing('Precognition-Success'); $response->assertJsonPath('errors', [ 'optional_integer_1' => [ 'The optional integer 1 field must be an integer.', @@ -363,6 +379,7 @@ public function testClientCanSpecifyInputsToValidateWhenUsingRequestValidate() ]); $response->assertStatus(422); + $response->assertHeaderMissing('Precognition-Success'); $response->assertJsonPath('errors', [ 'optional_integer_1' => [ 'The optional integer 1 field must be an integer.', @@ -401,6 +418,7 @@ public function testClientCanSpecifyInputsToValidateWhenUsingRequestValidateWith ]); $response->assertStatus(422); + $response->assertHeaderMissing('Precognition-Success'); $response->assertJsonPath('errors', [ 'optional_integer_1' => [ 'The optional integer 1 field must be an integer.', @@ -427,6 +445,7 @@ public function testClientCanSpecifyInputsToValidateWhenUsingControllerValidateW ]); $response->assertStatus(422); + $response->assertHeaderMissing('Precognition-Success'); $response->assertJsonPath('errors', [ 'optional_integer_1' => [ 'The optional integer 1 field must be an integer.', @@ -450,6 +469,7 @@ public function testItAppendsAnAdditionalVaryHeaderInsteadOfReplacingAnyExisting $response = $this->get('test-route', ['Precognition' => 'true']); $response->assertHeader('Vary', 'Foo, Precognition'); + $response->assertHeaderMissing('Precognition-Success'); } public function testSpacesAreImportantInValidationFilterLogicForJsonRequests() @@ -465,6 +485,7 @@ public function testSpacesAreImportantInValidationFilterLogicForJsonRequests() ]); $response->assertStatus(422); + $response->assertHeaderMissing('Precognition-Success'); $response->assertJsonPath('errors', [ ' input with spaces ' => [ 'The input with spaces field must be an integer.', @@ -482,6 +503,7 @@ public function testVaryHeaderIsAppliedToNonPrecognitionResponses() $response->assertOk(); $this->assertSame('ok', $response->content()); $response->assertHeader('Vary', 'Precognition'); + $response->assertHeaderMissing('Precognition-Success'); } public function testItStopsExecutionAfterSuccessfulValidationWithValidationFilteringAndFormRequest() @@ -502,6 +524,7 @@ public function testItStopsExecutionAfterSuccessfulValidationWithValidationFilte $this->assertFalse($this->app['ClassWasInstantiated']); $response->assertNoContent(); $response->assertHeader('Precognition', 'true'); + $response->assertHeader('Precognition-Success', 'true'); } public function testItStopsExecutionAfterFailedValidationWithNestedValidationFilteringUsingFormRequest() @@ -533,6 +556,7 @@ public function testItStopsExecutionAfterFailedValidationWithNestedValidationFil ], ]); $response->assertHeader('Precognition', 'true'); + $response->assertHeaderMissing('Precognition-Success'); } public function testItStopsExecutionAfterFailedValidationWithNestedValidationFilteringUsingRequestValidate() @@ -568,6 +592,7 @@ public function testItStopsExecutionAfterFailedValidationWithNestedValidationFil ], ]); $response->assertHeader('Precognition', 'true'); + $response->assertHeaderMissing('Precognition-Success'); } public function testItStopsExecutionAfterFailedValidationWithNestedValidationFilteringUsingControllerValidate() @@ -598,6 +623,7 @@ public function testItStopsExecutionAfterFailedValidationWithNestedValidationFil ], ]); $response->assertHeader('Precognition', 'true'); + $response->assertHeaderMissing('Precognition-Success'); } public function testItStopsExecutionAfterFailedValidationWithNestedValidationFilteringUsingControllerValidateWith() @@ -628,6 +654,7 @@ public function testItStopsExecutionAfterFailedValidationWithNestedValidationFil ], ]); $response->assertHeader('Precognition', 'true'); + $response->assertHeaderMissing('Precognition-Success'); } public function testItCanPassValidationForEscapedDotsAfterFilteringWithPrecognition() @@ -645,6 +672,7 @@ public function testItCanPassValidationForEscapedDotsAfterFilteringWithPrecognit $response->assertNoContent(); $response->assertHeader('Precognition', 'true'); + $response->assertHeader('Precognition-Success', 'true'); } public function testItCanFilterRulesWithEscapedDotsUsingFormRequest() @@ -670,6 +698,7 @@ public function testItCanFilterRulesWithEscapedDotsUsingFormRequest() ], ]); $response->assertHeader('Precognition', 'true'); + $response->assertHeaderMissing('Precognition-Success'); } public function testItCanFilterRulesWithEscapedDotsWhenUsingRequestValidate() @@ -699,6 +728,7 @@ public function testItCanFilterRulesWithEscapedDotsWhenUsingRequestValidate() ], ]); $response->assertHeader('Precognition', 'true'); + $response->assertHeaderMissing('Precognition-Success'); } public function testItCanFilterRulesWithEscapedDotsWhenUsingControllerValidate() @@ -723,6 +753,7 @@ public function testItCanFilterRulesWithEscapedDotsWhenUsingControllerValidate() ], ]); $response->assertHeader('Precognition', 'true'); + $response->assertHeaderMissing('Precognition-Success'); } public function testItCanFilterRulesWithEscapedDotsWhenUsingControllerValidateWith() @@ -770,6 +801,7 @@ public function testItContinuesExecutionAfterSuccessfulValidationWithoutValidati $response->assertOk(); $this->assertSame('expected response', $response->content()); $response->assertHeader('Precognition', 'true'); + $response->assertHeaderMissing('Precognition-Success'); } public function testItStopsExecutionAfterSuccessfulValidationWithValidationFilteringAndControllerValidate() @@ -787,6 +819,7 @@ public function testItStopsExecutionAfterSuccessfulValidationWithValidationFilte $response->assertNoContent(); $response->assertHeader('Precognition', 'true'); + $response->assertHeader('Precognition-Success', 'true'); } public function testItContinuesExecutionAfterSuccessfulValidationWithoutValidationFilteringAndControllerValidate() @@ -803,6 +836,7 @@ public function testItContinuesExecutionAfterSuccessfulValidationWithoutValidati $response->assertOk(); $this->assertSame('Post-validation code was executed.', $response->content()); $response->assertHeader('Precognition', 'true'); + $response->assertHeaderMissing('Precognition-Success'); } public function testItStopsExecutionAfterSuccessfulValidationWithValidationFilteringAndControllerValidateWithBag() @@ -820,6 +854,7 @@ public function testItStopsExecutionAfterSuccessfulValidationWithValidationFilte $response->assertNoContent(); $response->assertHeader('Precognition', 'true'); + $response->assertHeader('Precognition-Success', 'true'); } public function testItContinuesExecutionAfterSuccessfulValidationWithoutValidationFilteringAndControllerValidateWithBag() @@ -836,6 +871,7 @@ public function testItContinuesExecutionAfterSuccessfulValidationWithoutValidati $response->assertOk(); $this->assertSame('Post-validation code was executed.', $response->content()); $response->assertHeader('Precognition', 'true'); + $response->assertHeaderMissing('Precognition-Success'); } public function testItStopsExecutionAfterSuccessfulValidationWithValidationFilteringAndControllerValidateWith() @@ -853,6 +889,7 @@ public function testItStopsExecutionAfterSuccessfulValidationWithValidationFilte $response->assertNoContent(); $response->assertHeader('Precognition', 'true'); + $response->assertHeader('Precognition-Success', 'true'); } public function testItContinuesExecutionAfterSuccessfulValidationWithoutValidationFilteringAndControllerValidateWithXXXX() @@ -869,6 +906,7 @@ public function testItContinuesExecutionAfterSuccessfulValidationWithoutValidati $response->assertOk(); $this->assertSame('Post-validation code was executed.', $response->content()); $response->assertHeader('Precognition', 'true'); + $response->assertHeaderMissing('Precognition-Success'); } public function testItStopsExecutionAfterSuccessfulValidationWithValidationFilteringAndControllerValidateWithPassingValidator() @@ -886,6 +924,7 @@ public function testItStopsExecutionAfterSuccessfulValidationWithValidationFilte $response->assertNoContent(); $response->assertHeader('Precognition', 'true'); + $response->assertHeader('Precognition-Success', 'true'); } public function testItContinuesExecutionAfterSuccessfulValidationWithoutValidationFilteringAndControllerValidateWithPassingValidator() @@ -902,6 +941,7 @@ public function testItContinuesExecutionAfterSuccessfulValidationWithoutValidati $response->assertOk(); $this->assertSame('Post-validation code was executed.', $response->content()); $response->assertHeader('Precognition', 'true'); + $response->assertHeaderMissing('Precognition-Success'); } public function testItStopsExecutionAfterSuccessfulValidationWithValidationFilteringAndRequestValidate() @@ -933,6 +973,7 @@ public function testItStopsExecutionAfterSuccessfulValidationWithValidationFilte $response->assertNoContent(); $response->assertHeader('Precognition', 'true'); + $response->assertHeader('Precognition-Success', 'true'); } public function testItContinuesExecutionAfterSuccessfulValidationWithoutValidationFilteringAndRequestValidate() @@ -962,6 +1003,7 @@ public function testItContinuesExecutionAfterSuccessfulValidationWithoutValidati $response->assertOk(); $this->assertSame('Post-validation code was executed.', $response->content()); $response->assertHeader('Precognition', 'true'); + $response->assertHeaderMissing('Precognition-Success'); } public function testItDoesNotSetLastUrl() @@ -1000,6 +1042,7 @@ public function testItAppendsVaryHeaderToSymfonyResponse() $response = $this->get('test-route'); $response->assertOk(); $response->assertHeader('Expected', 'Header'); + $response->assertHeaderMissing('Precognition-Success'); } public function testItAppendsPrecognitionHeaderToSymfonyResponse() @@ -1016,6 +1059,18 @@ public function testItAppendsPrecognitionHeaderToSymfonyResponse() $response->assertHeader('Expected', 'Header'); $response->assertHeader('Precognition', 'true'); } + + public function testItCanNoContentWhileAlsoNotBeingPrecognitive() + { + Route::get('test-route', function () { + // + })->middleware([HandlePrecognitiveRequests::class, MiddlewareThatReturnsNoContent::class]); + + $response = $this->get('test-route', ['Precognition' => 'true']); + $response->assertNoContent(); + $response->assertHeader('Precognition', 'true'); + $response->assertHeaderMissing('Precognition-Success', 'true'); + } } class PrecognitionTestController @@ -1266,3 +1321,11 @@ public function handle($request, $next) }, null, ['Expected' => 'Header']); } } + +class MiddlewareThatReturnsNoContent +{ + public function handle() + { + return response()->noContent(); + } +}