SchemaTest.php 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453
  1. <?php
  2. namespace Test\PhpDevCommunity\RequestKit;
  3. use DateTime;
  4. use PhpDevCommunity\RequestKit\Exceptions\InvalidDataException;
  5. use PhpDevCommunity\RequestKit\Schema\Schema;
  6. use PhpDevCommunity\RequestKit\Type;
  7. use PhpDevCommunity\RequestKit\Utils\KeyValueObject;
  8. use PhpDevCommunity\UniTester\TestCase;
  9. class SchemaTest extends TestCase
  10. {
  11. private ?Schema $schema = null;
  12. protected function setUp(): void
  13. {
  14. $this->schema = Schema::create([
  15. 'name' => Type::string()->length(3, 100)->required(),
  16. 'age' => Type::int()->min(18)->max(99),
  17. 'email' => Type::email()->lowercase(),
  18. 'date_of_birth' => Type::date()->format('Y-m-d'),
  19. 'created_at' => Type::datetime()->format('Y-m-d H:i:s')->default(date('2025-01-01 12:00:00')),
  20. 'active' => Type::bool()->strict(),
  21. ]);
  22. }
  23. protected function tearDown(): void
  24. {
  25. // TODO: Implement tearDown() method.
  26. }
  27. protected function execute(): void
  28. {
  29. $this->testValidData();
  30. $this->testInvalidEmail();
  31. $this->testEdgeCaseAge();
  32. $this->testStrictBool();
  33. $this->testMissingOptionalField();
  34. $this->testMissingRequiredField();
  35. $this->testMultipleValidationErrors();
  36. $this->testNestedData();
  37. $this->testCollection();
  38. $this->testArray();
  39. $this->testExtend();
  40. $this->testExampleData();
  41. }
  42. public function testValidData(): void
  43. {
  44. $data = [
  45. 'name' => 'John Doe',
  46. 'age' => 25,
  47. 'email' => 'JOHN@EXAMPLE.COM',
  48. 'date_of_birth' => '1990-01-01',
  49. 'created_at' => '2023-01-01 12:00:00',
  50. 'active' => true,
  51. ];
  52. $result = $this->schema->process($data);
  53. $result = $result->toArray();
  54. $this->assertStrictEquals($result['name'], 'John Doe');
  55. $this->assertStrictEquals($result['age'], 25);
  56. $this->assertStrictEquals($result['email'], 'john@example.com');
  57. $this->assertStrictEquals($result['date_of_birth']->format('Y-m-d'), '1990-01-01');
  58. $this->assertStrictEquals($result['created_at']->format('Y-m-d H:i:s'), '2023-01-01 12:00:00');
  59. $this->assertStrictEquals($result['active'], true);
  60. }
  61. public function testInvalidEmail(): void
  62. {
  63. $data = [
  64. 'name' => 'John Doe',
  65. 'age' => 25,
  66. 'email' => 'invalid-email', // Email invalide
  67. 'date_of_birth' => '1990-01-01',
  68. 'created_at' => '2023-01-01 12:00:00',
  69. 'active' => true,
  70. ];
  71. $this->expectException(InvalidDataException::class, function () use ($data) {
  72. try {
  73. $result = $this->schema->process($data);
  74. } catch (InvalidDataException $e) {
  75. $this->assertNotEmpty($e->getErrors());
  76. $this->assertEquals(1, count($e->getErrors()));
  77. $this->assertNotEmpty($e->getError('email'));
  78. throw $e;
  79. }
  80. });
  81. }
  82. public function testEdgeCaseAge(): void
  83. {
  84. $data = [
  85. 'name' => 'John Doe',
  86. 'age' => '18', // Âge limite
  87. 'email' => 'john@example.com',
  88. 'date_of_birth' => '1990-01-01',
  89. 'created_at' => '2023-01-01 12:00:00',
  90. 'active' => true,
  91. ];
  92. $result = $this->schema->process($data);
  93. $result = $result->toArray();
  94. $this->assertEquals(18, $result['age']);
  95. }
  96. public function testStrictBool(): void
  97. {
  98. $data = [
  99. 'name' => 'John Doe',
  100. 'age' => 25,
  101. 'email' => 'john@example.com',
  102. 'date_of_birth' => '1990-01-01',
  103. 'created_at' => '2023-01-01 12:00:00',
  104. 'active' => 1, //
  105. ];
  106. $this->expectException(InvalidDataException::class, function () use ($data) {
  107. try {
  108. $result = $this->schema->process($data);
  109. } catch (InvalidDataException $e) {
  110. $this->assertNotEmpty($e->getErrors());
  111. $this->assertEquals(1, count($e->getErrors()));
  112. $this->assertNotEmpty($e->getError('active'));
  113. throw $e;
  114. }
  115. });
  116. }
  117. public function testMissingOptionalField(): void
  118. {
  119. $data = [
  120. 'name' => 'John Doe',
  121. 'age' => 25,
  122. 'email' => 'john@example.com',
  123. 'date_of_birth' => '1990-01-01',
  124. // 'created_at'
  125. 'active' => true,
  126. ];
  127. $result = $this->schema->process($data);
  128. $result = $result->toArray();
  129. $this->assertInstanceOf(DateTime::class, $result['created_at']);
  130. }
  131. public function testMissingRequiredField(): void
  132. {
  133. $data = [
  134. // 'name' manquant
  135. 'age' => 25,
  136. 'email' => 'john@example.com',
  137. 'date_of_birth' => '1990-01-01',
  138. 'created_at' => '2023-01-01 12:00:00',
  139. 'active' => true,
  140. ];
  141. $this->expectException(InvalidDataException::class, function () use ($data) {
  142. try {
  143. $result = $this->schema->process($data);
  144. } catch (InvalidDataException $e) {
  145. $this->assertNotEmpty($e->getErrors());
  146. $this->assertEquals(1, count($e->getErrors()));
  147. $this->assertNotEmpty($e->getError('name'));
  148. throw $e;
  149. }
  150. });
  151. }
  152. public function testMultipleValidationErrors(): void
  153. {
  154. $data = [
  155. 'name' => 'John Doe',
  156. 'age' => 17, // Âge invalide
  157. 'email' => 'invalid-email', // Email invalide
  158. 'date_of_birth' => '1990-01-01',
  159. 'created_at' => '2023-01-01 12:00:00',
  160. 'active' => true,
  161. ];
  162. $this->expectException(InvalidDataException::class, function () use ($data) {
  163. try {
  164. $result = $this->schema->process($data);
  165. } catch (InvalidDataException $e) {
  166. $this->assertNotEmpty($e->getErrors());
  167. $this->assertEquals(2, count($e->getErrors()));
  168. $this->assertNotEmpty($e->getError('age'));
  169. $this->assertNotEmpty($e->getError('email'));
  170. throw $e;
  171. }
  172. });
  173. }
  174. public function testNestedData(): void
  175. {
  176. $schema = Schema::create([
  177. 'user' => Type::item([
  178. 'name' => Type::string()->length(20, 50)->required(),
  179. 'age' => Type::int()->strict()->alias('my_age'),
  180. 'roles' => Type::arrayOf(Type::string()->strict())->required(),
  181. 'address' => Type::item([
  182. 'street' => Type::string()->length(15, 100),
  183. 'city' => Type::string()->allowed('Paris', 'London'),
  184. ]),
  185. ]),
  186. ]);
  187. $data = [
  188. 'user' => [
  189. // 'name' => 'John Doe',
  190. 'my_age' => '25',
  191. // 'roles' => [
  192. // 1,
  193. // 2,
  194. // ],
  195. 'address' => [
  196. 'street' => 'Main Street',
  197. 'city' => 'New York',
  198. ]
  199. ],
  200. ];
  201. $this->expectException(InvalidDataException::class, function () use ($schema, $data) {
  202. try {
  203. $schema->process($data);
  204. } catch (InvalidDataException $e) {
  205. $this->assertNotEmpty($e->getErrors());
  206. $this->assertEquals(5, count($e->getErrors()));
  207. $this->assertNotEmpty($e->getError('user.name'));
  208. $this->assertNotEmpty($e->getError('user.age'));
  209. $this->assertNotEmpty($e->getError('user.address.street'));
  210. $this->assertNotEmpty($e->getError('user.address.city'));
  211. $this->assertNotEmpty($e->getError('user.roles'));
  212. throw $e;
  213. }
  214. });
  215. }
  216. public function testCollection(): void
  217. {
  218. $schema = Schema::create([
  219. 'users' => Type::arrayOf(Type::item([
  220. 'name' => Type::string()->length(3, 50)->required(),
  221. 'age' => Type::int(),
  222. 'roles' => Type::arrayOf(Type::string())->required(),
  223. 'address' => Type::item([
  224. 'street' => Type::string()->length(5, 100),
  225. 'city' => Type::string()->allowed('Paris', 'London'),
  226. ]),
  227. ])),
  228. ]);
  229. $data = [
  230. 'users' => [
  231. [
  232. 'name' => 'John Doe',
  233. 'age' => '25',
  234. 'roles' => [
  235. 1,
  236. 2,
  237. ],
  238. 'address' => [
  239. 'street' => 'Main Street',
  240. 'city' => 'London',
  241. ]
  242. ],
  243. [
  244. 'name' => 'Jane Doe',
  245. 'age' => '30',
  246. 'roles' => [
  247. 3,
  248. 4,
  249. ],
  250. 'address' => [
  251. 'street' => 'Main Street',
  252. 'city' => 'Paris',
  253. ]
  254. ],
  255. ]
  256. ];
  257. $result = $schema->process($data);
  258. $this->assertStrictEquals(2, count($result->get('users')));
  259. $this->assertStrictEquals('John Doe', $result->get('users.0.name'));
  260. $this->assertStrictEquals(25, $result->get('users.0.age'));
  261. $this->assertStrictEquals(2, count($result->get('users.0.roles')));
  262. $this->assertStrictEquals('Main Street', $result->get('users.0.address.street'));
  263. $this->assertStrictEquals('London', $result->get('users.0.address.city'));
  264. $this->assertStrictEquals('Jane Doe', $result->get('users.1.name'));
  265. $this->assertStrictEquals(30, $result->get('users.1.age'));
  266. $this->assertStrictEquals(2, count($result->get('users.1.roles')));
  267. $this->assertStrictEquals('Main Street', $result->get('users.1.address.street'));
  268. $this->assertStrictEquals('Paris', $result->get('users.1.address.city'));
  269. }
  270. private function testExtend()
  271. {
  272. $schema1 = Schema::create([
  273. 'name' => Type::string()->length(20, 50)->required(),
  274. 'age' => Type::int()->strict()->alias('my_age'),
  275. 'roles' => Type::arrayOf(Type::string()->strict())->required(),
  276. 'address' => Type::item([
  277. 'street' => Type::string()->length(15, 100),
  278. 'city' => Type::string()->allowed('Paris', 'London'),
  279. ]),
  280. ]);
  281. $schema2 = $schema1->extend([
  282. 'password' => Type::string()->length(10, 100),
  283. 'address' => Type::item([
  284. 'zip' => Type::string()->length(5, 10),
  285. ]),
  286. ]);
  287. $this->assertStrictEquals(5, count($schema2->copyDefinitions()));
  288. /**
  289. * @var Type\ItemType $address
  290. */
  291. $address = $schema2->copyDefinitions()['address'];
  292. $this->assertStrictEquals(1, count($address->copyDefinitions()));
  293. }
  294. private function testExampleData()
  295. {
  296. $schema1 = Schema::create([
  297. 'name' => Type::string()->length(20, 50)->required()->example('John Doe'),
  298. 'age' => Type::int()->strict()->alias('my_age')->example(20),
  299. 'roles' => Type::arrayOf(Type::string()->strict())->required()->example('admin'),
  300. 'address' => Type::item([
  301. 'street' => Type::string()->length(15, 100),
  302. 'city' => Type::string()->allowed('Paris', 'London'),
  303. ])->example([
  304. 'street' => 'Main Street',
  305. 'city' => 'London',
  306. ]
  307. ),
  308. ]);
  309. $this->assertEquals($schema1->generateExampleData(), [
  310. 'name' => 'John Doe',
  311. 'age' => 20,
  312. 'roles' => ['admin'],
  313. 'address' => [
  314. 'street' => 'Main Street',
  315. 'city' => 'London',
  316. ]
  317. ]);
  318. $schema2 = Schema::create([
  319. 'name' => Type::string()->length(20, 50)->required()->example('John Doe'),
  320. 'age' => Type::int()->strict()->alias('my_age')->example(20),
  321. 'roles' => Type::arrayOf(Type::string()->strict())->required()->example('admin'),
  322. 'address' => Type::item([
  323. 'street' => Type::string()->length(15, 100)->example('Main Street'),
  324. 'city' => Type::string()->allowed('Paris', 'London')->example('London'),
  325. ]),
  326. ]);
  327. $this->assertEquals($schema2->generateExampleData(), [
  328. 'name' => 'John Doe',
  329. 'age' => 20,
  330. 'roles' => ['admin'],
  331. 'address' => [
  332. 'street' => 'Main Street',
  333. 'city' => 'London',
  334. ]
  335. ]);
  336. }
  337. private function testArray()
  338. {
  339. $schema = Schema::create([
  340. 'roles' => Type::arrayOf(Type::string()->strict())->required()->example('admin'),
  341. 'dependencies' => Type::arrayOf(Type::string()->strict())->acceptStringKeys()
  342. ]);
  343. $data = [
  344. 'roles' => ['admin'],
  345. 'dependencies' => [
  346. 'key1' => 'value1',
  347. 'key2' => 'value2',
  348. ],
  349. ];
  350. $result = $schema->process($data);
  351. $this->assertStrictEquals('admin', $result->get('roles.0'));
  352. $this->assertStrictEquals('value1', $result->get('dependencies.key1'));
  353. $this->assertStrictEquals('value2', $result->get('dependencies.key2'));
  354. $schema = Schema::create([
  355. 'roles' => Type::arrayOf(Type::string()->strict())->required()->example('admin')->acceptCommaSeparatedValues(),
  356. ]);
  357. $data = [
  358. 'roles' => 'admin,user,manager',
  359. ];
  360. $result = $schema->process($data);
  361. $this->assertStrictEquals('admin', $result->get('roles.0'));
  362. $this->assertStrictEquals('user', $result->get('roles.1'));
  363. $this->assertStrictEquals('manager', $result->get('roles.2'));
  364. $schema = Schema::create([
  365. 'autoload.psr-4' => Type::map(Type::string()->strict()->trim())->required(),
  366. 'dependencies' => Type::map(Type::string()->strict()->trim())
  367. ]);
  368. $data = [
  369. 'autoload.psr-4' => [
  370. 'App\\' => 'app/',
  371. ],
  372. 'dependencies' => [
  373. 'key1' => 'value1',
  374. 'key2' => 'value2',
  375. ],
  376. ];
  377. $result = $schema->process($data);
  378. $this->assertInstanceOf( KeyValueObject::class, $result->get('autoload.psr-4'));
  379. $this->assertInstanceOf( KeyValueObject::class, $result->get('dependencies'));
  380. $this->assertEquals(1, count($result->get('autoload.psr-4')));
  381. $this->assertEquals(2, count($result->get('dependencies')));
  382. $schema = Schema::create([
  383. 'autoload.psr-4' => Type::map(Type::string()->strict()->trim()),
  384. 'dependencies' => Type::map(Type::string()->strict()->trim())
  385. ]);
  386. $data = [
  387. 'autoload.psr-4' => [
  388. ],
  389. ];
  390. $result = $schema->process($data);
  391. $this->assertInstanceOf( KeyValueObject::class, $result->get('autoload.psr-4'));
  392. $this->assertInstanceOf( KeyValueObject::class, $result->get('dependencies'));
  393. $this->assertEquals(0, count($result->get('autoload.psr-4')));
  394. $this->assertEquals(0, count($result->get('dependencies')));
  395. }
  396. }