RulesCheckerIntegrationTest.php 44 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431
  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. }