ServerRequestFactoryTest.php 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723
  1. <?php
  2. declare(strict_types=1);
  3. /**
  4. * CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
  5. * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
  6. *
  7. * Licensed under The MIT License
  8. * For full copyright and license information, please see the LICENSE.txt
  9. * Redistributions of files must retain the above copyright notice.
  10. *
  11. * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
  12. * @link https://cakephp.org CakePHP(tm) Project
  13. * @since 3.3.0
  14. * @license https://opensource.org/licenses/mit-license.php MIT License
  15. */
  16. namespace Cake\Test\TestCase\Http;
  17. use Cake\Core\Configure;
  18. use Cake\Http\ServerRequestFactory;
  19. use Cake\Http\Session;
  20. use Cake\TestSuite\TestCase;
  21. use Laminas\Diactoros\Exception\InvalidArgumentException;
  22. use Laminas\Diactoros\UploadedFile;
  23. use Psr\Http\Message\UploadedFileInterface;
  24. /**
  25. * Test case for the server factory.
  26. */
  27. class ServerRequestFactoryTest extends TestCase
  28. {
  29. /**
  30. * @var array|null
  31. */
  32. protected $server;
  33. /**
  34. * @var array|null
  35. */
  36. protected $post;
  37. /**
  38. * @var array|null
  39. */
  40. protected $files;
  41. /**
  42. * @var array|null
  43. */
  44. protected $cookies;
  45. /**
  46. * @var array|null
  47. */
  48. protected $get;
  49. /**
  50. * setup
  51. *
  52. * @return void
  53. */
  54. public function setUp(): void
  55. {
  56. parent::setUp();
  57. $this->server = $_SERVER;
  58. $this->post = $_POST;
  59. $this->files = $_FILES;
  60. $this->cookies = $_COOKIE;
  61. $this->get = $_GET;
  62. }
  63. /**
  64. * teardown
  65. *
  66. * @return void
  67. */
  68. public function tearDown(): void
  69. {
  70. parent::tearDown();
  71. $_SERVER = $this->server;
  72. $_POST = $this->post;
  73. $_FILES = $this->files;
  74. $_COOKIE = $this->cookies;
  75. $_GET = $this->get;
  76. }
  77. /**
  78. * Test fromGlobals reads super globals
  79. *
  80. * @return void
  81. */
  82. public function testFromGlobalsSuperGlobals()
  83. {
  84. $_POST = [
  85. 'title' => 'custom',
  86. ];
  87. $_FILES = [
  88. 'image' => [
  89. 'tmp_name' => __FILE__,
  90. 'error' => 0,
  91. 'name' => 'cats.png',
  92. 'type' => 'image/png',
  93. 'size' => 2112,
  94. ],
  95. ];
  96. $_COOKIE = ['key' => 'value'];
  97. $_GET = ['query' => 'string'];
  98. $res = ServerRequestFactory::fromGlobals();
  99. $this->assertSame($_COOKIE['key'], $res->getCookie('key'));
  100. $this->assertSame($_GET['query'], $res->getQuery('query'));
  101. $this->assertArrayHasKey('title', $res->getData());
  102. $this->assertArrayHasKey('image', $res->getData());
  103. $this->assertCount(1, $res->getUploadedFiles());
  104. /** @var \Laminas\Diactoros\UploadedFile $expected */
  105. $expected = $res->getData('image');
  106. $this->assertInstanceOf(UploadedFileInterface::class, $expected);
  107. $this->assertSame($_FILES['image']['size'], $expected->getSize());
  108. $this->assertSame($_FILES['image']['error'], $expected->getError());
  109. $this->assertSame($_FILES['image']['name'], $expected->getClientFilename());
  110. $this->assertSame($_FILES['image']['type'], $expected->getClientMediaType());
  111. }
  112. /**
  113. * Test fromGlobals input
  114. *
  115. * @return void
  116. */
  117. public function testFromGlobalsInput()
  118. {
  119. $res = ServerRequestFactory::fromGlobals();
  120. $this->assertSame('', $res->input());
  121. }
  122. /**
  123. * Test fromGlobals includes the session
  124. *
  125. * @preserveGlobalState disabled
  126. * @runInSeparateProcess
  127. * @return void
  128. */
  129. public function testFromGlobalsUrlSession()
  130. {
  131. Configure::write('App.base', '/basedir');
  132. $server = [
  133. 'DOCUMENT_ROOT' => '/cake/repo/branches/1.2.x.x/webroot',
  134. 'PHP_SELF' => '/index.php',
  135. 'REQUEST_URI' => '/posts/add',
  136. ];
  137. $res = ServerRequestFactory::fromGlobals($server);
  138. $session = $res->getAttribute('session');
  139. $this->assertInstanceOf(Session::class, $session);
  140. $this->assertSame('/basedir/', ini_get('session.cookie_path'), 'Needs trailing / for cookie to work');
  141. }
  142. /**
  143. * Test fromGlobals with App.base defined.
  144. *
  145. * @return void
  146. */
  147. public function testFromGlobalsUrlBaseDefined()
  148. {
  149. Configure::write('App.base', 'basedir');
  150. $server = [
  151. 'DOCUMENT_ROOT' => '/cake/repo/branches/1.2.x.x/webroot',
  152. 'PHP_SELF' => '/index.php',
  153. 'REQUEST_URI' => '/posts/add',
  154. ];
  155. $res = ServerRequestFactory::fromGlobals($server);
  156. $this->assertSame('basedir', $res->getAttribute('base'));
  157. $this->assertSame('basedir/', $res->getAttribute('webroot'));
  158. $this->assertSame('/posts/add', $res->getUri()->getPath());
  159. }
  160. /**
  161. * Test fromGlobals with mod-rewrite server configuration.
  162. *
  163. * @return void
  164. */
  165. public function testFromGlobalsUrlModRewrite()
  166. {
  167. Configure::write('App.baseUrl', false);
  168. $server = [
  169. 'DOCUMENT_ROOT' => '/cake/repo/branches',
  170. 'PHP_SELF' => '/urlencode me/webroot/index.php',
  171. 'REQUEST_URI' => '/posts/view/1',
  172. ];
  173. $res = ServerRequestFactory::fromGlobals($server);
  174. $this->assertSame('/urlencode%20me', $res->getAttribute('base'));
  175. $this->assertSame('/urlencode%20me/', $res->getAttribute('webroot'));
  176. $this->assertSame('/posts/view/1', $res->getUri()->getPath());
  177. }
  178. /**
  179. * Test fromGlobals with mod-rewrite in the root dir.
  180. *
  181. * @return void
  182. */
  183. public function testFromGlobalsUrlModRewriteRootDir()
  184. {
  185. $server = [
  186. 'DOCUMENT_ROOT' => '/cake/repo/branches/1.2.x.x/webroot',
  187. 'PHP_SELF' => '/index.php',
  188. 'REQUEST_URI' => '/posts/add',
  189. ];
  190. $res = ServerRequestFactory::fromGlobals($server);
  191. $this->assertSame('', $res->getAttribute('base'));
  192. $this->assertSame('/', $res->getAttribute('webroot'));
  193. $this->assertSame('/posts/add', $res->getUri()->getPath());
  194. }
  195. /**
  196. * Test fromGlobals with App.baseUrl defined implying no
  197. * mod-rewrite and no virtual path.
  198. *
  199. * @return void
  200. */
  201. public function testFromGlobalsUrlNoModRewriteWebrootDir()
  202. {
  203. Configure::write('App', [
  204. 'dir' => 'app',
  205. 'webroot' => 'www',
  206. 'base' => false,
  207. 'baseUrl' => '/cake/index.php',
  208. ]);
  209. $server = [
  210. 'DOCUMENT_ROOT' => '/Users/markstory/Sites',
  211. 'SCRIPT_FILENAME' => '/Users/markstory/Sites/cake/www/index.php',
  212. 'PHP_SELF' => '/cake/www/index.php/posts/index',
  213. 'REQUEST_URI' => '/cake/www/index.php',
  214. ];
  215. $res = ServerRequestFactory::fromGlobals($server);
  216. $this->assertSame('/cake/www/', $res->getAttribute('webroot'));
  217. $this->assertSame('/cake/index.php', $res->getAttribute('base'));
  218. $this->assertSame('/', $res->getUri()->getPath());
  219. }
  220. /**
  221. * Test fromGlobals with App.baseUrl defined implying no
  222. * mod-rewrite
  223. *
  224. * @return void
  225. */
  226. public function testFromGlobalsUrlNoModRewrite()
  227. {
  228. Configure::write('App', [
  229. 'dir' => 'app',
  230. 'webroot' => 'webroot',
  231. 'base' => false,
  232. 'baseUrl' => '/cake/index.php',
  233. ]);
  234. $server = [
  235. 'DOCUMENT_ROOT' => '/Users/markstory/Sites',
  236. 'SCRIPT_FILENAME' => '/Users/markstory/Sites/cake/index.php',
  237. 'PHP_SELF' => '/cake/index.php/posts/index',
  238. 'REQUEST_URI' => '/cake/index.php/posts/index',
  239. ];
  240. $res = ServerRequestFactory::fromGlobals($server);
  241. $this->assertSame('/cake/webroot/', $res->getAttribute('webroot'));
  242. $this->assertSame('/cake/index.php', $res->getAttribute('base'));
  243. $this->assertSame('/posts/index', $res->getUri()->getPath());
  244. }
  245. /**
  246. * Test fromGlobals with App.baseUrl defined implying no
  247. * mod-rewrite in the root dir.
  248. *
  249. * @return void
  250. */
  251. public function testFromGlobalsUrlNoModRewriteRootDir()
  252. {
  253. Configure::write('App', [
  254. 'dir' => 'cake',
  255. 'webroot' => 'webroot',
  256. 'base' => false,
  257. 'baseUrl' => '/index.php',
  258. ]);
  259. $server = [
  260. 'DOCUMENT_ROOT' => '/Users/markstory/Sites/cake',
  261. 'SCRIPT_FILENAME' => '/Users/markstory/Sites/cake/index.php',
  262. 'PHP_SELF' => '/index.php/posts/add',
  263. 'REQUEST_URI' => '/index.php/posts/add',
  264. ];
  265. $res = ServerRequestFactory::fromGlobals($server);
  266. $this->assertSame('/webroot/', $res->getAttribute('webroot'));
  267. $this->assertSame('/index.php', $res->getAttribute('base'));
  268. $this->assertSame('/posts/add', $res->getUri()->getPath());
  269. }
  270. /**
  271. * Tests the default file upload merging behavior.
  272. *
  273. * @return void
  274. */
  275. public function testFromGlobalsWithFilesAsObjectsDefault()
  276. {
  277. $this->assertNull(Configure::read('App.uploadedFilesAsObjects'));
  278. $files = [
  279. 'file' => [
  280. 'name' => 'file.txt',
  281. 'type' => 'text/plain',
  282. 'tmp_name' => __FILE__,
  283. 'error' => 0,
  284. 'size' => 1234,
  285. ],
  286. ];
  287. $request = ServerRequestFactory::fromGlobals(null, null, null, null, $files);
  288. /** @var \Laminas\Diactoros\UploadedFile $expected */
  289. $expected = $request->getData('file');
  290. $this->assertSame($files['file']['size'], $expected->getSize());
  291. $this->assertSame($files['file']['error'], $expected->getError());
  292. $this->assertSame($files['file']['name'], $expected->getClientFilename());
  293. $this->assertSame($files['file']['type'], $expected->getClientMediaType());
  294. }
  295. /**
  296. * Tests the "as arrays" file upload merging behavior.
  297. *
  298. * @return void
  299. */
  300. public function testFromGlobalsWithFilesAsObjectsDisabled()
  301. {
  302. Configure::write('App.uploadedFilesAsObjects', false);
  303. $files = [
  304. 'file' => [
  305. 'name' => 'file.txt',
  306. 'type' => 'text/plain',
  307. 'tmp_name' => __FILE__,
  308. 'error' => 0,
  309. 'size' => 1234,
  310. ],
  311. ];
  312. $request = ServerRequestFactory::fromGlobals(null, null, null, null, $files);
  313. $expected = [
  314. 'file' => [
  315. 'tmp_name' => __FILE__,
  316. 'error' => 0,
  317. 'name' => 'file.txt',
  318. 'type' => 'text/plain',
  319. 'size' => 1234,
  320. ],
  321. ];
  322. $this->assertEquals($expected, $request->getData());
  323. }
  324. /**
  325. * Tests the "as objects" file upload merging behavior.
  326. *
  327. * @return void
  328. */
  329. public function testFromGlobalsWithFilesAsObjectsEnabled()
  330. {
  331. Configure::write('App.uploadedFilesAsObjects', true);
  332. $files = [
  333. 'file' => [
  334. 'name' => 'file.txt',
  335. 'type' => 'text/plain',
  336. 'tmp_name' => __FILE__,
  337. 'error' => 0,
  338. 'size' => 1234,
  339. ],
  340. ];
  341. $request = ServerRequestFactory::fromGlobals(null, null, null, null, $files);
  342. $expected = [
  343. 'file' => new UploadedFile(
  344. __FILE__,
  345. 1234,
  346. 0,
  347. 'file.txt',
  348. 'text/plain'
  349. ),
  350. ];
  351. $this->assertEquals($expected, $request->getData());
  352. }
  353. /**
  354. * Test processing files with `file` field names.
  355. *
  356. * @return void
  357. */
  358. public function testFilesNested()
  359. {
  360. $files = [
  361. 'image_main' => [
  362. 'name' => ['file' => 'born on.txt'],
  363. 'type' => ['file' => 'text/plain'],
  364. 'tmp_name' => ['file' => __FILE__],
  365. 'error' => ['file' => 0],
  366. 'size' => ['file' => 17178],
  367. ],
  368. 0 => [
  369. 'name' => ['image' => 'scratch.text'],
  370. 'type' => ['image' => 'text/plain'],
  371. 'tmp_name' => ['image' => __FILE__],
  372. 'error' => ['image' => 0],
  373. 'size' => ['image' => 1490],
  374. ],
  375. 'pictures' => [
  376. 'name' => [
  377. 0 => ['file' => 'a-file.png'],
  378. 1 => ['file' => 'a-moose.png'],
  379. ],
  380. 'type' => [
  381. 0 => ['file' => 'image/png'],
  382. 1 => ['file' => 'image/jpg'],
  383. ],
  384. 'tmp_name' => [
  385. 0 => ['file' => __FILE__],
  386. 1 => ['file' => __FILE__],
  387. ],
  388. 'error' => [
  389. 0 => ['file' => 0],
  390. 1 => ['file' => 0],
  391. ],
  392. 'size' => [
  393. 0 => ['file' => 17188],
  394. 1 => ['file' => 2010],
  395. ],
  396. ],
  397. ];
  398. $post = [
  399. 'pictures' => [
  400. 0 => ['name' => 'A cat'],
  401. 1 => ['name' => 'A moose'],
  402. ],
  403. 0 => [
  404. 'name' => 'A dog',
  405. ],
  406. ];
  407. $request = ServerRequestFactory::fromGlobals(null, null, $post, null, $files);
  408. $expected = [
  409. 'image_main' => [
  410. 'file' => new UploadedFile(
  411. __FILE__,
  412. 17178,
  413. 0,
  414. 'born on.txt',
  415. 'text/plain'
  416. ),
  417. ],
  418. 'pictures' => [
  419. 0 => [
  420. 'name' => 'A cat',
  421. 'file' => new UploadedFile(
  422. __FILE__,
  423. 17188,
  424. 0,
  425. 'a-file.png',
  426. 'image/png'
  427. ),
  428. ],
  429. 1 => [
  430. 'name' => 'A moose',
  431. 'file' => new UploadedFile(
  432. __FILE__,
  433. 2010,
  434. 0,
  435. 'a-moose.png',
  436. 'image/jpg'
  437. ),
  438. ],
  439. ],
  440. 0 => [
  441. 'name' => 'A dog',
  442. 'image' => new UploadedFile(
  443. __FILE__,
  444. 1490,
  445. 0,
  446. 'scratch.text',
  447. 'text/plain'
  448. ),
  449. ],
  450. ];
  451. $this->assertEquals($expected, $request->getData());
  452. $uploads = $request->getUploadedFiles();
  453. $this->assertCount(3, $uploads);
  454. $this->assertArrayHasKey(0, $uploads);
  455. $this->assertSame('scratch.text', $uploads[0]['image']->getClientFilename());
  456. $this->assertArrayHasKey('pictures', $uploads);
  457. $this->assertSame('a-file.png', $uploads['pictures'][0]['file']->getClientFilename());
  458. $this->assertSame('a-moose.png', $uploads['pictures'][1]['file']->getClientFilename());
  459. $this->assertArrayHasKey('image_main', $uploads);
  460. $this->assertSame('born on.txt', $uploads['image_main']['file']->getClientFilename());
  461. }
  462. /**
  463. * Test processing a file input with no .'s in it.
  464. *
  465. * @return void
  466. */
  467. public function testFilesFlat()
  468. {
  469. $files = [
  470. 'birth_cert' => [
  471. 'name' => 'born on.txt',
  472. 'type' => 'application/octet-stream',
  473. 'tmp_name' => __FILE__,
  474. 'error' => 0,
  475. 'size' => 123,
  476. ],
  477. ];
  478. Configure::write('App.uploadedFilesAsObjects', false);
  479. $request = ServerRequestFactory::fromGlobals([], [], [], [], $files);
  480. $this->assertEquals($files, $request->getData());
  481. Configure::write('App.uploadedFilesAsObjects', true);
  482. $uploads = $request->getUploadedFiles();
  483. $this->assertCount(1, $uploads);
  484. $this->assertArrayHasKey('birth_cert', $uploads);
  485. $this->assertSame('born on.txt', $uploads['birth_cert']->getClientFilename());
  486. $this->assertSame(0, $uploads['birth_cert']->getError());
  487. $this->assertSame('application/octet-stream', $uploads['birth_cert']->getClientMediaType());
  488. $this->assertEquals(123, $uploads['birth_cert']->getSize());
  489. }
  490. /**
  491. * Test that files in the 0th index work.
  492. *
  493. * @return void
  494. */
  495. public function testFilesZeroithIndex()
  496. {
  497. $files = [
  498. 0 => [
  499. 'name' => 'cake_sqlserver_patch.patch',
  500. 'type' => 'text/plain',
  501. 'tmp_name' => __FILE__,
  502. 'error' => 0,
  503. 'size' => 6271,
  504. ],
  505. ];
  506. Configure::write('App.uploadedFilesAsObjects', false);
  507. $request = ServerRequestFactory::fromGlobals([], [], [], [], $files);
  508. $this->assertEquals($files, $request->getData());
  509. Configure::write('App.uploadedFilesAsObjects', true);
  510. $uploads = $request->getUploadedFiles();
  511. $this->assertCount(1, $uploads);
  512. $this->assertEquals($files[0]['name'], $uploads[0]->getClientFilename());
  513. }
  514. /**
  515. * Tests that file uploads are merged into the post data as objects instead of as arrays.
  516. *
  517. * @return void
  518. */
  519. public function testFilesAsObjectsInRequestData()
  520. {
  521. $files = [
  522. 'flat' => [
  523. 'name' => 'flat.txt',
  524. 'type' => 'text/plain',
  525. 'tmp_name' => __FILE__,
  526. 'error' => 0,
  527. 'size' => 1,
  528. ],
  529. 'nested' => [
  530. 'name' => ['file' => 'nested.txt'],
  531. 'type' => ['file' => 'text/plain'],
  532. 'tmp_name' => ['file' => __FILE__],
  533. 'error' => ['file' => 0],
  534. 'size' => ['file' => 12],
  535. ],
  536. 0 => [
  537. 'name' => 'numeric.txt',
  538. 'type' => 'text/plain',
  539. 'tmp_name' => __FILE__,
  540. 'error' => 0,
  541. 'size' => 123,
  542. ],
  543. 1 => [
  544. 'name' => ['file' => 'numeric-nested.txt'],
  545. 'type' => ['file' => 'text/plain'],
  546. 'tmp_name' => ['file' => __FILE__],
  547. 'error' => ['file' => 0],
  548. 'size' => ['file' => 1234],
  549. ],
  550. 'deep' => [
  551. 'name' => [
  552. 0 => ['file' => 'deep-1.txt'],
  553. 1 => ['file' => 'deep-2.txt'],
  554. ],
  555. 'type' => [
  556. 0 => ['file' => 'text/plain'],
  557. 1 => ['file' => 'text/plain'],
  558. ],
  559. 'tmp_name' => [
  560. 0 => ['file' => __FILE__],
  561. 1 => ['file' => __FILE__],
  562. ],
  563. 'error' => [
  564. 0 => ['file' => 0],
  565. 1 => ['file' => 0],
  566. ],
  567. 'size' => [
  568. 0 => ['file' => 12345],
  569. 1 => ['file' => 123456],
  570. ],
  571. ],
  572. ];
  573. $post = [
  574. 'flat' => ['existing'],
  575. 'nested' => [
  576. 'name' => 'nested',
  577. 'file' => ['existing'],
  578. ],
  579. 'deep' => [
  580. 0 => [
  581. 'name' => 'deep 1',
  582. 'file' => ['existing'],
  583. ],
  584. 1 => [
  585. 'name' => 'deep 2',
  586. ],
  587. ],
  588. 1 => [
  589. 'name' => 'numeric nested',
  590. ],
  591. ];
  592. $expected = [
  593. 'flat' => new UploadedFile(
  594. __FILE__,
  595. 1,
  596. 0,
  597. 'flat.txt',
  598. 'text/plain'
  599. ),
  600. 'nested' => [
  601. 'name' => 'nested',
  602. 'file' => new UploadedFile(
  603. __FILE__,
  604. 12,
  605. 0,
  606. 'nested.txt',
  607. 'text/plain'
  608. ),
  609. ],
  610. 'deep' => [
  611. 0 => [
  612. 'name' => 'deep 1',
  613. 'file' => new UploadedFile(
  614. __FILE__,
  615. 12345,
  616. 0,
  617. 'deep-1.txt',
  618. 'text/plain'
  619. ),
  620. ],
  621. 1 => [
  622. 'name' => 'deep 2',
  623. 'file' => new UploadedFile(
  624. __FILE__,
  625. 123456,
  626. 0,
  627. 'deep-2.txt',
  628. 'text/plain'
  629. ),
  630. ],
  631. ],
  632. 0 => new UploadedFile(
  633. __FILE__,
  634. 123,
  635. 0,
  636. 'numeric.txt',
  637. 'text/plain'
  638. ),
  639. 1 => [
  640. 'name' => 'numeric nested',
  641. 'file' => new UploadedFile(
  642. __FILE__,
  643. 1234,
  644. 0,
  645. 'numeric-nested.txt',
  646. 'text/plain'
  647. ),
  648. ],
  649. ];
  650. $request = ServerRequestFactory::fromGlobals([], [], $post, [], $files);
  651. $this->assertEquals($expected, $request->getData());
  652. }
  653. /**
  654. * Test passing invalid files list structure.
  655. *
  656. * @return void
  657. */
  658. public function testFilesWithInvalidStructure()
  659. {
  660. $this->expectException(InvalidArgumentException::class);
  661. $this->expectExceptionMessage('Invalid value in files specification');
  662. ServerRequestFactory::fromGlobals([], [], [], [], [
  663. [
  664. 'invalid' => [
  665. 'data',
  666. ],
  667. ],
  668. ]);
  669. }
  670. }