FoundationFormRequestTest.php 9.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359
  1. <?php
  2. namespace Illuminate\Tests\Foundation;
  3. use Exception;
  4. use Illuminate\Auth\Access\AuthorizationException;
  5. use Illuminate\Auth\Access\Response;
  6. use Illuminate\Container\Container;
  7. use Illuminate\Contracts\Translation\Translator;
  8. use Illuminate\Contracts\Validation\Factory as ValidationFactoryContract;
  9. use Illuminate\Contracts\Validation\Validator;
  10. use Illuminate\Foundation\Http\FormRequest;
  11. use Illuminate\Http\RedirectResponse;
  12. use Illuminate\Routing\Redirector;
  13. use Illuminate\Routing\UrlGenerator;
  14. use Illuminate\Validation\Factory as ValidationFactory;
  15. use Illuminate\Validation\ValidationException;
  16. use Mockery as m;
  17. use PHPUnit\Framework\TestCase;
  18. class FoundationFormRequestTest extends TestCase
  19. {
  20. protected $mocks = [];
  21. protected function tearDown(): void
  22. {
  23. m::close();
  24. $this->mocks = [];
  25. }
  26. public function testValidatedMethodReturnsTheValidatedData()
  27. {
  28. $request = $this->createRequest(['name' => 'specified', 'with' => 'extras']);
  29. $request->validateResolved();
  30. $this->assertEquals(['name' => 'specified'], $request->validated());
  31. }
  32. public function testValidatedMethodReturnsTheValidatedDataNestedRules()
  33. {
  34. $payload = ['nested' => ['foo' => 'bar', 'baz' => ''], 'array' => [1, 2]];
  35. $request = $this->createRequest($payload, FoundationTestFormRequestNestedStub::class);
  36. $request->validateResolved();
  37. $this->assertEquals(['nested' => ['foo' => 'bar'], 'array' => [1, 2]], $request->validated());
  38. }
  39. public function testValidatedMethodReturnsTheValidatedDataNestedChildRules()
  40. {
  41. $payload = ['nested' => ['foo' => 'bar', 'with' => 'extras']];
  42. $request = $this->createRequest($payload, FoundationTestFormRequestNestedChildStub::class);
  43. $request->validateResolved();
  44. $this->assertEquals(['nested' => ['foo' => 'bar']], $request->validated());
  45. }
  46. public function testValidatedMethodReturnsTheValidatedDataNestedArrayRules()
  47. {
  48. $payload = ['nested' => [['bar' => 'baz', 'with' => 'extras'], ['bar' => 'baz2', 'with' => 'extras']]];
  49. $request = $this->createRequest($payload, FoundationTestFormRequestNestedArrayStub::class);
  50. $request->validateResolved();
  51. $this->assertEquals(['nested' => [['bar' => 'baz'], ['bar' => 'baz2']]], $request->validated());
  52. }
  53. public function testValidatedMethodNotValidateTwice()
  54. {
  55. $payload = ['name' => 'specified', 'with' => 'extras'];
  56. $request = $this->createRequest($payload, FoundationTestFormRequestTwiceStub::class);
  57. $request->validateResolved();
  58. $request->validated();
  59. $this->assertEquals(1, FoundationTestFormRequestTwiceStub::$count);
  60. }
  61. public function testValidateThrowsWhenValidationFails()
  62. {
  63. $this->expectException(ValidationException::class);
  64. $request = $this->createRequest(['no' => 'name']);
  65. $this->mocks['redirect']->shouldReceive('withInput->withErrors');
  66. $request->validateResolved();
  67. }
  68. public function testValidateMethodThrowsWhenAuthorizationFails()
  69. {
  70. $this->expectException(AuthorizationException::class);
  71. $this->expectExceptionMessage('This action is unauthorized.');
  72. $this->createRequest([], FoundationTestFormRequestForbiddenStub::class)->validateResolved();
  73. }
  74. public function testValidateThrowsExceptionFromAuthorizationResponse()
  75. {
  76. $this->expectException(AuthorizationException::class);
  77. $this->expectExceptionMessage('foo');
  78. $this->createRequest([], FoundationTestFormRequestForbiddenWithResponseStub::class)->validateResolved();
  79. }
  80. public function testValidateDoesntThrowExceptionFromResponseAllowed()
  81. {
  82. $this->createRequest([], FoundationTestFormRequestPassesWithResponseStub::class)->validateResolved();
  83. }
  84. public function testPrepareForValidationRunsBeforeValidation()
  85. {
  86. $this->createRequest([], FoundationTestFormRequestHooks::class)->validateResolved();
  87. }
  88. public function test_after_validation_runs_after_validation()
  89. {
  90. $request = $this->createRequest([], FoundationTestFormRequestHooks::class);
  91. $request->validateResolved();
  92. $this->assertEquals(['name' => 'Adam'], $request->all());
  93. }
  94. /**
  95. * Catch the given exception thrown from the executor, and return it.
  96. *
  97. * @param string $class
  98. * @param \Closure $executor
  99. * @return \Exception
  100. *
  101. * @throws \Exception
  102. */
  103. protected function catchException($class, $executor)
  104. {
  105. try {
  106. $executor();
  107. } catch (Exception $e) {
  108. if (is_a($e, $class)) {
  109. return $e;
  110. }
  111. throw $e;
  112. }
  113. throw new Exception("No exception thrown. Expected exception {$class}.");
  114. }
  115. /**
  116. * Create a new request of the given type.
  117. *
  118. * @param array $payload
  119. * @param string $class
  120. * @return \Illuminate\Foundation\Http\FormRequest
  121. */
  122. protected function createRequest($payload = [], $class = FoundationTestFormRequestStub::class)
  123. {
  124. $container = tap(new Container, function ($container) {
  125. $container->instance(
  126. ValidationFactoryContract::class,
  127. $this->createValidationFactory($container)
  128. );
  129. });
  130. $request = $class::create('/', 'GET', $payload);
  131. return $request->setRedirector($this->createMockRedirector($request))
  132. ->setContainer($container);
  133. }
  134. /**
  135. * Create a new validation factory.
  136. *
  137. * @param \Illuminate\Container\Container $container
  138. * @return \Illuminate\Validation\Factory
  139. */
  140. protected function createValidationFactory($container)
  141. {
  142. $translator = m::mock(Translator::class)->shouldReceive('get')
  143. ->zeroOrMoreTimes()->andReturn('error')->getMock();
  144. return new ValidationFactory($translator, $container);
  145. }
  146. /**
  147. * Create a mock redirector.
  148. *
  149. * @param \Illuminate\Http\Request $request
  150. * @return \Illuminate\Routing\Redirector
  151. */
  152. protected function createMockRedirector($request)
  153. {
  154. $redirector = $this->mocks['redirector'] = m::mock(Redirector::class);
  155. $redirector->shouldReceive('getUrlGenerator')->zeroOrMoreTimes()
  156. ->andReturn($generator = $this->createMockUrlGenerator());
  157. $redirector->shouldReceive('to')->zeroOrMoreTimes()
  158. ->andReturn($this->createMockRedirectResponse());
  159. $generator->shouldReceive('previous')->zeroOrMoreTimes()
  160. ->andReturn('previous');
  161. return $redirector;
  162. }
  163. /**
  164. * Create a mock URL generator.
  165. *
  166. * @return \Illuminate\Routing\UrlGenerator
  167. */
  168. protected function createMockUrlGenerator()
  169. {
  170. return $this->mocks['generator'] = m::mock(UrlGenerator::class);
  171. }
  172. /**
  173. * Create a mock redirect response.
  174. *
  175. * @return \Illuminate\Http\RedirectResponse
  176. */
  177. protected function createMockRedirectResponse()
  178. {
  179. return $this->mocks['redirect'] = m::mock(RedirectResponse::class);
  180. }
  181. }
  182. class FoundationTestFormRequestStub extends FormRequest
  183. {
  184. public function rules()
  185. {
  186. return ['name' => 'required'];
  187. }
  188. public function authorize()
  189. {
  190. return true;
  191. }
  192. }
  193. class FoundationTestFormRequestNestedStub extends FormRequest
  194. {
  195. public function rules()
  196. {
  197. return ['nested.foo' => 'required', 'array.*' => 'integer'];
  198. }
  199. public function authorize()
  200. {
  201. return true;
  202. }
  203. }
  204. class FoundationTestFormRequestNestedChildStub extends FormRequest
  205. {
  206. public function rules()
  207. {
  208. return ['nested.foo' => 'required'];
  209. }
  210. public function authorize()
  211. {
  212. return true;
  213. }
  214. }
  215. class FoundationTestFormRequestNestedArrayStub extends FormRequest
  216. {
  217. public function rules()
  218. {
  219. return ['nested.*.bar' => 'required'];
  220. }
  221. public function authorize()
  222. {
  223. return true;
  224. }
  225. }
  226. class FoundationTestFormRequestTwiceStub extends FormRequest
  227. {
  228. public static $count = 0;
  229. public function rules()
  230. {
  231. return ['name' => 'required'];
  232. }
  233. public function withValidator(Validator $validator)
  234. {
  235. $validator->after(function ($validator) {
  236. self::$count++;
  237. });
  238. }
  239. public function authorize()
  240. {
  241. return true;
  242. }
  243. }
  244. class FoundationTestFormRequestForbiddenStub extends FormRequest
  245. {
  246. public function authorize()
  247. {
  248. return false;
  249. }
  250. }
  251. class FoundationTestFormRequestHooks extends FormRequest
  252. {
  253. public function rules()
  254. {
  255. return ['name' => 'required'];
  256. }
  257. public function authorize()
  258. {
  259. return true;
  260. }
  261. public function prepareForValidation()
  262. {
  263. $this->replace(['name' => 'Taylor']);
  264. }
  265. public function passedValidation()
  266. {
  267. $this->replace(['name' => 'Adam']);
  268. }
  269. }
  270. class FoundationTestFormRequestForbiddenWithResponseStub extends FormRequest
  271. {
  272. public function authorize()
  273. {
  274. return Response::deny('foo');
  275. }
  276. }
  277. class FoundationTestFormRequestPassesWithResponseStub extends FormRequest
  278. {
  279. public function rules()
  280. {
  281. return [];
  282. }
  283. public function authorize()
  284. {
  285. return Response::allow('baz');
  286. }
  287. }