1: <?php
2:
3: 4: 5: 6: 7: 8: 9: 10:
11:
12: namespace Symfony\Component\HttpFoundation\Tests\Session\Storage\Handler;
13:
14: use Symfony\Component\HttpFoundation\Session\Storage\Handler\PdoSessionHandler;
15:
16: class PdoSessionHandlerTest extends \PHPUnit_Framework_TestCase
17: {
18: private $dbFile;
19:
20: protected function setUp()
21: {
22: if (!class_exists('PDO') || !in_array('sqlite', \PDO::getAvailableDrivers())) {
23: $this->markTestSkipped('This test requires SQLite support in your environment');
24: }
25: }
26:
27: protected function tearDown()
28: {
29:
30: if ($this->dbFile) {
31: @unlink($this->dbFile);
32: }
33: }
34:
35: protected function getPersistentSqliteDsn()
36: {
37: $this->dbFile = tempnam(sys_get_temp_dir(), 'sf2_sqlite_sessions');
38:
39: return 'sqlite:'.$this->dbFile;
40: }
41:
42: protected function getMemorySqlitePdo()
43: {
44: $pdo = new \PDO('sqlite::memory:');
45: $pdo->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION);
46: $storage = new PdoSessionHandler($pdo);
47: $storage->createTable();
48:
49: return $pdo;
50: }
51:
52: 53: 54:
55: public function testWrongPdoErrMode()
56: {
57: $pdo = $this->getMemorySqlitePdo();
58: $pdo->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_SILENT);
59:
60: $storage = new PdoSessionHandler($pdo);
61: }
62:
63: 64: 65:
66: public function testInexistentTable()
67: {
68: $storage = new PdoSessionHandler($this->getMemorySqlitePdo(), array('db_table' => 'inexistent_table'));
69: $storage->open('', 'sid');
70: $storage->read('id');
71: $storage->write('id', 'data');
72: $storage->close();
73: }
74:
75: 76: 77:
78: public function testCreateTableTwice()
79: {
80: $storage = new PdoSessionHandler($this->getMemorySqlitePdo());
81: $storage->createTable();
82: }
83:
84: public function testWithLazyDsnConnection()
85: {
86: $dsn = $this->getPersistentSqliteDsn();
87:
88: $storage = new PdoSessionHandler($dsn);
89: $storage->createTable();
90: $storage->open('', 'sid');
91: $data = $storage->read('id');
92: $storage->write('id', 'data');
93: $storage->close();
94: $this->assertSame('', $data, 'New session returns empty string data');
95:
96: $storage->open('', 'sid');
97: $data = $storage->read('id');
98: $storage->close();
99: $this->assertSame('data', $data, 'Written value can be read back correctly');
100: }
101:
102: public function testWithLazySavePathConnection()
103: {
104: $dsn = $this->getPersistentSqliteDsn();
105:
106:
107: $storage = new PdoSessionHandler(null);
108: $storage->open($dsn, 'sid');
109: $storage->createTable();
110: $data = $storage->read('id');
111: $storage->write('id', 'data');
112: $storage->close();
113: $this->assertSame('', $data, 'New session returns empty string data');
114:
115: $storage->open($dsn, 'sid');
116: $data = $storage->read('id');
117: $storage->close();
118: $this->assertSame('data', $data, 'Written value can be read back correctly');
119: }
120:
121: public function testReadWriteReadWithNullByte()
122: {
123: $sessionData = 'da'."\0".'ta';
124:
125: $storage = new PdoSessionHandler($this->getMemorySqlitePdo());
126: $storage->open('', 'sid');
127: $readData = $storage->read('id');
128: $storage->write('id', $sessionData);
129: $storage->close();
130: $this->assertSame('', $readData, 'New session returns empty string data');
131:
132: $storage->open('', 'sid');
133: $readData = $storage->read('id');
134: $storage->close();
135: $this->assertSame($sessionData, $readData, 'Written value can be read back correctly');
136: }
137:
138: public function testReadConvertsStreamToString()
139: {
140: $pdo = new MockPdo('pgsql');
141: $pdo->prepareResult = $this->getMock('PDOStatement');
142:
143: $content = 'foobar';
144: $stream = $this->createStream($content);
145:
146: $pdo->prepareResult->expects($this->once())->method('fetchAll')
147: ->will($this->returnValue(array(array($stream, 42, time()))));
148:
149: $storage = new PdoSessionHandler($pdo);
150: $result = $storage->read('foo');
151:
152: $this->assertSame($content, $result);
153: }
154:
155: public function testReadLockedConvertsStreamToString()
156: {
157: $pdo = new MockPdo('pgsql');
158: $selectStmt = $this->getMock('PDOStatement');
159: $insertStmt = $this->getMock('PDOStatement');
160:
161: $pdo->prepareResult = function ($statement) use ($selectStmt, $insertStmt) {
162: return 0 === strpos($statement, 'INSERT') ? $insertStmt : $selectStmt;
163: };
164:
165: $content = 'foobar';
166: $stream = $this->createStream($content);
167: $exception = null;
168:
169: $selectStmt->expects($this->atLeast(2))->method('fetchAll')
170: ->will($this->returnCallback(function () use (&$exception, $stream) {
171: return $exception ? array(array($stream, 42, time())) : array();
172: }));
173:
174: $insertStmt->expects($this->once())->method('execute')
175: ->will($this->returnCallback(function () use (&$exception) {
176: throw $exception = new \PDOException('', '23');
177: }));
178:
179: $storage = new PdoSessionHandler($pdo);
180: $result = $storage->read('foo');
181:
182: $this->assertSame($content, $result);
183: }
184:
185: public function testReadingRequiresExactlySameId()
186: {
187: $storage = new PdoSessionHandler($this->getMemorySqlitePdo());
188: $storage->open('', 'sid');
189: $storage->write('id', 'data');
190: $storage->write('test', 'data');
191: $storage->write('space ', 'data');
192: $storage->close();
193:
194: $storage->open('', 'sid');
195: $readDataCaseSensitive = $storage->read('ID');
196: $readDataNoCharFolding = $storage->read('tést');
197: $readDataKeepSpace = $storage->read('space ');
198: $readDataExtraSpace = $storage->read('space ');
199: $storage->close();
200:
201: $this->assertSame('', $readDataCaseSensitive, 'Retrieval by ID should be case-sensitive (collation setting)');
202: $this->assertSame('', $readDataNoCharFolding, 'Retrieval by ID should not do character folding (collation setting)');
203: $this->assertSame('data', $readDataKeepSpace, 'Retrieval by ID requires spaces as-is');
204: $this->assertSame('', $readDataExtraSpace, 'Retrieval by ID requires spaces as-is');
205: }
206:
207: 208: 209:
210: public function testWriteDifferentSessionIdThanRead()
211: {
212: $storage = new PdoSessionHandler($this->getMemorySqlitePdo());
213: $storage->open('', 'sid');
214: $storage->read('id');
215: $storage->destroy('id');
216: $storage->write('new_id', 'data_of_new_session_id');
217: $storage->close();
218:
219: $storage->open('', 'sid');
220: $data = $storage->read('new_id');
221: $storage->close();
222:
223: $this->assertSame('data_of_new_session_id', $data, 'Data of regenerated session id is available');
224: }
225:
226: public function testWrongUsageStillWorks()
227: {
228:
229: $storage = new PdoSessionHandler($this->getMemorySqlitePdo());
230: $storage->write('id', 'data');
231: $storage->write('other_id', 'other_data');
232: $storage->destroy('inexistent');
233: $storage->open('', 'sid');
234: $data = $storage->read('id');
235: $otherData = $storage->read('other_id');
236: $storage->close();
237:
238: $this->assertSame('data', $data);
239: $this->assertSame('other_data', $otherData);
240: }
241:
242: public function testSessionDestroy()
243: {
244: $pdo = $this->getMemorySqlitePdo();
245: $storage = new PdoSessionHandler($pdo);
246:
247: $storage->open('', 'sid');
248: $storage->read('id');
249: $storage->write('id', 'data');
250: $storage->close();
251: $this->assertEquals(1, $pdo->query('SELECT COUNT(*) FROM sessions')->fetchColumn());
252:
253: $storage->open('', 'sid');
254: $storage->read('id');
255: $storage->destroy('id');
256: $storage->close();
257: $this->assertEquals(0, $pdo->query('SELECT COUNT(*) FROM sessions')->fetchColumn());
258:
259: $storage->open('', 'sid');
260: $data = $storage->read('id');
261: $storage->close();
262: $this->assertSame('', $data, 'Destroyed session returns empty string');
263: }
264:
265: public function testSessionGC()
266: {
267: $previousLifeTime = ini_set('session.gc_maxlifetime', 1000);
268: $pdo = $this->getMemorySqlitePdo();
269: $storage = new PdoSessionHandler($pdo);
270:
271: $storage->open('', 'sid');
272: $storage->read('id');
273: $storage->write('id', 'data');
274: $storage->close();
275:
276: $storage->open('', 'sid');
277: $storage->read('gc_id');
278: ini_set('session.gc_maxlifetime', -1);
279: $storage->write('gc_id', 'data');
280: $storage->close();
281: $this->assertEquals(2, $pdo->query('SELECT COUNT(*) FROM sessions')->fetchColumn(), 'No session pruned because gc not called');
282:
283: $storage->open('', 'sid');
284: $data = $storage->read('gc_id');
285: $storage->gc(-1);
286: $storage->close();
287:
288: ini_set('session.gc_maxlifetime', $previousLifeTime);
289:
290: $this->assertSame('', $data, 'Session already considered garbage, so not returning data even if it is not pruned yet');
291: $this->assertEquals(1, $pdo->query('SELECT COUNT(*) FROM sessions')->fetchColumn(), 'Expired session is pruned');
292: }
293:
294: public function testGetConnection()
295: {
296: $storage = new PdoSessionHandler($this->getMemorySqlitePdo());
297:
298: $method = new \ReflectionMethod($storage, 'getConnection');
299: $method->setAccessible(true);
300:
301: $this->assertInstanceOf('\PDO', $method->invoke($storage));
302: }
303:
304: public function testGetConnectionConnectsIfNeeded()
305: {
306: $storage = new PdoSessionHandler('sqlite::memory:');
307:
308: $method = new \ReflectionMethod($storage, 'getConnection');
309: $method->setAccessible(true);
310:
311: $this->assertInstanceOf('\PDO', $method->invoke($storage));
312: }
313:
314: private function createStream($content)
315: {
316: $stream = tmpfile();
317: fwrite($stream, $content);
318: fseek($stream, 0);
319:
320: return $stream;
321: }
322: }
323:
324: class MockPdo extends \PDO
325: {
326: public $prepareResult;
327: private $driverName;
328: private $errorMode;
329:
330: public function __construct($driverName = null, $errorMode = null)
331: {
332: $this->driverName = $driverName;
333: $this->errorMode = null !== $errorMode ?: \PDO::ERRMODE_EXCEPTION;
334: }
335:
336: public function getAttribute($attribute)
337: {
338: if (\PDO::ATTR_ERRMODE === $attribute) {
339: return $this->errorMode;
340: }
341:
342: if (\PDO::ATTR_DRIVER_NAME === $attribute) {
343: return $this->driverName;
344: }
345:
346: return parent::getAttribute($attribute);
347: }
348:
349: public function prepare($statement, $driverOptions = array())
350: {
351: return is_callable($this->prepareResult)
352: ? call_user_func($this->prepareResult, $statement, $driverOptions)
353: : $this->prepareResult;
354: }
355:
356: public function beginTransaction()
357: {
358: }
359: }
360: