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

/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/MongoDbSessionHandlerTest.php

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