RulesCheckerIntegrationTest.php 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565
  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. * Fixtures to be loaded
  26. *
  27. * @var array
  28. */
  29. public $fixtures = ['core.articles', 'core.authors', 'core.tags', 'core.articles_tags'];
  30. /**
  31. * Tear down
  32. *
  33. * @return void
  34. */
  35. public function tearDown() {
  36. parent::tearDown();
  37. TableRegistry::clear();
  38. }
  39. /**
  40. * Tests saving belongsTo association and get a validation error
  41. *
  42. * @group save
  43. * @return void
  44. */
  45. public function testsSaveBelongsToWithValidationError() {
  46. $entity = new Entity([
  47. 'title' => 'A Title',
  48. 'body' => 'A body'
  49. ]);
  50. $entity->author = new Entity([
  51. 'name' => 'Jose'
  52. ]);
  53. $table = TableRegistry::get('articles');
  54. $table->belongsTo('authors');
  55. $table->association('authors')
  56. ->target()
  57. ->rulesChecker()
  58. ->add(function (Entity $author, array $options) use ($table) {
  59. $this->assertSame($options['repository'], $table->association('authors')->target());
  60. return false;
  61. }, ['errorField' => 'name', 'message' => 'This is an error']);
  62. $this->assertFalse($table->save($entity));
  63. $this->assertTrue($entity->isNew());
  64. $this->assertTrue($entity->author->isNew());
  65. $this->assertNull($entity->get('author_id'));
  66. $this->assertNotEmpty($entity->author->errors('name'));
  67. $this->assertEquals(['This is an error'], $entity->author->errors('name'));
  68. }
  69. /**
  70. * Tests saving hasOne association and returning a validation error will
  71. * abort the saving process
  72. *
  73. * @group save
  74. * @return void
  75. */
  76. public function testSaveHasOneWithValidationError() {
  77. $entity = new \Cake\ORM\Entity([
  78. 'name' => 'Jose'
  79. ]);
  80. $entity->article = new \Cake\ORM\Entity([
  81. 'title' => 'A Title',
  82. 'body' => 'A body'
  83. ]);
  84. $table = TableRegistry::get('authors');
  85. $table->hasOne('articles');
  86. $table->association('articles')
  87. ->target()
  88. ->rulesChecker()
  89. ->add(function (Entity $entity) {
  90. return false;
  91. }, ['errorField' => 'title', 'message' => 'This is an error']);
  92. $this->assertFalse($table->save($entity));
  93. $this->assertTrue($entity->isNew());
  94. $this->assertTrue($entity->article->isNew());
  95. $this->assertNull($entity->article->id);
  96. $this->assertNull($entity->article->get('author_id'));
  97. $this->assertFalse($entity->article->dirty('author_id'));
  98. $this->assertNotEmpty($entity->article->errors('title'));
  99. }
  100. /**
  101. * Tests saving multiple entities in a hasMany association and getting and
  102. * error while saving one of them. It should abort all the save operation
  103. * when options are set to defaults
  104. *
  105. * @return void
  106. */
  107. public function testSaveHasManyWithErrorsAtomic() {
  108. $entity = new \Cake\ORM\Entity([
  109. 'name' => 'Jose'
  110. ]);
  111. $entity->articles = [
  112. new \Cake\ORM\Entity([
  113. 'title' => '1',
  114. 'body' => 'A body'
  115. ]),
  116. new \Cake\ORM\Entity([
  117. 'title' => 'Another Title',
  118. 'body' => 'Another body'
  119. ])
  120. ];
  121. $table = TableRegistry::get('authors');
  122. $table->hasMany('articles');
  123. $table->association('articles')
  124. ->target()
  125. ->rulesChecker()
  126. ->add(function (Entity $entity) {
  127. return $entity->title === '1';
  128. }, ['errorField' => 'title', 'message' => 'This is an error']);
  129. $this->assertFalse($table->save($entity));
  130. $this->assertTrue($entity->isNew());
  131. $this->assertTrue($entity->articles[0]->isNew());
  132. $this->assertTrue($entity->articles[1]->isNew());
  133. $this->assertNull($entity->articles[0]->id);
  134. $this->assertNull($entity->articles[1]->id);
  135. $this->assertNull($entity->articles[0]->author_id);
  136. $this->assertNull($entity->articles[1]->author_id);
  137. $this->assertEmpty($entity->articles[0]->errors());
  138. $this->assertNotEmpty($entity->articles[1]->errors());
  139. }
  140. /**
  141. * Tests that it is possible to continue saving hasMany associations
  142. * even if any of the records fail validation when atomic is set
  143. * to false
  144. *
  145. * @return void
  146. */
  147. public function testSaveHasManyWithErrorsNonAtomic() {
  148. $entity = new \Cake\ORM\Entity([
  149. 'name' => 'Jose'
  150. ]);
  151. $entity->articles = [
  152. new \Cake\ORM\Entity([
  153. 'title' => 'A title',
  154. 'body' => 'A body'
  155. ]),
  156. new \Cake\ORM\Entity([
  157. 'title' => '1',
  158. 'body' => 'Another body'
  159. ])
  160. ];
  161. $table = TableRegistry::get('authors');
  162. $table->hasMany('articles');
  163. $table->association('articles')
  164. ->target()
  165. ->rulesChecker()
  166. ->add(function (Entity $article) {
  167. return is_numeric($article->title);
  168. }, ['errorField' => 'title', 'message' => 'This is an error']);
  169. $result = $table->save($entity, ['atomic' => false]);
  170. $this->assertSame($entity, $result);
  171. $this->assertFalse($entity->isNew());
  172. $this->assertTrue($entity->articles[0]->isNew());
  173. $this->assertFalse($entity->articles[1]->isNew());
  174. $this->assertEquals(4, $entity->articles[1]->id);
  175. $this->assertNull($entity->articles[0]->id);
  176. $this->assertNotEmpty($entity->articles[0]->errors('title'));
  177. }
  178. /**
  179. * Tests saving belongsToMany records with a validation error in a joint entity
  180. *
  181. * @group save
  182. * @return void
  183. */
  184. public function testSaveBelongsToManyWithValidationErrorInJointEntity() {
  185. $entity = new \Cake\ORM\Entity([
  186. 'title' => 'A Title',
  187. 'body' => 'A body'
  188. ]);
  189. $entity->tags = [
  190. new \Cake\ORM\Entity([
  191. 'name' => 'Something New'
  192. ]),
  193. new \Cake\ORM\Entity([
  194. 'name' => '100'
  195. ])
  196. ];
  197. $table = TableRegistry::get('articles');
  198. $table->belongsToMany('tags');
  199. $table->association('tags')
  200. ->junction()
  201. ->rulesChecker()
  202. ->add(function (Entity $entity) {
  203. return $entity->article_id > 4;
  204. });
  205. $this->assertFalse($table->save($entity));
  206. $this->assertTrue($entity->isNew());
  207. $this->assertTrue($entity->tags[0]->isNew());
  208. $this->assertTrue($entity->tags[1]->isNew());
  209. $this->assertNull($entity->tags[0]->id);
  210. $this->assertNull($entity->tags[1]->id);
  211. $this->assertNull($entity->tags[0]->_joinData);
  212. $this->assertNull($entity->tags[1]->_joinData);
  213. }
  214. /**
  215. * Tests saving belongsToMany records with a validation error in a joint entity
  216. * and atomic set to false
  217. *
  218. * @group save
  219. * @return void
  220. */
  221. public function testSaveBelongsToManyWithValidationErrorInJointEntityNonAtomic() {
  222. $entity = new \Cake\ORM\Entity([
  223. 'title' => 'A Title',
  224. 'body' => 'A body'
  225. ]);
  226. $entity->tags = [
  227. new \Cake\ORM\Entity([
  228. 'name' => 'Something New'
  229. ]),
  230. new \Cake\ORM\Entity([
  231. 'name' => 'New one'
  232. ])
  233. ];
  234. $table = TableRegistry::get('articles');
  235. $table->belongsToMany('tags');
  236. $table->association('tags')
  237. ->junction()
  238. ->rulesChecker()
  239. ->add(function (Entity $entity) {
  240. return $entity->tag_id > 4;
  241. });
  242. $this->assertSame($entity, $table->save($entity, ['atomic' => false]));
  243. $this->assertFalse($entity->isNew());
  244. $this->assertFalse($entity->tags[0]->isNew());
  245. $this->assertFalse($entity->tags[1]->isNew());
  246. $this->assertEquals(4, $entity->tags[0]->id);
  247. $this->assertEquals(5, $entity->tags[1]->id);
  248. $this->assertTrue($entity->tags[0]->_joinData->isNew());
  249. $this->assertEquals(4, $entity->tags[1]->_joinData->article_id);
  250. $this->assertEquals(5, $entity->tags[1]->_joinData->tag_id);
  251. }
  252. /**
  253. * Tests the isUnique domain rule
  254. *
  255. * @group save
  256. * @return void
  257. */
  258. public function testIsUniqueDomainRule() {
  259. $entity = new Entity([
  260. 'name' => 'larry'
  261. ]);
  262. $table = TableRegistry::get('Authors');
  263. $rules = $table->rulesChecker();
  264. $rules->add($rules->isUnique(['name']));
  265. $this->assertFalse($table->save($entity));
  266. $this->assertEquals(['This value is already in use'], $entity->errors('name'));
  267. $entity->name = 'jose';
  268. $this->assertSame($entity, $table->save($entity));
  269. $entity = $table->get(1);
  270. $entity->dirty('name', true);
  271. $this->assertSame($entity, $table->save($entity));
  272. }
  273. /**
  274. * Tests isUnique with multiple fields
  275. *
  276. * @group save
  277. * @return void
  278. */
  279. public function testIsUniqueMultipleFields() {
  280. $entity = new Entity([
  281. 'author_id' => 1,
  282. 'title' => 'First Article'
  283. ]);
  284. $table = TableRegistry::get('Articles');
  285. $rules = $table->rulesChecker();
  286. $rules->add($rules->isUnique(['title', 'author_id'], 'Nope'));
  287. $this->assertFalse($table->save($entity));
  288. $this->assertEquals(['title' => ['Nope']], $entity->errors());
  289. $entity->clean();
  290. $entity->author_id = 2;
  291. $this->assertSame($entity, $table->save($entity));
  292. }
  293. /**
  294. * Tests the existsIn domain rule
  295. *
  296. * @group save
  297. * @return void
  298. */
  299. public function testExistsInDomainRule() {
  300. $entity = new Entity([
  301. 'title' => 'An Article',
  302. 'author_id' => 500
  303. ]);
  304. $table = TableRegistry::get('Articles');
  305. $table->belongsTo('Authors');
  306. $rules = $table->rulesChecker();
  307. $rules->add($rules->existsIn('author_id', 'Authors'));
  308. $this->assertFalse($table->save($entity));
  309. $this->assertEquals(['This value does not exist'], $entity->errors('author_id'));
  310. }
  311. /**
  312. * Tests the existsIn domain rule when passing an object
  313. *
  314. * @group save
  315. * @return void
  316. */
  317. public function testExistsInDomainRuleWithObject() {
  318. $entity = new Entity([
  319. 'title' => 'An Article',
  320. 'author_id' => 500
  321. ]);
  322. $table = TableRegistry::get('Articles');
  323. $rules = $table->rulesChecker();
  324. $rules->add($rules->existsIn('author_id', TableRegistry::get('Authors'), 'Nope'));
  325. $this->assertFalse($table->save($entity));
  326. $this->assertEquals(['Nope'], $entity->errors('author_id'));
  327. }
  328. /**
  329. * Tests the checkRules save option
  330. *
  331. * @group save
  332. * @return void
  333. */
  334. public function testSkipRulesChecking() {
  335. $entity = new Entity([
  336. 'title' => 'An Article',
  337. 'author_id' => 500
  338. ]);
  339. $table = TableRegistry::get('Articles');
  340. $rules = $table->rulesChecker();
  341. $rules->add($rules->existsIn('author_id', TableRegistry::get('Authors'), 'Nope'));
  342. $this->assertSame($entity, $table->save($entity, ['checkRules' => false]));
  343. }
  344. /**
  345. * Tests the beforeRules event
  346. *
  347. * @group save
  348. * @return void
  349. */
  350. public function testUseBeforeRules() {
  351. $entity = new Entity([
  352. 'title' => 'An Article',
  353. 'author_id' => 500
  354. ]);
  355. $table = TableRegistry::get('Articles');
  356. $rules = $table->rulesChecker();
  357. $rules->add($rules->existsIn('author_id', TableRegistry::get('Authors'), 'Nope'));
  358. $table->eventManager()->attach(
  359. function ($event, Entity $entity, \ArrayObject $options, $operation, RulesChecker $check) {
  360. $this->assertEquals(
  361. ['atomic' => true, 'associated' => true, 'checkRules' => true],
  362. $options->getArrayCopy()
  363. );
  364. $this->assertEquals('create', $operation);
  365. $this->assertSame($event->subject()->rulesChecker(), $check);
  366. $event->stopPropagation();
  367. return true;
  368. },
  369. 'Model.beforeRules'
  370. );
  371. $this->assertSame($entity, $table->save($entity));
  372. }
  373. /**
  374. * Tests the afterRules event
  375. *
  376. * @group save
  377. * @return void
  378. */
  379. public function testUseAfterRules() {
  380. $entity = new Entity([
  381. 'title' => 'An Article',
  382. 'author_id' => 500
  383. ]);
  384. $table = TableRegistry::get('Articles');
  385. $rules = $table->rulesChecker();
  386. $rules->add($rules->existsIn('author_id', TableRegistry::get('Authors'), 'Nope'));
  387. $table->eventManager()->attach(
  388. function ($event, Entity $entity, \ArrayObject $options, $result, $operation, RulesChecker $check) {
  389. $this->assertEquals(
  390. ['atomic' => true, 'associated' => true, 'checkRules' => true],
  391. $options->getArrayCopy()
  392. );
  393. $this->assertEquals('create', $operation);
  394. $this->assertSame($event->subject()->rulesChecker(), $check);
  395. $this->assertFalse($result);
  396. $event->stopPropagation();
  397. return true;
  398. },
  399. 'Model.afterRules'
  400. );
  401. $this->assertSame($entity, $table->save($entity));
  402. }
  403. /**
  404. * Tests that rules can be changed using the buildRules event
  405. *
  406. * @group save
  407. * @return void
  408. */
  409. public function testUseBuildRulesEvent() {
  410. $entity = new Entity([
  411. 'title' => 'An Article',
  412. 'author_id' => 500
  413. ]);
  414. $table = TableRegistry::get('Articles');
  415. $table->eventManager()->attach(function ($event, $rules) {
  416. $rules->add($rules->existsIn('author_id', TableRegistry::get('Authors'), 'Nope'));
  417. }, 'Model.buildRules');
  418. $this->assertFalse($table->save($entity));
  419. }
  420. /**
  421. * Tests isUnique with untouched fields
  422. *
  423. * @group save
  424. * @return void
  425. */
  426. public function testIsUniqueWithCleanFields() {
  427. $table = TableRegistry::get('Articles');
  428. $entity = $table->get(1);
  429. $rules = $table->rulesChecker();
  430. $rules->add($rules->isUnique(['title', 'author_id'], 'Nope'));
  431. $entity->body = 'Foo';
  432. $this->assertSame($entity, $table->save($entity));
  433. $entity->title = 'Third Article';
  434. $this->assertFalse($table->save($entity));
  435. }
  436. /**
  437. * Tests the existsIn rule when passing non dirty fields
  438. *
  439. * @group save
  440. * @return void
  441. */
  442. public function testExistsInWithCleanFields() {
  443. $table = TableRegistry::get('Articles');
  444. $table->belongsTo('Authors');
  445. $rules = $table->rulesChecker();
  446. $rules->add($rules->existsIn('author_id', 'Authors'));
  447. $entity = $table->get(1);
  448. $entity->title = 'Foo';
  449. $entity->author_id = 1000;
  450. $entity->dirty('author_id', false);
  451. $this->assertSame($entity, $table->save($entity));
  452. }
  453. /**
  454. * Tests using rules to prevent delete operations
  455. *
  456. * @group delete
  457. * @return void
  458. */
  459. public function testDeleteRules() {
  460. $table = TableRegistry::get('Articles');
  461. $rules = $table->rulesChecker();
  462. $rules->addDelete(function ($entity) {
  463. return false;
  464. });
  465. $entity = $table->get(1);
  466. $this->assertFalse($table->delete($entity));
  467. }
  468. /**
  469. * Checks that it is possible to pass custom options to rules when saving
  470. *
  471. * @group save
  472. * @return void
  473. */
  474. public function testCustomOptionsPassingSave() {
  475. $entity = new Entity([
  476. 'name' => 'jose'
  477. ]);
  478. $table = TableRegistry::get('Authors');
  479. $rules = $table->rulesChecker();
  480. $rules->add(function ($entity, $options) {
  481. $this->assertEquals('bar', $options['foo']);
  482. $this->assertEquals('option', $options['another']);
  483. return false;
  484. }, ['another' => 'option']);
  485. $this->assertFalse($table->save($entity, ['foo' => 'bar']));
  486. }
  487. /**
  488. * Tests passing custom options to rules from delete
  489. *
  490. * @group delete
  491. * @return void
  492. */
  493. public function testCustomOptionsPassingDelete() {
  494. $table = TableRegistry::get('Articles');
  495. $rules = $table->rulesChecker();
  496. $rules->addDelete(function ($entity, $options) {
  497. $this->assertEquals('bar', $options['foo']);
  498. $this->assertEquals('option', $options['another']);
  499. return false;
  500. }, ['another' => 'option']);
  501. $entity = $table->get(1);
  502. $this->assertFalse($table->delete($entity, ['foo' => 'bar']));
  503. }
  504. }