SchemaTest.php 13 KB

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