MongoDbSessionHandlerTest.php 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220
  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 MongoDB\Client;
  12. use PHPUnit\Framework\MockObject\MockObject;
  13. use PHPUnit\Framework\SkippedTestSuiteError;
  14. use PHPUnit\Framework\TestCase;
  15. use Symfony\Component\HttpFoundation\Session\Storage\Handler\MongoDbSessionHandler;
  16. /**
  17. * @author Markus Bachmann <markus.bachmann@bachi.biz>
  18. *
  19. * @group time-sensitive
  20. *
  21. * @requires extension mongodb
  22. */
  23. class MongoDbSessionHandlerTest extends TestCase
  24. {
  25. /**
  26. * @var MockObject&Client
  27. */
  28. private $mongo;
  29. private $storage;
  30. public $options;
  31. public static function setUpBeforeClass(): void
  32. {
  33. if (!class_exists(Client::class)) {
  34. throw new SkippedTestSuiteError('The mongodb/mongodb package is required.');
  35. }
  36. }
  37. protected function setUp(): void
  38. {
  39. parent::setUp();
  40. $this->mongo = $this->getMockBuilder(Client::class)
  41. ->disableOriginalConstructor()
  42. ->getMock();
  43. $this->options = [
  44. 'id_field' => '_id',
  45. 'data_field' => 'data',
  46. 'time_field' => 'time',
  47. 'expiry_field' => 'expires_at',
  48. 'database' => 'sf-test',
  49. 'collection' => 'session-test',
  50. ];
  51. $this->storage = new MongoDbSessionHandler($this->mongo, $this->options);
  52. }
  53. public function testConstructorShouldThrowExceptionForMissingOptions()
  54. {
  55. $this->expectException(\InvalidArgumentException::class);
  56. new MongoDbSessionHandler($this->mongo, []);
  57. }
  58. public function testOpenMethodAlwaysReturnTrue()
  59. {
  60. $this->assertTrue($this->storage->open('test', 'test'), 'The "open" method should always return true');
  61. }
  62. public function testCloseMethodAlwaysReturnTrue()
  63. {
  64. $this->assertTrue($this->storage->close(), 'The "close" method should always return true');
  65. }
  66. public function testRead()
  67. {
  68. $collection = $this->createMongoCollectionMock();
  69. $this->mongo->expects($this->once())
  70. ->method('selectCollection')
  71. ->with($this->options['database'], $this->options['collection'])
  72. ->willReturn($collection);
  73. // defining the timeout before the actual method call
  74. // allows to test for "greater than" values in the $criteria
  75. $testTimeout = time() + 1;
  76. $collection->expects($this->once())
  77. ->method('findOne')
  78. ->willReturnCallback(function ($criteria) use ($testTimeout) {
  79. $this->assertArrayHasKey($this->options['id_field'], $criteria);
  80. $this->assertEquals('foo', $criteria[$this->options['id_field']]);
  81. $this->assertArrayHasKey($this->options['expiry_field'], $criteria);
  82. $this->assertArrayHasKey('$gte', $criteria[$this->options['expiry_field']]);
  83. $this->assertInstanceOf(\MongoDB\BSON\UTCDateTime::class, $criteria[$this->options['expiry_field']]['$gte']);
  84. $this->assertGreaterThanOrEqual(round((string) $criteria[$this->options['expiry_field']]['$gte'] / 1000), $testTimeout);
  85. return [
  86. $this->options['id_field'] => 'foo',
  87. $this->options['expiry_field'] => new \MongoDB\BSON\UTCDateTime(),
  88. $this->options['data_field'] => new \MongoDB\BSON\Binary('bar', \MongoDB\BSON\Binary::TYPE_OLD_BINARY),
  89. ];
  90. });
  91. $this->assertEquals('bar', $this->storage->read('foo'));
  92. }
  93. public function testWrite()
  94. {
  95. $collection = $this->createMongoCollectionMock();
  96. $this->mongo->expects($this->once())
  97. ->method('selectCollection')
  98. ->with($this->options['database'], $this->options['collection'])
  99. ->willReturn($collection);
  100. $collection->expects($this->once())
  101. ->method('updateOne')
  102. ->willReturnCallback(function ($criteria, $updateData, $options) {
  103. $this->assertEquals([$this->options['id_field'] => 'foo'], $criteria);
  104. $this->assertEquals(['upsert' => true], $options);
  105. $data = $updateData['$set'];
  106. $expectedExpiry = time() + (int) \ini_get('session.gc_maxlifetime');
  107. $this->assertInstanceOf(\MongoDB\BSON\Binary::class, $data[$this->options['data_field']]);
  108. $this->assertEquals('bar', $data[$this->options['data_field']]->getData());
  109. $this->assertInstanceOf(\MongoDB\BSON\UTCDateTime::class, $data[$this->options['time_field']]);
  110. $this->assertInstanceOf(\MongoDB\BSON\UTCDateTime::class, $data[$this->options['expiry_field']]);
  111. $this->assertGreaterThanOrEqual($expectedExpiry, round((string) $data[$this->options['expiry_field']] / 1000));
  112. });
  113. $this->assertTrue($this->storage->write('foo', 'bar'));
  114. }
  115. public function testReplaceSessionData()
  116. {
  117. $collection = $this->createMongoCollectionMock();
  118. $this->mongo->expects($this->once())
  119. ->method('selectCollection')
  120. ->with($this->options['database'], $this->options['collection'])
  121. ->willReturn($collection);
  122. $data = [];
  123. $collection->expects($this->exactly(2))
  124. ->method('updateOne')
  125. ->willReturnCallback(function ($criteria, $updateData, $options) use (&$data) {
  126. $data = $updateData;
  127. });
  128. $this->storage->write('foo', 'bar');
  129. $this->storage->write('foo', 'foobar');
  130. $this->assertEquals('foobar', $data['$set'][$this->options['data_field']]->getData());
  131. }
  132. public function testDestroy()
  133. {
  134. $collection = $this->createMongoCollectionMock();
  135. $this->mongo->expects($this->once())
  136. ->method('selectCollection')
  137. ->with($this->options['database'], $this->options['collection'])
  138. ->willReturn($collection);
  139. $collection->expects($this->once())
  140. ->method('deleteOne')
  141. ->with([$this->options['id_field'] => 'foo']);
  142. $this->assertTrue($this->storage->destroy('foo'));
  143. }
  144. public function testGc()
  145. {
  146. $collection = $this->createMongoCollectionMock();
  147. $this->mongo->expects($this->once())
  148. ->method('selectCollection')
  149. ->with($this->options['database'], $this->options['collection'])
  150. ->willReturn($collection);
  151. $collection->expects($this->once())
  152. ->method('deleteMany')
  153. ->willReturnCallback(function ($criteria) {
  154. $this->assertInstanceOf(\MongoDB\BSON\UTCDateTime::class, $criteria[$this->options['expiry_field']]['$lt']);
  155. $this->assertGreaterThanOrEqual(time() - 1, round((string) $criteria[$this->options['expiry_field']]['$lt'] / 1000));
  156. $result = $this->createMock(\MongoDB\DeleteResult::class);
  157. $result->method('getDeletedCount')->willReturn(42);
  158. return $result;
  159. });
  160. $this->assertSame(42, $this->storage->gc(1));
  161. }
  162. public function testGetConnection()
  163. {
  164. $method = new \ReflectionMethod($this->storage, 'getMongo');
  165. $method->setAccessible(true);
  166. $this->assertInstanceOf(Client::class, $method->invoke($this->storage));
  167. }
  168. private function createMongoCollectionMock(): \MongoDB\Collection
  169. {
  170. $collection = $this->getMockBuilder(\MongoDB\Collection::class)
  171. ->disableOriginalConstructor()
  172. ->getMock();
  173. return $collection;
  174. }
  175. }