PageRenderTime 59ms CodeModel.GetById 28ms RepoModel.GetById 0ms app.codeStats 1ms

/tests/cases/data/ModelTest.php

http://github.com/UnionOfRAD/lithium
PHP | 1278 lines | 1023 code | 214 blank | 41 comment | 1 complexity | d7a61b7d1ee5f7a7b6fe8c0729e6d3d4 MD5 | raw file
  1. <?php
  2. /**
  3. * li₃: the most RAD framework for PHP (http://li3.me)
  4. *
  5. * Copyright 2009, Union of RAD. All rights reserved. This source
  6. * code is distributed under the terms of the BSD 3-Clause License.
  7. * The full license text can be found in the LICENSE.txt file.
  8. */
  9. namespace lithium\tests\cases\data;
  10. use lithium\aop\Filters;
  11. use lithium\data\model\Query;
  12. use stdClass;
  13. use lithium\util\Inflector;
  14. use lithium\data\Model;
  15. use lithium\data\Schema;
  16. use lithium\data\Connections;
  17. use lithium\tests\mocks\data\MockTag;
  18. use lithium\tests\mocks\data\MockPost;
  19. use lithium\tests\mocks\data\MockComment;
  20. use lithium\tests\mocks\data\MockTagging;
  21. use lithium\tests\mocks\data\MockCreator;
  22. use lithium\tests\mocks\data\MockPostForValidates;
  23. use lithium\tests\mocks\data\MockProduct;
  24. use lithium\tests\mocks\data\MockSubProduct;
  25. use lithium\tests\mocks\data\MockBadConnection;
  26. use lithium\tests\mocks\core\MockCallable;
  27. use lithium\tests\mocks\data\MockSource;
  28. use lithium\tests\mocks\data\model\MockDatabase;
  29. class ModelTest extends \lithium\test\Unit {
  30. protected $_altSchema = null;
  31. public function setUp() {
  32. Connections::add('mocksource', ['object' => new MockSource()]);
  33. Connections::add('mockconn', ['object' => new MockDatabase()]);
  34. MockPost::config(['meta' => ['connection' => 'mocksource']]);
  35. MockTag::config(['meta' => ['connection' => 'mocksource']]);
  36. MockComment::config(['meta' => ['connection' => 'mocksource']]);
  37. MockCreator::config(['meta' => ['connection' => 'mocksource']]);
  38. MockSubProduct::config(['meta' => ['connection' => 'mockconn']]);
  39. MockProduct::config(['meta' => ['connection' => 'mockconn']]);
  40. MockPostForValidates::config(['meta' => ['connection' => 'mockconn']]);
  41. $this->_altSchema = new Schema([
  42. 'fields' => [
  43. 'id' => ['type' => 'integer'],
  44. 'author_id' => ['type' => 'integer'],
  45. 'title' => ['type' => 'string'],
  46. 'body' => ['type' => 'text']
  47. ]
  48. ]);
  49. }
  50. public function tearDown() {
  51. Connections::remove('mocksource');
  52. Connections::remove('mockconn');
  53. $models = [
  54. 'lithium\tests\mocks\data\MockPost',
  55. 'lithium\tests\mocks\data\MockTag',
  56. 'lithium\tests\mocks\data\MockComment',
  57. 'lithium\tests\mocks\data\MockCreator',
  58. 'lithium\tests\mocks\data\MockSubProduct',
  59. 'lithium\tests\mocks\data\MockProduct',
  60. 'lithium\tests\mocks\data\MockPostForValidates'
  61. ];
  62. foreach ($models as $model) {
  63. $model::reset();
  64. Filters::clear($model);
  65. }
  66. }
  67. public function testOverrideMeta() {
  68. MockTag::reset();
  69. MockTag::meta(['id' => 'key']);
  70. $meta = MockTag::meta();
  71. $this->assertFalse($meta['connection']);
  72. $this->assertEqual('mock_tags', $meta['source']);
  73. $this->assertEqual('key', $meta['id']);
  74. }
  75. public function testClassInitialization() {
  76. $expected = MockPost::instances();
  77. MockPost::config();
  78. $this->assertEqual($expected, MockPost::instances());
  79. Model::config();
  80. $this->assertEqual($expected, MockPost::instances());
  81. $this->assertEqual('mock_posts', MockPost::meta('source'));
  82. MockPost::config(['meta' => ['source' => 'post']]);
  83. $this->assertEqual('post', MockPost::meta('source'));
  84. MockPost::config(['meta' => ['source' => false]]);
  85. $this->assertFalse(MockPost::meta('source'));
  86. MockPost::config(['meta' => ['source' => null]]);
  87. $this->assertIdentical('mock_posts', MockPost::meta('source'));
  88. MockPost::config();
  89. $this->assertEqual('mock_posts', MockPost::meta('source'));
  90. $this->assertEqual('mocksource', MockPost::meta('connection'));
  91. MockPost::config(['meta' => ['source' => 'toreset']]);
  92. MockPost::reset();
  93. MockPost::config(['meta' => ['connection' => 'mocksource']]);
  94. $this->assertEqual('mock_posts', MockPost::meta('source'));
  95. $this->assertEqual('mocksource', MockPost::meta('connection'));
  96. MockPost::config(['query' => ['with' => ['MockComment'], 'limit' => 10]]);
  97. $expected = [
  98. 'with' => ['MockComment'],
  99. 'limit' => 10,
  100. 'conditions' => null,
  101. 'fields' => null,
  102. 'order' => null,
  103. 'page' => null,
  104. 'having' => null,
  105. 'group' => null,
  106. 'offset' => null,
  107. 'joins' => []
  108. ];
  109. $this->assertEqual($expected, MockPost::query());
  110. $finder = [
  111. 'fields' => ['title', 'body']
  112. ];
  113. MockPost::finder('myFinder', $finder);
  114. $result = MockPost::find('myFinder');
  115. $expected = $finder + [
  116. 'order' => null,
  117. 'limit' => 10,
  118. 'conditions' => null,
  119. 'page' => null,
  120. 'with' => ['MockComment'],
  121. 'type' => 'read',
  122. 'model' => 'lithium\tests\mocks\data\MockPost',
  123. 'having' => null,
  124. 'group' => null,
  125. 'offset' => null,
  126. 'joins' => []
  127. ];
  128. $this->assertEqual($expected, $result['options']);
  129. $finder = [
  130. 'fields' => ['id', 'title']
  131. ];
  132. MockPost::reset();
  133. MockPost::config(['meta' => ['connection' => 'mocksource']]);
  134. $result = MockPost::finder('myFinder');
  135. $this->assertNull($result);
  136. }
  137. public function testInstanceMethods() {
  138. MockPost::instanceMethods([]);
  139. $methods = MockPost::instanceMethods();
  140. $this->assertEmpty($methods);
  141. MockPost::instanceMethods([
  142. 'first' => [
  143. 'lithium\tests\mocks\data\source\MockMongoPost',
  144. 'testInstanceMethods'
  145. ],
  146. 'second' => function($entity) {}
  147. ]);
  148. $methods = MockPost::instanceMethods();
  149. $this->assertCount(2, $methods);
  150. MockPost::instanceMethods([
  151. 'third' => function($entity) {}
  152. ]);
  153. $methods = MockPost::instanceMethods();
  154. $this->assertCount(3, $methods);
  155. }
  156. public function testMetaInformation() {
  157. $class = 'lithium\tests\mocks\data\MockPost';
  158. $expected = compact('class') + [
  159. 'name' => 'MockPost',
  160. 'key' => 'id',
  161. 'title' => 'title',
  162. 'source' => 'mock_posts',
  163. 'connection' => 'mocksource',
  164. 'locked' => true
  165. ];
  166. $this->assertEqual($expected, MockPost::meta());
  167. $class = 'lithium\tests\mocks\data\MockComment';
  168. $expected = compact('class') + [
  169. 'name' => 'MockComment',
  170. 'key' => 'comment_id',
  171. 'title' => 'comment_id',
  172. 'source' => 'mock_comments',
  173. 'connection' => 'mocksource',
  174. 'locked' => true
  175. ];
  176. $this->assertEqual($expected, MockComment::meta());
  177. $expected += ['foo' => 'bar'];
  178. MockComment::meta('foo', 'bar');
  179. $this->assertEqual($expected, MockComment::meta());
  180. $expected += ['bar' => true, 'baz' => false];
  181. MockComment::meta(['bar' => true, 'baz' => false]);
  182. $this->assertEqual($expected, MockComment::meta());
  183. }
  184. public function testSchemaLoading() {
  185. $result = MockPost::schema();
  186. $this->assertNotEmpty($result);
  187. $this->assertEqual($result->fields(), MockPost::schema()->fields());
  188. MockPost::config(['schema' => $this->_altSchema]);
  189. $this->assertEqual($this->_altSchema->fields(), MockPost::schema()->fields());
  190. }
  191. public function testSchemaInheritance() {
  192. $result = MockSubProduct::schema();
  193. $this->assertTrue(array_key_exists('price', $result->fields()));
  194. }
  195. public function testInitializationInheritance() {
  196. $meta = [
  197. 'name' => 'MockSubProduct',
  198. 'source' => 'mock_products',
  199. 'title' => 'name',
  200. 'class' => 'lithium\tests\mocks\data\MockSubProduct',
  201. 'connection' => 'mockconn',
  202. 'key' => 'id',
  203. 'locked' => true
  204. ];
  205. $this->assertEqual($meta, MockSubProduct::meta());
  206. $this->assertArrayHasKey('MockCreator', MockSubProduct::relations());
  207. $this->assertCount(4, MockSubProduct::finders());
  208. $this->assertCount(1, MockSubProduct::initializers());
  209. $config = ['query' => ['with' => ['MockCreator']]];
  210. MockProduct::config(compact('config'));
  211. $this->assertEqual(MockProduct::query(), MockSubProduct::query());
  212. $expected = ['limit' => 50] + MockProduct::query();
  213. MockSubProduct::config(['query' => $expected]);
  214. $this->assertEqual($expected, MockSubProduct::query());
  215. MockPostForValidates::config([
  216. 'classes' => ['connections' => 'lithium\tests\mocks\data\MockConnections'],
  217. 'meta' => ['connection' => new MockCallable()]
  218. ]);
  219. $conn = MockPostForValidates::connection();
  220. $this->assertInstanceOf('lithium\tests\mocks\core\MockCallable', $conn);
  221. }
  222. public function testCustomAttributesInheritance() {
  223. $expected = [
  224. 'prop1' => 'value1',
  225. 'prop2' => 'value2'
  226. ];
  227. $result = MockSubProduct::attribute('_custom');
  228. $this->assertEqual($expected, $result);
  229. }
  230. public function testAttributesInheritanceWithObject() {
  231. $expected = [
  232. 'id' => ['type' => 'id'],
  233. 'title' => ['type' => 'string', 'null' => false],
  234. 'body' => ['type' => 'text', 'null' => false]
  235. ];
  236. $schema = new Schema(['fields' => $expected]);
  237. MockSubProduct::config(compact('schema'));
  238. $result = MockSubProduct::schema();
  239. $this->assertEqual($expected, $result->fields());
  240. }
  241. public function testFieldIntrospection() {
  242. $this->assertNotEmpty(MockComment::hasField('comment_id'));
  243. $this->assertEmpty(MockComment::hasField('foo'));
  244. $this->assertEqual('comment_id', MockComment::hasField(['comment_id']));
  245. }
  246. /**
  247. * Tests introspecting the relationship settings for the model as a whole, various relationship
  248. * types, and individual relationships.
  249. *
  250. * @todo Some tests will need to change when full relationship support is built out.
  251. */
  252. public function testRelationshipIntrospection() {
  253. $result = array_keys(MockPost::relations());
  254. $expected = ['MockComment'];
  255. $this->assertEqual($expected, $result);
  256. $result = MockPost::relations('hasMany');
  257. $this->assertEqual($expected, $result);
  258. $result = array_keys(MockComment::relations());
  259. $expected = ['MockPost'];
  260. $this->assertEqual($expected, $result);
  261. $result = MockComment::relations('belongsTo');
  262. $this->assertEqual($expected, $result);
  263. $this->assertEmpty(MockComment::relations('hasMany'));
  264. $this->assertEmpty(MockPost::relations('belongsTo'));
  265. $expected = [
  266. 'name' => 'MockPost',
  267. 'type' => 'belongsTo',
  268. 'key' => ['mock_post_id' => 'id'],
  269. 'from' => 'lithium\tests\mocks\data\MockComment',
  270. 'to' => 'lithium\tests\mocks\data\MockPost',
  271. 'link' => 'key',
  272. 'fields' => true,
  273. 'fieldName' => 'mock_post',
  274. 'constraints' => [],
  275. 'strategy' => null,
  276. 'init' => true
  277. ];
  278. $this->assertEqual($expected, MockComment::relations('MockPost')->data());
  279. $expected = [
  280. 'name' => 'MockComment',
  281. 'type' => 'hasMany',
  282. 'from' => 'lithium\tests\mocks\data\MockPost',
  283. 'to' => 'lithium\tests\mocks\data\MockComment',
  284. 'fields' => true,
  285. 'key' => ['id' => 'mock_post_id'],
  286. 'link' => 'key',
  287. 'fieldName' => 'mock_comments',
  288. 'constraints' => [],
  289. 'strategy' => null,
  290. 'init' => true
  291. ];
  292. $this->assertEqual($expected, MockPost::relations('MockComment')->data());
  293. MockPost::config(['meta' => ['connection' => false]]);
  294. MockComment::config(['meta' => ['connection' => false]]);
  295. MockTag::config(['meta' => ['connection' => false]]);
  296. }
  297. /**
  298. * Verifies that modifying the default query through the `query()` method works.
  299. *
  300. * @link https://github.com/UnionOfRAD/lithium/issues/1314
  301. */
  302. public function testDefaultQueryModification() {
  303. MockPost::query(['limit' => 23]);
  304. $result = MockPost::query();
  305. $this->assertEqual(23, $result['limit']);
  306. }
  307. public function testSimpleRecordCreation() {
  308. $comment = MockComment::create([
  309. 'author_id' => 451,
  310. 'text' => 'Do you ever read any of the books you burn?'
  311. ]);
  312. $this->assertFalse($comment->exists());
  313. $this->assertNull($comment->comment_id);
  314. $expected = 'Do you ever read any of the books you burn?';
  315. $this->assertEqual($expected, $comment->text);
  316. $comment = MockComment::create(
  317. ['author_id' => 111, 'text' => 'This comment should already exist'],
  318. ['exists' => true]
  319. );
  320. $this->assertTrue($comment->exists());
  321. }
  322. public function testSimpleFind() {
  323. $result = MockPost::find('all');
  324. $this->assertInstanceOf('lithium\data\model\Query', $result['query']);
  325. }
  326. public function testMagicFinders() {
  327. $result = MockPost::findById(5);
  328. $result2 = MockPost::findFirstById(5);
  329. $this->assertEqual($result2, $result);
  330. $expected = ['id' => 5];
  331. $this->assertEqual($expected, $result['query']->conditions());
  332. $this->assertEqual('read', $result['query']->type());
  333. $result = MockPost::findAllByFoo(13, ['order' => ['created_at' => 'desc']]);
  334. $this->assertEmpty($result['query']->data());
  335. $this->assertEqual(['foo' => 13], $result['query']->conditions());
  336. $this->assertEqual(['created_at' => 'desc'], $result['query']->order());
  337. $this->assertException('/Method `findFoo` not defined or handled in class/', function() {
  338. MockPost::findFoo();
  339. });
  340. }
  341. /**
  342. * Tests the find 'first' filter on a simple record set.
  343. */
  344. public function testSimpleFindFirst() {
  345. $result = MockComment::first();
  346. $this->assertInstanceOf('lithium\data\entity\Record', $result);
  347. $expected = 'First comment';
  348. $this->assertEqual($expected, $result->text);
  349. }
  350. public function testSimpleFindList() {
  351. $result = MockComment::find('list');
  352. $this->assertNotEmpty($result);
  353. $this->assertInternalType('array', $result);
  354. }
  355. public function testFilteredFindInvokedMagically() {
  356. Filters::apply('lithium\tests\mocks\data\MockComment', 'find', function($params, $next) {
  357. $result = $next($params);
  358. if ($result !== null) {
  359. $result->filtered = true;
  360. }
  361. return $result;
  362. });
  363. $result = MockComment::first();
  364. $this->assertTrue($result->filtered);
  365. }
  366. public function testCustomFinder() {
  367. $finder = function() {};
  368. MockPost::finder('custom', $finder);
  369. $this->assertIdentical($finder, MockPost::finder('custom'));
  370. $finder = [
  371. 'fields' => ['id', 'email'],
  372. 'conditions' => ['id' => 2]
  373. ];
  374. MockPost::finder('arrayTest', $finder);
  375. $result = MockPost::find('arrayTest');
  376. $expected = $finder + [
  377. 'order' => null,
  378. 'limit' => null,
  379. 'page' => null,
  380. 'with' => [],
  381. 'type' => 'read',
  382. 'model' => 'lithium\tests\mocks\data\MockPost',
  383. 'having' => null,
  384. 'group' => null,
  385. 'offset' => null,
  386. 'joins' => []
  387. ];
  388. $this->assertEqual($expected, $result['options']);
  389. }
  390. public function testCustomFindMethods() {
  391. $result = MockPost::findFirstById(5);
  392. $query = $result['query'];
  393. $this->assertEqual(['id' => 5], $query->conditions());
  394. $this->assertEqual(1, $query->limit());
  395. }
  396. public function testKeyGeneration() {
  397. $this->assertEqual('comment_id', MockComment::key());
  398. $this->assertEqual(['post_id', 'tag_id'], MockTagging::key());
  399. $result = MockComment::key(['comment_id' => 5, 'body' => 'This is a comment']);
  400. $this->assertEqual(['comment_id' => 5], $result);
  401. $result = MockTagging::key([
  402. 'post_id' => 2,
  403. 'tag_id' => 5,
  404. 'created' => '2009-06-16 10:00:00'
  405. ]);
  406. $this->assertEqual('id', MockPost::key());
  407. $this->assertEqual(['id' => 5], MockPost::key(5));
  408. $this->assertEqual(['post_id' => 2, 'tag_id' => 5], $result);
  409. $key = new stdClass();
  410. $key->foo = 'bar';
  411. $this->assertEqual(['id' => $key], MockPost::key($key));
  412. $this->assertNull(MockPost::key([]));
  413. $model = 'lithium\tests\mocks\data\MockModelCompositePk';
  414. $this->assertNull($model::key(['client_id' => 3]));
  415. $result = $model::key(['invoice_id' => 5, 'payment' => '100']);
  416. $this->assertNull($result);
  417. $expected = ['client_id' => 3, 'invoice_id' => 5];
  418. $result = $model::key([
  419. 'client_id' => 3,
  420. 'invoice_id' => 5,
  421. 'payment' => '100']
  422. );
  423. $this->assertEqual($expected, $result);
  424. }
  425. public function testValidatesFalse() {
  426. $post = MockPostForValidates::create();
  427. $result = $post->validates();
  428. $this->assertFalse($result);
  429. $result = $post->errors();
  430. $this->assertNotEmpty($result);
  431. $expected = [
  432. 'title' => ['please enter a title'],
  433. 'email' => ['email is empty', 'email is not valid']
  434. ];
  435. $result = $post->errors();
  436. $this->assertEqual($expected, $result);
  437. }
  438. public function testValidatesWithWhitelist() {
  439. $post = MockPostForValidates::create();
  440. $whitelist = ['title'];
  441. $post->title = 'title';
  442. $result = $post->validates(compact('whitelist'));
  443. $this->assertTrue($result);
  444. $post->title = '';
  445. $result = $post->validates(compact('whitelist'));
  446. $this->assertFalse($result);
  447. $result = $post->errors();
  448. $this->assertNotEmpty($result);
  449. $expected = ['title' => ['please enter a title']];
  450. $result = $post->errors();
  451. $this->assertEqual($expected, $result);
  452. }
  453. public function testValidatesTitle() {
  454. $post = MockPostForValidates::create(['title' => 'new post']);
  455. $result = $post->validates();
  456. $this->assertFalse($result);
  457. $result = $post->errors();
  458. $this->assertNotEmpty($result);
  459. $expected = [
  460. 'email' => ['email is empty', 'email is not valid']
  461. ];
  462. $result = $post->errors();
  463. $this->assertEqual($expected, $result);
  464. }
  465. public function testValidatesEmailIsNotEmpty() {
  466. $post = MockPostForValidates::create(['title' => 'new post', 'email' => 'something']);
  467. $result = $post->validates();
  468. $this->assertFalse($result);
  469. $result = $post->errors();
  470. $this->assertNotEmpty($result);
  471. $expected = ['email' => [1 => 'email is not valid']];
  472. $result = $post->errors();
  473. $this->assertEqual($expected, $result);
  474. }
  475. public function testValidatesEmailIsValid() {
  476. $post = MockPostForValidates::create([
  477. 'title' => 'new post', 'email' => 'something@test.com'
  478. ]);
  479. $result = $post->validates();
  480. $this->assertTrue($result);
  481. $result = $post->errors();
  482. $this->assertEmpty($result);
  483. }
  484. public function testCustomValidationCriteria() {
  485. $validates = [
  486. 'title' => 'A custom message here for empty titles.',
  487. 'email' => [
  488. ['notEmpty', 'message' => 'email is empty.']
  489. ]
  490. ];
  491. $post = MockPostForValidates::create([
  492. 'title' => 'custom validation', 'email' => 'asdf'
  493. ]);
  494. $result = $post->validates(['rules' => $validates]);
  495. $this->assertTrue($result);
  496. $this->assertIdentical([], $post->errors());
  497. }
  498. public function testValidatesCustomEventFalse() {
  499. $post = MockPostForValidates::create();
  500. $events = 'customEvent';
  501. $this->assertFalse($post->validates(compact('events')));
  502. $this->assertNotEmpty($post->errors());
  503. $expected = [
  504. 'title' => ['please enter a title'],
  505. 'email' => [
  506. 0 => 'email is empty',
  507. 1 => 'email is not valid',
  508. 3 => 'email is not in 1st list'
  509. ]
  510. ];
  511. $result = $post->errors();
  512. $this->assertEqual($expected, $result);
  513. }
  514. public function testValidatesCustomEventValid() {
  515. $post = MockPostForValidates::create([
  516. 'title' => 'new post', 'email' => 'something@test.com'
  517. ]);
  518. $events = 'customEvent';
  519. $result = $post->validates(compact('events'));
  520. $this->assertTrue($result);
  521. $result = $post->errors();
  522. $this->assertEmpty($result);
  523. }
  524. public function testValidatesCustomEventsFalse() {
  525. $post = MockPostForValidates::create();
  526. $events = ['customEvent','anotherCustomEvent'];
  527. $result = $post->validates(compact('events'));
  528. $this->assertFalse($result);
  529. $result = $post->errors();
  530. $this->assertNotEmpty($result);
  531. $expected = [
  532. 'title' => ['please enter a title'],
  533. 'email' => [
  534. 0 => 'email is empty',
  535. 1 => 'email is not valid',
  536. 3 => 'email is not in 1st list',
  537. 4 => 'email is not in 2nd list'
  538. ]
  539. ];
  540. $result = $post->errors();
  541. $this->assertEqual($expected, $result);
  542. }
  543. public function testValidatesCustomEventsFirstValid() {
  544. $post = MockPostForValidates::create([
  545. 'title' => 'new post', 'email' => 'foo@bar.com'
  546. ]);
  547. $events = ['customEvent','anotherCustomEvent'];
  548. $result = $post->validates(compact('events'));
  549. $this->assertFalse($result);
  550. $result = $post->errors();
  551. $this->assertNotEmpty($result);
  552. $expected = [
  553. 'email' => [4 => 'email is not in 2nd list']
  554. ];
  555. $result = $post->errors();
  556. $this->assertEqual($expected, $result);
  557. }
  558. public function testValidatesCustomEventsValid() {
  559. $post = MockPostForValidates::create([
  560. 'title' => 'new post', 'email' => 'something@test.com'
  561. ]);
  562. $events = ['customEvent','anotherCustomEvent'];
  563. $result = $post->validates(compact('events'));
  564. $this->assertTrue($result);
  565. $result = $post->errors();
  566. $this->assertEmpty($result);
  567. }
  568. public function testValidationInheritance() {
  569. $product = MockProduct::create();
  570. $antique = MockSubProduct::create();
  571. $errors = [
  572. 'name' => ['Name cannot be empty.'],
  573. 'price' => [
  574. 'Price cannot be empty.',
  575. 'Price must have a numeric value.'
  576. ]
  577. ];
  578. $this->assertFalse($product->validates());
  579. $this->assertEqual($errors, $product->errors());
  580. $errors += [
  581. 'refurb' => ['Must have a boolean value.']
  582. ];
  583. $this->assertFalse($antique->validates());
  584. $this->assertEqual($errors, $antique->errors());
  585. }
  586. public function testErrorsIsClearedOnEachValidates() {
  587. $post = MockPostForValidates::create(['title' => 'new post']);
  588. $result = $post->validates();
  589. $this->assertFalse($result);
  590. $result = $post->errors();
  591. $this->assertNotEmpty($result);
  592. $post->email = 'contact@li3.me';
  593. $result = $post->validates();
  594. $this->assertTrue($result);
  595. $result = $post->errors();
  596. $this->assertEmpty($result);
  597. }
  598. public function testDefaultValuesFromSchema() {
  599. $creator = MockCreator::create();
  600. $expected = [
  601. 'name' => 'Moe',
  602. 'sign' => 'bar',
  603. 'age' => 0
  604. ];
  605. $result = $creator->data();
  606. $this->assertEqual($expected, $result);
  607. $creator = MockCreator::create(['name' => 'Homer']);
  608. $expected = [
  609. 'name' => 'Homer',
  610. 'sign' => 'bar',
  611. 'age' => 0
  612. ];
  613. $result = $creator->data();
  614. $this->assertEqual($expected, $result);
  615. $creator = MockCreator::create([
  616. 'sign' => 'Beer', 'skin' => 'yellow', 'age' => 12, 'hair' => false
  617. ]);
  618. $expected = [
  619. 'name' => 'Moe',
  620. 'sign' => 'Beer',
  621. 'skin' => 'yellow',
  622. 'age' => 12,
  623. 'hair' => false
  624. ];
  625. $result = $creator->data();
  626. $this->assertEqual($expected, $result);
  627. $expected = 'mock_creators';
  628. $result = MockCreator::meta('source');
  629. $this->assertEqual($expected, $result);
  630. $creator = MockCreator::create(['name' => 'Homer'], ['defaults' => false]);
  631. $expected = [
  632. 'name' => 'Homer'
  633. ];
  634. $result = $creator->data();
  635. $this->assertEqual($expected, $result);
  636. }
  637. public function testCreateCollection() {
  638. MockCreator::config([
  639. 'meta' => ['key' => 'name', 'connection' => 'mockconn']
  640. ]);
  641. $expected = [
  642. ['name' => 'Homer'],
  643. ['name' => 'Bart'],
  644. ['name' => 'Marge'],
  645. ['name' => 'Lisa']
  646. ];
  647. $data = [];
  648. foreach ($expected as $value) {
  649. $data[] = MockCreator::create($value, ['defaults' => false]);
  650. }
  651. $result = MockCreator::create($data, ['class' => 'set']);
  652. $this->assertCount(4, $result);
  653. $this->assertInstanceOf('lithium\data\collection\RecordSet', $result);
  654. $this->assertEqual($expected, $result->to('array', ['indexed' => false]));
  655. }
  656. public function testModelWithNoBackend() {
  657. MockPost::reset();
  658. $this->assertFalse(MockPost::meta('connection'));
  659. $schema = MockPost::schema();
  660. MockPost::config(['schema' => $this->_altSchema]);
  661. $this->assertEqual($this->_altSchema->fields(), MockPost::schema()->fields());
  662. $post = MockPost::create(['title' => 'New post']);
  663. $this->assertInstanceOf('lithium\data\Entity', $post);
  664. $this->assertEqual('New post', $post->title);
  665. }
  666. public function testSave() {
  667. MockPost::config(['schema' => $this->_altSchema]);
  668. MockPost::config(['schema' => new Schema()]);
  669. $data = ['title' => 'New post', 'author_id' => 13, 'foo' => 'bar'];
  670. $record = MockPost::create($data);
  671. $result = $record->save();
  672. $this->assertEqual('create', $result['query']->type());
  673. $this->assertEqual($data, $result['query']->data());
  674. $this->assertEqual('lithium\tests\mocks\data\MockPost', $result['query']->model());
  675. MockPost::config(['schema' => $this->_altSchema]);
  676. $record->tags = ["baz", "qux"];
  677. $otherData = ['body' => 'foobar'];
  678. $result = $record->save($otherData);
  679. $data['body'] = 'foobar';
  680. $data['tags'] = ["baz", "qux"];
  681. $expected = ['title' => 'New post', 'author_id' => 13, 'body' => 'foobar'];
  682. $this->assertNotEqual($data, $result['query']->data());
  683. }
  684. public function testSaveWithNoCallbacks() {
  685. MockPost::config(['schema' => $this->_altSchema]);
  686. $data = ['title' => 'New post', 'author_id' => 13];
  687. $record = MockPost::create($data);
  688. $result = $record->save(null, ['callbacks' => false]);
  689. $this->assertEqual('create', $result['query']->type());
  690. $this->assertEqual($data, $result['query']->data());
  691. $this->assertEqual('lithium\tests\mocks\data\MockPost', $result['query']->model());
  692. }
  693. public function testSaveWithFailedValidation() {
  694. $data = ['title' => '', 'author_id' => 13];
  695. $record = MockPost::create($data);
  696. $result = $record->save(null, [
  697. 'validate' => [
  698. 'title' => 'A title must be present'
  699. ]
  700. ]);
  701. $this->assertFalse($result);
  702. }
  703. public function testSaveFailedWithValidationByModelDefinition() {
  704. $post = MockPostForValidates::create();
  705. $result = $post->save();
  706. $this->assertFalse($result);
  707. $result = $post->errors();
  708. $this->assertNotEmpty($result);
  709. $expected = [
  710. 'title' => ['please enter a title'],
  711. 'email' => ['email is empty', 'email is not valid']
  712. ];
  713. $result = $post->errors();
  714. $this->assertEqual($expected, $result);
  715. }
  716. public function testSaveFailedWithValidationByModelDefinitionAndTriggeredCustomEvents() {
  717. $post = MockPostForValidates::create();
  718. $events = ['customEvent','anotherCustomEvent'];
  719. $result = $post->save(null,compact('events'));
  720. $this->assertFalse($result);
  721. $result = $post->errors();
  722. $this->assertNotEmpty($result);
  723. $expected = [
  724. 'title' => ['please enter a title'],
  725. 'email' => [
  726. 0 => 'email is empty',
  727. 1 => 'email is not valid',
  728. 3 => 'email is not in 1st list',
  729. 4 => 'email is not in 2nd list'
  730. ]
  731. ];
  732. $result = $post->errors();
  733. $this->assertEqual($expected, $result);
  734. }
  735. public function testSaveWithValidationAndWhitelist() {
  736. $post = MockPostForValidates::create();
  737. $whitelist = ['title'];
  738. $post->title = 'title';
  739. $result = $post->save(null, compact('whitelist'));
  740. $this->assertTrue($result);
  741. $post->title = '';
  742. $result = $post->save(null, compact('whitelist'));
  743. $this->assertFalse($result);
  744. $result = $post->errors();
  745. $this->assertNotEmpty($result);
  746. $expected = ['title' => ['please enter a title']];
  747. $result = $post->errors();
  748. $this->assertEqual($expected, $result);
  749. }
  750. public function testWhitelistWhenLockedUsingCreateForData() {
  751. MockPost::config([
  752. 'schema' => $this->_altSchema,
  753. 'meta' => [
  754. 'locked' => true,
  755. 'connection' => 'mocksource'
  756. ]
  757. ]);
  758. $data = ['title' => 'New post', 'foo' => 'bar'];
  759. $record = MockPost::create($data);
  760. $expected = ['title' => 'New post'];
  761. $result = $record->save();
  762. $this->assertEqual($expected, $result['query']->data());
  763. $data = ['foo' => 'bar'];
  764. $record = MockPost::create($data);
  765. $expected = [];
  766. $result = $record->save();
  767. $this->assertEqual($expected, $result['query']->data());
  768. }
  769. public function testWhitelistWhenLockedUsingSaveForData() {
  770. MockPost::config([
  771. 'schema' => $this->_altSchema,
  772. 'meta' => [
  773. 'locked' => true,
  774. 'connection' => 'mocksource'
  775. ]
  776. ]);
  777. $data = ['title' => 'New post', 'foo' => 'bar'];
  778. $record = MockPost::create();
  779. $expected = ['title' => 'New post'];
  780. $result = $record->save($data);
  781. $this->assertEqual($expected, $result['query']->data());
  782. $data = ['foo' => 'bar'];
  783. $record = MockPost::create();
  784. $expected = [];
  785. $result = $record->save($data);
  786. $this->assertEqual($expected, $result['query']->data());
  787. }
  788. public function testWhitelistWhenLockedUsingCreateWithValidAndSaveForInvalidData() {
  789. MockPost::config([
  790. 'schema' => $this->_altSchema,
  791. 'meta' => [
  792. 'locked' => true,
  793. 'connection' => 'mocksource'
  794. ]
  795. ]);
  796. $data = ['title' => 'New post'];
  797. $record = MockPost::create($data);
  798. $expected = ['title' => 'New post'];
  799. $data = ['foo' => 'bar'];
  800. $result = $record->save($data);
  801. $this->assertEqual($expected, $result['query']->data());
  802. }
  803. public function testImplicitKeyFind() {
  804. $result = MockPost::find(10);
  805. $this->assertEqual('read', $result['query']->type());
  806. $this->assertEqual('lithium\tests\mocks\data\MockPost', $result['query']->model());
  807. $this->assertEqual(['id' => 10], $result['query']->conditions());
  808. }
  809. public function testDelete() {
  810. $record = MockPost::create(['id' => 5], ['exists' => true]);
  811. $result = $record->delete();
  812. $this->assertEqual('delete', $result['query']->type());
  813. $this->assertEqual('mock_posts', $result['query']->source());
  814. $this->assertEqual(['id' => 5], $result['query']->conditions());
  815. }
  816. public function testMultiRecordUpdate() {
  817. $result = MockPost::update(
  818. ['published' => false],
  819. ['expires' => ['>=' => '2010-05-13']]
  820. );
  821. $query = $result['query'];
  822. $this->assertEqual('update', $query->type());
  823. $this->assertEqual(['published' => false], $query->data());
  824. $this->assertEqual(['expires' => ['>=' => '2010-05-13']], $query->conditions());
  825. }
  826. public function testMultiRecordDelete() {
  827. $result = MockPost::remove(['published' => false]);
  828. $query = $result['query'];
  829. $this->assertEqual('delete', $query->type());
  830. $this->assertEqual(['published' => false], $query->conditions());
  831. $keys = array_keys(array_filter($query->export(MockPost::connection())));
  832. $this->assertEqual(['conditions', 'model', 'type', 'source', 'alias'], $keys);
  833. }
  834. public function testFindFirst() {
  835. MockTag::config(['meta' => ['key' => 'id']]);
  836. $tag = MockTag::find('first', ['conditions' => ['id' => 2]]);
  837. $tag2 = MockTag::find(2);
  838. $tag3 = MockTag::first(2);
  839. $expected = $tag['query']->export(MockTag::connection());
  840. $this->assertEqual($expected, $tag2['query']->export(MockTag::connection()));
  841. $this->assertEqual($expected, $tag3['query']->export(MockTag::connection()));
  842. $tag = MockTag::find('first', [
  843. 'conditions' => ['id' => 2],
  844. 'return' => 'array'
  845. ]);
  846. $expected['return'] = 'array';
  847. $this->assertTrue($tag instanceof Query);
  848. $this->assertEqual($expected, $tag->export(MockTag::connection()));
  849. }
  850. /**
  851. * Tests that varying `count` syntaxes all produce the same query operation (i.e.
  852. * `Model::count(...)`, `Model::find('count', ...)` etc).
  853. */
  854. public function testCountSyntax() {
  855. $base = MockPost::count(['email' => 'foo@example.com']);
  856. $query = $base['query'];
  857. $this->assertEqual('read', $query->type());
  858. $this->assertEqual('count', $query->calculate());
  859. $this->assertEqual(['email' => 'foo@example.com'], $query->conditions());
  860. $result = MockPost::find('count', ['conditions' => [
  861. 'email' => 'foo@example.com'
  862. ]]);
  863. $this->assertEqual($query, $result['query']);
  864. $result = MockPost::count(['conditions' => ['email' => 'foo@example.com']]);
  865. $this->assertEqual($query, $result['query']);
  866. }
  867. /**
  868. * Test that magic count condition-less syntax works.
  869. *
  870. * @link https://github.com/UnionOfRAD/lithium/issues/1282
  871. */
  872. public function testCountSyntaxWithoutConditions() {
  873. $result = MockPost::count([
  874. 'group' => 'name'
  875. ]);
  876. $this->assertEqual('name', $result['query']->group());
  877. $this->assertIdentical([], $result['query']->conditions());
  878. }
  879. public function testSettingNestedObjectDefaults() {
  880. $schema = MockPost::schema()->append([
  881. 'nested.value' => ['type' => 'string', 'default' => 'foo']
  882. ]);
  883. $this->assertEqual('foo', MockPost::create()->nested['value']);
  884. $data = ['nested' => ['value' => 'bar']];
  885. $this->assertEqual('bar', MockPost::create($data)->nested['value']);
  886. }
  887. /**
  888. * Tests that objects can be passed as keys to `Model::find()` and be properly translated to
  889. * query conditions.
  890. */
  891. public function testFindByObjectKey() {
  892. $key = (object) ['foo' => 'bar'];
  893. $result = MockPost::find($key);
  894. $this->assertEqual(['id' => $key], $result['query']->conditions());
  895. }
  896. public function testLiveConfiguration() {
  897. MockBadConnection::config(['meta' => ['connection' => false]]);
  898. $result = MockBadConnection::meta('connection');
  899. $this->assertFalse($result);
  900. }
  901. public function testLazyLoad() {
  902. $object = MockPost::object();
  903. $object->belongsTo = ['Unexisting'];
  904. MockPost::config();
  905. MockPost::initialize('lithium\tests\mocks\data\MockPost');
  906. $exception = 'Related model class \'lithium\tests\mocks\data\Unexisting\' not found.';
  907. $this->assertException($exception, function() {
  908. MockPost::relations('Unexisting');
  909. });
  910. }
  911. public function testLazyMetadataInit() {
  912. MockPost::config([
  913. 'schema' => new Schema([
  914. 'fields' => [
  915. 'id' => ['type' => 'integer'],
  916. 'name' => ['type' => 'string'],
  917. 'label' => ['type' => 'string']
  918. ]
  919. ])
  920. ]);
  921. $this->assertIdentical('mock_posts', MockPost::meta('source'));
  922. $this->assertIdentical('name', MockPost::meta('title'));
  923. $this->assertEmpty(MockPost::meta('unexisting'));
  924. $config = [
  925. 'schema' => new Schema([
  926. 'fields' => [
  927. 'id' => ['type' => 'integer'],
  928. 'name' => ['type' => 'string'],
  929. 'label' => ['type' => 'string']
  930. ]
  931. ]),
  932. 'initializers' => [
  933. 'source' => function($self) {
  934. return Inflector::tableize($self::meta('name'));
  935. },
  936. 'name' => function($self) {
  937. return Inflector::singularize('CoolPosts');
  938. },
  939. 'title' => function($self) {
  940. static $i = 1;
  941. return 'label' . $i++;
  942. }
  943. ]
  944. ];
  945. MockPost::reset();
  946. MockPost::config($config);
  947. $this->assertIdentical('cool_posts', MockPost::meta('source'));
  948. $this->assertIdentical('label1', MockPost::meta('title'));
  949. $this->assertNotIdentical('label2', MockPost::meta('title'));
  950. $this->assertIdentical('label1', MockPost::meta('title'));
  951. $meta = MockPost::meta();
  952. $this->assertIdentical('label1', $meta['title']);
  953. $this->assertIdentical('CoolPost', MockPost::meta('name'));
  954. MockPost::reset();
  955. unset($config['initializers']['title']);
  956. $config['initializers']['source'] = function($self) {
  957. return Inflector::underscore($self::meta('name'));
  958. };
  959. MockPost::config($config);
  960. $this->assertIdentical('cool_post', MockPost::meta('source'));
  961. $this->assertIdentical('name', MockPost::meta('title'));
  962. $this->assertIdentical('CoolPost', MockPost::meta('name'));
  963. MockPost::reset();
  964. MockPost::config($config);
  965. $expected = [
  966. 'class' => 'lithium\\tests\\mocks\\data\\MockPost',
  967. 'connection' => false,
  968. 'key' => 'id',
  969. 'name' => 'CoolPost',
  970. 'title' => 'name',
  971. 'source' => 'cool_post'
  972. ];
  973. $this->assertEqual($expected, MockPost::meta());
  974. }
  975. public function testHasFinder() {
  976. $this->assertTrue(MockPost::hasFinder('all'));
  977. $this->assertTrue(MockPost::hasFinder('count'));
  978. $this->assertTrue(MockPost::hasFinder('findByFoo'));
  979. $this->assertTrue(MockPost::hasFinder('findFooByBar'));
  980. $this->assertTrue(MockPost::hasFinder('fooByBar'));
  981. $this->assertTrue(MockPost::hasFinder('FooByBar'));
  982. $this->assertFalse(MockPost::hasFinder('fooBarBaz'));
  983. }
  984. public function testFieldName() {
  985. MockPost::bind('hasMany', 'MockTag');
  986. $relation = MockPost::relations('MockComment');
  987. $this->assertEqual('mock_comments', $relation->fieldName());
  988. $relation = MockPost::relations('MockTag');
  989. $this->assertEqual('mock_tags', $relation->fieldName());
  990. $relation = MockComment::relations('MockPost');
  991. $this->assertEqual('mock_post', $relation->fieldName());
  992. }
  993. public function testRelationFromFieldName() {
  994. MockPost::bind('hasMany', 'MockTag');
  995. $this->assertEqual('MockComment', MockPost::relations('mock_comments')->name());
  996. $this->assertEqual('MockTag', MockPost::relations('mock_tags')->name());
  997. $this->assertEqual('MockPost', MockComment::relations('mock_post')->name());
  998. $this->assertNull(MockPost::relations('undefined'));
  999. }
  1000. public function testValidateWithRequiredFalse(){
  1001. $post = MockPost::create([
  1002. 'title' => 'post title',
  1003. ]);
  1004. $post->validates(['rules' => [
  1005. 'title' => 'A custom message here for empty titles.',
  1006. 'email' => [
  1007. ['notEmpty', 'message' => 'email is empty.', 'required' => false]
  1008. ]
  1009. ]]);
  1010. $this->assertEmpty($post->errors());
  1011. }
  1012. public function testValidateWithRequiredTrue(){
  1013. $post = MockPost::create([
  1014. 'title' => 'post title',
  1015. ]);
  1016. $post->sync(1);
  1017. $post->validates(['rules' => [
  1018. 'title' => 'A custom message here for empty titles.',
  1019. 'email' => [
  1020. ['notEmpty', 'message' => 'email is empty.', 'required' => true]
  1021. ]
  1022. ]]);
  1023. $this->assertNotEmpty($post->errors());
  1024. }
  1025. public function testValidateWithRequiredNull(){
  1026. $validates = [
  1027. 'title' => 'A custom message here for empty titles.',
  1028. 'email' => [
  1029. ['notEmpty', 'message' => 'email is empty.', 'required' => null]
  1030. ]
  1031. ];
  1032. $post = MockPost::create([
  1033. 'title' => 'post title',
  1034. ]);
  1035. $post->validates(['rules' => $validates]);
  1036. $this->assertNotEmpty($post->errors());
  1037. $post->sync(1);
  1038. $post->validates(['rules' => $validates]);
  1039. $this->assertEmpty($post->errors());
  1040. }
  1041. /* Deprecated / BC */
  1042. /**
  1043. * @deprecated
  1044. */
  1045. public function testRespondsToParentCall() {
  1046. error_reporting(($backup = error_reporting()) & ~E_USER_DEPRECATED);
  1047. $this->assertTrue(MockPost::respondsTo('invokeMethod'));
  1048. $this->assertFalse(MockPost::respondsTo('fooBarBaz'));
  1049. error_reporting($backup);
  1050. }
  1051. /**
  1052. * @deprecated
  1053. */
  1054. public function testRespondsToInstanceMethod() {
  1055. error_reporting(($backup = error_reporting()) & ~E_USER_DEPRECATED);
  1056. $this->assertFalse(MockPost::respondsTo('foo_Bar_Baz'));
  1057. MockPost::instanceMethods([
  1058. 'foo_Bar_Baz' => function($entity) {}
  1059. ]);
  1060. $this->assertTrue(MockPost::respondsTo('foo_Bar_Baz'));
  1061. error_reporting($backup);
  1062. }
  1063. }
  1064. ?>