e52fbd2f84
Storage of session metadata has moved into the session handler class. This allows for other classes to fully control session handling and removes the dependancy on the core sessions database table. Previously, the standard method of interaction with the session metadata was direct DB calls; this may break other plugins as there are now proper APIs available through the session manager. Co-authored-by: Darren Cocco <moodle@darren.cocco.id.au> Co-authored-by: Trisha Milan <trishamilan@catalyst-au.net> Co-authored-by: Andrew Nicols <andrew@nicols.co.uk>
591 lines
22 KiB
PHP
591 lines
22 KiB
PHP
<?php
|
|
// This file is part of Moodle - http://moodle.org/
|
|
//
|
|
// Moodle is free software: you can redistribute it and/or modify
|
|
// it under the terms of the GNU General Public License as published by
|
|
// the Free Software Foundation, either version 3 of the License, or
|
|
// (at your option) any later version.
|
|
//
|
|
// Moodle is distributed in the hope that it will be useful,
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
// GNU General Public License for more details.
|
|
//
|
|
// You should have received a copy of the GNU General Public License
|
|
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
namespace core\session;
|
|
|
|
use core\tests\session\mock_handler;
|
|
use Redis;
|
|
use RedisException;
|
|
|
|
/**
|
|
* Unit tests for classes/session/redis.php.
|
|
*
|
|
* NOTE: in order to execute this test you need to set up
|
|
* Redis server and add configuration a constant
|
|
* to config.php or phpunit.xml configuration file:
|
|
*
|
|
* define('TEST_SESSION_REDIS_HOST', '127.0.0.1');
|
|
*
|
|
* @package core
|
|
* @covers \core\session\redis
|
|
* @author Russell Smith <mr-russ@smith2001.net>
|
|
* @copyright 2016 Russell Smith
|
|
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
|
* @runClassInSeparateProcess
|
|
* @covers \core\session\redis
|
|
*/
|
|
final class redis_test extends \advanced_testcase {
|
|
/** @var string $keyprefix This key prefix used when testing Redis */
|
|
protected string $keyprefix = '';
|
|
/** @var ?Redis $redis The current testing redis connection */
|
|
protected ?Redis $redis = null;
|
|
/** @var bool $encrypted Is the current testing redis connection encrypted*/
|
|
protected bool $encrypted = false;
|
|
/** @var int $acquiretimeout how long we wait for session lock in seconds when testing Redis */
|
|
protected int $acquiretimeout = 1;
|
|
/** @var int $lockexpire how long to wait in seconds before expiring the lock when testing Redis */
|
|
protected int $lockexpire = 70;
|
|
|
|
#[\Override]
|
|
public function setUp(): void {
|
|
global $CFG;
|
|
parent::setUp();
|
|
|
|
if (!extension_loaded('redis')) {
|
|
$this->markTestSkipped('Redis extension not loaded.');
|
|
}
|
|
if (!defined('TEST_SESSION_REDIS_HOST')) {
|
|
$this->markTestSkipped('Session test server not set. define: TEST_SESSION_REDIS_HOST');
|
|
}
|
|
$version = phpversion('Redis');
|
|
if (!$version) {
|
|
$this->markTestSkipped('Redis extension version missing');
|
|
} else if (version_compare($version, \core\session\redis::REDIS_MIN_EXTENSION_VERSION) <= 0) {
|
|
$this->markTestSkipped('Redis extension version must be at least ' . \core\session\redis::REDIS_MIN_EXTENSION_VERSION .
|
|
': now running "' . $version . '"');
|
|
}
|
|
|
|
$this->resetAfterTest();
|
|
|
|
$this->keyprefix = 'phpunit'.rand(1, 100000);
|
|
|
|
if (strpos(TEST_SESSION_REDIS_HOST, ':')) {
|
|
list($server, $port) = explode(':', TEST_SESSION_REDIS_HOST);
|
|
} else {
|
|
$server = TEST_SESSION_REDIS_HOST;
|
|
$port = 6379;
|
|
}
|
|
$CFG->session_redis_host = $server;
|
|
$CFG->session_redis_port = $port;
|
|
|
|
$opts = [];
|
|
if (defined('TEST_SESSION_REDIS_ENCRYPT') && TEST_SESSION_REDIS_ENCRYPT) {
|
|
$this->encrypted = true;
|
|
$sslopts = $CFG->session_redis_encrypt = ['verify_peer' => false, 'verify_peer_name' => false];
|
|
$opts['stream'] = $sslopts;
|
|
}
|
|
$CFG->session_redis_prefix = $this->keyprefix;
|
|
|
|
// Set a very short lock timeout to ensure tests run quickly. We are running single threaded,
|
|
// so unless we lock and expect it to be there, we will always see a lock.
|
|
$CFG->session_redis_acquire_lock_timeout = $this->acquiretimeout;
|
|
$CFG->session_redis_lock_expire = $this->lockexpire;
|
|
|
|
$this->redis = new Redis();
|
|
$this->redis->connect($server, $port, 1, null, 1, 0, $opts);
|
|
if (!$this->redis->ping()) {
|
|
$this->markTestSkipped("Redis ping failed");
|
|
}
|
|
}
|
|
|
|
public function tearDown(): void {
|
|
if (!extension_loaded('redis') || !defined('TEST_SESSION_REDIS_HOST')) {
|
|
return;
|
|
}
|
|
|
|
$list = $this->redis->keys($this->keyprefix.'*');
|
|
foreach ($list as $keyname) {
|
|
$this->redis->del($keyname);
|
|
}
|
|
$this->redis->close();
|
|
parent::tearDown();
|
|
}
|
|
|
|
public function test_normal_session_read_only(): void {
|
|
$sess = new \core\session\redis();
|
|
$sess->set_requires_write_lock(false);
|
|
$sess->init();
|
|
$this->assertSame('', $sess->read('sess1'));
|
|
$this->assertTrue($sess->close());
|
|
}
|
|
|
|
public function test_normal_session_start_stop_works(): void {
|
|
$sess = new \core\session\redis();
|
|
$sess->init();
|
|
$sess->set_requires_write_lock(true);
|
|
$this->assertTrue($sess->open('Not used', 'Not used'));
|
|
$this->assertSame('', $sess->read('sess1'));
|
|
$this->assertTrue($sess->write('sess1', 'DATA'));
|
|
$this->assertTrue($sess->close());
|
|
|
|
// Read the session again to ensure locking did what it should.
|
|
$this->assertTrue($sess->open('Not used', 'Not used'));
|
|
$this->assertSame('DATA', $sess->read('sess1'));
|
|
$this->assertTrue($sess->write('sess1', 'DATA-new'));
|
|
$this->assertTrue($sess->close());
|
|
$this->assert_session_no_locks();
|
|
}
|
|
|
|
public function test_compression_read_and_write_works(): void {
|
|
global $CFG;
|
|
|
|
$CFG->session_redis_compressor = \core\session\redis::COMPRESSION_GZIP;
|
|
|
|
$sess = new \core\session\redis();
|
|
$sess->init();
|
|
$this->assertTrue($sess->write('sess1', 'DATA'));
|
|
$this->assertSame('DATA', $sess->read('sess1'));
|
|
$this->assertTrue($sess->close());
|
|
|
|
if (extension_loaded('zstd')) {
|
|
$CFG->session_redis_compressor = \core\session\redis::COMPRESSION_ZSTD;
|
|
|
|
$sess = new \core\session\redis();
|
|
$sess->init();
|
|
$this->assertTrue($sess->write('sess2', 'DATA'));
|
|
$this->assertSame('DATA', $sess->read('sess2'));
|
|
$this->assertTrue($sess->close());
|
|
}
|
|
|
|
$CFG->session_redis_compressor = \core\session\redis::COMPRESSION_NONE;
|
|
}
|
|
|
|
public function test_session_blocks_with_existing_session(): void {
|
|
$sess = new \core\session\redis();
|
|
$sess->init();
|
|
$sess->set_requires_write_lock(true);
|
|
$this->assertTrue($sess->open('Not used', 'Not used'));
|
|
$this->assertSame('', $sess->read('sess1'));
|
|
$this->assertTrue($sess->write('sess1', 'DATA'));
|
|
$this->assertTrue($sess->close());
|
|
|
|
// Sessions are not locked until they have been saved once.
|
|
$this->assertTrue($sess->open('Not used', 'Not used'));
|
|
$this->assertSame('DATA', $sess->read('sess1'));
|
|
|
|
$sessblocked = new \core\session\redis();
|
|
$sessblocked->init();
|
|
$sessblocked->set_requires_write_lock(true);
|
|
$this->assertTrue($sessblocked->open('Not used', 'Not used'));
|
|
|
|
// Trap the error log and send it to stdOut so we can expect output at the right times.
|
|
$errorlog = tempnam(sys_get_temp_dir(), "rediserrorlog");
|
|
$this->iniSet('error_log', $errorlog);
|
|
try {
|
|
$sessblocked->read('sess1');
|
|
$this->fail('Session lock must fail to be obtained.');
|
|
} catch (\core\session\exception $e) {
|
|
$this->assertStringContainsString("Unable to obtain lock for session id session_se", $e->getMessage());
|
|
$this->assertStringContainsString('within 1 sec.', $e->getMessage());
|
|
$this->assertStringContainsString('session lock timeout (1 min 10 secs) ', $e->getMessage());
|
|
$this->assertStringContainsString('Cannot obtain session lock for sid: session_sess1', file_get_contents($errorlog));
|
|
}
|
|
|
|
$this->assertTrue($sessblocked->close());
|
|
$this->assertTrue($sess->write('sess1', 'DATA-new'));
|
|
$this->assertTrue($sess->close());
|
|
$this->assert_session_no_locks();
|
|
}
|
|
|
|
public function test_session_is_destroyed_when_it_does_not_exist(): void {
|
|
$sess = new \core\session\redis();
|
|
$sess->init();
|
|
$sess->set_requires_write_lock(true);
|
|
$this->assertTrue($sess->open('Not used', 'Not used'));
|
|
$this->assertTrue($sess->destroy('sess-destroy'));
|
|
$this->assert_session_no_locks();
|
|
}
|
|
|
|
public function test_session_is_destroyed_when_we_have_it_open(): void {
|
|
$sess = new \core\session\redis();
|
|
$sess->init();
|
|
$sess->set_requires_write_lock(true);
|
|
$this->assertTrue($sess->open('Not used', 'Not used'));
|
|
$this->assertSame('', $sess->read('sess-destroy'));
|
|
$this->assertTrue($sess->destroy('sess-destroy'));
|
|
$this->assertTrue($sess->close());
|
|
$this->assert_session_no_locks();
|
|
}
|
|
|
|
public function test_multiple_sessions_do_not_interfere_with_each_other(): void {
|
|
$sess1 = new \core\session\redis();
|
|
$sess1->set_requires_write_lock(true);
|
|
$sess1->init();
|
|
$sess2 = new \core\session\redis();
|
|
$sess2->set_requires_write_lock(true);
|
|
$sess2->init();
|
|
|
|
// Initialize session 1.
|
|
$this->assertTrue($sess1->open('Not used', 'Not used'));
|
|
$this->assertSame('', $sess1->read('sess1'));
|
|
$this->assertTrue($sess1->write('sess1', 'DATA'));
|
|
$this->assertTrue($sess1->close());
|
|
|
|
// Initialize session 2.
|
|
$this->assertTrue($sess2->open('Not used', 'Not used'));
|
|
$this->assertSame('', $sess2->read('sess2'));
|
|
$this->assertTrue($sess2->write('sess2', 'DATA2'));
|
|
$this->assertTrue($sess2->close());
|
|
|
|
// Open and read session 1 and 2.
|
|
$this->assertTrue($sess1->open('Not used', 'Not used'));
|
|
$this->assertSame('DATA', $sess1->read('sess1'));
|
|
$this->assertTrue($sess2->open('Not used', 'Not used'));
|
|
$this->assertSame('DATA2', $sess2->read('sess2'));
|
|
|
|
// Write both sessions.
|
|
$this->assertTrue($sess1->write('sess1', 'DATAX'));
|
|
$this->assertTrue($sess2->write('sess2', 'DATA2X'));
|
|
|
|
// Read both sessions.
|
|
$this->assertTrue($sess1->open('Not used', 'Not used'));
|
|
$this->assertTrue($sess2->open('Not used', 'Not used'));
|
|
$this->assertEquals('DATAX', $sess1->read('sess1'));
|
|
$this->assertEquals('DATA2X', $sess2->read('sess2'));
|
|
|
|
// Close both sessions
|
|
$this->assertTrue($sess1->close());
|
|
$this->assertTrue($sess2->close());
|
|
|
|
// Read the session again to ensure locking did what it should.
|
|
$this->assert_session_no_locks();
|
|
}
|
|
|
|
public function test_multiple_sessions_work_with_a_single_instance(): void {
|
|
$sess = new \core\session\redis();
|
|
$sess->init();
|
|
$sess->set_requires_write_lock(true);
|
|
|
|
// Initialize session 1.
|
|
$this->assertTrue($sess->open('Not used', 'Not used'));
|
|
$this->assertSame('', $sess->read('sess1'));
|
|
$this->assertTrue($sess->write('sess1', 'DATA'));
|
|
$this->assertSame('', $sess->read('sess2'));
|
|
$this->assertTrue($sess->write('sess2', 'DATA2'));
|
|
$this->assertSame('DATA', $sess->read('sess1'));
|
|
$this->assertSame('DATA2', $sess->read('sess2'));
|
|
$this->assertTrue($sess->destroy('sess2'));
|
|
|
|
$this->assertTrue($sess->close());
|
|
$this->assert_session_no_locks();
|
|
|
|
$this->assertTrue($sess->close());
|
|
}
|
|
|
|
public function test_session_exists_returns_valid_values(): void {
|
|
$sess = new \core\session\redis();
|
|
$sess->init();
|
|
$sess->set_requires_write_lock(true);
|
|
|
|
$this->assertTrue($sess->open('Not used', 'Not used'));
|
|
$this->assertSame('', $sess->read('sess1'));
|
|
|
|
$this->assertFalse($sess->session_exists('sess1'), 'Session must not exist yet, it has not been saved');
|
|
$this->assertTrue($sess->write('sess1', 'DATA'));
|
|
$this->assertTrue($sess->session_exists('sess1'), 'Session must exist now.');
|
|
$this->assertTrue($sess->destroy('sess1'));
|
|
$this->assertFalse($sess->session_exists('sess1'), 'Session should be destroyed.');
|
|
}
|
|
|
|
public function test_destroy_removes_the_session_from_redis(): void {
|
|
global $DB;
|
|
|
|
$sess = new \core\session\redis();
|
|
$sess->init();
|
|
|
|
$mockhandler = new mock_handler();
|
|
|
|
$this->assertTrue($sess->open('Not used', 'Not used'));
|
|
$this->assertTrue($sess->write('sess1', 'DATA'));
|
|
$this->assertTrue($sess->write('sess2', 'DATA'));
|
|
$this->assertTrue($sess->write('sess3', 'DATA'));
|
|
|
|
$sessiondata = new \stdClass();
|
|
$sessiondata->userid = 2;
|
|
$sessiondata->timecreated = time();
|
|
$sessiondata->timemodified = time();
|
|
|
|
$sessiondata->sid = 'sess1';
|
|
$mockhandler->add_test_session($sessiondata);
|
|
$sessiondata->sid = 'sess2';
|
|
$mockhandler->add_test_session($sessiondata);
|
|
$sessiondata->sid = 'sess3';
|
|
$mockhandler->add_test_session($sessiondata);
|
|
|
|
$this->assertNotEquals('', $sess->read('sess1'));
|
|
$sess->destroy('sess1');
|
|
$this->assertEquals('', $sess->read('sess1'));
|
|
|
|
$this->assertEmpty($this->redis->keys($this->keyprefix.'sess1.lock'));
|
|
|
|
$sess->destroy_all();
|
|
|
|
$mockhandler = new mock_handler();
|
|
$this->assertEquals(
|
|
3,
|
|
$mockhandler->count_sessions(),
|
|
'Moodle handles session database, plugin must not change it.',
|
|
);
|
|
$this->assert_session_no_locks();
|
|
$this->assertEmpty($this->redis->keys($this->keyprefix.'*'), 'There should be no session data left.');
|
|
}
|
|
|
|
public function test_exception_when_connection_attempts_exceeded(): void {
|
|
global $CFG;
|
|
|
|
$CFG->session_redis_port = 111111;
|
|
$actual = '';
|
|
|
|
$sess = new \core\session\redis();
|
|
try {
|
|
$sess->init();
|
|
} catch (RedisException $e) {
|
|
$actual = $e->getMessage();
|
|
}
|
|
|
|
// The Redis session test config allows the user to put the port number inside the host. e.g. 127.0.0.1:6380.
|
|
// Therefore, to get the host, we need to explode it.
|
|
list($host, ) = explode(':', TEST_SESSION_REDIS_HOST);
|
|
|
|
$expected = "Failed to connect (try 5 out of 5) to Redis at $host:111111";
|
|
$this->assertDebuggingCalledCount(5);
|
|
$this->assertStringContainsString($expected, $actual);
|
|
}
|
|
|
|
/**
|
|
* Assert that we don't have any session locks in Redis.
|
|
*/
|
|
protected function assert_session_no_locks(): void {
|
|
$this->assertEmpty($this->redis->keys($this->keyprefix.'*.lock'));
|
|
}
|
|
|
|
public function test_session_redis_encrypt(): void {
|
|
global $CFG;
|
|
|
|
$CFG->session_redis_encrypt = ['verify_peer' => false, 'verify_peer_name' => false];
|
|
|
|
$sess = new \core\session\redis();
|
|
|
|
$prop = new \ReflectionProperty(\core\session\redis::class, 'sslopts');
|
|
|
|
$this->assertEquals($CFG->session_redis_encrypt, $prop->getValue($sess));
|
|
}
|
|
|
|
/**
|
|
* Test the get maxlifetime method.
|
|
*/
|
|
public function test_get_maxlifetime(): void {
|
|
global $CFG;
|
|
|
|
// Set the timeout to something known for the test.
|
|
set_config('sessiontimeout', 100);
|
|
|
|
// Generate a test user.
|
|
$user = $this->getDataGenerator()->create_user();
|
|
|
|
// Create a new redis session object.
|
|
$session = new \core\session\redis();
|
|
$session->init();
|
|
|
|
// The get_maxlifetime is private, so we need to use reflection to access it.
|
|
$method = new \ReflectionMethod(\core\session\redis::class, 'get_maxlifetime');
|
|
|
|
// Test guest timeout, which should be longer.
|
|
$result = $method->invoke($session, $CFG->siteguest);
|
|
$this->assertEquals(500, $result);
|
|
|
|
// Test first access timeout.
|
|
$result = $method->invoke($session, 0, true);
|
|
$this->assertEquals(180, $result);
|
|
|
|
// Test with a real user.
|
|
$result = $method->invoke($session, $user->id);
|
|
$this->assertEquals(180, $result);
|
|
|
|
}
|
|
|
|
/**
|
|
* Test the add session method.
|
|
*/
|
|
public function test_add_session(): void {
|
|
|
|
// Set the timeout to something known for the test.
|
|
set_config('sessiontimeout', 100);
|
|
|
|
// Generate a test user.
|
|
$user = $this->getDataGenerator()->create_user();
|
|
|
|
// Create a new redis session object.
|
|
$session = new \core\session\redis();
|
|
$session->init();
|
|
|
|
// Create two sessions for the user.
|
|
session_id('id1');
|
|
$session1data = $session->add_session($user->id);
|
|
session_id('id2');
|
|
$session2data = $session->add_session($user->id);
|
|
|
|
$session1 = $session->get_session_by_sid('id1');
|
|
$session2 = $session->get_session_by_sid('id2');
|
|
|
|
// Assert that the sessions were created and have expected data.
|
|
$this->assertEqualsCanonicalizing((array)$session1data, (array)$session1);
|
|
$this->assertEqualsCanonicalizing((array)$session2data, (array)$session2);
|
|
|
|
// Check that the session hash has a ttl set.
|
|
$this->assertGreaterThan(-1, $this->redis->ttl($this->keyprefix . 'session_id1'));
|
|
|
|
// Check that the session ttl is less or equal to what we set it.
|
|
$this->assertLessThanOrEqual(180, $this->redis->ttl($this->keyprefix . 'session_id1'));
|
|
|
|
}
|
|
|
|
/**
|
|
* Test writing session data.
|
|
*/
|
|
public function test_write(): void {
|
|
// Set the timeout to something known for the test.
|
|
set_config('sessiontimeout', 100);
|
|
|
|
// Generate a test user.
|
|
$user = $this->getDataGenerator()->create_user();
|
|
|
|
// Create a new redis session object.
|
|
$session = new \core\session\redis();
|
|
$session->init();
|
|
|
|
// Create two sessions for the user.
|
|
session_id('id1');
|
|
$session->add_session($user->id);
|
|
session_id('id2');
|
|
$session->add_session($user->id);
|
|
|
|
$testdata = 'some test data';
|
|
|
|
// Write some data to the store.
|
|
$result = $session->write('id2', $testdata);
|
|
|
|
// Check that the write was successful.
|
|
$this->assertTrue($result);
|
|
|
|
// Check that the data was written to the store.
|
|
$getdata = $this->redis->hget($this->keyprefix . 'session_id2', 'sessdata');
|
|
$this->assertStringContainsString($testdata, $getdata);
|
|
}
|
|
|
|
/**
|
|
* Test reading session data.
|
|
*/
|
|
public function test_read(): void {
|
|
// Set the timeout to something known for the test.
|
|
set_config('sessiontimeout', 100);
|
|
|
|
// Generate a test user.
|
|
$user = $this->getDataGenerator()->create_user();
|
|
|
|
// Create a new redis session object.
|
|
$session = new \core\session\redis();
|
|
$session->init();
|
|
|
|
// Create two sessions for the user.
|
|
session_id('id1');
|
|
$session->add_session($user->id);
|
|
session_id('id2');
|
|
$session->add_session($user->id);
|
|
|
|
$testdata = 'some test data';
|
|
|
|
// Write some session data to the store.
|
|
$session->write('id2', $testdata);
|
|
|
|
// Read the session data.
|
|
$result = $session->read('id2');
|
|
|
|
// Check that the read was successful.
|
|
$this->assertEquals($result, $testdata);
|
|
|
|
}
|
|
|
|
/**
|
|
* Test updating a session.
|
|
*/
|
|
public function test_update_session(): void {
|
|
// Set the timeout to something known for the test.
|
|
set_config('sessiontimeout', 100);
|
|
|
|
// Generate a test user.
|
|
$user = $this->getDataGenerator()->create_user();
|
|
|
|
// Create a new redis session object.
|
|
$session = new \core\session\redis();
|
|
$session->init();
|
|
|
|
// Create two sessions for the user.
|
|
session_id('id1');
|
|
$session->add_session($user->id);
|
|
session_id('id2');
|
|
$sessiondata = $session->add_session($user->id);
|
|
|
|
// Update the session data.
|
|
$sessiondata->lastip = '8.8.8.8';
|
|
$session->update_session($sessiondata);
|
|
|
|
// Check the value was updated.
|
|
$updatedsession = $session->get_session_by_sid('id2');
|
|
$this->assertEquals('8.8.8.8', $updatedsession->lastip);
|
|
|
|
// Test session update when userid is not set, should not error.
|
|
unset($sessiondata->userid);
|
|
$session->update_session($sessiondata);
|
|
$this->assertDebuggingNotCalled();
|
|
}
|
|
|
|
/**
|
|
* Test destroying a session by auth plugin.
|
|
*/
|
|
public function test_destroy_by_auth_plugin(): void {
|
|
// Create test users.
|
|
$user1 = $this->getDataGenerator()->create_user();
|
|
$user2 = $this->getDataGenerator()->create_user(['auth' => 'db']);
|
|
|
|
// Create a new redis session object.
|
|
$session = new \core\session\redis();
|
|
$session->init();
|
|
|
|
// Create sessions for the users.
|
|
session_id('id1');
|
|
$session1data = $session->add_session($user1->id);
|
|
session_id('id2');
|
|
$session2data = $session->add_session($user2->id);
|
|
|
|
$session1 = $session->get_session_by_sid('id1');
|
|
$session2 = $session->get_session_by_sid('id2');
|
|
|
|
// Assert that the sessions were created and have expected data.
|
|
$this->assertEqualsCanonicalizing((array) $session1data, (array) $session1);
|
|
$this->assertEqualsCanonicalizing((array) $session2data, (array) $session2);
|
|
|
|
// Destroy the session by auth plugin.
|
|
$session->destroy_by_auth_plugin('manual');
|
|
|
|
// Check that the session was destroyed.
|
|
$this->assertFalse($session->session_exists('id1'));
|
|
|
|
// Check the session with db auth plugin was not destroyed.
|
|
$this->assertTrue($session->session_exists('id2'));
|
|
}
|
|
}
|