MongoDbSessionHandlerTest.php 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333
  1. <?php
  2. /*
  3. * This file is part of the Symfony package.
  4. *
  5. * (c) Fabien Potencier <fabien@symfony.com>
  6. *
  7. * For the full copyright and license information, please view the LICENSE
  8. * file that was distributed with this source code.
  9. */
  10. namespace Symfony\Component\HttpFoundation\Tests\Session\Storage\Handler;
  11. use PHPUnit\Framework\TestCase;
  12. use Symfony\Component\HttpFoundation\Session\Storage\Handler\MongoDbSessionHandler;
  13. /**
  14. * @author Markus Bachmann <markus.bachmann@bachi.biz>
  15. * @group time-sensitive
  16. * @group legacy
  17. */
  18. class MongoDbSessionHandlerTest extends TestCase
  19. {
  20. /**
  21. * @var \PHPUnit_Framework_MockObject_MockObject
  22. */
  23. private $mongo;
  24. private $storage;
  25. public $options;
  26. protected function setUp()
  27. {
  28. parent::setUp();
  29. if (\extension_loaded('mongodb')) {
  30. if (!class_exists('MongoDB\Client')) {
  31. $this->markTestSkipped('The mongodb/mongodb package is required.');
  32. }
  33. } elseif (!\extension_loaded('mongo')) {
  34. $this->markTestSkipped('The Mongo or MongoDB extension is required.');
  35. }
  36. if (phpversion('mongodb')) {
  37. $mongoClass = 'MongoDB\Client';
  38. } else {
  39. $mongoClass = version_compare(phpversion('mongo'), '1.3.0', '<') ? 'Mongo' : 'MongoClient';
  40. }
  41. $this->mongo = $this->getMockBuilder($mongoClass)
  42. ->disableOriginalConstructor()
  43. ->getMock();
  44. $this->options = [
  45. 'id_field' => '_id',
  46. 'data_field' => 'data',
  47. 'time_field' => 'time',
  48. 'expiry_field' => 'expires_at',
  49. 'database' => 'sf2-test',
  50. 'collection' => 'session-test',
  51. ];
  52. $this->storage = new MongoDbSessionHandler($this->mongo, $this->options);
  53. }
  54. /**
  55. * @expectedException \InvalidArgumentException
  56. */
  57. public function testConstructorShouldThrowExceptionForInvalidMongo()
  58. {
  59. new MongoDbSessionHandler(new \stdClass(), $this->options);
  60. }
  61. /**
  62. * @expectedException \InvalidArgumentException
  63. */
  64. public function testConstructorShouldThrowExceptionForMissingOptions()
  65. {
  66. new MongoDbSessionHandler($this->mongo, []);
  67. }
  68. public function testOpenMethodAlwaysReturnTrue()
  69. {
  70. $this->assertTrue($this->storage->open('test', 'test'), 'The "open" method should always return true');
  71. }
  72. public function testCloseMethodAlwaysReturnTrue()
  73. {
  74. $this->assertTrue($this->storage->close(), 'The "close" method should always return true');
  75. }
  76. public function testRead()
  77. {
  78. $collection = $this->createMongoCollectionMock();
  79. $this->mongo->expects($this->once())
  80. ->method('selectCollection')
  81. ->with($this->options['database'], $this->options['collection'])
  82. ->will($this->returnValue($collection));
  83. // defining the timeout before the actual method call
  84. // allows to test for "greater than" values in the $criteria
  85. $testTimeout = time() + 1;
  86. $collection->expects($this->once())
  87. ->method('findOne')
  88. ->will($this->returnCallback(function ($criteria) use ($testTimeout) {
  89. $this->assertArrayHasKey($this->options['id_field'], $criteria);
  90. $this->assertEquals($criteria[$this->options['id_field']], 'foo');
  91. $this->assertArrayHasKey($this->options['expiry_field'], $criteria);
  92. $this->assertArrayHasKey('$gte', $criteria[$this->options['expiry_field']]);
  93. if (phpversion('mongodb')) {
  94. $this->assertInstanceOf('MongoDB\BSON\UTCDateTime', $criteria[$this->options['expiry_field']]['$gte']);
  95. $this->assertGreaterThanOrEqual(round((string) $criteria[$this->options['expiry_field']]['$gte'] / 1000), $testTimeout);
  96. } else {
  97. $this->assertInstanceOf('MongoDate', $criteria[$this->options['expiry_field']]['$gte']);
  98. $this->assertGreaterThanOrEqual($criteria[$this->options['expiry_field']]['$gte']->sec, $testTimeout);
  99. }
  100. $fields = [
  101. $this->options['id_field'] => 'foo',
  102. ];
  103. if (phpversion('mongodb')) {
  104. $fields[$this->options['data_field']] = new \MongoDB\BSON\Binary('bar', \MongoDB\BSON\Binary::TYPE_OLD_BINARY);
  105. $fields[$this->options['id_field']] = new \MongoDB\BSON\UTCDateTime(time() * 1000);
  106. } else {
  107. $fields[$this->options['data_field']] = new \MongoBinData('bar', \MongoBinData::BYTE_ARRAY);
  108. $fields[$this->options['id_field']] = new \MongoDate();
  109. }
  110. return $fields;
  111. }));
  112. $this->assertEquals('bar', $this->storage->read('foo'));
  113. }
  114. public function testWrite()
  115. {
  116. $collection = $this->createMongoCollectionMock();
  117. $this->mongo->expects($this->once())
  118. ->method('selectCollection')
  119. ->with($this->options['database'], $this->options['collection'])
  120. ->will($this->returnValue($collection));
  121. $data = [];
  122. $methodName = phpversion('mongodb') ? 'updateOne' : 'update';
  123. $collection->expects($this->once())
  124. ->method($methodName)
  125. ->will($this->returnCallback(function ($criteria, $updateData, $options) use (&$data) {
  126. $this->assertEquals([$this->options['id_field'] => 'foo'], $criteria);
  127. if (phpversion('mongodb')) {
  128. $this->assertEquals(['upsert' => true], $options);
  129. } else {
  130. $this->assertEquals(['upsert' => true, 'multiple' => false], $options);
  131. }
  132. $data = $updateData['$set'];
  133. }));
  134. $expectedExpiry = time() + (int) ini_get('session.gc_maxlifetime');
  135. $this->assertTrue($this->storage->write('foo', 'bar'));
  136. if (phpversion('mongodb')) {
  137. $this->assertEquals('bar', $data[$this->options['data_field']]->getData());
  138. $this->assertInstanceOf('MongoDB\BSON\UTCDateTime', $data[$this->options['time_field']]);
  139. $this->assertInstanceOf('MongoDB\BSON\UTCDateTime', $data[$this->options['expiry_field']]);
  140. $this->assertGreaterThanOrEqual($expectedExpiry, round((string) $data[$this->options['expiry_field']] / 1000));
  141. } else {
  142. $this->assertEquals('bar', $data[$this->options['data_field']]->bin);
  143. $this->assertInstanceOf('MongoDate', $data[$this->options['time_field']]);
  144. $this->assertInstanceOf('MongoDate', $data[$this->options['expiry_field']]);
  145. $this->assertGreaterThanOrEqual($expectedExpiry, $data[$this->options['expiry_field']]->sec);
  146. }
  147. }
  148. public function testWriteWhenUsingExpiresField()
  149. {
  150. $this->options = [
  151. 'id_field' => '_id',
  152. 'data_field' => 'data',
  153. 'time_field' => 'time',
  154. 'database' => 'sf2-test',
  155. 'collection' => 'session-test',
  156. 'expiry_field' => 'expiresAt',
  157. ];
  158. $this->storage = new MongoDbSessionHandler($this->mongo, $this->options);
  159. $collection = $this->createMongoCollectionMock();
  160. $this->mongo->expects($this->once())
  161. ->method('selectCollection')
  162. ->with($this->options['database'], $this->options['collection'])
  163. ->will($this->returnValue($collection));
  164. $data = [];
  165. $methodName = phpversion('mongodb') ? 'updateOne' : 'update';
  166. $collection->expects($this->once())
  167. ->method($methodName)
  168. ->will($this->returnCallback(function ($criteria, $updateData, $options) use (&$data) {
  169. $this->assertEquals([$this->options['id_field'] => 'foo'], $criteria);
  170. if (phpversion('mongodb')) {
  171. $this->assertEquals(['upsert' => true], $options);
  172. } else {
  173. $this->assertEquals(['upsert' => true, 'multiple' => false], $options);
  174. }
  175. $data = $updateData['$set'];
  176. }));
  177. $this->assertTrue($this->storage->write('foo', 'bar'));
  178. if (phpversion('mongodb')) {
  179. $this->assertEquals('bar', $data[$this->options['data_field']]->getData());
  180. $this->assertInstanceOf('MongoDB\BSON\UTCDateTime', $data[$this->options['time_field']]);
  181. $this->assertInstanceOf('MongoDB\BSON\UTCDateTime', $data[$this->options['expiry_field']]);
  182. } else {
  183. $this->assertEquals('bar', $data[$this->options['data_field']]->bin);
  184. $this->assertInstanceOf('MongoDate', $data[$this->options['time_field']]);
  185. $this->assertInstanceOf('MongoDate', $data[$this->options['expiry_field']]);
  186. }
  187. }
  188. public function testReplaceSessionData()
  189. {
  190. $collection = $this->createMongoCollectionMock();
  191. $this->mongo->expects($this->once())
  192. ->method('selectCollection')
  193. ->with($this->options['database'], $this->options['collection'])
  194. ->will($this->returnValue($collection));
  195. $data = [];
  196. $methodName = phpversion('mongodb') ? 'updateOne' : 'update';
  197. $collection->expects($this->exactly(2))
  198. ->method($methodName)
  199. ->will($this->returnCallback(function ($criteria, $updateData, $options) use (&$data) {
  200. $data = $updateData;
  201. }));
  202. $this->storage->write('foo', 'bar');
  203. $this->storage->write('foo', 'foobar');
  204. if (phpversion('mongodb')) {
  205. $this->assertEquals('foobar', $data['$set'][$this->options['data_field']]->getData());
  206. } else {
  207. $this->assertEquals('foobar', $data['$set'][$this->options['data_field']]->bin);
  208. }
  209. }
  210. public function testDestroy()
  211. {
  212. $collection = $this->createMongoCollectionMock();
  213. $this->mongo->expects($this->once())
  214. ->method('selectCollection')
  215. ->with($this->options['database'], $this->options['collection'])
  216. ->will($this->returnValue($collection));
  217. $methodName = phpversion('mongodb') ? 'deleteOne' : 'remove';
  218. $collection->expects($this->once())
  219. ->method($methodName)
  220. ->with([$this->options['id_field'] => 'foo']);
  221. $this->assertTrue($this->storage->destroy('foo'));
  222. }
  223. public function testGc()
  224. {
  225. $collection = $this->createMongoCollectionMock();
  226. $this->mongo->expects($this->once())
  227. ->method('selectCollection')
  228. ->with($this->options['database'], $this->options['collection'])
  229. ->will($this->returnValue($collection));
  230. $methodName = phpversion('mongodb') ? 'deleteMany' : 'remove';
  231. $collection->expects($this->once())
  232. ->method($methodName)
  233. ->will($this->returnCallback(function ($criteria) {
  234. if (phpversion('mongodb')) {
  235. $this->assertInstanceOf('MongoDB\BSON\UTCDateTime', $criteria[$this->options['expiry_field']]['$lt']);
  236. $this->assertGreaterThanOrEqual(time() - 1, round((string) $criteria[$this->options['expiry_field']]['$lt'] / 1000));
  237. } else {
  238. $this->assertInstanceOf('MongoDate', $criteria[$this->options['expiry_field']]['$lt']);
  239. $this->assertGreaterThanOrEqual(time() - 1, $criteria[$this->options['expiry_field']]['$lt']->sec);
  240. }
  241. }));
  242. $this->assertTrue($this->storage->gc(1));
  243. }
  244. public function testGetConnection()
  245. {
  246. $method = new \ReflectionMethod($this->storage, 'getMongo');
  247. $method->setAccessible(true);
  248. if (phpversion('mongodb')) {
  249. $mongoClass = 'MongoDB\Client';
  250. } else {
  251. $mongoClass = version_compare(phpversion('mongo'), '1.3.0', '<') ? 'Mongo' : 'MongoClient';
  252. }
  253. $this->assertInstanceOf($mongoClass, $method->invoke($this->storage));
  254. }
  255. private function createMongoCollectionMock()
  256. {
  257. $collectionClass = 'MongoCollection';
  258. if (phpversion('mongodb')) {
  259. $collectionClass = 'MongoDB\Collection';
  260. }
  261. $collection = $this->getMockBuilder($collectionClass)
  262. ->disableOriginalConstructor()
  263. ->getMock();
  264. return $collection;
  265. }
  266. }