RulesCheckerIntegrationTest.php 44 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432
  1. <?php
  2. /**
  3. * CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
  4. * Copyright (c) Cake Software Foundation, Inc. (https://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. (https://cakefoundation.org)
  11. * @link https://cakephp.org CakePHP(tm) Project
  12. * @since 3.0.0
  13. * @license https://opensource.org/licenses/mit-license.php MIT License
  14. */
  15. namespace Cake\Test\TestCase\ORM;
  16. use Cake\Event\Event;
  17. use Cake\ORM\Entity;
  18. use Cake\ORM\RulesChecker;
  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 = [
  31. 'core.Articles', 'core.ArticlesTags', 'core.Authors', 'core.Tags',
  32. 'core.SpecialTags', 'core.Categories', 'core.SiteArticles', 'core.SiteAuthors',
  33. 'core.Comments',
  34. ];
  35. /**
  36. * Tests saving belongsTo association and get a validation error
  37. *
  38. * @group save
  39. * @return void
  40. */
  41. public function testsSaveBelongsToWithValidationError()
  42. {
  43. $entity = new Entity([
  44. 'title' => 'A Title',
  45. 'body' => 'A body',
  46. ]);
  47. $entity->author = new Entity([
  48. 'name' => 'Jose',
  49. ]);
  50. $table = $this->getTableLocator()->get('articles');
  51. $table->belongsTo('authors');
  52. $table->getAssociation('authors')
  53. ->getTarget()
  54. ->rulesChecker()
  55. ->add(
  56. function (Entity $author, array $options) use ($table) {
  57. $this->assertSame($options['repository'], $table->getAssociation('authors')->getTarget());
  58. return false;
  59. },
  60. ['errorField' => 'name', 'message' => 'This is an error']
  61. );
  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->getError('name'));
  67. $this->assertEquals(['This is an error'], $entity->author->getError('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. {
  78. $entity = new Entity([
  79. 'name' => 'Jose',
  80. ]);
  81. $entity->article = new Entity([
  82. 'title' => 'A Title',
  83. 'body' => 'A body',
  84. ]);
  85. $table = $this->getTableLocator()->get('authors');
  86. $table->hasOne('articles');
  87. $table->getAssociation('articles')
  88. ->getTarget()
  89. ->rulesChecker()
  90. ->add(
  91. function (Entity $entity) {
  92. return false;
  93. },
  94. ['errorField' => 'title', 'message' => 'This is an error']
  95. );
  96. $this->assertFalse($table->save($entity));
  97. $this->assertTrue($entity->isNew());
  98. $this->assertTrue($entity->article->isNew());
  99. $this->assertNull($entity->article->id);
  100. $this->assertNull($entity->article->get('author_id'));
  101. $this->assertFalse($entity->article->isDirty('author_id'));
  102. $this->assertNotEmpty($entity->article->getError('title'));
  103. $this->assertSame('A Title', $entity->article->getInvalidField('title'));
  104. }
  105. /**
  106. * Tests saving multiple entities in a hasMany association and getting and
  107. * error while saving one of them. It should abort all the save operation
  108. * when options are set to defaults
  109. *
  110. * @return void
  111. */
  112. public function testSaveHasManyWithErrorsAtomic()
  113. {
  114. $entity = new Entity([
  115. 'name' => 'Jose',
  116. ]);
  117. $entity->articles = [
  118. new Entity([
  119. 'title' => '1',
  120. 'body' => 'A body',
  121. ]),
  122. new Entity([
  123. 'title' => 'Another Title',
  124. 'body' => 'Another body',
  125. ]),
  126. ];
  127. $table = $this->getTableLocator()->get('authors');
  128. $table->hasMany('articles');
  129. $table->getAssociation('articles')
  130. ->getTarget()
  131. ->rulesChecker()
  132. ->add(
  133. function (Entity $entity, $options) use ($table) {
  134. $this->assertSame($table, $options['_sourceTable']);
  135. return $entity->title === '1';
  136. },
  137. ['errorField' => 'title', 'message' => 'This is an error']
  138. );
  139. $this->assertFalse($table->save($entity));
  140. $this->assertTrue($entity->isNew());
  141. $this->assertTrue($entity->articles[0]->isNew());
  142. $this->assertTrue($entity->articles[1]->isNew());
  143. $this->assertNull($entity->articles[0]->id);
  144. $this->assertNull($entity->articles[1]->id);
  145. $this->assertNull($entity->articles[0]->author_id);
  146. $this->assertNull($entity->articles[1]->author_id);
  147. $this->assertEmpty($entity->articles[0]->getErrors());
  148. $this->assertNotEmpty($entity->articles[1]->getErrors());
  149. }
  150. /**
  151. * Tests that it is possible to continue saving hasMany associations
  152. * even if any of the records fail validation when atomic is set
  153. * to false
  154. *
  155. * @return void
  156. */
  157. public function testSaveHasManyWithErrorsNonAtomic()
  158. {
  159. $entity = new Entity([
  160. 'name' => 'Jose',
  161. ]);
  162. $entity->articles = [
  163. new Entity([
  164. 'title' => 'A title',
  165. 'body' => 'A body',
  166. ]),
  167. new Entity([
  168. 'title' => '1',
  169. 'body' => 'Another body',
  170. ]),
  171. ];
  172. $table = $this->getTableLocator()->get('authors');
  173. $table->hasMany('articles');
  174. $table->getAssociation('articles')
  175. ->getTarget()
  176. ->rulesChecker()
  177. ->add(
  178. function (Entity $article) {
  179. return is_numeric($article->title);
  180. },
  181. ['errorField' => 'title', 'message' => 'This is an error']
  182. );
  183. $result = $table->save($entity, ['atomic' => false]);
  184. $this->assertSame($entity, $result);
  185. $this->assertFalse($entity->isNew());
  186. $this->assertTrue($entity->articles[0]->isNew());
  187. $this->assertFalse($entity->articles[1]->isNew());
  188. $this->assertEquals(4, $entity->articles[1]->id);
  189. $this->assertNull($entity->articles[0]->id);
  190. $this->assertNotEmpty($entity->articles[0]->getError('title'));
  191. }
  192. /**
  193. * Tests saving belongsToMany records with a validation error in a joint entity
  194. *
  195. * @group save
  196. * @return void
  197. */
  198. public function testSaveBelongsToManyWithValidationErrorInJointEntity()
  199. {
  200. $entity = new Entity([
  201. 'title' => 'A Title',
  202. 'body' => 'A body',
  203. ]);
  204. $entity->tags = [
  205. new Entity([
  206. 'name' => 'Something New',
  207. ]),
  208. new Entity([
  209. 'name' => '100',
  210. ]),
  211. ];
  212. $table = $this->getTableLocator()->get('articles');
  213. $table->belongsToMany('tags');
  214. $table->getAssociation('tags')
  215. ->junction()
  216. ->rulesChecker()
  217. ->add(function (Entity $entity) {
  218. return $entity->article_id > 4;
  219. });
  220. $this->assertFalse($table->save($entity));
  221. $this->assertTrue($entity->isNew());
  222. $this->assertTrue($entity->tags[0]->isNew());
  223. $this->assertTrue($entity->tags[1]->isNew());
  224. $this->assertNull($entity->tags[0]->id);
  225. $this->assertNull($entity->tags[1]->id);
  226. $this->assertNull($entity->tags[0]->_joinData);
  227. $this->assertNull($entity->tags[1]->_joinData);
  228. }
  229. /**
  230. * Tests saving belongsToMany records with a validation error in a joint entity
  231. * and atomic set to false
  232. *
  233. * @group save
  234. * @return void
  235. */
  236. public function testSaveBelongsToManyWithValidationErrorInJointEntityNonAtomic()
  237. {
  238. $entity = new Entity([
  239. 'title' => 'A Title',
  240. 'body' => 'A body',
  241. ]);
  242. $entity->tags = [
  243. new Entity([
  244. 'name' => 'Something New',
  245. ]),
  246. new Entity([
  247. 'name' => 'New one',
  248. ]),
  249. ];
  250. $table = $this->getTableLocator()->get('articles');
  251. $table->belongsToMany('tags');
  252. $table->getAssociation('tags')
  253. ->junction()
  254. ->rulesChecker()
  255. ->add(function (Entity $entity) {
  256. return $entity->tag_id > 4;
  257. });
  258. $this->assertSame($entity, $table->save($entity, ['atomic' => false]));
  259. $this->assertFalse($entity->isNew());
  260. $this->assertFalse($entity->tags[0]->isNew());
  261. $this->assertFalse($entity->tags[1]->isNew());
  262. $this->assertEquals(4, $entity->tags[0]->id);
  263. $this->assertEquals(5, $entity->tags[1]->id);
  264. $this->assertTrue($entity->tags[0]->_joinData->isNew());
  265. $this->assertEquals(4, $entity->tags[1]->_joinData->article_id);
  266. $this->assertEquals(5, $entity->tags[1]->_joinData->tag_id);
  267. }
  268. /**
  269. * Test adding rule with name
  270. *
  271. * @group save
  272. * @return void
  273. */
  274. public function testAddingRuleWithName()
  275. {
  276. $entity = new Entity([
  277. 'name' => 'larry',
  278. ]);
  279. $table = $this->getTableLocator()->get('Authors');
  280. $rules = $table->rulesChecker();
  281. $rules->add(
  282. function () {
  283. return false;
  284. },
  285. 'ruleName',
  286. ['errorField' => 'name']
  287. );
  288. $this->assertFalse($table->save($entity));
  289. $this->assertEquals(['ruleName' => 'invalid'], $entity->getError('name'));
  290. }
  291. /**
  292. * Ensure that add(isUnique()) only invokes a rule once.
  293. *
  294. * @return void
  295. */
  296. public function testIsUniqueRuleSingleInvocation()
  297. {
  298. $entity = new Entity([
  299. 'name' => 'larry',
  300. ]);
  301. $table = $this->getTableLocator()->get('Authors');
  302. $rules = $table->rulesChecker();
  303. $rules->add($rules->isUnique(['name']), '_isUnique', ['errorField' => 'title']);
  304. $this->assertFalse($table->save($entity));
  305. $this->assertEquals(
  306. ['_isUnique' => 'This value is already in use'],
  307. $entity->getError('title'),
  308. 'Provided field should have errors'
  309. );
  310. $this->assertEmpty($entity->getError('name'), 'Errors should not apply to original field.');
  311. }
  312. /**
  313. * Tests the isUnique domain rule
  314. *
  315. * @group save
  316. * @return void
  317. */
  318. public function testIsUniqueDomainRule()
  319. {
  320. $entity = new Entity([
  321. 'name' => 'larry',
  322. ]);
  323. $table = $this->getTableLocator()->get('Authors');
  324. $rules = $table->rulesChecker();
  325. $rules->add($rules->isUnique(['name']));
  326. $this->assertFalse($table->save($entity));
  327. $this->assertEquals(['_isUnique' => 'This value is already in use'], $entity->getError('name'));
  328. $entity->name = 'jose';
  329. $this->assertSame($entity, $table->save($entity));
  330. $entity = $table->get(1);
  331. $entity->setDirty('name', true);
  332. $this->assertSame($entity, $table->save($entity));
  333. }
  334. /**
  335. * Tests isUnique with multiple fields
  336. *
  337. * @group save
  338. * @return void
  339. */
  340. public function testIsUniqueMultipleFields()
  341. {
  342. $entity = new Entity([
  343. 'author_id' => 1,
  344. 'title' => 'First Article',
  345. ]);
  346. $table = $this->getTableLocator()->get('Articles');
  347. $rules = $table->rulesChecker();
  348. $rules->add($rules->isUnique(['title', 'author_id'], 'Nope'));
  349. $this->assertFalse($table->save($entity));
  350. $this->assertEquals(['title' => ['_isUnique' => 'Nope']], $entity->getErrors());
  351. $entity->clean();
  352. $entity->author_id = 2;
  353. $this->assertSame($entity, $table->save($entity));
  354. }
  355. /**
  356. * Tests isUnique with allowMultipleNulls
  357. *
  358. * @group save
  359. * @return void
  360. */
  361. public function testIsUniqueAllowMultipleNulls()
  362. {
  363. $entity = new Entity([
  364. 'article_id' => 11,
  365. 'tag_id' => 11,
  366. 'author_id' => null,
  367. ]);
  368. $table = $this->getTableLocator()->get('SpecialTags');
  369. $rules = $table->rulesChecker();
  370. $rules->add($rules->isUnique(['author_id'], [
  371. 'allowMultipleNulls' => false,
  372. 'message' => 'All fields are required',
  373. ]));
  374. $this->assertFalse($table->save($entity));
  375. $this->assertEquals(['_isUnique' => 'All fields are required'], $entity->getError('author_id'));
  376. $entity->author_id = 11;
  377. $this->assertSame($entity, $table->save($entity));
  378. $entity = $table->get(1);
  379. $entity->setDirty('author_id', true);
  380. $this->assertSame($entity, $table->save($entity));
  381. }
  382. /**
  383. * Tests isUnique with multiple fields and allowMultipleNulls
  384. *
  385. * @group save
  386. * @return void
  387. */
  388. public function testIsUniqueMultipleFieldsAllowMultipleNulls()
  389. {
  390. $entity = new Entity([
  391. 'article_id' => 10,
  392. 'tag_id' => 12,
  393. 'author_id' => null,
  394. ]);
  395. $table = $this->getTableLocator()->get('SpecialTags');
  396. $rules = $table->rulesChecker();
  397. $rules->add($rules->isUnique(['author_id', 'article_id'], [
  398. 'allowMultipleNulls' => false,
  399. 'message' => 'Nope',
  400. ]));
  401. $this->assertFalse($table->save($entity));
  402. $this->assertEquals(['author_id' => ['_isUnique' => 'Nope']], $entity->getErrors());
  403. $entity->clean();
  404. $entity->article_id = 10;
  405. $entity->tag_id = 12;
  406. $entity->author_id = 12;
  407. $this->assertSame($entity, $table->save($entity));
  408. }
  409. /**
  410. * Tests isUnique with multiple fields emulates SQL UNIQUE keys
  411. *
  412. * @group save
  413. * @return void
  414. */
  415. public function testIsUniqueMultipleFieldsOneIsNull()
  416. {
  417. $entity = new Entity([
  418. 'author_id' => null,
  419. 'title' => 'First Article',
  420. ]);
  421. $table = $this->getTableLocator()->get('Articles');
  422. $rules = $table->rulesChecker();
  423. $rules->add($rules->isUnique(['title', 'author_id'], 'Nope'));
  424. $this->assertSame($entity, $table->save($entity));
  425. // Make a matching record
  426. $entity = new Entity([
  427. 'author_id' => null,
  428. 'title' => 'New Article',
  429. ]);
  430. $this->assertSame($entity, $table->save($entity));
  431. }
  432. /**
  433. * Tests the existsIn domain rule
  434. *
  435. * @group save
  436. * @return void
  437. */
  438. public function testExistsInDomainRule()
  439. {
  440. $entity = new Entity([
  441. 'title' => 'An Article',
  442. 'author_id' => 500,
  443. ]);
  444. $table = $this->getTableLocator()->get('Articles');
  445. $table->belongsTo('Authors');
  446. $rules = $table->rulesChecker();
  447. $rules->add($rules->existsIn('author_id', 'Authors'));
  448. $this->assertFalse($table->save($entity));
  449. $this->assertEquals(['_existsIn' => 'This value does not exist'], $entity->getError('author_id'));
  450. }
  451. /**
  452. * Ensure that add(existsIn()) only invokes a rule once.
  453. *
  454. * @return void
  455. */
  456. public function testExistsInRuleSingleInvocation()
  457. {
  458. $entity = new Entity([
  459. 'title' => 'larry',
  460. 'author_id' => 500,
  461. ]);
  462. $table = $this->getTableLocator()->get('Articles');
  463. $table->belongsTo('Authors');
  464. $rules = $table->rulesChecker();
  465. $rules->add($rules->existsIn('author_id', 'Authors'), '_existsIn', ['errorField' => 'other']);
  466. $this->assertFalse($table->save($entity));
  467. $this->assertEquals(
  468. ['_existsIn' => 'This value does not exist'],
  469. $entity->getError('other'),
  470. 'Provided field should have errors'
  471. );
  472. $this->assertEmpty($entity->getError('author_id'), 'Errors should not apply to original field.');
  473. }
  474. /**
  475. * Tests the existsIn domain rule when passing an object
  476. *
  477. * @group save
  478. * @return void
  479. */
  480. public function testExistsInDomainRuleWithObject()
  481. {
  482. $entity = new Entity([
  483. 'title' => 'An Article',
  484. 'author_id' => 500,
  485. ]);
  486. $table = $this->getTableLocator()->get('Articles');
  487. $rules = $table->rulesChecker();
  488. $rules->add($rules->existsIn('author_id', $this->getTableLocator()->get('Authors'), 'Nope'));
  489. $this->assertFalse($table->save($entity));
  490. $this->assertEquals(['_existsIn' => 'Nope'], $entity->getError('author_id'));
  491. }
  492. /**
  493. * ExistsIn uses the schema to verify that nullable fields are ok.
  494. *
  495. * @return
  496. */
  497. public function testExistsInNullValue()
  498. {
  499. $entity = new Entity([
  500. 'title' => 'An Article',
  501. 'author_id' => null,
  502. ]);
  503. $table = $this->getTableLocator()->get('Articles');
  504. $table->belongsTo('Authors');
  505. $rules = $table->rulesChecker();
  506. $rules->add($rules->existsIn('author_id', 'Authors'));
  507. $this->assertEquals($entity, $table->save($entity));
  508. $this->assertEquals([], $entity->getError('author_id'));
  509. }
  510. /**
  511. * Test ExistsIn on a new entity that doesn't have the field populated.
  512. *
  513. * This use case is important for saving records and their
  514. * associated belongsTo records in one pass.
  515. *
  516. * @return void
  517. */
  518. public function testExistsInNotNullValueNewEntity()
  519. {
  520. $entity = new Entity([
  521. 'name' => 'A Category',
  522. ]);
  523. $table = $this->getTableLocator()->get('Categories');
  524. $table->belongsTo('Categories', [
  525. 'foreignKey' => 'parent_id',
  526. 'bindingKey' => 'id',
  527. ]);
  528. $rules = $table->rulesChecker();
  529. $rules->add($rules->existsIn('parent_id', 'Categories'));
  530. $this->assertTrue($table->checkRules($entity, RulesChecker::CREATE));
  531. $this->assertEmpty($entity->getError('parent_id'));
  532. }
  533. /**
  534. * Tests exists in uses the bindingKey of the association
  535. *
  536. * @return
  537. */
  538. public function testExistsInWithBindingKey()
  539. {
  540. $entity = new Entity([
  541. 'title' => 'An Article',
  542. ]);
  543. $table = $this->getTableLocator()->get('Articles');
  544. $table->belongsTo('Authors', [
  545. 'bindingKey' => 'name',
  546. 'foreignKey' => 'title',
  547. ]);
  548. $rules = $table->rulesChecker();
  549. $rules->add($rules->existsIn('title', 'Authors'));
  550. $this->assertFalse($table->save($entity));
  551. $this->assertNotEmpty($entity->getError('title'));
  552. $entity->clean();
  553. $entity->title = 'larry';
  554. $this->assertEquals($entity, $table->save($entity));
  555. }
  556. /**
  557. * Tests existsIn with invalid associations
  558. *
  559. * @group save
  560. * @return void
  561. */
  562. public function testExistsInInvalidAssociation()
  563. {
  564. $this->expectException(\RuntimeException::class);
  565. $this->expectExceptionMessage('ExistsIn rule for \'author_id\' is invalid. \'NotValid\' is not associated with \'Cake\ORM\Table\'.');
  566. $entity = new Entity([
  567. 'title' => 'An Article',
  568. 'author_id' => 500,
  569. ]);
  570. $table = $this->getTableLocator()->get('Articles');
  571. $table->belongsTo('Authors');
  572. $rules = $table->rulesChecker();
  573. $rules->add($rules->existsIn('author_id', 'NotValid'));
  574. $table->save($entity);
  575. }
  576. /**
  577. * Tests existsIn does not prevent new entities from saving if parent entity is new
  578. *
  579. * @return void
  580. */
  581. public function testExistsInHasManyNewEntities()
  582. {
  583. $table = $this->getTableLocator()->get('Articles');
  584. $table->hasMany('Comments');
  585. $table->Comments->belongsTo('Articles');
  586. $rules = $table->Comments->rulesChecker();
  587. $rules->add($rules->existsIn(['article_id'], $table));
  588. $article = $table->newEntity([
  589. 'title' => 'new article',
  590. 'comments' => [
  591. $table->Comments->newEntity([
  592. 'user_id' => 1,
  593. 'comment' => 'comment 1',
  594. ]),
  595. $table->Comments->newEntity([
  596. 'user_id' => 1,
  597. 'comment' => 'comment 2',
  598. ]),
  599. ],
  600. ]);
  601. $this->assertNotFalse($table->save($article));
  602. }
  603. /**
  604. * Tests existsIn does not prevent new entities from saving if parent entity is new,
  605. * getting the parent entity from the association
  606. *
  607. * @return void
  608. */
  609. public function testExistsInHasManyNewEntitiesViaAssociation()
  610. {
  611. $table = $this->getTableLocator()->get('Articles');
  612. $table->hasMany('Comments');
  613. $table->Comments->belongsTo('Articles');
  614. $rules = $table->Comments->rulesChecker();
  615. $rules->add($rules->existsIn(['article_id'], 'Articles'));
  616. $article = $table->newEntity([
  617. 'title' => 'test',
  618. ]);
  619. $article->comments = [
  620. $table->Comments->newEntity([
  621. 'user_id' => 1,
  622. 'comment' => 'test',
  623. ]),
  624. ];
  625. $this->assertNotFalse($table->save($article));
  626. }
  627. /**
  628. * Tests the checkRules save option
  629. *
  630. * @group save
  631. * @return void
  632. */
  633. public function testSkipRulesChecking()
  634. {
  635. $entity = new Entity([
  636. 'title' => 'An Article',
  637. 'author_id' => 500,
  638. ]);
  639. $table = $this->getTableLocator()->get('Articles');
  640. $rules = $table->rulesChecker();
  641. $rules->add($rules->existsIn('author_id', $this->getTableLocator()->get('Authors'), 'Nope'));
  642. $this->assertSame($entity, $table->save($entity, ['checkRules' => false]));
  643. }
  644. /**
  645. * Tests the beforeRules event
  646. *
  647. * @group save
  648. * @return void
  649. */
  650. public function testUseBeforeRules()
  651. {
  652. $entity = new Entity([
  653. 'title' => 'An Article',
  654. 'author_id' => 500,
  655. ]);
  656. $table = $this->getTableLocator()->get('Articles');
  657. $rules = $table->rulesChecker();
  658. $rules->add($rules->existsIn('author_id', $this->getTableLocator()->get('Authors'), 'Nope'));
  659. $table->getEventManager()->on(
  660. 'Model.beforeRules',
  661. function (Event $event, Entity $entity, \ArrayObject $options, $operation) {
  662. $this->assertEquals(
  663. [
  664. 'atomic' => true,
  665. 'associated' => true,
  666. 'checkRules' => true,
  667. 'checkExisting' => true,
  668. '_primary' => true,
  669. ],
  670. $options->getArrayCopy()
  671. );
  672. $this->assertEquals('create', $operation);
  673. $event->stopPropagation();
  674. return true;
  675. }
  676. );
  677. $this->assertSame($entity, $table->save($entity));
  678. }
  679. /**
  680. * Tests the afterRules event
  681. *
  682. * @group save
  683. * @return void
  684. */
  685. public function testUseAfterRules()
  686. {
  687. $entity = new Entity([
  688. 'title' => 'An Article',
  689. 'author_id' => 500,
  690. ]);
  691. $table = $this->getTableLocator()->get('Articles');
  692. $rules = $table->rulesChecker();
  693. $rules->add($rules->existsIn('author_id', $this->getTableLocator()->get('Authors'), 'Nope'));
  694. $table->getEventManager()->on(
  695. 'Model.afterRules',
  696. function (Event $event, Entity $entity, \ArrayObject $options, $result, $operation) {
  697. $this->assertEquals(
  698. [
  699. 'atomic' => true,
  700. 'associated' => true,
  701. 'checkRules' => true,
  702. 'checkExisting' => true,
  703. '_primary' => true,
  704. ],
  705. $options->getArrayCopy()
  706. );
  707. $this->assertEquals('create', $operation);
  708. $this->assertFalse($result);
  709. $event->stopPropagation();
  710. return true;
  711. }
  712. );
  713. $this->assertSame($entity, $table->save($entity));
  714. }
  715. /**
  716. * Tests that rules can be changed using the buildRules event
  717. *
  718. * @group save
  719. * @return void
  720. */
  721. public function testUseBuildRulesEvent()
  722. {
  723. $entity = new Entity([
  724. 'title' => 'An Article',
  725. 'author_id' => 500,
  726. ]);
  727. $table = $this->getTableLocator()->get('Articles');
  728. $table->getEventManager()->on('Model.buildRules', function (Event $event, RulesChecker $rules) {
  729. $rules->add($rules->existsIn('author_id', $this->getTableLocator()->get('Authors'), 'Nope'));
  730. });
  731. $this->assertFalse($table->save($entity));
  732. }
  733. /**
  734. * Tests isUnique with untouched fields
  735. *
  736. * @group save
  737. * @return void
  738. */
  739. public function testIsUniqueWithCleanFields()
  740. {
  741. $table = $this->getTableLocator()->get('Articles');
  742. $entity = $table->get(1);
  743. $rules = $table->rulesChecker();
  744. $rules->add($rules->isUnique(['title', 'author_id'], 'Nope'));
  745. $entity->body = 'Foo';
  746. $this->assertSame($entity, $table->save($entity));
  747. $entity->title = 'Third Article';
  748. $this->assertFalse($table->save($entity));
  749. }
  750. /**
  751. * Tests isUnique rule with conflicting columns
  752. *
  753. * @group save
  754. * @return void
  755. */
  756. public function testIsUniqueAliasPrefix()
  757. {
  758. $entity = new Entity([
  759. 'title' => 'An Article',
  760. 'author_id' => 1,
  761. ]);
  762. $table = $this->getTableLocator()->get('Articles');
  763. $table->belongsTo('Authors');
  764. $rules = $table->rulesChecker();
  765. $rules->add($rules->isUnique(['author_id']));
  766. $table->Authors->getEventManager()->on('Model.beforeFind', function (Event $event, $query) {
  767. $query->leftJoin(['a2' => 'authors']);
  768. });
  769. $this->assertFalse($table->save($entity));
  770. $this->assertEquals(['_isUnique' => 'This value is already in use'], $entity->getError('author_id'));
  771. }
  772. /**
  773. * Tests the existsIn rule when passing non dirty fields
  774. *
  775. * @group save
  776. * @return void
  777. */
  778. public function testExistsInWithCleanFields()
  779. {
  780. $table = $this->getTableLocator()->get('Articles');
  781. $table->belongsTo('Authors');
  782. $rules = $table->rulesChecker();
  783. $rules->add($rules->existsIn('author_id', 'Authors'));
  784. $entity = $table->get(1);
  785. $entity->title = 'Foo';
  786. $entity->author_id = 1000;
  787. $entity->setDirty('author_id', false);
  788. $this->assertSame($entity, $table->save($entity));
  789. }
  790. /**
  791. * Tests the existsIn with conflicting columns
  792. *
  793. * @group save
  794. * @return void
  795. */
  796. public function testExistsInAliasPrefix()
  797. {
  798. $entity = new Entity([
  799. 'title' => 'An Article',
  800. 'author_id' => 500,
  801. ]);
  802. $table = $this->getTableLocator()->get('Articles');
  803. $table->belongsTo('Authors');
  804. $rules = $table->rulesChecker();
  805. $rules->add($rules->existsIn('author_id', 'Authors'));
  806. $table->Authors->getEventManager()->on('Model.beforeFind', function (Event $event, $query) {
  807. $query->leftJoin(['a2' => 'authors']);
  808. });
  809. $this->assertFalse($table->save($entity));
  810. $this->assertEquals(['_existsIn' => 'This value does not exist'], $entity->getError('author_id'));
  811. }
  812. /**
  813. * Tests that using an array in existsIn() sets the error message correctly
  814. *
  815. * @return void
  816. */
  817. public function testExistsInErrorWithArrayField()
  818. {
  819. $entity = new Entity([
  820. 'title' => 'An Article',
  821. 'author_id' => 500,
  822. ]);
  823. $table = $this->getTableLocator()->get('Articles');
  824. $table->belongsTo('Authors');
  825. $rules = $table->rulesChecker();
  826. $rules->add($rules->existsIn(['author_id'], 'Authors'));
  827. $this->assertFalse($table->save($entity));
  828. $this->assertEquals(['_existsIn' => 'This value does not exist'], $entity->getError('author_id'));
  829. }
  830. /**
  831. * Tests new allowNullableNulls flag with author id set to null
  832. *
  833. * @return void
  834. */
  835. public function testExistsInAllowNullableNullsOn()
  836. {
  837. $entity = new Entity([
  838. 'id' => 10,
  839. 'author_id' => null,
  840. 'site_id' => 1,
  841. 'name' => 'New Site Article without Author',
  842. ]);
  843. $table = $this->getTableLocator()->get('SiteArticles');
  844. $table->belongsTo('SiteAuthors');
  845. $rules = $table->rulesChecker();
  846. $rules->add($rules->existsIn(['author_id', 'site_id'], 'SiteAuthors', [
  847. 'allowNullableNulls' => true,
  848. ]));
  849. $this->assertInstanceOf('Cake\ORM\Entity', $table->save($entity));
  850. }
  851. /**
  852. * Tests new allowNullableNulls flag with author id set to null
  853. *
  854. * @return void
  855. */
  856. public function testExistsInAllowNullableNullsOff()
  857. {
  858. $entity = new Entity([
  859. 'id' => 10,
  860. 'author_id' => null,
  861. 'site_id' => 1,
  862. 'name' => 'New Site Article without Author',
  863. ]);
  864. $table = $this->getTableLocator()->get('SiteArticles');
  865. $table->belongsTo('SiteAuthors');
  866. $rules = $table->rulesChecker();
  867. $rules->add($rules->existsIn(['author_id', 'site_id'], 'SiteAuthors', [
  868. 'allowNullableNulls' => false,
  869. ]));
  870. $this->assertFalse($table->save($entity));
  871. }
  872. /**
  873. * Tests new allowNullableNulls flag with author id set to null
  874. *
  875. * @return
  876. */
  877. public function testExistsInAllowNullableNullsDefaultValue()
  878. {
  879. $entity = new Entity([
  880. 'id' => 10,
  881. 'author_id' => null,
  882. 'site_id' => 1,
  883. 'name' => 'New Site Article without Author',
  884. ]);
  885. $table = $this->getTableLocator()->get('SiteArticles');
  886. $table->belongsTo('SiteAuthors');
  887. $rules = $table->rulesChecker();
  888. $rules->add($rules->existsIn(['author_id', 'site_id'], 'SiteAuthors'));
  889. $this->assertFalse($table->save($entity));
  890. }
  891. /**
  892. * Tests new allowNullableNulls flag with author id set to null
  893. *
  894. * @return
  895. */
  896. public function testExistsInAllowNullableNullsCustomMessage()
  897. {
  898. $entity = new Entity([
  899. 'id' => 10,
  900. 'author_id' => null,
  901. 'site_id' => 1,
  902. 'name' => 'New Site Article without Author',
  903. ]);
  904. $table = $this->getTableLocator()->get('SiteArticles');
  905. $table->belongsTo('SiteAuthors');
  906. $rules = $table->rulesChecker();
  907. $rules->add($rules->existsIn(['author_id', 'site_id'], 'SiteAuthors', [
  908. 'allowNullableNulls' => false,
  909. 'message' => 'Niente',
  910. ]));
  911. $this->assertFalse($table->save($entity));
  912. $this->assertEquals(['author_id' => ['_existsIn' => 'Niente']], $entity->getErrors());
  913. }
  914. /**
  915. * Tests new allowNullableNulls flag with author id set to 1
  916. *
  917. * @return
  918. */
  919. public function testExistsInAllowNullableNullsOnAllKeysSet()
  920. {
  921. $entity = new Entity([
  922. 'id' => 10,
  923. 'author_id' => 1,
  924. 'site_id' => 1,
  925. 'name' => 'New Site Article with Author',
  926. ]);
  927. $table = $this->getTableLocator()->get('SiteArticles');
  928. $table->belongsTo('SiteAuthors');
  929. $rules = $table->rulesChecker();
  930. $rules->add($rules->existsIn(['author_id', 'site_id'], 'SiteAuthors', ['allowNullableNulls' => true]));
  931. $this->assertInstanceOf('Cake\ORM\Entity', $table->save($entity));
  932. }
  933. /**
  934. * Tests new allowNullableNulls flag with author id set to 1
  935. *
  936. * @return
  937. */
  938. public function testExistsInAllowNullableNullsOffAllKeysSet()
  939. {
  940. $entity = new Entity([
  941. 'id' => 10,
  942. 'author_id' => 1,
  943. 'site_id' => 1,
  944. 'name' => 'New Site Article with Author',
  945. ]);
  946. $table = $this->getTableLocator()->get('SiteArticles');
  947. $table->belongsTo('SiteAuthors');
  948. $rules = $table->rulesChecker();
  949. $rules->add($rules->existsIn(['author_id', 'site_id'], 'SiteAuthors', ['allowNullableNulls' => false]));
  950. $this->assertInstanceOf('Cake\ORM\Entity', $table->save($entity));
  951. }
  952. /**
  953. * Tests new allowNullableNulls flag with author id set to 1
  954. *
  955. * @return
  956. */
  957. public function testExistsInAllowNullableNullsOnAllKeysCustomMessage()
  958. {
  959. $entity = new Entity([
  960. 'id' => 10,
  961. 'author_id' => 1,
  962. 'site_id' => 1,
  963. 'name' => 'New Site Article with Author',
  964. ]);
  965. $table = $this->getTableLocator()->get('SiteArticles');
  966. $table->belongsTo('SiteAuthors');
  967. $rules = $table->rulesChecker();
  968. $rules->add($rules->existsIn(['author_id', 'site_id'], 'SiteAuthors', [
  969. 'allowNullableNulls' => true,
  970. 'message' => 'will not error']));
  971. $this->assertInstanceOf('Cake\ORM\Entity', $table->save($entity));
  972. }
  973. /**
  974. * Tests new allowNullableNulls flag with author id set to 99999999 (does not exist)
  975. *
  976. * @return
  977. */
  978. public function testExistsInAllowNullableNullsOnInvalidKey()
  979. {
  980. $entity = new Entity([
  981. 'id' => 10,
  982. 'author_id' => 99999999,
  983. 'site_id' => 1,
  984. 'name' => 'New Site Article with Author',
  985. ]);
  986. $table = $this->getTableLocator()->get('SiteArticles');
  987. $table->belongsTo('SiteAuthors');
  988. $rules = $table->rulesChecker();
  989. $rules->add($rules->existsIn(['author_id', 'site_id'], 'SiteAuthors', [
  990. 'allowNullableNulls' => true,
  991. 'message' => 'will error']));
  992. $this->assertFalse($table->save($entity));
  993. $this->assertEquals(['author_id' => ['_existsIn' => 'will error']], $entity->getErrors());
  994. }
  995. /**
  996. * Tests new allowNullableNulls flag with author id set to 99999999 (does not exist)
  997. * and site_id set to 99999999 (does not exist)
  998. *
  999. * @return
  1000. */
  1001. public function testExistsInAllowNullableNullsOnInvalidKeys()
  1002. {
  1003. $entity = new Entity([
  1004. 'id' => 10,
  1005. 'author_id' => 99999999,
  1006. 'site_id' => 99999999,
  1007. 'name' => 'New Site Article with Author',
  1008. ]);
  1009. $table = $this->getTableLocator()->get('SiteArticles');
  1010. $table->belongsTo('SiteAuthors');
  1011. $rules = $table->rulesChecker();
  1012. $rules->add($rules->existsIn(['author_id', 'site_id'], 'SiteAuthors', [
  1013. 'allowNullableNulls' => true,
  1014. 'message' => 'will error']));
  1015. $this->assertFalse($table->save($entity));
  1016. $this->assertEquals(['author_id' => ['_existsIn' => 'will error']], $entity->getErrors());
  1017. }
  1018. /**
  1019. * Tests new allowNullableNulls flag with author id set to 1 (does exist)
  1020. * and site_id set to 99999999 (does not exist)
  1021. *
  1022. * @return
  1023. */
  1024. public function testExistsInAllowNullableNullsOnInvalidKeySecond()
  1025. {
  1026. $entity = new Entity([
  1027. 'id' => 10,
  1028. 'author_id' => 1,
  1029. 'site_id' => 99999999,
  1030. 'name' => 'New Site Article with Author',
  1031. ]);
  1032. $table = $this->getTableLocator()->get('SiteArticles');
  1033. $table->belongsTo('SiteAuthors');
  1034. $rules = $table->rulesChecker();
  1035. $rules->add($rules->existsIn(['author_id', 'site_id'], 'SiteAuthors', [
  1036. 'allowNullableNulls' => true,
  1037. 'message' => 'will error']));
  1038. $this->assertFalse($table->save($entity));
  1039. $this->assertEquals(['author_id' => ['_existsIn' => 'will error']], $entity->getErrors());
  1040. }
  1041. /**
  1042. * Tests new allowNullableNulls with saveMany
  1043. *
  1044. * @return
  1045. */
  1046. public function testExistsInAllowNullableNullsSaveMany()
  1047. {
  1048. $entities = [
  1049. new Entity([
  1050. 'id' => 1,
  1051. 'author_id' => null,
  1052. 'site_id' => 1,
  1053. 'name' => 'New Site Article without Author',
  1054. ]),
  1055. new Entity([
  1056. 'id' => 2,
  1057. 'author_id' => 1,
  1058. 'site_id' => 1,
  1059. 'name' => 'New Site Article with Author',
  1060. ]),
  1061. ];
  1062. $table = $this->getTableLocator()->get('SiteArticles');
  1063. $table->belongsTo('SiteAuthors');
  1064. $rules = $table->rulesChecker();
  1065. $rules->add($rules->existsIn(['author_id', 'site_id'], 'SiteAuthors', [
  1066. 'allowNullableNulls' => true,
  1067. 'message' => 'will error with array_combine warning']));
  1068. $result = $table->saveMany($entities);
  1069. $this->assertCount(2, $result);
  1070. $this->assertInstanceOf(Entity::class, $result[0]);
  1071. $this->assertEmpty($result[0]->getErrors());
  1072. $this->assertInstanceOf(Entity::class, $result[1]);
  1073. $this->assertEmpty($result[1]->getErrors());
  1074. }
  1075. /**
  1076. * Tests using rules to prevent delete operations
  1077. *
  1078. * @group delete
  1079. * @return void
  1080. */
  1081. public function testDeleteRules()
  1082. {
  1083. $table = $this->getTableLocator()->get('Articles');
  1084. $rules = $table->rulesChecker();
  1085. $rules->addDelete(function ($entity) {
  1086. return false;
  1087. });
  1088. $entity = $table->get(1);
  1089. $this->assertFalse($table->delete($entity));
  1090. }
  1091. /**
  1092. * Checks that it is possible to pass custom options to rules when saving
  1093. *
  1094. * @group save
  1095. * @return void
  1096. */
  1097. public function testCustomOptionsPassingSave()
  1098. {
  1099. $entity = new Entity([
  1100. 'name' => 'jose',
  1101. ]);
  1102. $table = $this->getTableLocator()->get('Authors');
  1103. $rules = $table->rulesChecker();
  1104. $rules->add(function ($entity, $options) {
  1105. $this->assertEquals('bar', $options['foo']);
  1106. $this->assertEquals('option', $options['another']);
  1107. return false;
  1108. }, ['another' => 'option']);
  1109. $this->assertFalse($table->save($entity, ['foo' => 'bar']));
  1110. }
  1111. /**
  1112. * Tests passing custom options to rules from delete
  1113. *
  1114. * @group delete
  1115. * @return void
  1116. */
  1117. public function testCustomOptionsPassingDelete()
  1118. {
  1119. $table = $this->getTableLocator()->get('Articles');
  1120. $rules = $table->rulesChecker();
  1121. $rules->addDelete(function ($entity, $options) {
  1122. $this->assertEquals('bar', $options['foo']);
  1123. $this->assertEquals('option', $options['another']);
  1124. return false;
  1125. }, ['another' => 'option']);
  1126. $entity = $table->get(1);
  1127. $this->assertFalse($table->delete($entity, ['foo' => 'bar']));
  1128. }
  1129. /**
  1130. * Test adding rules that return error string
  1131. *
  1132. * @group save
  1133. * @return void
  1134. */
  1135. public function testCustomErrorMessageFromRule()
  1136. {
  1137. $entity = new Entity([
  1138. 'name' => 'larry',
  1139. ]);
  1140. $table = $this->getTableLocator()->get('Authors');
  1141. $rules = $table->rulesChecker();
  1142. $rules->add(function () {
  1143. return 'So much nope';
  1144. }, ['errorField' => 'name']);
  1145. $this->assertFalse($table->save($entity));
  1146. $this->assertEquals(['So much nope'], $entity->getError('name'));
  1147. }
  1148. /**
  1149. * Test adding rules with no errorField do not accept strings
  1150. *
  1151. * @group save
  1152. * @return void
  1153. */
  1154. public function testCustomErrorMessageFromRuleNoErrorField()
  1155. {
  1156. $entity = new Entity([
  1157. 'name' => 'larry',
  1158. ]);
  1159. $table = $this->getTableLocator()->get('Authors');
  1160. $rules = $table->rulesChecker();
  1161. $rules->add(function () {
  1162. return 'So much nope';
  1163. });
  1164. $this->assertFalse($table->save($entity));
  1165. $this->assertEmpty($entity->getErrors());
  1166. }
  1167. /**
  1168. * Tests that using existsIn for a hasMany association will not be called
  1169. * as the foreign key for the association was automatically validated already.
  1170. *
  1171. * @group save
  1172. * @return void
  1173. */
  1174. public function testAvoidExistsInOnAutomaticSaving()
  1175. {
  1176. $entity = new Entity([
  1177. 'name' => 'Jose',
  1178. ]);
  1179. $entity->articles = [
  1180. new Entity([
  1181. 'title' => '1',
  1182. 'body' => 'A body',
  1183. ]),
  1184. new Entity([
  1185. 'title' => 'Another Title',
  1186. 'body' => 'Another body',
  1187. ]),
  1188. ];
  1189. $table = $this->getTableLocator()->get('authors');
  1190. $table->hasMany('articles');
  1191. $table->getAssociation('articles')->belongsTo('authors');
  1192. $checker = $table->getAssociation('articles')->getTarget()->rulesChecker();
  1193. $checker->add(function ($entity, $options) use ($checker) {
  1194. $rule = $checker->existsIn('author_id', 'authors');
  1195. $id = $entity->author_id;
  1196. $entity->author_id = 5000;
  1197. $result = $rule($entity, $options);
  1198. $this->assertTrue($result);
  1199. $entity->author_id = $id;
  1200. return true;
  1201. });
  1202. $this->assertSame($entity, $table->save($entity));
  1203. }
  1204. /**
  1205. * Tests the existsIn domain rule respects the conditions set for the associations
  1206. *
  1207. * @group save
  1208. * @return void
  1209. */
  1210. public function testExistsInDomainRuleWithAssociationConditions()
  1211. {
  1212. $entity = new Entity([
  1213. 'title' => 'An Article',
  1214. 'author_id' => 1,
  1215. ]);
  1216. $table = $this->getTableLocator()->get('Articles');
  1217. $table->belongsTo('Authors', [
  1218. 'conditions' => ['Authors.name !=' => 'mariano'],
  1219. ]);
  1220. $rules = $table->rulesChecker();
  1221. $rules->add($rules->existsIn('author_id', 'Authors'));
  1222. $this->assertFalse($table->save($entity));
  1223. $this->assertEquals(['_existsIn' => 'This value does not exist'], $entity->getError('author_id'));
  1224. }
  1225. /**
  1226. * Tests that associated items have a count of X.
  1227. *
  1228. * @return void
  1229. */
  1230. public function testCountOfAssociatedItems()
  1231. {
  1232. $entity = new Entity([
  1233. 'title' => 'A Title',
  1234. 'body' => 'A body',
  1235. ]);
  1236. $entity->tags = [
  1237. new Entity([
  1238. 'name' => 'Something New',
  1239. ]),
  1240. new Entity([
  1241. 'name' => '100',
  1242. ]),
  1243. ];
  1244. $this->getTableLocator()->get('ArticlesTags');
  1245. $table = $this->getTableLocator()->get('articles');
  1246. $table->belongsToMany('tags');
  1247. $rules = $table->rulesChecker();
  1248. $rules->add($rules->validCount('tags', 3));
  1249. $this->assertFalse($table->save($entity));
  1250. $this->assertEquals($entity->getErrors(), [
  1251. 'tags' => [
  1252. '_validCount' => 'The count does not match >3',
  1253. ],
  1254. ]);
  1255. // Testing that undesired types fail
  1256. $entity->tags = null;
  1257. $this->assertFalse($table->save($entity));
  1258. $entity->tags = new \stdClass();
  1259. $this->assertFalse($table->save($entity));
  1260. $entity->tags = 'string';
  1261. $this->assertFalse($table->save($entity));
  1262. $entity->tags = 123456;
  1263. $this->assertFalse($table->save($entity));
  1264. $entity->tags = 0.512;
  1265. $this->assertFalse($table->save($entity));
  1266. }
  1267. }