MongoDbSessionHandlerTest.php 12 KB

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