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:
2026-06-10 17:11:56 -04:00
parent 7b96f992be
commit 650557163d
3 changed files with 119 additions and 19 deletions
+22 -6
View File
@@ -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');
}
}
+27 -13
View File
@@ -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);
+70
View File
@@ -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();
}
}