RulesCheckerIntegrationTest.php 44 KB

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