WIP: AI Test cleanup, fixing all the tests, next up RejectListener.
Added tests for GetTotpTrait, also fixed it to work as intended.
This commit is contained in:
@@ -4,12 +4,16 @@ declare(strict_types=1);
|
||||
namespace App\Trait;
|
||||
|
||||
use App\ConfigBag;
|
||||
use OTPHP\Exception\InvalidProvisioningUriException;
|
||||
use OTPHP\Factory;
|
||||
use OTPHP\TOTPInterface;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\HttpKernel\Exception\HttpException;
|
||||
use Symfony\Contracts\Service\Attribute\Required;
|
||||
|
||||
trait GetTotpTrait {
|
||||
use HasLoggerTrait;
|
||||
|
||||
protected readonly ConfigBag $config;
|
||||
|
||||
#[Required]
|
||||
@@ -18,12 +22,24 @@ trait GetTotpTrait {
|
||||
}
|
||||
|
||||
protected function getTotp(): TOTPInterface {
|
||||
$otp = Factory::loadFromProvisioningUri(
|
||||
$this->config->totpUri(), $this->config->clock()
|
||||
);
|
||||
if ($otp instanceof TOTPInterface) {
|
||||
return $otp;
|
||||
try {
|
||||
$otp = Factory::loadFromProvisioningUri(
|
||||
$this->config->totpUri(), $this->config->clock()
|
||||
);
|
||||
if ($otp instanceof TOTPInterface) {
|
||||
return $otp;
|
||||
}
|
||||
} catch (InvalidProvisioningUriException $uriException) {
|
||||
/* always present a pretty error publicly */
|
||||
$this->logger->emergency(
|
||||
'System is unusable, invalid TOTP_URI configured: {exception}',
|
||||
['exception' => $uriException]
|
||||
);
|
||||
throw new HttpException(
|
||||
Response::HTTP_INTERNAL_SERVER_ERROR,
|
||||
'Internal Server Error',
|
||||
$uriException,
|
||||
);
|
||||
}
|
||||
throw new HttpException(500, 'Internal Server Exception');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,8 +5,10 @@ namespace App\Tests\Unit\Listener;
|
||||
|
||||
use App\ConfigBag;
|
||||
use App\Listener\RejectListener;
|
||||
use App\Utilities;
|
||||
use OTPHP\TOTP;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Psr\Cache\CacheItemPoolInterface;
|
||||
use Psr\Clock\ClockInterface;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
@@ -19,13 +21,14 @@ use Twig\Environment;
|
||||
|
||||
final class RejectListenerTest extends TestCase {
|
||||
private function createEvent(Request $request): RequestEvent {
|
||||
$kernel = $this->createMock(HttpKernelInterface::class);
|
||||
$kernel = $this->createStub(HttpKernelInterface::class);
|
||||
return new RequestEvent($kernel, $request, HttpKernelInterface::MAIN_REQUEST);
|
||||
}
|
||||
|
||||
private function createConfigBag(bool $teapot = false): ConfigBag {
|
||||
$clock = $this->createMock(ClockInterface::class);
|
||||
$utilities = $this->createMock(\App\Utilities::class);
|
||||
$clock = $this->createStub(ClockInterface::class);
|
||||
$cache = $this->createStub(CacheItemPoolInterface::class);
|
||||
$utilities = new Utilities($clock, $cache);
|
||||
$totp = TOTP::generate($clock);
|
||||
$totp->setLabel('Test');
|
||||
|
||||
@@ -44,21 +47,26 @@ final class RejectListenerTest extends TestCase {
|
||||
|
||||
private function createLimiter(int $remainingTokens): LimiterInterface {
|
||||
$limit = $this->createMock(RateLimit::class);
|
||||
$limit->method('getRemainingTokens')->willReturn($remainingTokens);
|
||||
$limit->expects($this->once())->method('getRemainingTokens')
|
||||
->willReturn($remainingTokens);
|
||||
|
||||
$limiter = $this->createMock(LimiterInterface::class);
|
||||
$limiter->method('consume')->with(0)->willReturn($limit);
|
||||
$limiter->expects($this->once())->method('consume')->with(0)
|
||||
->willReturn($limit);
|
||||
return $limiter;
|
||||
}
|
||||
|
||||
/** when not blocked, expect no response */
|
||||
public function testOnKernelRequestWhenNotBlocked(): void {
|
||||
$twig = $this->createMock(Environment::class);
|
||||
$twig->expects($this->never())->method('render');
|
||||
$config = $this->createConfigBag();
|
||||
$rateLimiter = $this->createMock(RateLimiterFactoryInterface::class);
|
||||
$rateLimiter->method('create')->willReturn($this->createLimiter(5));
|
||||
$rateLimiter->expects($this->once())->method('create')
|
||||
->willReturn($this->createLimiter(5));
|
||||
|
||||
$listener = new RejectListener($config, $twig, $rateLimiter);
|
||||
$listener->setLogger($this->createMock(LoggerInterface::class));
|
||||
$listener->setLogger($this->createStub(LoggerInterface::class));
|
||||
|
||||
$request = Request::create('https://example.com/');
|
||||
$event = $this->createEvent($request);
|
||||
@@ -67,16 +75,19 @@ final class RejectListenerTest extends TestCase {
|
||||
self::assertNull($event->getResponse());
|
||||
}
|
||||
|
||||
/** when blocked, expect too-many response */
|
||||
public function testOnKernelRequestWhenBlocked(): void {
|
||||
$twig = $this->createMock(Environment::class);
|
||||
$twig->method('render')->with('error.html.twig')->willReturn('<html>blocked</html>');
|
||||
$twig->expects($this->once())->method('render')
|
||||
->with('error.html.twig')->willReturn('<html>blocked</html>');
|
||||
|
||||
$config = $this->createConfigBag();
|
||||
$rateLimiter = $this->createMock(RateLimiterFactoryInterface::class);
|
||||
$rateLimiter->method('create')->willReturn($this->createLimiter(0));
|
||||
$rateLimiter->expects($this->once())->method('create')
|
||||
->willReturn($this->createLimiter(0));
|
||||
|
||||
$listener = new RejectListener($config, $twig, $rateLimiter);
|
||||
$listener->setLogger($this->createMock(LoggerInterface::class));
|
||||
$listener->setLogger($this->createStub(LoggerInterface::class));
|
||||
|
||||
$request = Request::create('https://example.com/');
|
||||
$event = $this->createEvent($request);
|
||||
@@ -88,16 +99,19 @@ final class RejectListenerTest extends TestCase {
|
||||
self::assertSame('<html>blocked</html>', $response->getContent());
|
||||
}
|
||||
|
||||
/** when blocked with teapot expect teapot response */
|
||||
public function testOnKernelRequestWhenBlockedWithTeapot(): void {
|
||||
$twig = $this->createMock(Environment::class);
|
||||
$twig->method('render')->with('error.html.twig')->willReturn('<html>teapot</html>');
|
||||
$twig->expects($this->once())->method('render')
|
||||
->with('error.html.twig')->willReturn('<html>teapot</html>');
|
||||
|
||||
$config = $this->createConfigBag(true);
|
||||
$rateLimiter = $this->createMock(RateLimiterFactoryInterface::class);
|
||||
$rateLimiter->method('create')->willReturn($this->createLimiter(0));
|
||||
$rateLimiter->expects($this->once())->method('create')
|
||||
->willReturn($this->createLimiter(0));
|
||||
|
||||
$listener = new RejectListener($config, $twig, $rateLimiter);
|
||||
$listener->setLogger($this->createMock(LoggerInterface::class));
|
||||
$listener->setLogger($this->createStub(LoggerInterface::class));
|
||||
|
||||
$request = Request::create('https://example.com/');
|
||||
$event = $this->createEvent($request);
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
<?php
|
||||
|
||||
namespace App\Tests\Unit\Trait;
|
||||
|
||||
use App\ConfigBag;
|
||||
use App\Trait\GetTotpTrait;
|
||||
use App\Utilities;
|
||||
use OTPHP\TOTP;
|
||||
use OTPHP\TOTPInterface;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Psr\Cache\CacheItemPoolInterface;
|
||||
use Psr\Clock\ClockInterface;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Symfony\Component\HttpKernel\Exception\HttpException;
|
||||
|
||||
class GetTotpTraitTest extends TestCase {
|
||||
use GetTotpTrait;
|
||||
|
||||
private function createConfigBag(string $totpUri): ConfigBag {
|
||||
$clock = $this->createStub(ClockInterface::class);
|
||||
$cache = $this->createStub(CacheItemPoolInterface::class);
|
||||
$utilities = new Utilities($clock, $cache);
|
||||
|
||||
return new ConfigBag(
|
||||
$utilities,
|
||||
$clock,
|
||||
3600,
|
||||
$totpUri,
|
||||
1800,
|
||||
true,
|
||||
'Error!',
|
||||
'Teapot!',
|
||||
'Too Many!'
|
||||
);
|
||||
}
|
||||
|
||||
/** will create the totp instance from the config */
|
||||
public function testGetTotpFromConfig(): void {
|
||||
$clock = $this->createStub(ClockInterface::class);
|
||||
$totp = TOTP::generate($clock);
|
||||
$totp->setLabel('test');
|
||||
|
||||
$config = $this->createConfigBag($totp->getProvisioningUri());
|
||||
$this->setConfig($config);
|
||||
|
||||
$logger = $this->createMock(LoggerInterface::class);
|
||||
$logger->expects($this->never())->method($this->anything());
|
||||
$this->setLogger($logger);
|
||||
|
||||
$this->assertInstanceOf(TOTPInterface::class, $this->getTotp());
|
||||
$this->assertSame($totp->getProvisioningUri(), $this->getTotp()->getProvisioningUri());
|
||||
/* uri should match, but it will not be the same object instances */
|
||||
$this->assertNotSame($totp, $this->getTotp());
|
||||
}
|
||||
|
||||
/** will emit pretty error, if config is invalid */
|
||||
public function testGetTotpInvalidConfig(): void {
|
||||
$config = $this->createConfigBag('otpauth://invalid-totp-uri');
|
||||
$this->setConfig($config);
|
||||
|
||||
$logger = $this->createMock(LoggerInterface::class);
|
||||
$logger->expects($this->once())->method('emergency');
|
||||
$this->setLogger($logger);
|
||||
|
||||
$this->expectException(HttpException::class);
|
||||
$this->expectExceptionMessage('Internal Server Error');
|
||||
|
||||
$this->getTotp();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user