4 * This file is part of the Symfony package.
6 * (c) Fabien Potencier <fabien@symfony.com>
8 * For the full copyright and license information, please view the LICENSE
9 * file that was distributed with this source code.
12 namespace Symfony\Component\HttpFoundation\Tests\Session\Storage\Handler;
14 use PHPUnit\Framework\TestCase;
15 use Symfony\Component\HttpFoundation\Session\Storage\Handler\MongoDbSessionHandler;
18 * @author Markus Bachmann <markus.bachmann@bachi.biz>
19 * @group time-sensitive
22 class MongoDbSessionHandlerTest extends TestCase
25 * @var \PHPUnit_Framework_MockObject_MockObject
31 protected function setUp()
35 if (extension_loaded('mongodb')) {
36 if (!class_exists('MongoDB\Client')) {
37 $this->markTestSkipped('The mongodb/mongodb package is required.');
39 } elseif (!extension_loaded('mongo')) {
40 $this->markTestSkipped('The Mongo or MongoDB extension is required.');
43 if (phpversion('mongodb')) {
44 $mongoClass = 'MongoDB\Client';
46 $mongoClass = version_compare(phpversion('mongo'), '1.3.0', '<') ? 'Mongo' : 'MongoClient';
49 $this->mongo = $this->getMockBuilder($mongoClass)
50 ->disableOriginalConstructor()
53 $this->options = array(
55 'data_field' => 'data',
56 'time_field' => 'time',
57 'expiry_field' => 'expires_at',
58 'database' => 'sf2-test',
59 'collection' => 'session-test',
62 $this->storage = new MongoDbSessionHandler($this->mongo, $this->options);
66 * @expectedException \InvalidArgumentException
68 public function testConstructorShouldThrowExceptionForInvalidMongo()
70 new MongoDbSessionHandler(new \stdClass(), $this->options);
74 * @expectedException \InvalidArgumentException
76 public function testConstructorShouldThrowExceptionForMissingOptions()
78 new MongoDbSessionHandler($this->mongo, array());
81 public function testOpenMethodAlwaysReturnTrue()
83 $this->assertTrue($this->storage->open('test', 'test'), 'The "open" method should always return true');
86 public function testCloseMethodAlwaysReturnTrue()
88 $this->assertTrue($this->storage->close(), 'The "close" method should always return true');
91 public function testRead()
93 $collection = $this->createMongoCollectionMock();
95 $this->mongo->expects($this->once())
96 ->method('selectCollection')
97 ->with($this->options['database'], $this->options['collection'])
98 ->will($this->returnValue($collection));
100 // defining the timeout before the actual method call
101 // allows to test for "greater than" values in the $criteria
102 $testTimeout = time() + 1;
104 $collection->expects($this->once())
106 ->will($this->returnCallback(function ($criteria) use ($testTimeout) {
107 $this->assertArrayHasKey($this->options['id_field'], $criteria);
108 $this->assertEquals($criteria[$this->options['id_field']], 'foo');
110 $this->assertArrayHasKey($this->options['expiry_field'], $criteria);
111 $this->assertArrayHasKey('$gte', $criteria[$this->options['expiry_field']]);
113 if (phpversion('mongodb')) {
114 $this->assertInstanceOf('MongoDB\BSON\UTCDateTime', $criteria[$this->options['expiry_field']]['$gte']);
115 $this->assertGreaterThanOrEqual(round((string) $criteria[$this->options['expiry_field']]['$gte'] / 1000), $testTimeout);
117 $this->assertInstanceOf('MongoDate', $criteria[$this->options['expiry_field']]['$gte']);
118 $this->assertGreaterThanOrEqual($criteria[$this->options['expiry_field']]['$gte']->sec, $testTimeout);
122 $this->options['id_field'] => 'foo',
125 if (phpversion('mongodb')) {
126 $fields[$this->options['data_field']] = new \MongoDB\BSON\Binary('bar', \MongoDB\BSON\Binary::TYPE_OLD_BINARY);
127 $fields[$this->options['id_field']] = new \MongoDB\BSON\UTCDateTime(time() * 1000);
129 $fields[$this->options['data_field']] = new \MongoBinData('bar', \MongoBinData::BYTE_ARRAY);
130 $fields[$this->options['id_field']] = new \MongoDate();
136 $this->assertEquals('bar', $this->storage->read('foo'));
139 public function testWrite()
141 $collection = $this->createMongoCollectionMock();
143 $this->mongo->expects($this->once())
144 ->method('selectCollection')
145 ->with($this->options['database'], $this->options['collection'])
146 ->will($this->returnValue($collection));
150 $methodName = phpversion('mongodb') ? 'updateOne' : 'update';
152 $collection->expects($this->once())
153 ->method($methodName)
154 ->will($this->returnCallback(function ($criteria, $updateData, $options) use (&$data) {
155 $this->assertEquals(array($this->options['id_field'] => 'foo'), $criteria);
157 if (phpversion('mongodb')) {
158 $this->assertEquals(array('upsert' => true), $options);
160 $this->assertEquals(array('upsert' => true, 'multiple' => false), $options);
163 $data = $updateData['$set'];
166 $expectedExpiry = time() + (int) ini_get('session.gc_maxlifetime');
167 $this->assertTrue($this->storage->write('foo', 'bar'));
169 if (phpversion('mongodb')) {
170 $this->assertEquals('bar', $data[$this->options['data_field']]->getData());
171 $this->assertInstanceOf('MongoDB\BSON\UTCDateTime', $data[$this->options['time_field']]);
172 $this->assertInstanceOf('MongoDB\BSON\UTCDateTime', $data[$this->options['expiry_field']]);
173 $this->assertGreaterThanOrEqual($expectedExpiry, round((string) $data[$this->options['expiry_field']] / 1000));
175 $this->assertEquals('bar', $data[$this->options['data_field']]->bin);
176 $this->assertInstanceOf('MongoDate', $data[$this->options['time_field']]);
177 $this->assertInstanceOf('MongoDate', $data[$this->options['expiry_field']]);
178 $this->assertGreaterThanOrEqual($expectedExpiry, $data[$this->options['expiry_field']]->sec);
182 public function testWriteWhenUsingExpiresField()
184 $this->options = array(
186 'data_field' => 'data',
187 'time_field' => 'time',
188 'database' => 'sf2-test',
189 'collection' => 'session-test',
190 'expiry_field' => 'expiresAt',
193 $this->storage = new MongoDbSessionHandler($this->mongo, $this->options);
195 $collection = $this->createMongoCollectionMock();
197 $this->mongo->expects($this->once())
198 ->method('selectCollection')
199 ->with($this->options['database'], $this->options['collection'])
200 ->will($this->returnValue($collection));
204 $methodName = phpversion('mongodb') ? 'updateOne' : 'update';
206 $collection->expects($this->once())
207 ->method($methodName)
208 ->will($this->returnCallback(function ($criteria, $updateData, $options) use (&$data) {
209 $this->assertEquals(array($this->options['id_field'] => 'foo'), $criteria);
211 if (phpversion('mongodb')) {
212 $this->assertEquals(array('upsert' => true), $options);
214 $this->assertEquals(array('upsert' => true, 'multiple' => false), $options);
217 $data = $updateData['$set'];
220 $this->assertTrue($this->storage->write('foo', 'bar'));
222 if (phpversion('mongodb')) {
223 $this->assertEquals('bar', $data[$this->options['data_field']]->getData());
224 $this->assertInstanceOf('MongoDB\BSON\UTCDateTime', $data[$this->options['time_field']]);
225 $this->assertInstanceOf('MongoDB\BSON\UTCDateTime', $data[$this->options['expiry_field']]);
227 $this->assertEquals('bar', $data[$this->options['data_field']]->bin);
228 $this->assertInstanceOf('MongoDate', $data[$this->options['time_field']]);
229 $this->assertInstanceOf('MongoDate', $data[$this->options['expiry_field']]);
233 public function testReplaceSessionData()
235 $collection = $this->createMongoCollectionMock();
237 $this->mongo->expects($this->once())
238 ->method('selectCollection')
239 ->with($this->options['database'], $this->options['collection'])
240 ->will($this->returnValue($collection));
244 $methodName = phpversion('mongodb') ? 'updateOne' : 'update';
246 $collection->expects($this->exactly(2))
247 ->method($methodName)
248 ->will($this->returnCallback(function ($criteria, $updateData, $options) use (&$data) {
252 $this->storage->write('foo', 'bar');
253 $this->storage->write('foo', 'foobar');
255 if (phpversion('mongodb')) {
256 $this->assertEquals('foobar', $data['$set'][$this->options['data_field']]->getData());
258 $this->assertEquals('foobar', $data['$set'][$this->options['data_field']]->bin);
262 public function testDestroy()
264 $collection = $this->createMongoCollectionMock();
266 $this->mongo->expects($this->once())
267 ->method('selectCollection')
268 ->with($this->options['database'], $this->options['collection'])
269 ->will($this->returnValue($collection));
271 $methodName = phpversion('mongodb') ? 'deleteOne' : 'remove';
273 $collection->expects($this->once())
274 ->method($methodName)
275 ->with(array($this->options['id_field'] => 'foo'));
277 $this->assertTrue($this->storage->destroy('foo'));
280 public function testGc()
282 $collection = $this->createMongoCollectionMock();
284 $this->mongo->expects($this->once())
285 ->method('selectCollection')
286 ->with($this->options['database'], $this->options['collection'])
287 ->will($this->returnValue($collection));
289 $methodName = phpversion('mongodb') ? 'deleteMany' : 'remove';
291 $collection->expects($this->once())
292 ->method($methodName)
293 ->will($this->returnCallback(function ($criteria) {
294 if (phpversion('mongodb')) {
295 $this->assertInstanceOf('MongoDB\BSON\UTCDateTime', $criteria[$this->options['expiry_field']]['$lt']);
296 $this->assertGreaterThanOrEqual(time() - 1, round((string) $criteria[$this->options['expiry_field']]['$lt'] / 1000));
298 $this->assertInstanceOf('MongoDate', $criteria[$this->options['expiry_field']]['$lt']);
299 $this->assertGreaterThanOrEqual(time() - 1, $criteria[$this->options['expiry_field']]['$lt']->sec);
303 $this->assertTrue($this->storage->gc(1));
306 public function testGetConnection()
308 $method = new \ReflectionMethod($this->storage, 'getMongo');
309 $method->setAccessible(true);
311 if (phpversion('mongodb')) {
312 $mongoClass = 'MongoDB\Client';
314 $mongoClass = version_compare(phpversion('mongo'), '1.3.0', '<') ? 'Mongo' : 'MongoClient';
317 $this->assertInstanceOf($mongoClass, $method->invoke($this->storage));
320 private function createMongoCollectionMock()
322 $collectionClass = 'MongoCollection';
323 if (phpversion('mongodb')) {
324 $collectionClass = 'MongoDB\Collection';
327 $collection = $this->getMockBuilder($collectionClass)
328 ->disableOriginalConstructor()