PageRenderTime 52ms CodeModel.GetById 21ms RepoModel.GetById 0ms app.codeStats 0ms

/tests/cases/data/source/MongoDbTest.php

http://github.com/UnionOfRAD/lithium
PHP | 1138 lines | 916 code | 198 blank | 24 comment | 0 complexity | aff90bae0492a8b0e57f6c195fb6be59 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\source;
  10. use lithium\data\source\mongo_db\Schema;
  11. use lithium\data\source\MongoDb;
  12. use MongoId;
  13. use MongoCode;
  14. use MongoDate;
  15. use MongoRegex;
  16. use lithium\data\Connections;
  17. use lithium\data\model\Query;
  18. use lithium\data\entity\Document;
  19. use lithium\tests\mocks\data\MockPost;
  20. use lithium\tests\mocks\data\MockComment;
  21. use lithium\tests\mocks\core\MockCallable;
  22. use lithium\tests\mocks\data\source\MockMongoSource;
  23. use lithium\tests\mocks\data\source\MockMongoConnection;
  24. use lithium\tests\mocks\data\source\mongo_db\MockResultResource;
  25. use lithium\tests\mocks\data\source\MockMongoPost;
  26. class MongoDbTest extends \lithium\test\Unit {
  27. protected $_db = null;
  28. protected $_model = 'lithium\tests\mocks\data\source\MockMongoPost';
  29. protected $_query = null;
  30. protected $_testConfig = [
  31. 'adapter' => false,
  32. 'database' => 'test',
  33. 'host' => 'localhost',
  34. 'port' => '27017',
  35. 'autoConnect' => false
  36. ];
  37. protected $_schema = [
  38. '_id' => 'id',
  39. 'guid' => 'id',
  40. 'title' => 'string',
  41. 'tags' => ['type' => 'string', 'array' => true],
  42. 'comments' => 'MongoId',
  43. 'authors' => ['type' => 'MongoId', 'array' => true],
  44. 'created' => 'MongoDate',
  45. 'modified' => 'datetime',
  46. 'voters' => ['type' => 'id', 'array' => true],
  47. 'rank_count' => ['type' => 'integer', 'default' => 0],
  48. 'rank' => ['type' => 'float', 'default' => 0.0],
  49. 'notifications.foo' => 'boolean',
  50. 'notifications.bar' => 'boolean',
  51. 'notifications.baz' => 'boolean'
  52. ];
  53. protected $_configs = [];
  54. public function skip() {
  55. $this->skipIf(!MongoDb::enabled(), 'The `MongoDb` class is not enabled.');
  56. }
  57. public function setUp() {
  58. $this->_db = new MongoDb($this->_testConfig);
  59. $this->_db->server = new MockMongoConnection();
  60. $this->_db->connection = new MockMongoConnection();
  61. Connections::add('mockconn', ['object' => $this->_db]);
  62. MockMongoPost::config(['meta' => ['key' => '_id', 'connection' => 'mockconn']]);
  63. $type = 'create';
  64. $this->_query = new Query(compact('model', 'type') + [
  65. 'entity' => new Document(['model' => $this->_model])
  66. ]);
  67. }
  68. public function tearDown() {
  69. Connections::remove('mockconn');
  70. MockPost::reset();
  71. MockComment::reset();
  72. MockMongoPost::reset();
  73. }
  74. public function testBadConnection() {
  75. $db = new MongoDb(['host' => null, 'autoConnect' => false]);
  76. $this->assertException('Could not connect to the database.', function() use ($db) {
  77. $db->connect();
  78. });
  79. $this->assertTrue($db->disconnect());
  80. }
  81. public function testGoodConnectionBadDatabase() {
  82. $db = new MongoDb(['database' => null, 'autoConnnect' => false]);
  83. $this->assertException('Could not connect to the database.', function() use ($db) {
  84. $db->connect();
  85. });
  86. }
  87. public function testSources() {
  88. $this->_db->connection->results = [[]];
  89. $this->assertEqual([], $this->_db->sources());
  90. }
  91. public function testDescribe() {
  92. $result = $this->_db->describe('test')->fields();
  93. $expected = ['_id' => ['type' => 'id']];
  94. $this->assertEqual($expected, $result);
  95. }
  96. public function testName() {
  97. $result = $this->_db->name('{(\'Li\':"βˆ†")}');
  98. $expected = '{(\'Li\':"βˆ†")}';
  99. $this->assertEqual($expected, $result);
  100. }
  101. public function testSchema() {
  102. $result = $this->_db->schema($this->_query);
  103. $expected = [];
  104. $this->assertEqual($expected, $result);
  105. }
  106. public function testCreateSuccess() {
  107. array_push($this->_db->connection->results, true);
  108. $this->_query->data(['title' => 'Test Post']);
  109. $this->assertTrue($this->_db->create($this->_query));
  110. $query = array_pop($this->_db->connection->queries);
  111. $this->assertEmpty($this->_db->connection->queries);
  112. $this->assertEqual('insert', $query['type']);
  113. $this->assertEqual('posts', $query['collection']);
  114. $this->assertEqual(['title', '_id'], array_keys($query['data']));
  115. $this->assertInstanceOf('MongoId', $query['data']['_id']);
  116. }
  117. public function testConditions() {
  118. $result = $this->_db->conditions(null, null);
  119. $this->assertEqual([], $result);
  120. $function = 'function() { return this.x < y;}';
  121. $conditions = new MongoCode($function);
  122. $result = $this->_db->conditions($conditions, null);
  123. $this->assertInternalType('array', $result);
  124. $this->assertTrue(isset($result['$where']));
  125. $this->assertEqual($conditions, $result['$where']);
  126. $conditions = $function;
  127. $result = $this->_db->conditions($conditions, null);
  128. $this->assertInternalType('array', $result);
  129. $this->assertTrue(isset($result['$where']));
  130. $this->assertEqual($conditions, $result['$where']);
  131. $conditions = ['key' => 'value', 'anotherkey' => 'some other value'];
  132. $result = $this->_db->conditions($conditions, null);
  133. $this->assertInternalType('array', $result);
  134. $this->assertEqual($conditions, $result);
  135. $conditions = ['key' => ['one', 'two', 'three']];
  136. $result = $this->_db->conditions($conditions, null);
  137. $this->assertInternalType('array', $result);
  138. $this->assertTrue(isset($result['key']));
  139. $this->assertTrue(isset($result['key']['$in']));
  140. $this->assertEqual($conditions['key'], $result['key']['$in']);
  141. $conditions = ['$or' => [
  142. ['key' => 'value'],
  143. ['other key' => 'another value']
  144. ]];
  145. $result = $this->_db->conditions($conditions, null);
  146. $this->assertTrue(isset($result['$or']));
  147. $this->assertEqual($conditions['$or'][0]['key'], $result['$or'][0]['key']);
  148. $conditions = ['$and' => [
  149. ['key' => 'value'],
  150. ['other key' => 'another value']
  151. ]];
  152. $result = $this->_db->conditions($conditions, null);
  153. $this->assertTrue(isset($result['$and']));
  154. $this->assertEqual($conditions['$and'][0]['key'], $result['$and'][0]['key']);
  155. $conditions = ['$nor' => [
  156. ['key' => 'value'],
  157. ['other key' => 'another value']
  158. ]];
  159. $result = $this->_db->conditions($conditions, null);
  160. $this->assertTrue(isset($result['$nor']));
  161. $this->assertEqual($conditions['$nor'][0]['key'], $result['$nor'][0]['key']);
  162. $conditions = ['key' => ['or' => [1, 2]]];
  163. $result = $this->_db->conditions($conditions, null);
  164. $this->assertEqual(['key' => ['$or' => [1, 2]]], $result);
  165. }
  166. public function testMongoConditionalOperators() {
  167. $conditions = ['key' => ['<' => 10]];
  168. $expected = ['key' => ['$lt' => 10]];
  169. $result = $this->_db->conditions($conditions, null);
  170. $this->assertEqual($expected, $result);
  171. $conditions = ['key' => ['<=' => 10]];
  172. $expected = ['key' => ['$lte' => 10]];
  173. $result = $this->_db->conditions($conditions, null);
  174. $this->assertEqual($expected, $result);
  175. $conditions = ['key' => ['>' => 10]];
  176. $expected = ['key' => ['$gt' => 10]];
  177. $result = $this->_db->conditions($conditions, null);
  178. $this->assertEqual($expected, $result);
  179. $conditions = ['key' => ['>=' => 10]];
  180. $expected = ['key' => ['$gte' => 10]];
  181. $result = $this->_db->conditions($conditions, null);
  182. $this->assertEqual($expected, $result);
  183. $conditions = ['key' => ['!=' => 10]];
  184. $expected = ['key' => ['$ne' => 10]];
  185. $result = $this->_db->conditions($conditions, null);
  186. $this->assertEqual($expected, $result);
  187. $conditions = ['key' => ['<>' => 10]];
  188. $expected = ['key' => ['$ne' => 10]];
  189. $result = $this->_db->conditions($conditions, null);
  190. $this->assertEqual($expected, $result);
  191. $conditions = ['key' => ['!=' => [10, 20, 30]]];
  192. $expected = ['key' => ['$nin' => [10, 20, 30]]];
  193. $result = $this->_db->conditions($conditions, null);
  194. $this->assertEqual($expected, $result);
  195. $conditions = ['key' => ['<>' => [10, 20, 30]]];
  196. $expected = ['key' => ['$nin' => [10, 20, 30]]];
  197. $result = $this->_db->conditions($conditions, null);
  198. $this->assertEqual($expected, $result);
  199. $conditions = ['key' => ['like' => '/regex/i']];
  200. $result = $this->_db->conditions($conditions, null);
  201. $expected = ['key' => new MongoRegex('/regex/i')];
  202. $this->assertEqual($expected, $result);
  203. }
  204. public function testConditionsWithSchema() {
  205. $schema = new Schema(['fields' => [
  206. '_id' => ['type' => 'id'],
  207. 'tags' => ['type' => 'string', 'array' => true],
  208. 'users' => ['type' => 'id', 'array' => true]
  209. ]]);
  210. $query = new Query(['schema' => $schema, 'type' => 'read']);
  211. $id = new MongoId();
  212. $userId = new MongoId();
  213. $conditions = [
  214. '_id' => (string) $id,
  215. 'tags' => 'yellow',
  216. 'users' => (string) $userId
  217. ];
  218. $result = $this->_db->conditions($conditions, $query);
  219. $expected = [
  220. '_id' => $id,
  221. 'tags' => 'yellow',
  222. 'users' => $userId
  223. ];
  224. $this->assertEqual($expected, $result);
  225. }
  226. public function testReadNoConditions() {
  227. $this->_db->connect();
  228. $connection = $this->_db->connection;
  229. $this->_db->connection = new MockMongoSource();
  230. $this->_db->connection->resultSets = [['ok' => true]];
  231. $data = ['title' => 'Test Post'];
  232. $options = ['w' => 1, 'wTimeoutMS' => 10000, 'fsync' => false];
  233. $this->_query->data($data);
  234. $this->assertTrue($this->_db->create($this->_query));
  235. $this->assertEqual(compact('data', 'options'), end($this->_db->connection->queries));
  236. $this->_db->connection->resultSets = [[['_id' => new MongoId()] + $data]];
  237. $result = $this->_db->read($this->_query);
  238. $this->assertInstanceOf('lithium\data\collection\DocumentSet', $result);
  239. $this->assertEqual(1, $result->count());
  240. $this->assertEqual('Test Post', $result->first()->title);
  241. $this->_db->connection = $connection;
  242. }
  243. public function testReadWithConditions() {
  244. $this->_db->connect();
  245. $connection = $this->_db->connection;
  246. $this->_db->connection = new MockMongoSource();
  247. $this->_db->connection->resultSets = [['ok' => true]];
  248. $data = ['title' => 'Test Post'];
  249. $options = ['w' => 1, 'wTimeoutMS' => 10000, 'fsync' => false];
  250. $this->_query->data($data);
  251. $this->assertTrue($this->_db->create($this->_query));
  252. $this->_query->data(null);
  253. $this->_db->connection->resultSets = [[]];
  254. $this->_query->conditions(['title' => 'Nonexistent Post']);
  255. $result = $this->_db->read($this->_query);
  256. $this->assertNotEmpty($result);
  257. $this->assertEqual(0, $result->count());
  258. $this->_db->connection->resultSets = [[$data]];
  259. $this->_query->conditions($data);
  260. $result = $this->_db->read($this->_query);
  261. $this->assertNotEmpty($result);
  262. $this->assertEqual(1, $result->count());
  263. $this->_db->connection = $connection;
  264. }
  265. public function testUpdate() {
  266. $model = $this->_model;
  267. $data = ['title' => 'Test Post'];
  268. $this->_query->model($model);
  269. $this->_query->data($data);
  270. $this->_db->connection->results = [true];
  271. $this->_db->create($this->_query);
  272. $result = array_pop($this->_db->connection->queries);
  273. $data['_id'] = $result['data']['_id'];
  274. $expected = compact('data') + [
  275. 'collection' => 'posts',
  276. 'type' => 'insert',
  277. 'options' => ['w' => 1, 'wTimeoutMS' => 10000, 'fsync' => false]
  278. ];
  279. $this->assertEqual($expected, $result);
  280. $this->_db->connection->results = [
  281. new MockResultResource(['data' => [$data]]),
  282. new MockResultResource(['data' => [$data]])
  283. ];
  284. $this->_db->connection->queries = [];
  285. $result = $this->_db->read(new Query(compact('model')));
  286. $original = $result->first()->to('array');
  287. $this->assertEqual(['title', '_id'], array_keys($original));
  288. $this->assertEqual('Test Post', $original['title']);
  289. $this->assertPattern('/^[0-9a-f]{24}$/', $original['_id']);
  290. $this->_db->connection->results = [true];
  291. $this->_db->connection->queries = [];
  292. $update = ['title' => 'New Post Title'];
  293. $this->_query = new Query(compact('model') + [
  294. 'data' => $update,
  295. 'conditions' => ['_id' => $original['_id']]
  296. ]);
  297. $this->assertTrue($this->_db->update($this->_query));
  298. $result = array_pop($this->_db->connection->queries);
  299. $expected = [
  300. 'type' => 'update',
  301. 'collection' => 'posts',
  302. 'conditions' => ['_id' => '4f188fb17675ab167900010e'],
  303. 'update' => ['$set' => ['title' => 'New Post Title']],
  304. 'options' => [
  305. 'upsert' => false, 'multiple' => true, 'w' => 1, 'wTimeoutMS' => 10000, 'fsync' => false
  306. ]
  307. ];
  308. array_push($this->_db->connection->results, new MockResultResource([
  309. 'data' => [$update + $original]
  310. ]));
  311. $this->_db->connection->queries = [];
  312. $result = $this->_db->read(new Query(compact('model') + [
  313. 'conditions' => ['_id' => $original['_id']]
  314. ]));
  315. $this->assertEqual(1, $result->count());
  316. $updated = $result->first();
  317. $updated = $updated ? $updated->to('array') : [];
  318. $this->assertEqual($original['_id'], $updated['_id']);
  319. $this->assertEqual('New Post Title', $updated['title']);
  320. $expected = [
  321. 'type' => 'find',
  322. 'collection' => 'posts',
  323. 'fields' => [],
  324. 'conditions' => ['_id' => $original['_id']]
  325. ];
  326. $this->assertEqual($expected, array_pop($this->_db->connection->queries));
  327. }
  328. public function testDelete() {
  329. $model = $this->_model;
  330. $data = ['title' => 'Delete Me'];
  331. array_push($this->_db->connection->results, true);
  332. $this->_query->data($data);
  333. $this->_db->create($this->_query);
  334. array_push($this->_db->connection->results, new MockResultResource([
  335. 'data' => []
  336. ]));
  337. $this->assertFalse($this->_db->read($this->_query)->first());
  338. $result = array_pop($this->_db->connection->queries);
  339. $conditions = ['_id' => $this->_query->entity()->_id];
  340. $this->assertEqual($conditions, $result['conditions']);
  341. $this->assertTrue($this->_query->entity()->exists());
  342. $id = new MongoId();
  343. $this->_query = new Query(compact('model') + [
  344. 'entity' => new Document(compact('model') + ['data' => ['_id' => $id]])
  345. ]);
  346. array_push($this->_db->connection->results, true);
  347. $this->_query->conditions($conditions);
  348. $this->assertTrue($this->_db->delete($this->_query));
  349. $this->assertFalse($this->_query->entity()->exists());
  350. $expected = compact('conditions') + [
  351. 'type' => 'remove',
  352. 'collection' => 'posts',
  353. 'options' => ['justOne' => false, 'w' => 1, 'wTimeoutMS' => 10000, 'fsync' => false]
  354. ];
  355. $this->assertEqual($expected, array_pop($this->_db->connection->queries));
  356. }
  357. public function testCreate() {
  358. $data = ['title' => 'New Item'];
  359. $result = MockMongoPost::create($data, ['defaults' => false]);
  360. $this->assertInstanceOf('lithium\data\entity\Document', $result);
  361. $expected = $data;
  362. $result = $result->to('array');
  363. $this->assertEqual($expected, $result);
  364. }
  365. public function testCalculation() {
  366. $this->_db->connection->results = [new MockResultResource(['data' => [5]])];
  367. $this->assertIdentical(5, $this->_db->calculation('count', $this->_query));
  368. }
  369. public function testEnabled() {
  370. $this->assertTrue(MongoDb::enabled());
  371. $this->assertTrue(MongoDb::enabled('arrays'));
  372. $this->assertTrue(MongoDb::enabled('booleans'));
  373. $this->assertTrue(MongoDb::enabled('relationships'));
  374. }
  375. public function testArbitraryMethodCalls() {
  376. $this->assertInternalType('array', $this->_db->listDBs());
  377. }
  378. public function testDocumentSorting() {
  379. MockMongoPost::config(['meta' => ['source' => 'ordered_docs', 'locked' => false]]);
  380. $first = ['title' => 'First document', 'position' => 1];
  381. $second = ['title' => 'Second document', 'position' => 2];
  382. $third = ['title' => 'Third document', 'position' => 3];
  383. MockMongoPost::create($third)->save();
  384. MockMongoPost::create($first)->save();
  385. MockMongoPost::create($second)->save();
  386. $result = $this->_db->connection->queries;
  387. $createOpts = [
  388. 'validate' => true,
  389. 'events' => 'create',
  390. 'whitelist' => null,
  391. 'callbacks' => true,
  392. 'locked' => false,
  393. 'w' => 1,
  394. 'wTimeoutMS' => 10000,
  395. 'fsync' => false
  396. ];
  397. $baseInsert = [
  398. 'type' => 'insert',
  399. 'collection' => 'ordered_docs',
  400. 'options' => $createOpts
  401. ];
  402. $expected = [
  403. $baseInsert + ['data' => ['_id' => $result[0]['data']['_id']] + $third],
  404. $baseInsert + ['data' => ['_id' => $result[1]['data']['_id']] + $first],
  405. $baseInsert + ['data' => ['_id' => $result[2]['data']['_id']] + $second]
  406. ];
  407. $this->assertEqual($expected, $result);
  408. array_push($this->_db->connection->results, new MockResultResource([
  409. 'data' => [$first, $second, $third]
  410. ]));
  411. $this->_db->connection->queries = [];
  412. $documents = MockMongoPost::all(['order' => 'position']);
  413. $this->assertEqual($first['title'], $documents[0]->title);
  414. $this->assertEqual($second['title'], $documents[1]->title);
  415. $this->assertEqual($third['title'], $documents[2]->title);
  416. $expected = [
  417. 'type' => 'find',
  418. 'collection' => 'ordered_docs',
  419. 'conditions' => [],
  420. 'fields' => []
  421. ];
  422. $this->assertEqual($expected, array_pop($this->_db->connection->queries));
  423. $result = $documents->result()->resource()->query['sort'];
  424. $this->assertEqual(['position' => 1], $result);
  425. array_push($this->_db->connection->results, new MockResultResource([
  426. 'data' => [$first, $second, $third]
  427. ]));
  428. $documents = MockMongoPost::all(['order' => ['position' => 'asc']]);
  429. $this->assertEqual($first['title'], $documents[0]->title);
  430. $this->assertEqual($second['title'], $documents[1]->title);
  431. $this->assertEqual($third['title'], $documents[2]->title);
  432. $this->assertEqual($expected, array_pop($this->_db->connection->queries));
  433. $result = $documents->result()->resource()->query['sort'];
  434. $this->assertEqual(['position' => 1], $result);
  435. array_push($this->_db->connection->results, new MockResultResource([
  436. 'data' => [$third, $second, $first]
  437. ]));
  438. $documents = MockMongoPost::all(['order' => ['position' => 'desc']]);
  439. $this->assertEqual($third['title'], $documents[0]->title);
  440. $this->assertEqual($second['title'], $documents[1]->title);
  441. $this->assertEqual($first['title'], $documents[2]->title);
  442. $this->assertEqual($expected, array_pop($this->_db->connection->queries));
  443. $result = $documents->result()->resource()->query['sort'];
  444. $this->assertEqual(['position' => -1], $result);
  445. }
  446. public function testMongoIdPreservation() {
  447. $post = MockMongoPost::create(['_id' => new MongoId(), 'title' => 'A post']);
  448. $post->save();
  449. $result = array_pop($this->_db->connection->queries);
  450. $data = $result['data'];
  451. $this->assertEqual('A post', $data['title']);
  452. $this->assertInstanceOf('MongoId', $data['_id']);
  453. $post->sync();
  454. $post->title = 'An updated post';
  455. $post->save();
  456. $result = array_pop($this->_db->connection->queries);
  457. $this->assertEqual(['_id' => $post->_id], $result['conditions']);
  458. $this->assertEqual(['$set' => ['title' => 'An updated post']], $result['update']);
  459. }
  460. public function testRelationshipGeneration() {
  461. $from = 'lithium\tests\mocks\data\MockComment';
  462. $to = 'lithium\tests\mocks\data\MockPost';
  463. $from::config([
  464. 'meta' => ['connection' => 'mockconn', 'key' => '_id'],
  465. 'schema' => new Schema(['fields' => ['comment_id']])
  466. ]);
  467. $to::config(['meta' => ['key' => '_id', 'connection' => 'mockconn']]);
  468. $result = $this->_db->relationship($from, 'belongsTo', 'MockPost');
  469. $expected = compact('to', 'from') + [
  470. 'name' => 'MockPost',
  471. 'type' => 'belongsTo',
  472. 'key' => [],
  473. 'link' => 'contained',
  474. 'fields' => true,
  475. 'fieldName' => 'mockPost',
  476. 'constraints' => [],
  477. 'init' => true
  478. ];
  479. $this->assertEqual($expected, $result->data());
  480. $from::config(['meta' => ['name' => 'Groups'], 'schema' => new Schema([
  481. 'fields' => ['_id' => 'id', 'users' => ['id', 'array' => true]]
  482. ])]);
  483. $to::config(['meta' => ['name' => 'Users'], 'schema' => new Schema([
  484. 'fields' => ['_id' => 'id', 'group' => 'id']
  485. ])]);
  486. $result = $this->_db->relationship($from, 'hasMany', 'Users', compact('to'));
  487. $this->assertEqual('keylist', $result->link());
  488. $this->assertEqual(['users' => '_id'], $result->key());
  489. $to::config(['meta' => ['name' => 'Permissions']]);
  490. $result = $this->_db->relationship($from, 'hasMany', 'Permissions', compact('to'));
  491. $this->assertEqual('key', $result->link());
  492. $this->assertEqual(['_id' => 'group'], $result->key());
  493. }
  494. public function testRelationshipGenerationWithPluralNamingConvention() {
  495. $from = 'lithium\tests\mocks\data\MockComments';
  496. $to = 'lithium\tests\mocks\data\MockPosts';
  497. $from::config([
  498. 'meta' => ['connection' => 'mockconn', 'key' => '_id'],
  499. 'schema' => new Schema(['fields' => ['mockPost' => 'id']])
  500. ]);
  501. $to::config([
  502. 'meta' => ['connection' => 'mockconn', 'key' => '_id'],
  503. 'schema' => new Schema(['fields' => ['mockComments' => 'id']])
  504. ]);
  505. $result = $this->_db->relationship($from, 'belongsTo', 'MockPosts');
  506. $expected = compact('to', 'from') + [
  507. 'name' => 'MockPosts',
  508. 'type' => 'belongsTo',
  509. 'key' => [
  510. 'mockPost' => '_id'
  511. ],
  512. 'link' => 'key',
  513. 'fields' => true,
  514. 'fieldName' => 'mockPost',
  515. 'constraints' => [],
  516. 'init' => true
  517. ];
  518. $this->assertEqual($expected, $result->data());
  519. }
  520. public function testCreateNoConnectionException() {
  521. $db = new MongoDb(['host' => '__invalid__', 'autoConnect' => false]);
  522. $this->assertException('Could not connect to the database.', function() use ($db) {
  523. $db->create(null);
  524. });
  525. }
  526. public function testReadNoConnectionException() {
  527. $db = new MongoDb(['host' => '__invalid__', 'autoConnect' => false]);
  528. $this->assertException('Could not connect to the database.', function() use ($db) {
  529. $db->read(null);
  530. });
  531. }
  532. public function testUpdateNoConnectionException() {
  533. $db = new MongoDb(['host' => '__invalid__', 'autoConnect' => false]);
  534. $this->assertException('Could not connect to the database.', function() use ($db) {
  535. $db->update(null);
  536. });
  537. }
  538. public function testDeleteNoConnectionException() {
  539. $db = new MongoDb(['host' => '__invalid__', 'autoConnect' => false]);
  540. $this->assertException('Could not connect to the database.', function() use ($db) {
  541. $db->delete(null);
  542. });
  543. }
  544. public function testSourcesNoConnectionException() {
  545. $db = new MongoDb(['host' => null, 'autoConnect' => false]);
  546. $this->assertException('Could not connect to the database.', function() use ($db) {
  547. $db->sources(null);
  548. });
  549. }
  550. public function testAtomicUpdate() {
  551. MockMongoPost::config(['meta' => ['source' => 'posts']]);
  552. $data = ['initial' => 'one', 'values' => 'two'];
  553. $this->_db->connection = new MockMongoConnection();
  554. $this->_db->connection->results = [true, true];
  555. $document = MockMongoPost::create($data);
  556. $this->assertTrue($document->save());
  557. $result = array_shift($this->_db->connection->queries);
  558. $expected = [
  559. 'type' => 'insert',
  560. 'collection' => 'posts',
  561. 'data' => ['initial' => 'one', 'values' => 'two', '_id' => $document->_id],
  562. 'options' => [
  563. 'validate' => true, 'events' => 'create', 'whitelist' => null, 'callbacks' => true,
  564. 'locked' => false, 'w' => 1, 'wTimeoutMS' => 10000, 'fsync' => false
  565. ]
  566. ];
  567. $this->assertEqual($expected, $result);
  568. $duplicate = MockMongoPost::create(['_id' => $document->_id], ['exists' => true]);
  569. $duplicate->values = 'new';
  570. $this->assertTrue($duplicate->save());
  571. $result = array_shift($this->_db->connection->queries);
  572. $expected = [
  573. 'type' => 'update',
  574. 'collection' => 'posts',
  575. 'conditions' => ['_id' => $document->_id],
  576. 'update' => ['$set' => ['values' => 'new']],
  577. 'options' => [
  578. 'validate' => true, 'events' => 'update', 'whitelist' => null,
  579. 'callbacks' => true, 'locked' => false, 'upsert' => false, 'multiple' => true,
  580. 'w' => 1, 'wTimeoutMS' => 10000, 'fsync' => false
  581. ]
  582. ];
  583. $this->assertEqual($expected, $result);
  584. array_push($this->_db->connection->results, new MockResultResource(['data' => [
  585. ['_id' => $duplicate->_id, 'initial' => 'one', 'values' => 'new']
  586. ]]));
  587. $document = MockMongoPost::find($duplicate->_id);
  588. $expected = ['_id' => (string) $duplicate->_id, 'initial' => 'one', 'values' => 'new'];
  589. $this->assertEqual($expected, $document->data());
  590. $result = array_shift($this->_db->connection->queries);
  591. $expected = [
  592. 'type' => 'find', 'collection' => 'posts', 'fields' => [], 'conditions' => [
  593. '_id' => $duplicate->_id
  594. ]
  595. ];
  596. $this->assertEqual($expected, $result);
  597. }
  598. /**
  599. * Tests that the MongoDB adapter will not attempt to overwrite the _id field on document
  600. * update.
  601. */
  602. public function testPreserveId() {
  603. $document = MockMongoPost::create(['_id' => 'custom'], ['exists' => true]);
  604. array_push($this->_db->connection->results, true);
  605. $this->assertTrue($document->save(['_id' => 'custom2', 'foo' => 'bar']));
  606. $result = array_shift($this->_db->connection->queries);
  607. $expected = ['$set' => ['foo' => 'bar']];
  608. $this->assertEqual($expected, $result['update']);
  609. }
  610. public function testCastingConditionsValues() {
  611. $query = new Query(['schema' => new Schema(['fields' => $this->_schema])]);
  612. $conditions = ['_id' => new MongoId("4c8f86167675abfabdbe0300")];
  613. $result = $this->_db->conditions($conditions, $query);
  614. $this->assertEqual($conditions, $result);
  615. $conditions = ['_id' => "4c8f86167675abfabdbe0300"];
  616. $result = $this->_db->conditions($conditions, $query);
  617. $this->assertEqual(array_keys($conditions), array_keys($result));
  618. $this->assertInstanceOf('MongoId', $result['_id']);
  619. $this->assertEqual($conditions['_id'], (string) $result['_id']);
  620. $conditions = ['_id' => [
  621. "4c8f86167675abfabdbe0300", "4c8f86167675abfabdbf0300", "4c8f86167675abfabdc00300"
  622. ]];
  623. $result = $this->_db->conditions($conditions, $query);
  624. $this->assertCount(3, $result['_id']['$in']);
  625. foreach ([0, 1, 2] as $i) {
  626. $this->assertInstanceOf('MongoId', $result['_id']['$in'][$i]);
  627. }
  628. $conditions = ['voters' => ['$all' => [
  629. "4c8f86167675abfabdbf0300", "4c8f86167675abfabdc00300"
  630. ]]];
  631. $result = $this->_db->conditions($conditions, $query);
  632. $this->assertCount(2, $result['voters']['$all']);
  633. $result = $result['voters']['$all'];
  634. foreach ([0, 1] as $i) {
  635. $this->assertInstanceOf('MongoId', $result[$i]);
  636. $this->assertEqual($conditions['voters']['$all'][$i], (string) $result[$i]);
  637. }
  638. $conditions = ['$or' => [
  639. ['_id' => "4c8f86167675abfabdbf0300"],
  640. ['guid' => "4c8f86167675abfabdbf0300"]
  641. ]];
  642. $result = $this->_db->conditions($conditions, $query);
  643. $this->assertEqual(['$or'], array_keys($result));
  644. $this->assertCount(2, $result['$or']);
  645. foreach (['_id', 'guid'] as $i => $key) {
  646. $this->assertInstanceOf('MongoId', $result['$or'][$i][$key]);
  647. $this->assertEqual($conditions['$or'][$i][$key], (string) $result['$or'][$i][$key]);
  648. }
  649. }
  650. public function testCastingElemMatchValuesInConditions(){
  651. $query = new Query([
  652. 'schema' => new Schema([
  653. 'fields' => [
  654. '_id' => ['type' => 'id'],
  655. 'members' => ['type' => 'object', 'array' => true],
  656. 'members.user_id' => ['type' => 'id'],
  657. 'members.pattern' => ['type' => 'regex'],
  658. ]
  659. ])
  660. ]);
  661. $user_id = new MongoId();
  662. $conditions = ['members' => [
  663. '$elemMatch' => [
  664. 'user_id' => (string) $user_id,
  665. 'pattern' => '/test/i',
  666. ]
  667. ]];
  668. $result = $this->_db->conditions($conditions, $query);
  669. $this->assertEqual($conditions, $result);
  670. $this->assertInstanceOf('MongoId', $result['members']['$elemMatch']['user_id']);
  671. $this->assertInstanceOf('MongoRegex', $result['members']['$elemMatch']['pattern']);
  672. }
  673. public function testNotCastingConditionsForSpecialQueryOpts(){
  674. $query = new Query([
  675. 'schema' => new Schema(['fields' => $this->_schema])
  676. ]);
  677. $conditions = ['title' => ['$exists' => true]];
  678. $result = $this->_db->conditions($conditions, $query);
  679. $this->assertIdentical($conditions, $result);
  680. $conditions = ['title' => ['$size' => 1]];
  681. $result = $this->_db->conditions($conditions, $query);
  682. $this->assertIdentical($conditions, $result);
  683. $conditions = ['title' => ['$type' => 1]];
  684. $result = $this->_db->conditions($conditions, $query);
  685. $this->assertIdentical($conditions, $result);
  686. $conditions = ['title' => ['$mod' => [3]]];
  687. $result = $this->_db->conditions($conditions, $query);
  688. $expected = ['title' => ['$mod' => [3, 0]]];
  689. $this->assertIdentical($expected, $result);
  690. $conditions = ['tags' => ['$exists' => true]];
  691. $result = $this->_db->conditions($conditions, $query);
  692. $this->assertIdentical($conditions, $result);
  693. $conditions = ['tags' => ['$size' => 1]];
  694. $result = $this->_db->conditions($conditions, $query);
  695. $this->assertIdentical($conditions, $result);
  696. $conditions = ['tags' => ['$type' => 1]];
  697. $result = $this->_db->conditions($conditions, $query);
  698. $this->assertIdentical($conditions, $result);
  699. $conditions = ['created' => ['$mod' => [7, 0]]];
  700. $result = $this->_db->conditions($conditions, $query);
  701. $this->assertIdentical($conditions, $result);
  702. }
  703. public function testMultiOperationConditions() {
  704. $conditions = ['loc' => ['$near' => [50, 50], '$maxDistance' => 5]];
  705. $result = $this->_db->conditions($conditions, $this->_query);
  706. $this->assertEqual($conditions, $result);
  707. }
  708. public function testCreateWithEmbeddedObjects() {
  709. $data = [
  710. '_id' => new MongoId(),
  711. 'created' => new MongoDate(strtotime('-1 hour')),
  712. 'list' => ['foo', 'bar', 'baz']
  713. ];
  714. $entity = new Document(compact('data') + ['exists' => false]);
  715. $query = new Query(['type' => 'create'] + compact('entity'));
  716. $result = $query->export($this->_db);
  717. $this->assertIdentical($data, $result['data']['data']);
  718. }
  719. public function testUpdateWithEmbeddedObjects() {
  720. $data = [
  721. '_id' => new MongoId(),
  722. 'created' => new MongoDate(strtotime('-1 hour')),
  723. 'list' => ['foo', 'bar', 'baz']
  724. ];
  725. $fields = ['updated' => ['type' => 'MongoDate']];
  726. $schema = new Schema(compact('fields'));
  727. $entity = new Document(compact('data', 'schema', 'model') + ['exists' => true]);
  728. $entity->updated = time();
  729. $entity->list[] = 'dib';
  730. $query = new Query(['type' => 'update'] + compact('entity'));
  731. $result = $query->export($this->_db);
  732. $expected = ['_id', 'created', 'list', 'updated'];
  733. $this->assertEqual($expected, array_keys($result['data']['update']));
  734. $this->assertInstanceOf('MongoDate', $result['data']['update']['updated']);
  735. }
  736. /**
  737. * Assert that Mongo and the Mongo Exporter don't mangle manual geospatial queries.
  738. */
  739. public function testGeoQueries() {
  740. $coords = [84.13, 11.38];
  741. $coords2 = array_map(function($point) { return $point + 5; }, $coords);
  742. $conditions = ['location' => ['$near' => $coords]];
  743. $query = new Query(compact('conditions') + ['model' => $this->_model]);
  744. $result = $query->export($this->_db);
  745. $this->assertEqual($result['conditions'], $conditions);
  746. $conditions = ['location' => [
  747. '$within' => ['$box' => [$coords2, $coords]]
  748. ]];
  749. $query = new Query(compact('conditions') + ['model' => $this->_model]);
  750. $result = $query->export($this->_db);
  751. $this->assertEqual($conditions, $result['conditions']);
  752. }
  753. public function testSchemaCallback() {
  754. $schema = ['_id' => ['type' => 'id'], 'created' => ['type' => 'date']];
  755. $db = new MongoDb(['autoConnect' => false, 'schema' => function() use ($schema) {
  756. return $schema;
  757. }]);
  758. $this->assertEqual($schema, $db->describe(null)->fields());
  759. }
  760. public function testSetReadPreference() {
  761. $prefs = [
  762. "SECONDARY",
  763. ['dc' => 'east', 'use' => 'reporting']
  764. ];
  765. $db = new MongoDb([
  766. 'autoConnect' => true,
  767. 'readPreference' => $prefs,
  768. 'classes' => [
  769. 'server' => 'lithium\tests\mocks\core\MockCallable'
  770. ]
  771. ]);
  772. $result = $db->server->call;
  773. $this->assertEqual('setReadPreference', $result['method']);
  774. $this->assertEqual($prefs, $result['params']);
  775. }
  776. public function testSetReadPreferenceBeforeAccessCollection() {
  777. $prefs = [
  778. "SECONDARY",
  779. ['dc' => 'east', 'use' => 'reporting']
  780. ];
  781. $db = new MongoDb([
  782. 'database' => 'test',
  783. 'autoConnect' => true,
  784. 'readPreference' => $prefs,
  785. 'classes' => [
  786. 'server' => 'lithium\tests\mocks\core\MockCallable'
  787. ]
  788. ]);
  789. $trace = $db->server->trace;
  790. $this->assertEqual('__call', $trace[1][0]);
  791. $this->assertEqual('setReadPreference', $trace[1][1][0]);
  792. $this->assertEqual('__get', $trace[2][0]);
  793. $this->assertEqual('test', $trace[2][1][0]);
  794. }
  795. public function testDefaultSafeOptions() {
  796. $this->_db = new MongoDb($this->_testConfig + ['w' => 1, 'wTimeoutMS' => 10000]);
  797. $this->_db->server = new MockMongoConnection();
  798. $this->_db->connection = new MockCallable();
  799. $this->_db->connection->custom = new MockCallable();
  800. $query = new Query(['type' => 'read', 'source' => 'custom']);
  801. $this->_db->create($query);
  802. $result = $this->_db->connection->custom->call;
  803. $expected = [null, ['w' => 1, 'wTimeoutMS' => 10000, 'fsync' => false]];
  804. $this->assertEqual('insert', $result['method']);
  805. $this->assertEqual($expected, $result['params']);
  806. $query = new Query(['type' => 'read', 'source' => 'custom', 'data' => ['something']]);
  807. $this->_db->update($query);
  808. $result = $this->_db->connection->custom->call;
  809. $expected = ['upsert' => false, 'multiple' => true, 'w' => 1, 'wTimeoutMS' => 10000, 'fsync' => false];
  810. $this->assertEqual('update', $result['method']);
  811. $this->assertEqual($expected, $result['params'][2]);
  812. $query = new Query(['type' => 'read', 'source' => 'custom']);
  813. $this->_db->delete($query);
  814. $result = $this->_db->connection->custom->call;
  815. $expected = ['justOne' => false, 'w' => 1, 'wTimeoutMS' => 10000, 'fsync' => false];
  816. $this->assertEqual('remove', $result['method']);
  817. $this->assertEqual($expected, $result['params'][1]);
  818. $this->_db = new MongoDb($this->_testConfig + ['w' => 1, 'wTimeoutMS' => 10000]);
  819. $this->_db->server = new MockMongoConnection();
  820. $this->_db->connection = new MockCallable();
  821. $this->_db->connection->custom = new MockCallable();
  822. $query = new Query(['type' => 'read', 'source' => 'custom']);
  823. $this->_db->create($query);
  824. $result = $this->_db->connection->custom->call;
  825. $expected = [null, ['w' => 1, 'wTimeoutMS' => 10000, 'fsync' => false]];
  826. $this->assertEqual('insert', $result['method']);
  827. $this->assertEqual($expected, $result['params']);
  828. $query = new Query(['type' => 'read', 'source' => 'custom', 'data' => ['something']]);
  829. $this->_db->update($query);
  830. $result = $this->_db->connection->custom->call;
  831. $expected = [
  832. 'upsert' => false, 'multiple' => true, 'w' => 1, 'wTimeoutMS' => 10000, 'fsync' => false
  833. ];
  834. $this->assertEqual('update', $result['method']);
  835. $this->assertEqual($expected, $result['params'][2]);
  836. $query = new Query(['type' => 'read', 'source' => 'custom']);
  837. $this->_db->delete($query);
  838. $result = $this->_db->connection->custom->call;
  839. $expected = ['justOne' => false, 'w' => 1, 'wTimeoutMS' => 10000, 'fsync' => false];
  840. $this->assertEqual('remove', $result['method']);
  841. $this->assertEqual($expected, $result['params'][1]);
  842. }
  843. public function testGridFsCRUDWithDefaultPrefix() {
  844. $source = 'fs.files';
  845. $data = ['filename' => 'lithium', 'file' => 'some_datas'];
  846. MockMongoPost::config(['meta' => ['source' => $source]]);
  847. $this->assertTrue(MockMongoPost::create()->save($data));
  848. $this->assertIdentical('fs', $this->_db->connection->gridFsPrefix);
  849. $this->_db->connection->gridFsPrefix = null;
  850. MockMongoPost::config(['meta' => ['source' => $source]]);
  851. $this->_db->connection->results = [new MockResultResource(['data' => $data])];
  852. $this->assertNotEmpty(MockMongoPost::find('all'));
  853. $this->assertIdentical('fs', $this->_db->connection->gridFsPrefix);
  854. $this->_db->connection->gridFsPrefix = null;
  855. MockMongoPost::create($data + ['_id' => new MongoId], ['exists' => true])->delete();
  856. $this->assertIdentical('fs', $this->_db->connection->gridFsPrefix);
  857. $this->_db->connection->gridFsPrefix = null;
  858. }
  859. public function testGridFsCreateWithCustomPrefix() {
  860. $data = ['filename' => 'lithium', 'file' => 'some_datas'];
  861. $db = new MongoDb($this->_testConfig + ['gridPrefix' => 'custom']);
  862. $db->server = new MockMongoConnection();
  863. $db->connection = new MockMongoConnection();
  864. Connections::add('temp', ['object' => $db]);
  865. MockMongoPost::config(['meta' => ['source' => 'fs.files', 'connection' => 'temp']]);
  866. MockMongoPost::config(['meta' => ['source' => 'fs.files']]);
  867. $this->assertFalse(MockMongoPost::create()->save($data));
  868. $this->assertIdentical(null, $db->connection->gridFsPrefix);
  869. MockMongoPost::config(['meta' => ['source' => 'custom.files']]);
  870. $this->assertTrue(MockMongoPost::create()->save($data));
  871. $this->assertIdentical('custom', $db->connection->gridFsPrefix);
  872. Connections::remove('temp');
  873. }
  874. public function testGridFsReadWithCustomPrefix() {
  875. $data = ['filename' => 'lithium', 'file' => 'some_datas'];
  876. $result = new MockResultResource(['data' => [
  877. ['filename' => 'lithium', 'file' => 'some_datas']
  878. ]]);
  879. $db = new MongoDb($this->_testConfig + ['gridPrefix' => 'custom']);
  880. $db->server = new MockMongoConnection();
  881. $db->connection = new MockMongoConnection();
  882. Connections::add('temp', ['object' => $db]);
  883. MockMongoPost::config(['meta' => ['source' => 'fs.files', 'connection' => 'temp']]);
  884. $db->connection->results = [$result];
  885. $this->assertNotEmpty(MockMongoPost::find('all'));
  886. $this->assertIdentical(null, $db->connection->gridFsPrefix);
  887. MockMongoPost::config(['meta' => ['source' => 'custom.files']]);
  888. $db->connection->results = [$result];
  889. $this->assertNotEmpty(MockMongoPost::find('all'));
  890. $this->assertIdentical('custom', $db->connection->gridFsPrefix);
  891. Connections::remove('temp');
  892. }
  893. public function testGridFsDeleteWithCustomPrefix() {
  894. $data = ['_id' => new MongoId];
  895. $db = new MongoDb($this->_testConfig + ['gridPrefix' => 'custom']);
  896. $db->server = new MockMongoConnection();
  897. $db->connection = new MockMongoConnection();
  898. Connections::add('temp', ['object' => $db]);
  899. MockMongoPost::config(['meta' => ['source' => 'fs.files', 'connection' => 'temp']]);
  900. MockMongoPost::create($data, ['exists' => true])->delete();
  901. $this->assertIdentical(null, $db->connection->gridFsPrefix);
  902. MockMongoPost::config(['meta' => ['source' => 'custom.files']]);
  903. MockMongoPost::create($data, ['exists' => true])->delete();
  904. $this->assertIdentical('custom', $db->connection->gridFsPrefix);
  905. Connections::remove('temp');
  906. }
  907. /* Deprecated / BC */
  908. /**
  909. * @deprecated
  910. */
  911. public function testRespondsToParentCall() {
  912. error_reporting(($backup = error_reporting()) & ~E_USER_DEPRECATED);
  913. $db = new MongoDb($this->_testConfig);
  914. $this->assertTrue($db->respondsTo('_parents'));
  915. $this->assertFalse($db->respondsTo('fooBarBaz'));
  916. error_reporting($backup);
  917. }
  918. /**
  919. * @deprecated
  920. */
  921. public function testRespondsToWithNoServer() {
  922. error_reporting(($backup = error_reporting()) & ~E_USER_DEPRECATED);
  923. $db = new MongoDb($this->_testConfig);
  924. $this->assertFalse($db->respondsTo('listDBs'));
  925. $this->assertFalse($db->respondsTo('foobarbaz'));
  926. error_reporting($backup);
  927. }
  928. /**
  929. * @deprecated
  930. */
  931. public function testRespondsToWithServer() {
  932. error_reporting(($backup = error_reporting()) & ~E_USER_DEPRECATED);
  933. $db = new MongoDb($this->_testConfig);
  934. $db->server = new MockMongoConnection();
  935. $this->assertTrue($db->respondsTo('listDBs'));
  936. $this->assertFalse($db->respondsTo('foobarbaz'));
  937. error_reporting($backup);
  938. }
  939. }
  940. ?>