76a8bc396fca097e5442948b20ee7822dfc7e428
[Packages/TYPO3.CMS.git] / typo3 / sysext / core / Tests / Functional / Session / Backend / RedisSessionBackendTest.php
1 <?php
2 declare(strict_types = 1);
3 namespace TYPO3\CMS\Core\Tests\Functional\Session\Backend;
4
5 /*
6 * This file is part of the TYPO3 CMS project.
7 *
8 * It is free software; you can redistribute it and/or modify it under
9 * the terms of the GNU General Public License, either version 2
10 * of the License, or any later version.
11 *
12 * For the full copyright and license information, please read the
13 * LICENSE.txt file that was distributed with this source code.
14 *
15 * The TYPO3 project - inspiring people to share!
16 */
17
18 use TYPO3\CMS\Core\Session\Backend\Exception\SessionNotCreatedException;
19 use TYPO3\CMS\Core\Session\Backend\Exception\SessionNotFoundException;
20 use TYPO3\CMS\Core\Session\Backend\Exception\SessionNotUpdatedException;
21 use TYPO3\CMS\Core\Session\Backend\RedisSessionBackend;
22 use TYPO3\TestingFramework\Core\Functional\FunctionalTestCase;
23
24 /**
25 * Test case
26 */
27 class RedisSessionBackendTest extends FunctionalTestCase
28 {
29 /**
30 * @var RedisSessionBackend Prepared and connected redis test subject
31 */
32 protected $subject;
33
34 /**
35 * @var array
36 */
37 protected $testSessionRecord = [
38 'ses_id' => 'randomSessionId',
39 'ses_userid' => 1,
40 // serialize(['foo' => 'bar', 'boo' => 'far'])
41 'ses_data' => 'a:2:{s:3:"foo";s:3:"bar";s:3:"boo";s:3:"far";}',
42 ];
43
44 /**
45 * Set configuration for RedisSessionBackend
46 */
47 protected function setUp()
48 {
49 parent::setUp();
50
51 if (!extension_loaded('redis')) {
52 $this->markTestSkipped('redis extension was not available');
53 }
54 try {
55 if (!@fsockopen('127.0.0.1', 6379)) {
56 $this->markTestSkipped('redis server not reachable');
57 }
58 } catch (\Exception $e) {
59 $this->markTestSkipped('redis server not reachable');
60 }
61 $redis = new \Redis();
62 $redis->connect('127.0.0.1');
63 $redis->select(0);
64 // Clear db to ensure no sessions exist currently
65 $redis->flushDB();
66
67 $this->subject = new RedisSessionBackend();
68 $this->subject->initialize(
69 'default',
70 [
71 'database' => 0,
72 'port' => 6379,
73 'hostname' => 'localhost',
74 ]
75 );
76 }
77
78 /**
79 * @test
80 */
81 public function cannotUpdateNonExistingRecord()
82 {
83 $this->expectException(SessionNotUpdatedException::class);
84 $this->expectExceptionCode(1484389971);
85 $this->subject->update('iSoNotExist', []);
86 }
87
88 /**
89 * @test
90 */
91 public function canValidateSessionBackend()
92 {
93 $this->subject->validateConfiguration();
94 }
95
96 /**
97 * @test
98 * @covers SessionBackendInterface::set
99 */
100 public function sessionDataIsStoredProperly()
101 {
102 $record = $this->subject->set('randomSessionId', $this->testSessionRecord);
103
104 $expected = array_merge($this->testSessionRecord, ['ses_tstamp' => $GLOBALS['EXEC_TIME']]);
105
106 $this->assertEquals($record, $expected);
107 $this->assertArraySubset($expected, $this->subject->get('randomSessionId'));
108 }
109
110 /**
111 * @test
112 */
113 public function anonymousSessionDataIsStoredProperly()
114 {
115 $record = $this->subject->set('randomSessionId', array_merge($this->testSessionRecord, ['ses_anonymous' => 1]));
116
117 $expected = array_merge($this->testSessionRecord, ['ses_anonymous' => 1, 'ses_tstamp' => $GLOBALS['EXEC_TIME']]);
118
119 $this->assertEquals($record, $expected);
120 $this->assertArraySubset($expected, $this->subject->get('randomSessionId'));
121 }
122
123 /**
124 * @test
125 * @covers SessionBackendInterface::get
126 */
127 public function throwExceptionOnNonExistingSessionId()
128 {
129 $this->expectException(SessionNotFoundException::class);
130 $this->expectExceptionCode(1481885583);
131 $this->subject->get('IDoNotExist');
132 }
133
134 /**
135 * @test
136 * @covers SessionBackendInterface::update
137 */
138 public function mergeSessionDataWithNewData()
139 {
140 $this->subject->set('randomSessionId', $this->testSessionRecord);
141
142 $updateData = [
143 'ses_data' => serialize(['foo' => 'baz', 'idontwantto' => 'set the world on fire']),
144 'ses_tstamp' => $GLOBALS['EXEC_TIME']
145 ];
146 $expectedMergedData = array_merge($this->testSessionRecord, $updateData);
147 $this->subject->update('randomSessionId', $updateData);
148 $fetchedRecord = $this->subject->get('randomSessionId');
149 $this->assertArraySubset($expectedMergedData, $fetchedRecord);
150 }
151
152 /**
153 * @test
154 * @covers SessionBackendInterface::set
155 */
156 public function existingSessionMustNotBeOverridden()
157 {
158 $this->expectException(SessionNotCreatedException::class);
159 $this->expectExceptionCode(1481895647);
160
161 $this->subject->set('randomSessionId', $this->testSessionRecord);
162
163 $newData = array_merge($this->testSessionRecord, ['ses_data' => serialize(['foo' => 'baz', 'idontwantto' => 'set the world on fire'])]);
164 $this->subject->set('randomSessionId', $newData);
165 }
166
167 /**
168 * @test
169 * @covers SessionBackendInterface::update
170 */
171 public function cannotChangeSessionId()
172 {
173 $this->subject->set('randomSessionId', $this->testSessionRecord);
174
175 $newSessionId = 'newRandomSessionId';
176 $newData = array_merge($this->testSessionRecord, ['ses_id' => $newSessionId]);
177
178 // old session id has to exist, no exception must be thrown at this point
179 $this->subject->get('randomSessionId');
180
181 // Change session id
182 $this->subject->update('randomSessionId', $newData);
183
184 // no session with key newRandomSessionId should exist
185 $this->expectException(SessionNotFoundException::class);
186 $this->expectExceptionCode(1481885583);
187 $this->subject->get('newRandomSessionId');
188 }
189
190 /**
191 * @test
192 * @covers SessionBackendInterface::remove
193 */
194 public function sessionGetsDestroyed()
195 {
196 $this->subject->set('randomSessionId', $this->testSessionRecord);
197
198 // Remove session
199 $this->assertTrue($this->subject->remove('randomSessionId'));
200
201 // Check if session was really removed
202 $this->expectException(SessionNotFoundException::class);
203 $this->expectExceptionCode(1481885583);
204 $this->subject->get('randomSessionId');
205 }
206
207 /**
208 * @test
209 * @covers SessionBackendInterface::getAll
210 */
211 public function canLoadAllSessions()
212 {
213 $this->subject->set('randomSessionId', $this->testSessionRecord);
214 $this->subject->set('randomSessionId2', $this->testSessionRecord);
215
216 // Check if session was really removed
217 $this->assertEquals(2, count($this->subject->getAll()));
218 }
219
220 /**
221 * @test
222 */
223 public function canCollectGarbage()
224 {
225 $GLOBALS['EXEC_TIME'] = 150;
226 $authenticatedSession = array_merge($this->testSessionRecord, ['ses_id' => 'authenticatedSession']);
227 $anonymousSession = array_merge($this->testSessionRecord, ['ses_id' => 'anonymousSession', 'ses_anonymous' => 1]);
228
229 $this->subject->set('authenticatedSession', $authenticatedSession);
230 $this->subject->set('anonymousSession', $anonymousSession);
231
232 // Assert that we set authenticated session correctly
233 $this->assertArraySubset(
234 $authenticatedSession,
235 $this->subject->get('authenticatedSession')
236 );
237
238 // assert that we set anonymous session correctly
239 $this->assertArraySubset(
240 $anonymousSession,
241 $this->subject->get('anonymousSession')
242 );
243
244 // Run the garbage collection
245 $GLOBALS['EXEC_TIME'] = 200;
246 // 150 + 10 < 200 but 150 + 60 >= 200
247 $this->subject->collectGarbage(60, 10);
248
249 // Authenticated session should still be there
250 $this->assertArraySubset(
251 $authenticatedSession,
252 $this->subject->get('authenticatedSession')
253 );
254
255 // Non-authenticated session should be removed
256 $this->expectException(SessionNotFoundException::class);
257 $this->expectExceptionCode(1481885583);
258 $this->subject->get('anonymousSession');
259 }
260
261 /**
262 * @test
263 */
264 public function canPartiallyUpdateAfterGet()
265 {
266 $updatedRecord = array_merge(
267 $this->testSessionRecord,
268 ['ses_tstamp' => $GLOBALS['EXEC_TIME']]
269 );
270 $sessionId = 'randomSessionId';
271 $this->subject->set($sessionId, $this->testSessionRecord);
272 $this->subject->update($sessionId, []);
273 $this->assertArraySubset($updatedRecord, $this->subject->get($sessionId));
274 }
275 }