‪TYPO3CMS  ‪main
RedisSessionBackendTest.php
Go to the documentation of this file.
1 <?php
2 
3 declare(strict_types=1);
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 
19 
20 use PHPUnit\Framework\Attributes\RequiresPhpExtension;
21 use PHPUnit\Framework\Attributes\Test;
26 use TYPO3\TestingFramework\Core\Functional\FunctionalTestCase;
27 
28 #[RequiresPhpExtension('redis')]
29 final class ‪RedisSessionBackendTest extends FunctionalTestCase
30 {
31  protected bool ‪$initializeDatabase = false;
32 
34  private \Redis ‪$redis;
35 
36  protected array ‪$testSessionRecord = [
37  // RedisSessionBackend::hash('randomSessionId') with encryption key 12345
38  'ses_id' => '21c0e911565a67315cdc384889c470fd291feafbfa62e31ecf7409430640bc7a',
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  protected function ‪setUp(): void
45  {
46  parent::setUp();
47  ‪$GLOBALS['TYPO3_CONF_VARS']['SYS']['encryptionKey'] = '12345';
48  if (!getenv('typo3TestingRedisHost')) {
49  self::markTestSkipped('environment variable "typo3TestingRedisHost" must be set to run this test');
50  }
51  // Note we assume that if that typo3TestingRedisHost env is set, we can use that for testing,
52  // there is no test to see if the daemon is actually up and running. Tests will fail if env
53  // is set but daemon is down.
54  // We know this env is set, otherwise setUp() would skip the tests
55  $redisHost = getenv('typo3TestingRedisHost');
56  // If typo3TestingRedisPort env is set, use it, otherwise fall back to standard port
57  $env = getenv('typo3TestingRedisPort');
58  $redisPort = is_string($env) ? (int)$env : 6379;
59  $this->redis = new \Redis();
60  $this->redis->connect($redisHost, $redisPort);
61  $this->redis->select(0);
62  // Clear db to ensure no sessions exist currently
63  $this->redis->flushDB();
64  $this->subject = new ‪RedisSessionBackend();
65  $this->subject->initialize(
66  'default',
67  [
68  'database' => 0,
69  'port' => $redisPort,
70  'hostname' => $redisHost,
71  ]
72  );
73  }
74 
75  #[Test]
76  public function ‪cannotUpdateNonExistingRecord(): void
77  {
78  $this->expectException(SessionNotUpdatedException::class);
79  $this->expectExceptionCode(1484389971);
80  $this->subject->update('iSoNotExist', []);
81  }
82 
83  #[Test]
84  public function ‪canValidateSessionBackend(): void
85  {
86  $this->subject->validateConfiguration();
87  }
88 
89  #[Test]
90  public function ‪sessionDataIsStoredProperly(): void
91  {
92  ‪$record = $this->subject->set('randomSessionId', $this->testSessionRecord);
93  $expected = array_merge($this->testSessionRecord, ['ses_tstamp' => ‪$GLOBALS['EXEC_TIME']]);
94  self::assertEquals(‪$record, $expected);
95  $result = $this->subject->get('randomSessionId');
96  self::assertSame($expected, $result);
97  }
98 
99  #[Test]
101  {
102  ‪$record = $this->subject->set('randomSessionId', array_merge($this->testSessionRecord, ['ses_userid' => 0]));
103  $expected = array_merge($this->testSessionRecord, ['ses_userid' => 0, 'ses_tstamp' => ‪$GLOBALS['EXEC_TIME']]);
104  self::assertEquals(‪$record, $expected);
105  self::assertSame($expected, $this->subject->get('randomSessionId'));
106  }
107 
108  #[Test]
110  {
111  $this->expectException(SessionNotFoundException::class);
112  $this->expectExceptionCode(1481885583);
113  $this->subject->get('IDoNotExist');
114  }
115 
116  #[Test]
117  public function ‪mergeSessionDataWithNewData(): void
118  {
119  $this->subject->set('randomSessionId', $this->testSessionRecord);
120  $updateData = [
121  'ses_data' => serialize(['foo' => 'baz', 'idontwantto' => 'set the world on fire']),
122  'ses_tstamp' => ‪$GLOBALS['EXEC_TIME'],
123  ];
124  $expectedMergedData = array_merge($this->testSessionRecord, $updateData);
125  $this->subject->update('randomSessionId', $updateData);
126  $fetchedRecord = $this->subject->get('randomSessionId');
127  self::assertSame($expectedMergedData, $fetchedRecord);
128  }
129 
130  #[Test]
132  {
133  $this->expectException(SessionNotCreatedException::class);
134  $this->expectExceptionCode(1481895647);
135  $this->subject->set('randomSessionId', $this->testSessionRecord);
136  $newData = array_merge($this->testSessionRecord, ['ses_data' => serialize(['foo' => 'baz', 'idontwantto' => 'set the world on fire'])]);
137  $this->subject->set('randomSessionId', $newData);
138  }
139 
140  #[Test]
141  public function ‪cannotChangeSessionId(): void
142  {
143  $this->subject->set('randomSessionId', $this->testSessionRecord);
144  $newSessionId = 'newRandomSessionId';
145  $newData = array_merge($this->testSessionRecord, ['ses_id' => $newSessionId]);
146  // old session id has to exist, no exception must be thrown at this point
147  $this->subject->get('randomSessionId');
148  // Change session id
149  $this->subject->update('randomSessionId', $newData);
150  // no session with key newRandomSessionId should exist
151  $this->expectException(SessionNotFoundException::class);
152  $this->expectExceptionCode(1481885583);
153  $this->subject->get('newRandomSessionId');
154  }
155 
156  #[Test]
157  public function ‪sessionGetsDestroyed(): void
158  {
159  $this->subject->set('randomSessionId', $this->testSessionRecord);
160  // Remove session
161  self::assertTrue($this->subject->remove('randomSessionId'));
162  // Check if session was really removed
163  $this->expectException(SessionNotFoundException::class);
164  $this->expectExceptionCode(1481885583);
165  $this->subject->get('randomSessionId');
166  }
167 
168  #[Test]
169  public function ‪canLoadAllSessions(): void
170  {
171  $this->subject->set('randomSessionId', $this->testSessionRecord);
172  $this->subject->set('randomSessionId2', $this->testSessionRecord);
173  // Check if session was really removed
174  self::assertCount(2, $this->subject->getAll());
175  }
176 
177  #[Test]
178  public function ‪canCollectGarbage(): void
179  {
180  ‪$GLOBALS['EXEC_TIME'] = 150;
181  $authenticatedSession = array_merge($this->testSessionRecord, ['ses_id' => 'authenticatedSession']);
182  $anonymousSession = array_merge($this->testSessionRecord, ['ses_id' => 'anonymousSession', 'ses_userid' => 0]);
183  $this->subject->set('authenticatedSession', $authenticatedSession);
184  $this->subject->set('anonymousSession', $anonymousSession);
185  // Assert that we set authenticated session correctly
186  self::assertSame(
187  $authenticatedSession['ses_data'],
188  $this->subject->get('authenticatedSession')['ses_data']
189  );
190  self::assertSame(
191  $authenticatedSession['ses_userid'],
192  $this->subject->get('authenticatedSession')['ses_userid']
193  );
194  // assert that we set anonymous session correctly
195  self::assertSame(
196  $anonymousSession['ses_data'],
197  $this->subject->get('anonymousSession')['ses_data']
198  );
199  // Run the garbage collection
200  ‪$GLOBALS['EXEC_TIME'] = 200;
201  // 150 + 10 < 200 but 150 + 60 >= 200
202  $this->subject->collectGarbage(60, 10);
203  // Authenticated session should still be there
204  self::assertSame(
205  $authenticatedSession['ses_data'],
206  $this->subject->get('authenticatedSession')['ses_data']
207  );
208  self::assertSame(
209  $authenticatedSession['ses_userid'],
210  $this->subject->get('authenticatedSession')['ses_userid']
211  );
212  // Non-authenticated session should be removed
213  $this->expectException(SessionNotFoundException::class);
214  $this->expectExceptionCode(1481885583);
215  $this->subject->get('anonymousSession');
216  }
217 
218  #[Test]
219  public function ‪canPartiallyUpdateAfterGet(): void
220  {
221  $updatedRecord = array_merge(
222  $this->testSessionRecord,
223  ['ses_tstamp' => ‪$GLOBALS['EXEC_TIME']]
224  );
225  $sessionId = 'randomSessionId';
226  $this->subject->set($sessionId, $this->testSessionRecord);
227  $this->subject->update($sessionId, []);
228  self::assertSame($updatedRecord, $this->subject->get($sessionId));
229  }
230 }
‪TYPO3\CMS\Core\Tests\Functional\Session\Backend\RedisSessionBackendTest\$initializeDatabase
‪bool $initializeDatabase
Definition: RedisSessionBackendTest.php:31
‪TYPO3\CMS\Core\Tests\Functional\Session\Backend\RedisSessionBackendTest\setUp
‪setUp()
Definition: RedisSessionBackendTest.php:44
‪TYPO3\CMS\Core\Session\Backend\Exception\SessionNotCreatedException
Definition: SessionNotCreatedException.php:23
‪TYPO3\CMS\Core\Tests\Functional\Session\Backend\RedisSessionBackendTest\mergeSessionDataWithNewData
‪mergeSessionDataWithNewData()
Definition: RedisSessionBackendTest.php:117
‪TYPO3\CMS\Core\Tests\Functional\Session\Backend\RedisSessionBackendTest\canPartiallyUpdateAfterGet
‪canPartiallyUpdateAfterGet()
Definition: RedisSessionBackendTest.php:219
‪TYPO3\CMS\Core\Tests\Functional\Session\Backend\RedisSessionBackendTest\anonymousSessionDataIsStoredProperly
‪anonymousSessionDataIsStoredProperly()
Definition: RedisSessionBackendTest.php:100
‪TYPO3\CMS\Core\Tests\Functional\Session\Backend\RedisSessionBackendTest\existingSessionMustNotBeOverridden
‪existingSessionMustNotBeOverridden()
Definition: RedisSessionBackendTest.php:131
‪TYPO3\CMS\Core\Tests\Functional\Session\Backend\RedisSessionBackendTest\$testSessionRecord
‪array $testSessionRecord
Definition: RedisSessionBackendTest.php:36
‪TYPO3\CMS\Core\Tests\Functional\Session\Backend\RedisSessionBackendTest\$subject
‪RedisSessionBackend $subject
Definition: RedisSessionBackendTest.php:33
‪TYPO3\CMS\Core\Tests\Functional\Session\Backend\RedisSessionBackendTest\canValidateSessionBackend
‪canValidateSessionBackend()
Definition: RedisSessionBackendTest.php:84
‪TYPO3\CMS\Webhooks\Message\$record
‪identifier readonly int readonly array $record
Definition: PageModificationMessage.php:36
‪TYPO3\CMS\Core\Tests\Functional\Session\Backend\RedisSessionBackendTest\sessionDataIsStoredProperly
‪sessionDataIsStoredProperly()
Definition: RedisSessionBackendTest.php:90
‪TYPO3\CMS\Core\Session\Backend\RedisSessionBackend
Definition: RedisSessionBackend.php:33
‪TYPO3\CMS\Core\Tests\Functional\Session\Backend\RedisSessionBackendTest\canCollectGarbage
‪canCollectGarbage()
Definition: RedisSessionBackendTest.php:178
‪TYPO3\CMS\Core\Session\Backend\Exception\SessionNotUpdatedException
Definition: SessionNotUpdatedException.php:23
‪TYPO3\CMS\Core\Tests\Functional\Session\Backend\RedisSessionBackendTest\canLoadAllSessions
‪canLoadAllSessions()
Definition: RedisSessionBackendTest.php:169
‪TYPO3\CMS\Core\Tests\Functional\Session\Backend\RedisSessionBackendTest\cannotChangeSessionId
‪cannotChangeSessionId()
Definition: RedisSessionBackendTest.php:141
‪$GLOBALS
‪$GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['adminpanel']['modules']
Definition: ext_localconf.php:25
‪TYPO3\CMS\Core\Tests\Functional\Session\Backend
Definition: DatabaseSessionBackendTest.php:18
‪TYPO3\CMS\Core\Tests\Functional\Session\Backend\RedisSessionBackendTest\$redis
‪Redis $redis
Definition: RedisSessionBackendTest.php:34
‪TYPO3\CMS\Core\Tests\Functional\Session\Backend\RedisSessionBackendTest\sessionGetsDestroyed
‪sessionGetsDestroyed()
Definition: RedisSessionBackendTest.php:157
‪TYPO3\CMS\Core\Tests\Functional\Session\Backend\RedisSessionBackendTest\cannotUpdateNonExistingRecord
‪cannotUpdateNonExistingRecord()
Definition: RedisSessionBackendTest.php:76
‪TYPO3\CMS\Core\Tests\Functional\Session\Backend\RedisSessionBackendTest\throwExceptionOnNonExistingSessionId
‪throwExceptionOnNonExistingSessionId()
Definition: RedisSessionBackendTest.php:109
‪TYPO3\CMS\Core\Session\Backend\Exception\SessionNotFoundException
Definition: SessionNotFoundException.php:23
‪TYPO3\CMS\Core\Tests\Functional\Session\Backend\RedisSessionBackendTest
Definition: RedisSessionBackendTest.php:30