RulesCheckerIntegrationTest.php 30 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024
  1. <?php
  2. /**
  3. * CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
  4. * Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
  5. *
  6. * Licensed under The MIT License
  7. * For full copyright and license information, please see the LICENSE.txt
  8. * Redistributions of files must retain the above copyright notice.
  9. *
  10. * @copyright Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
  11. * @link http://cakephp.org CakePHP(tm) Project
  12. * @since 3.0.0
  13. * @license http://www.opensource.org/licenses/mit-license.php MIT License
  14. */
  15. namespace Cake\Test\TestCase\ORM;
  16. use Cake\ORM\Entity;
  17. use Cake\ORM\RulesChecker;
  18. use Cake\ORM\TableRegistry;
  19. use Cake\TestSuite\TestCase;
  20. /**
  21. * Tests the integration between the ORM and the domain checker
  22. */
  23. class RulesCheckerIntegrationTest extends TestCase
  24. {
  25. /**
  26. * Fixtures to be loaded
  27. *
  28. * @var array
  29. */
  30. public $fixtures = ['core.articles', 'core.articles_tags', 'core.authors', 'core.tags', 'core.categories'];
  31. /**
  32. * Tear down
  33. *
  34. * @return void
  35. */
  36. public function tearDown()
  37. {
  38. parent::tearDown();
  39. TableRegistry::clear();
  40. }
  41. /**
  42. * Tests saving belongsTo association and get a validation error
  43. *
  44. * @group save
  45. * @return void
  46. */
  47. public function testsSaveBelongsToWithValidationError()
  48. {
  49. $entity = new Entity([
  50. 'title' => 'A Title',
  51. 'body' => 'A body'
  52. ]);
  53. $entity->author = new Entity([
  54. 'name' => 'Jose'
  55. ]);
  56. $table = TableRegistry::get('articles');
  57. $table->belongsTo('authors');
  58. $table->association('authors')
  59. ->target()
  60. ->rulesChecker()
  61. ->add(
  62. function (Entity $author, array $options) use ($table) {
  63. $this->assertSame($options['repository'], $table->association('authors')->target());
  64. return false;
  65. },
  66. ['errorField' => 'name', 'message' => 'This is an error']
  67. );
  68. $this->assertFalse($table->save($entity));
  69. $this->assertTrue($entity->isNew());
  70. $this->assertTrue($entity->author->isNew());
  71. $this->assertNull($entity->get('author_id'));
  72. $this->assertNotEmpty($entity->author->errors('name'));
  73. $this->assertEquals(['This is an error'], $entity->author->errors('name'));
  74. }
  75. /**
  76. * Tests saving hasOne association and returning a validation error will
  77. * abort the saving process
  78. *
  79. * @group save
  80. * @return void
  81. */
  82. public function testSaveHasOneWithValidationError()
  83. {
  84. $entity = new \Cake\ORM\Entity([
  85. 'name' => 'Jose'
  86. ]);
  87. $entity->article = new \Cake\ORM\Entity([
  88. 'title' => 'A Title',
  89. 'body' => 'A body'
  90. ]);
  91. $table = TableRegistry::get('authors');
  92. $table->hasOne('articles');
  93. $table->association('articles')
  94. ->target()
  95. ->rulesChecker()
  96. ->add(
  97. function (Entity $entity) {
  98. return false;
  99. },
  100. ['errorField' => 'title', 'message' => 'This is an error']
  101. );
  102. $this->assertFalse($table->save($entity));
  103. $this->assertTrue($entity->isNew());
  104. $this->assertTrue($entity->article->isNew());
  105. $this->assertNull($entity->article->id);
  106. $this->assertNull($entity->article->get('author_id'));
  107. $this->assertFalse($entity->article->dirty('author_id'));
  108. $this->assertNotEmpty($entity->article->errors('title'));
  109. $this->assertSame('A Title', $entity->article->invalid('title'));
  110. }
  111. /**
  112. * Tests saving multiple entities in a hasMany association and getting and
  113. * error while saving one of them. It should abort all the save operation
  114. * when options are set to defaults
  115. *
  116. * @return void
  117. */
  118. public function testSaveHasManyWithErrorsAtomic()
  119. {
  120. $entity = new \Cake\ORM\Entity([
  121. 'name' => 'Jose'
  122. ]);
  123. $entity->articles = [
  124. new \Cake\ORM\Entity([
  125. 'title' => '1',
  126. 'body' => 'A body'
  127. ]),
  128. new \Cake\ORM\Entity([
  129. 'title' => 'Another Title',
  130. 'body' => 'Another body'
  131. ])
  132. ];
  133. $table = TableRegistry::get('authors');
  134. $table->hasMany('articles');
  135. $table->association('articles')
  136. ->target()
  137. ->rulesChecker()
  138. ->add(
  139. function (Entity $entity, $options) use ($table) {
  140. $this->assertSame($table, $options['_sourceTable']);
  141. return $entity->title === '1';
  142. },
  143. ['errorField' => 'title', 'message' => 'This is an error']
  144. );
  145. $this->assertFalse($table->save($entity));
  146. $this->assertTrue($entity->isNew());
  147. $this->assertTrue($entity->articles[0]->isNew());
  148. $this->assertTrue($entity->articles[1]->isNew());
  149. $this->assertNull($entity->articles[0]->id);
  150. $this->assertNull($entity->articles[1]->id);
  151. $this->assertNull($entity->articles[0]->author_id);
  152. $this->assertNull($entity->articles[1]->author_id);
  153. $this->assertEmpty($entity->articles[0]->errors());
  154. $this->assertNotEmpty($entity->articles[1]->errors());
  155. }
  156. /**
  157. * Tests that it is possible to continue saving hasMany associations
  158. * even if any of the records fail validation when atomic is set
  159. * to false
  160. *
  161. * @return void
  162. */
  163. public function testSaveHasManyWithErrorsNonAtomic()
  164. {
  165. $entity = new \Cake\ORM\Entity([
  166. 'name' => 'Jose'
  167. ]);
  168. $entity->articles = [
  169. new \Cake\ORM\Entity([
  170. 'title' => 'A title',
  171. 'body' => 'A body'
  172. ]),
  173. new \Cake\ORM\Entity([
  174. 'title' => '1',
  175. 'body' => 'Another body'
  176. ])
  177. ];
  178. $table = TableRegistry::get('authors');
  179. $table->hasMany('articles');
  180. $table->association('articles')
  181. ->target()
  182. ->rulesChecker()
  183. ->add(
  184. function (Entity $article) {
  185. return is_numeric($article->title);
  186. },
  187. ['errorField' => 'title', 'message' => 'This is an error']
  188. );
  189. $result = $table->save($entity, ['atomic' => false]);
  190. $this->assertSame($entity, $result);
  191. $this->assertFalse($entity->isNew());
  192. $this->assertTrue($entity->articles[0]->isNew());
  193. $this->assertFalse($entity->articles[1]->isNew());
  194. $this->assertEquals(4, $entity->articles[1]->id);
  195. $this->assertNull($entity->articles[0]->id);
  196. $this->assertNotEmpty($entity->articles[0]->errors('title'));
  197. }
  198. /**
  199. * Tests saving belongsToMany records with a validation error in a joint entity
  200. *
  201. * @group save
  202. * @return void
  203. */
  204. public function testSaveBelongsToManyWithValidationErrorInJointEntity()
  205. {
  206. $entity = new \Cake\ORM\Entity([
  207. 'title' => 'A Title',
  208. 'body' => 'A body'
  209. ]);
  210. $entity->tags = [
  211. new \Cake\ORM\Entity([
  212. 'name' => 'Something New'
  213. ]),
  214. new \Cake\ORM\Entity([
  215. 'name' => '100'
  216. ])
  217. ];
  218. $table = TableRegistry::get('articles');
  219. $table->belongsToMany('tags');
  220. $table->association('tags')
  221. ->junction()
  222. ->rulesChecker()
  223. ->add(function (Entity $entity) {
  224. return $entity->article_id > 4;
  225. });
  226. $this->assertFalse($table->save($entity));
  227. $this->assertTrue($entity->isNew());
  228. $this->assertTrue($entity->tags[0]->isNew());
  229. $this->assertTrue($entity->tags[1]->isNew());
  230. $this->assertNull($entity->tags[0]->id);
  231. $this->assertNull($entity->tags[1]->id);
  232. $this->assertNull($entity->tags[0]->_joinData);
  233. $this->assertNull($entity->tags[1]->_joinData);
  234. }
  235. /**
  236. * Tests saving belongsToMany records with a validation error in a joint entity
  237. * and atomic set to false
  238. *
  239. * @group save
  240. * @return void
  241. */
  242. public function testSaveBelongsToManyWithValidationErrorInJointEntityNonAtomic()
  243. {
  244. $entity = new \Cake\ORM\Entity([
  245. 'title' => 'A Title',
  246. 'body' => 'A body'
  247. ]);
  248. $entity->tags = [
  249. new \Cake\ORM\Entity([
  250. 'name' => 'Something New'
  251. ]),
  252. new \Cake\ORM\Entity([
  253. 'name' => 'New one'
  254. ])
  255. ];
  256. $table = TableRegistry::get('articles');
  257. $table->belongsToMany('tags');
  258. $table->association('tags')
  259. ->junction()
  260. ->rulesChecker()
  261. ->add(function (Entity $entity) {
  262. return $entity->tag_id > 4;
  263. });
  264. $this->assertSame($entity, $table->save($entity, ['atomic' => false]));
  265. $this->assertFalse($entity->isNew());
  266. $this->assertFalse($entity->tags[0]->isNew());
  267. $this->assertFalse($entity->tags[1]->isNew());
  268. $this->assertEquals(4, $entity->tags[0]->id);
  269. $this->assertEquals(5, $entity->tags[1]->id);
  270. $this->assertTrue($entity->tags[0]->_joinData->isNew());
  271. $this->assertEquals(4, $entity->tags[1]->_joinData->article_id);
  272. $this->assertEquals(5, $entity->tags[1]->_joinData->tag_id);
  273. }
  274. /**
  275. * Test adding rule with name
  276. *
  277. * @group save
  278. * @return void
  279. */
  280. public function testAddingRuleWithName()
  281. {
  282. $entity = new Entity([
  283. 'name' => 'larry'
  284. ]);
  285. $table = TableRegistry::get('Authors');
  286. $rules = $table->rulesChecker();
  287. $rules->add(
  288. function () {
  289. return false;
  290. },
  291. 'ruleName',
  292. ['errorField' => 'name']
  293. );
  294. $this->assertFalse($table->save($entity));
  295. $this->assertEquals(['ruleName' => 'invalid'], $entity->errors('name'));
  296. }
  297. /**
  298. * Ensure that add(isUnique()) only invokes a rule once.
  299. *
  300. * @return void
  301. */
  302. public function testIsUniqueRuleSingleInvocation()
  303. {
  304. $entity = new Entity([
  305. 'name' => 'larry'
  306. ]);
  307. $table = TableRegistry::get('Authors');
  308. $rules = $table->rulesChecker();
  309. $rules->add($rules->isUnique(['name']), '_isUnique', ['errorField' => 'title']);
  310. $this->assertFalse($table->save($entity));
  311. $this->assertEquals(
  312. ['_isUnique' => 'This value is already in use'],
  313. $entity->errors('title'),
  314. 'Provided field should have errors'
  315. );
  316. $this->assertEmpty($entity->errors('name'), 'Errors should not apply to original field.');
  317. }
  318. /**
  319. * Tests the isUnique domain rule
  320. *
  321. * @group save
  322. * @return void
  323. */
  324. public function testIsUniqueDomainRule()
  325. {
  326. $entity = new Entity([
  327. 'name' => 'larry'
  328. ]);
  329. $table = TableRegistry::get('Authors');
  330. $rules = $table->rulesChecker();
  331. $rules->add($rules->isUnique(['name']));
  332. $this->assertFalse($table->save($entity));
  333. $this->assertEquals(['_isUnique' => 'This value is already in use'], $entity->errors('name'));
  334. $entity->name = 'jose';
  335. $this->assertSame($entity, $table->save($entity));
  336. $entity = $table->get(1);
  337. $entity->dirty('name', true);
  338. $this->assertSame($entity, $table->save($entity));
  339. }
  340. /**
  341. * Tests isUnique with multiple fields
  342. *
  343. * @group save
  344. * @return void
  345. */
  346. public function testIsUniqueMultipleFields()
  347. {
  348. $entity = new Entity([
  349. 'author_id' => 1,
  350. 'title' => 'First Article'
  351. ]);
  352. $table = TableRegistry::get('Articles');
  353. $rules = $table->rulesChecker();
  354. $rules->add($rules->isUnique(['title', 'author_id'], 'Nope'));
  355. $this->assertFalse($table->save($entity));
  356. $this->assertEquals(['title' => ['_isUnique' => 'Nope']], $entity->errors());
  357. $entity->clean();
  358. $entity->author_id = 2;
  359. $this->assertSame($entity, $table->save($entity));
  360. }
  361. /**
  362. * Tests isUnique with multiple fields emulates SQL UNIQUE keys
  363. *
  364. * @group save
  365. * @return void
  366. */
  367. public function testIsUniqueMultipleFieldsOneIsNull()
  368. {
  369. $entity = new Entity([
  370. 'author_id' => null,
  371. 'title' => 'First Article'
  372. ]);
  373. $table = TableRegistry::get('Articles');
  374. $rules = $table->rulesChecker();
  375. $rules->add($rules->isUnique(['title', 'author_id'], 'Nope'));
  376. $this->assertSame($entity, $table->save($entity));
  377. // Make a matching record
  378. $entity = new Entity([
  379. 'author_id' => null,
  380. 'title' => 'New Article'
  381. ]);
  382. $this->assertSame($entity, $table->save($entity));
  383. }
  384. /**
  385. * Tests the existsIn domain rule
  386. *
  387. * @group save
  388. * @return void
  389. */
  390. public function testExistsInDomainRule()
  391. {
  392. $entity = new Entity([
  393. 'title' => 'An Article',
  394. 'author_id' => 500
  395. ]);
  396. $table = TableRegistry::get('Articles');
  397. $table->belongsTo('Authors');
  398. $rules = $table->rulesChecker();
  399. $rules->add($rules->existsIn('author_id', 'Authors'));
  400. $this->assertFalse($table->save($entity));
  401. $this->assertEquals(['_existsIn' => 'This value does not exist'], $entity->errors('author_id'));
  402. }
  403. /**
  404. * Ensure that add(existsIn()) only invokes a rule once.
  405. *
  406. * @return void
  407. */
  408. public function testExistsInRuleSingleInvocation()
  409. {
  410. $entity = new Entity([
  411. 'title' => 'larry',
  412. 'author_id' => 500,
  413. ]);
  414. $table = TableRegistry::get('Articles');
  415. $table->belongsTo('Authors');
  416. $rules = $table->rulesChecker();
  417. $rules->add($rules->existsIn('author_id', 'Authors'), '_existsIn', ['errorField' => 'other']);
  418. $this->assertFalse($table->save($entity));
  419. $this->assertEquals(
  420. ['_existsIn' => 'This value does not exist'],
  421. $entity->errors('other'),
  422. 'Provided field should have errors'
  423. );
  424. $this->assertEmpty($entity->errors('author_id'), 'Errors should not apply to original field.');
  425. }
  426. /**
  427. * Tests the existsIn domain rule when passing an object
  428. *
  429. * @group save
  430. * @return void
  431. */
  432. public function testExistsInDomainRuleWithObject()
  433. {
  434. $entity = new Entity([
  435. 'title' => 'An Article',
  436. 'author_id' => 500
  437. ]);
  438. $table = TableRegistry::get('Articles');
  439. $rules = $table->rulesChecker();
  440. $rules->add($rules->existsIn('author_id', TableRegistry::get('Authors'), 'Nope'));
  441. $this->assertFalse($table->save($entity));
  442. $this->assertEquals(['_existsIn' => 'Nope'], $entity->errors('author_id'));
  443. }
  444. /**
  445. * ExistsIn uses the schema to verify that nullable fields are ok.
  446. *
  447. * @return
  448. */
  449. public function testExistsInNullValue()
  450. {
  451. $entity = new Entity([
  452. 'title' => 'An Article',
  453. 'author_id' => null
  454. ]);
  455. $table = TableRegistry::get('Articles');
  456. $table->belongsTo('Authors');
  457. $rules = $table->rulesChecker();
  458. $rules->add($rules->existsIn('author_id', 'Authors'));
  459. $this->assertEquals($entity, $table->save($entity));
  460. $this->assertEquals([], $entity->errors('author_id'));
  461. }
  462. /**
  463. * Test ExistsIn on a new entity that doesn't have the field populated.
  464. *
  465. * This use case is important for saving records and their
  466. * associated belongsTo records in one pass.
  467. *
  468. * @return void
  469. */
  470. public function testExistsInNotNullValueNewEntity()
  471. {
  472. $entity = new Entity([
  473. 'name' => 'A Category',
  474. ]);
  475. $table = TableRegistry::get('Categories');
  476. $table->belongsTo('Categories', [
  477. 'foreignKey' => 'parent_id',
  478. 'bindingKey' => 'id',
  479. ]);
  480. $rules = $table->rulesChecker();
  481. $rules->add($rules->existsIn('parent_id', 'Categories'));
  482. $this->assertTrue($table->checkRules($entity, RulesChecker::CREATE));
  483. $this->assertEmpty($entity->errors('parent_id'));
  484. }
  485. /**
  486. * Tests exists in uses the bindingKey of the association
  487. *
  488. * @return
  489. */
  490. public function testExistsInWithBindingKey()
  491. {
  492. $entity = new Entity([
  493. 'title' => 'An Article',
  494. ]);
  495. $table = TableRegistry::get('Articles');
  496. $table->belongsTo('Authors', [
  497. 'bindingKey' => 'name',
  498. 'foreignKey' => 'title'
  499. ]);
  500. $rules = $table->rulesChecker();
  501. $rules->add($rules->existsIn('title', 'Authors'));
  502. $this->assertFalse($table->save($entity));
  503. $this->assertNotEmpty($entity->errors('title'));
  504. $entity->clean();
  505. $entity->title = 'larry';
  506. $this->assertEquals($entity, $table->save($entity));
  507. }
  508. /**
  509. * Tests existsIn with invalid associations
  510. *
  511. * @group save
  512. * @expectedException RuntimeException
  513. * @expectedExceptionMessage ExistsIn rule for 'author_id' is invalid. 'NotValid' is not associated with 'Cake\ORM\Table'.
  514. * @return void
  515. */
  516. public function testExistsInInvalidAssociation()
  517. {
  518. $entity = new Entity([
  519. 'title' => 'An Article',
  520. 'author_id' => 500
  521. ]);
  522. $table = TableRegistry::get('Articles');
  523. $table->belongsTo('Authors');
  524. $rules = $table->rulesChecker();
  525. $rules->add($rules->existsIn('author_id', 'NotValid'));
  526. $table->save($entity);
  527. }
  528. /**
  529. * Tests the checkRules save option
  530. *
  531. * @group save
  532. * @return void
  533. */
  534. public function testSkipRulesChecking()
  535. {
  536. $entity = new Entity([
  537. 'title' => 'An Article',
  538. 'author_id' => 500
  539. ]);
  540. $table = TableRegistry::get('Articles');
  541. $rules = $table->rulesChecker();
  542. $rules->add($rules->existsIn('author_id', TableRegistry::get('Authors'), 'Nope'));
  543. $this->assertSame($entity, $table->save($entity, ['checkRules' => false]));
  544. }
  545. /**
  546. * Tests the beforeRules event
  547. *
  548. * @group save
  549. * @return void
  550. */
  551. public function testUseBeforeRules()
  552. {
  553. $entity = new Entity([
  554. 'title' => 'An Article',
  555. 'author_id' => 500
  556. ]);
  557. $table = TableRegistry::get('Articles');
  558. $rules = $table->rulesChecker();
  559. $rules->add($rules->existsIn('author_id', TableRegistry::get('Authors'), 'Nope'));
  560. $table->eventManager()->attach(
  561. function ($event, Entity $entity, \ArrayObject $options, $operation) {
  562. $this->assertEquals(
  563. [
  564. 'atomic' => true,
  565. 'associated' => true,
  566. 'checkRules' => true,
  567. 'checkExisting' => true,
  568. '_primary' => true,
  569. ],
  570. $options->getArrayCopy()
  571. );
  572. $this->assertEquals('create', $operation);
  573. $event->stopPropagation();
  574. return true;
  575. },
  576. 'Model.beforeRules'
  577. );
  578. $this->assertSame($entity, $table->save($entity));
  579. }
  580. /**
  581. * Tests the afterRules event
  582. *
  583. * @group save
  584. * @return void
  585. */
  586. public function testUseAfterRules()
  587. {
  588. $entity = new Entity([
  589. 'title' => 'An Article',
  590. 'author_id' => 500
  591. ]);
  592. $table = TableRegistry::get('Articles');
  593. $rules = $table->rulesChecker();
  594. $rules->add($rules->existsIn('author_id', TableRegistry::get('Authors'), 'Nope'));
  595. $table->eventManager()->attach(
  596. function ($event, Entity $entity, \ArrayObject $options, $result, $operation) {
  597. $this->assertEquals(
  598. [
  599. 'atomic' => true,
  600. 'associated' => true,
  601. 'checkRules' => true,
  602. 'checkExisting' => true,
  603. '_primary' => true,
  604. ],
  605. $options->getArrayCopy()
  606. );
  607. $this->assertEquals('create', $operation);
  608. $this->assertFalse($result);
  609. $event->stopPropagation();
  610. return true;
  611. },
  612. 'Model.afterRules'
  613. );
  614. $this->assertSame($entity, $table->save($entity));
  615. }
  616. /**
  617. * Tests that rules can be changed using the buildRules event
  618. *
  619. * @group save
  620. * @return void
  621. */
  622. public function testUseBuildRulesEvent()
  623. {
  624. $entity = new Entity([
  625. 'title' => 'An Article',
  626. 'author_id' => 500
  627. ]);
  628. $table = TableRegistry::get('Articles');
  629. $table->eventManager()->attach(function ($event, $rules) {
  630. $rules->add($rules->existsIn('author_id', TableRegistry::get('Authors'), 'Nope'));
  631. }, 'Model.buildRules');
  632. $this->assertFalse($table->save($entity));
  633. }
  634. /**
  635. * Tests isUnique with untouched fields
  636. *
  637. * @group save
  638. * @return void
  639. */
  640. public function testIsUniqueWithCleanFields()
  641. {
  642. $table = TableRegistry::get('Articles');
  643. $entity = $table->get(1);
  644. $rules = $table->rulesChecker();
  645. $rules->add($rules->isUnique(['title', 'author_id'], 'Nope'));
  646. $entity->body = 'Foo';
  647. $this->assertSame($entity, $table->save($entity));
  648. $entity->title = 'Third Article';
  649. $this->assertFalse($table->save($entity));
  650. }
  651. /**
  652. * Tests isUnique rule with coflicting columns
  653. *
  654. * @group save
  655. * @return void
  656. */
  657. public function testIsUniqueAliasPrefix()
  658. {
  659. $entity = new Entity([
  660. 'title' => 'An Article',
  661. 'author_id' => 1
  662. ]);
  663. $table = TableRegistry::get('Articles');
  664. $table->belongsTo('Authors');
  665. $rules = $table->rulesChecker();
  666. $rules->add($rules->isUnique(['author_id']));
  667. $table->Authors->eventManager()->on('Model.beforeFind', function ($event, $query) {
  668. $query->leftJoin(['a2' => 'authors']);
  669. });
  670. $this->assertFalse($table->save($entity));
  671. $this->assertEquals(['_isUnique' => 'This value is already in use'], $entity->errors('author_id'));
  672. }
  673. /**
  674. * Tests the existsIn rule when passing non dirty fields
  675. *
  676. * @group save
  677. * @return void
  678. */
  679. public function testExistsInWithCleanFields()
  680. {
  681. $table = TableRegistry::get('Articles');
  682. $table->belongsTo('Authors');
  683. $rules = $table->rulesChecker();
  684. $rules->add($rules->existsIn('author_id', 'Authors'));
  685. $entity = $table->get(1);
  686. $entity->title = 'Foo';
  687. $entity->author_id = 1000;
  688. $entity->dirty('author_id', false);
  689. $this->assertSame($entity, $table->save($entity));
  690. }
  691. /**
  692. * Tests the existsIn with coflicting columns
  693. *
  694. * @group save
  695. * @return void
  696. */
  697. public function testExistsInAliasPrefix()
  698. {
  699. $entity = new Entity([
  700. 'title' => 'An Article',
  701. 'author_id' => 500
  702. ]);
  703. $table = TableRegistry::get('Articles');
  704. $table->belongsTo('Authors');
  705. $rules = $table->rulesChecker();
  706. $rules->add($rules->existsIn('author_id', 'Authors'));
  707. $table->Authors->eventManager()->on('Model.beforeFind', function ($event, $query) {
  708. $query->leftJoin(['a2' => 'authors']);
  709. });
  710. $this->assertFalse($table->save($entity));
  711. $this->assertEquals(['_existsIn' => 'This value does not exist'], $entity->errors('author_id'));
  712. }
  713. /**
  714. * Tests that using an array in existsIn() sets the error message correctly
  715. *
  716. * @return
  717. */
  718. public function testExistsInErrorWithArrayField()
  719. {
  720. $entity = new Entity([
  721. 'title' => 'An Article',
  722. 'author_id' => 500
  723. ]);
  724. $table = TableRegistry::get('Articles');
  725. $table->belongsTo('Authors');
  726. $rules = $table->rulesChecker();
  727. $rules->add($rules->existsIn(['author_id'], 'Authors'));
  728. $this->assertFalse($table->save($entity));
  729. $this->assertEquals(['_existsIn' => 'This value does not exist'], $entity->errors('author_id'));
  730. }
  731. /**
  732. * Tests using rules to prevent delete operations
  733. *
  734. * @group delete
  735. * @return void
  736. */
  737. public function testDeleteRules()
  738. {
  739. $table = TableRegistry::get('Articles');
  740. $rules = $table->rulesChecker();
  741. $rules->addDelete(function ($entity) {
  742. return false;
  743. });
  744. $entity = $table->get(1);
  745. $this->assertFalse($table->delete($entity));
  746. }
  747. /**
  748. * Checks that it is possible to pass custom options to rules when saving
  749. *
  750. * @group save
  751. * @return void
  752. */
  753. public function testCustomOptionsPassingSave()
  754. {
  755. $entity = new Entity([
  756. 'name' => 'jose'
  757. ]);
  758. $table = TableRegistry::get('Authors');
  759. $rules = $table->rulesChecker();
  760. $rules->add(function ($entity, $options) {
  761. $this->assertEquals('bar', $options['foo']);
  762. $this->assertEquals('option', $options['another']);
  763. return false;
  764. }, ['another' => 'option']);
  765. $this->assertFalse($table->save($entity, ['foo' => 'bar']));
  766. }
  767. /**
  768. * Tests passing custom options to rules from delete
  769. *
  770. * @group delete
  771. * @return void
  772. */
  773. public function testCustomOptionsPassingDelete()
  774. {
  775. $table = TableRegistry::get('Articles');
  776. $rules = $table->rulesChecker();
  777. $rules->addDelete(function ($entity, $options) {
  778. $this->assertEquals('bar', $options['foo']);
  779. $this->assertEquals('option', $options['another']);
  780. return false;
  781. }, ['another' => 'option']);
  782. $entity = $table->get(1);
  783. $this->assertFalse($table->delete($entity, ['foo' => 'bar']));
  784. }
  785. /**
  786. * Test adding rules that return error string
  787. *
  788. * @group save
  789. * @return void
  790. */
  791. public function testCustomErrorMessageFromRule()
  792. {
  793. $entity = new Entity([
  794. 'name' => 'larry'
  795. ]);
  796. $table = TableRegistry::get('Authors');
  797. $rules = $table->rulesChecker();
  798. $rules->add(function () {
  799. return 'So much nope';
  800. }, ['errorField' => 'name']);
  801. $this->assertFalse($table->save($entity));
  802. $this->assertEquals(['So much nope'], $entity->errors('name'));
  803. }
  804. /**
  805. * Test adding rules with no errorField do not accept strings
  806. *
  807. * @group save
  808. * @return void
  809. */
  810. public function testCustomErrorMessageFromRuleNoErrorField()
  811. {
  812. $entity = new Entity([
  813. 'name' => 'larry'
  814. ]);
  815. $table = TableRegistry::get('Authors');
  816. $rules = $table->rulesChecker();
  817. $rules->add(function () {
  818. return 'So much nope';
  819. });
  820. $this->assertFalse($table->save($entity));
  821. $this->assertEmpty($entity->errors());
  822. }
  823. /**
  824. * Tests that using existsIn for a hasMany association will not be called
  825. * as the foreign key for the association was automatically validated already.
  826. *
  827. * @group save
  828. * @return void
  829. */
  830. public function testAvoidExistsInOnAutomaticSaving()
  831. {
  832. $entity = new \Cake\ORM\Entity([
  833. 'name' => 'Jose'
  834. ]);
  835. $entity->articles = [
  836. new \Cake\ORM\Entity([
  837. 'title' => '1',
  838. 'body' => 'A body'
  839. ]),
  840. new \Cake\ORM\Entity([
  841. 'title' => 'Another Title',
  842. 'body' => 'Another body'
  843. ])
  844. ];
  845. $table = TableRegistry::get('authors');
  846. $table->hasMany('articles');
  847. $table->association('articles')->belongsTo('authors');
  848. $checker = $table->association('articles')->target()->rulesChecker();
  849. $checker->add(function ($entity, $options) use ($checker) {
  850. $rule = $checker->existsIn('author_id', 'authors');
  851. $id = $entity->author_id;
  852. $entity->author_id = 5000;
  853. $result = $rule($entity, $options);
  854. $this->assertTrue($result);
  855. $entity->author_id = $id;
  856. return true;
  857. });
  858. $this->assertSame($entity, $table->save($entity));
  859. }
  860. /**
  861. * Tests that associated items have a count of X.
  862. *
  863. * @group save
  864. * @return void
  865. */
  866. public function testCountOfAssociatedItems()
  867. {
  868. $entity = new \Cake\ORM\Entity([
  869. 'title' => 'A Title',
  870. 'body' => 'A body'
  871. ]);
  872. $entity->tags = [
  873. new \Cake\ORM\Entity([
  874. 'name' => 'Something New'
  875. ]),
  876. new \Cake\ORM\Entity([
  877. 'name' => '100'
  878. ])
  879. ];
  880. TableRegistry::get('ArticlesTags');
  881. $table = TableRegistry::get('articles');
  882. $table->belongsToMany('tags');
  883. $rules = $table->rulesChecker();
  884. $rules->add($rules->validCount('tags', 3));
  885. $this->assertFalse($table->save($entity));
  886. $this->assertEquals($entity->errors(), [
  887. 'tags' => [
  888. '_validCount' => 'The count does not match >3'
  889. ]
  890. ]);
  891. // Testing that undesired types fail
  892. $entity->tags = null;
  893. $this->assertFalse($table->save($entity));
  894. $entity->tags = new \stdClass();
  895. $this->assertFalse($table->save($entity));
  896. $entity->tags = 'string';
  897. $this->assertFalse($table->save($entity));
  898. $entity->tags = 123456;
  899. $this->assertFalse($table->save($entity));
  900. $entity->tags = 0.512;
  901. $this->assertFalse($table->save($entity));
  902. }
  903. }