diff --git a/src/Illuminate/Testing/TestResponse.php b/src/Illuminate/Testing/TestResponse.php index 9d70863e296e..3f2bc4bffa46 100644 --- a/src/Illuminate/Testing/TestResponse.php +++ b/src/Illuminate/Testing/TestResponse.php @@ -4,6 +4,7 @@ use ArrayAccess; use Closure; +use Illuminate\Contracts\Validation\Rule as RuleContract; use Illuminate\Contracts\View\View; use Illuminate\Cookie\CookieValuePrefix; use Illuminate\Database\Eloquent\Model; @@ -18,6 +19,7 @@ use Illuminate\Testing\Assert as PHPUnit; use Illuminate\Testing\Constraints\SeeInOrder; use Illuminate\Testing\Fluent\AssertableJson; +use Illuminate\Validation\Validator; use LogicException; use Symfony\Component\HttpFoundation\Cookie; use Symfony\Component\HttpFoundation\StreamedResponse; @@ -914,6 +916,35 @@ public function assertJsonValidationErrorFor($key, $responseKey = 'errors') return $this; } + /** + * Assert that the response has the given JSON validation errors. + * + * @param string|array $attribute + * @param string|\Illuminate\Contracts\Validation\Rule|null $rule + * @param string $responseKey + * @return $this + */ + public function assertJsonValidationErrorRule(string|array $attribute, string|RuleContract|null $rule = null, $responseKey = 'errors') + { + $validationRules = $attribute; + + if (is_string($attribute)) { + PHPUnit::assertNotNull($rule, 'No validation rule was provided.'); + + $validationRules = [$attribute => $rule]; + } + + $validator = new Validator(app('translator'), [], []); + + foreach ($validationRules as $attribute => $rule) { + $this->assertJsonValidationErrors([ + $attribute => $validator->getErrorMessage($attribute, $rule), + ], $responseKey); + } + + return $this; + } + /** * Assert that the response has no JSON validation errors for the given keys. * diff --git a/src/Illuminate/Validation/Validator.php b/src/Illuminate/Validation/Validator.php index bb5451b4d535..9064938cc58b 100755 --- a/src/Illuminate/Validation/Validator.php +++ b/src/Illuminate/Validation/Validator.php @@ -1394,6 +1394,60 @@ public function setFallbackMessages(array $messages) $this->fallbackMessages = $messages; } + /** + * Get the error messages for an attribute and a validation rule. + * + * @param string $attribute + * @param string|\Illuminate\Contracts\Validation\Rule $rule + * @return array + */ + public function getErrorMessage(string $attribute, string|RuleContract $rule): array + { + [$rule, $parameters] = ValidationRuleParser::parse($rule); + $result = []; + + if ($rule === '') { + return $result; + } + + // First we will get the correct keys for the given attribute in case the field is nested in + // an array. Then we determine if the given rule accepts other field names as parameters. + // If so, we will replace any asterisks found in the parameters with the correct keys. + if ($this->dependsOnOtherFields($rule)) { + $parameters = $this->replaceDotInParameters($parameters); + + if ($keys = $this->getExplicitKeys($attribute)) { + $parameters = $this->replaceAsterisksInParameters($parameters, $keys); + } + } + + if ($rule instanceof RuleContract) { + $messages = $rule->message(); + + $messages = $messages ? (array) $messages : [get_class($rule)]; + + foreach ($messages as $message) { + $result[] = $this->makeReplacements( + $message, $attribute, get_class($rule), [] + ); + } + + return $result; + } + + $attribute = str_replace( + [$this->dotPlaceholder, '__asterisk__'], + ['.', '*'], + $attribute + ); + + return [ + $this->makeReplacements( + $this->getMessage($attribute, $rule), $attribute, $rule, $parameters + ), + ]; + } + /** * Get the Presence Verifier implementation. * diff --git a/tests/Integration/Testing/TestResponseTest.php b/tests/Integration/Testing/TestResponseTest.php new file mode 100644 index 000000000000..c5f345603f12 --- /dev/null +++ b/tests/Integration/Testing/TestResponseTest.php @@ -0,0 +1,76 @@ + 'ok', + 'errors' => ['key' => 'The key field is required.'], + ]; + + $testResponse = TestResponse::fromBaseResponse( + (new Response)->setContent(json_encode($data)) + ); + + $testResponse->assertJsonValidationErrorRule('key', 'required'); + } + + public function testassertJsonValidationErrorRuleWithArray() + { + $data = [ + 'status' => 'ok', + 'errors' => ['key' => 'The key field is required.'], + ]; + + $testResponse = TestResponse::fromBaseResponse( + (new Response)->setContent(json_encode($data)) + ); + + $testResponse->assertJsonValidationErrorRule(['key' => 'required']); + } + + public function testassertJsonValidationErrorRuleWithCustomRule() + { + $rule = new class implements RuleContract + { + public function passes($attribute, $value) + { + return true; + } + + public function message() + { + return ':attribute must be baz'; + } + }; + + $data = [ + 'status' => 'ok', + 'errors' => ['key' => 'key must be baz'], + ]; + + $testResponse = TestResponse::fromBaseResponse( + (new Response)->setContent(json_encode($data)) + ); + + $testResponse->assertJsonValidationErrorRule('key', $rule); + } + + public function testassertJsonValidationErrorRuleWithNoRule() + { + $this->expectException(ExpectationFailedException::class); + $this->expectExceptionMessage('No validation rule was provided.'); + + $response = TestResponse::fromBaseResponse(new Response()); + $response->assertJsonValidationErrorRule('foo'); + } +} diff --git a/tests/Validation/ValidationValidatorTest.php b/tests/Validation/ValidationValidatorTest.php index 2bc5536705c4..e95dac5efdde 100755 --- a/tests/Validation/ValidationValidatorTest.php +++ b/tests/Validation/ValidationValidatorTest.php @@ -7096,6 +7096,54 @@ public function testArrayKeysValidationFailsWithNotAnArray() ); } + public function testGetErrorMessageWithBuiltinRule() + { + $trans = $this->getIlluminateArrayTranslator(); + $trans->addLines([ + 'validation.required_array_keys' => 'The :attribute field must contain entries for :values', + 'validation.required' => 'The :attribute field is required.', + ], 'en'); + + $validator = new Validator($trans, [], []); + + $this->assertSame( + ['The foo field is required.'], + $validator->getErrorMessage('foo', 'required') + ); + + $this->assertSame( + ['The foo field must contain entries for bar, baz'], + $validator->getErrorMessage('foo', 'required_array_keys:bar,baz') + ); + } + + public function testGetErrorMessageWithCustomRule() + { + $rule = new class implements Rule + { + public function passes($attribute, $value) + { + return true; + } + + public function message() + { + return ':attribute must be baz'; + } + }; + + $validator = new Validator( + $this->getIlluminateArrayTranslator(), + [], + [] + ); + + $this->assertSame( + ['foo must be baz'], + $validator->getErrorMessage('foo', $rule) + ); + } + protected function getTranslator() { return m::mock(TranslatorContract::class);