sessionless-1.x-dev/tests/src/Unit/WebTokenServiceUnitTest.php
tests/src/Unit/WebTokenServiceUnitTest.php
<?php
declare(strict_types=1);
namespace Drupal\Tests\sessionless\Unit;
use Drupal\Core\Cache\MemoryCache\MemoryCacheFactory;
use Drupal\Core\Cache\NullBackend;
use Drupal\Core\KeyValueStore\KeyValueMemoryFactory;
use Drupal\Core\Lock\NullLockBackend;
use Drupal\Core\State\State;
use Drupal\sessionless\KeyStorage\KeyStorage;
use Drupal\sessionless\KeyStorage\KeyStorageInterface;
use Drupal\sessionless\Serialization\JsonSafeCompressedPhpSerialization;
use Drupal\sessionless\SessionlessEncryptAndSign;
use Drupal\sessionless\SessionlessOnlySign;
use Drupal\sessionless\SessionlessInterface;
use Drupal\Tests\sessionless\Traits\ClassWithPrivateProperty;
use Drupal\Tests\sessionless\Traits\SessionlessServices;
use Drupal\Tests\sessionless\Unit\Double\FakeTime;
use Drupal\Tests\UnitTestCase;
use Drupal\TestTools\Random;
use Symfony\Component\Stopwatch\Stopwatch;
/**
* @group sessionless
*/
final class WebTokenServiceUnitTest extends UnitTestCase {
protected function setUp(): void {
parent::setUp();
}
/**
* @dataProvider provideWebTokenService
*/
public function testWebTokenService(SessionlessInterface $tokenService): void {
// Class with private property contains nul bytes when serialized.
$data = [
'randomObject' => Random::object(10),
'classWithPrivateString' => new ClassWithPrivateProperty(Random::string(500)),
];
$token = $tokenService->encode($data);
$this->assertIsString($token);
// $this->assertSame('', $token);
$this->assertMatchesRegularExpression('~^[A-Za-z0-9-_=]+\.[A-Za-z0-9-_=]+\.?[A-Za-z0-9-_.+/=]*$~', $token);
$recoveredData = $tokenService->decode($token);
// The recreated object is equal, but not same.
$this->assertEquals($data, $recoveredData);
$this->assertNotSame($data, $recoveredData);
}
/**
* @dataProvider provideWebTokenServiceAndKeyStorage
*/
public function testKeyChange(SessionlessInterface $tokenService, KeyStorageInterface $keyStorage): void {
$data = random_bytes(100000);
$token1a = $tokenService->encode($data);
$token1b = $tokenService->encode($data);
$keyVersion1 = $keyStorage->getKeyVersion();
$keyStorage->dropSecretKeys();
$keyVersion2 = $keyStorage->getKeyVersion();
$token2a = $tokenService->encode($data);
$token2b = $tokenService->encode($data);
// Key version changed.
$this->assertNotSame($keyVersion1, $keyVersion2);
// Same key, same token.
// Note: For encrypted tokens, this is ONLY the case when cached, because
// the session encryption key is random.
$this->assertSame($token1a, $token1b);
$this->assertSame($token2a, $token2b);
// Different key, different token.
$this->assertNotSame($token1a, $token2a);
}
/**
* @dataProvider provideWebTokenService
*/
public function testAlgorithmsCaching(SessionlessInterface $tokenService): void {
$data = random_bytes(100000);
$stopwatch = new Stopwatch(true);
$stopwatch->start('1a');
$tokenService->encode($data);
$stopwatch->stop('1a');
$stopwatch->start('1b');
$tokenService->encode($data);
$stopwatch->stop('1b');
// Every second run was significantly faster (but still needs packing).
$this->assertLessThan($stopwatch->getEvent('1a')->getDuration() / 2, $stopwatch->getEvent('1b')->getDuration());
}
public static function provideWebTokenService() {
yield 'onlySigned' => [self::createWebTokenService(false)->webTokenService];
yield 'encryptedSigned' => [self::createWebTokenService(true)->webTokenService];
}
public static function provideWebTokenServiceAndKeyStorage() {
$services = self::createWebTokenService(FALSE);
yield 'onlySigned' => [$services->webTokenService, $services->keyStorage];
$services = self::createWebTokenService(TRUE);
yield 'encryptedSigned' => [$services->webTokenService, $services->keyStorage];
}
public static function createWebTokenService(bool $encrypt): SessionlessServices {
$state = new State(new KeyValueMemoryFactory(), new NullBackend(''), new NullLockBackend());
$keyStorage = new KeyStorage($state);
$serializer = new JsonSafeCompressedPhpSerialization();
$time = new FakeTime();
$cacheFactory = new MemoryCacheFactory($time);
$cache = $cacheFactory->get('sessionless');
if ($encrypt) {
$tokenService = new SessionlessEncryptAndSign($keyStorage, $serializer, $cache);
}
else {
$tokenService = new SessionlessOnlySign($keyStorage, $serializer, $cache);
}
return new SessionlessServices($tokenService, $keyStorage);
}
}
