101 lines
2.9 KiB
PHP
101 lines
2.9 KiB
PHP
|
|
<?php
|
|||
|
|
/**
|
|||
|
|
* File: tests/unit/Core/TOTPServiceTest.php
|
|||
|
|
*
|
|||
|
|
* Author: Funky Waddle
|
|||
|
|
* Date: 2026-09-12
|
|||
|
|
*
|
|||
|
|
* Purpose:
|
|||
|
|
* Unit tests for the TOTPService.
|
|||
|
|
*/
|
|||
|
|
declare(strict_types=1);
|
|||
|
|
namespace Tests\unit\Core;
|
|||
|
|
|
|||
|
|
use SiteWeaver\Core\Services\TOTPService;
|
|||
|
|
use OTPHP\TOTP;
|
|||
|
|
use Psr\Clock\ClockInterface;
|
|||
|
|
use PHPUnit\Framework\TestCase;
|
|||
|
|
|
|||
|
|
final class TOTPServiceTest extends TestCase
|
|||
|
|
{
|
|||
|
|
private ?ClockInterface $clock;
|
|||
|
|
|
|||
|
|
protected function setUp(): void
|
|||
|
|
{
|
|||
|
|
$this->clock = new class implements ClockInterface {
|
|||
|
|
public function now(): \DateTimeImmutable
|
|||
|
|
{
|
|||
|
|
return new \DateTimeImmutable();
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
protected function tearDown(): void
|
|||
|
|
{
|
|||
|
|
$this->clock = null;
|
|||
|
|
}
|
|||
|
|
public function testGenerateAndVerify(): void
|
|||
|
|
{
|
|||
|
|
$totpService = new TOTPService();
|
|||
|
|
$secret = $totpService->generateSecret();
|
|||
|
|
|
|||
|
|
// Generate a current OTP using the secret and clock
|
|||
|
|
$otp = TOTP::create($secret, clock: $this->clock)->now();
|
|||
|
|
|
|||
|
|
$this->assertTrue($totpService->verify($secret, $otp));
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public function testGenerateSecretUniqueness(): void
|
|||
|
|
{
|
|||
|
|
$totpService = new TOTPService();
|
|||
|
|
$secret1 = $totpService->generateSecret();
|
|||
|
|
$secret2 = $totpService->generateSecret();
|
|||
|
|
|
|||
|
|
$this->assertNotEquals($secret1, $secret2);
|
|||
|
|
$this->assertNotEmpty($secret1);
|
|||
|
|
$this->assertNotEmpty($secret2);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public function testVerifyWithWrongOTP(): void
|
|||
|
|
{
|
|||
|
|
$totpService = new TOTPService();
|
|||
|
|
$secret = $totpService->generateSecret();
|
|||
|
|
|
|||
|
|
// correct OTP
|
|||
|
|
$otpCorrect = TOTP::create($secret, clock: $this->clock)->now();
|
|||
|
|
$this->assertTrue($totpService->verify($secret, $otpCorrect));
|
|||
|
|
|
|||
|
|
// wrong OTP
|
|||
|
|
$wrongOtp = '000000';
|
|||
|
|
$this->assertFalse($totpService->verify($secret, $wrongOtp));
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public function testGetProvisioningUri(): void
|
|||
|
|
{
|
|||
|
|
$totpService = new TOTPService();
|
|||
|
|
$secret = $totpService->generateSecret();
|
|||
|
|
|
|||
|
|
// Use a known account name and issuer
|
|||
|
|
$accountName = 'alice@example.com';
|
|||
|
|
$issuer = 'SiteWeaverCMS';
|
|||
|
|
|
|||
|
|
$uri = $totpService->getProvisioningUri($accountName, $issuer);
|
|||
|
|
|
|||
|
|
// The URI should start with otpauth://totp/
|
|||
|
|
$this->assertStringStartsWith('otpauth://totp/', $uri);
|
|||
|
|
|
|||
|
|
// It should contain the issuer and account name (URL‑encoded)
|
|||
|
|
$expectedIssuer = rawurlencode($issuer);
|
|||
|
|
$expectedAccount = rawurlencode($accountName);
|
|||
|
|
$this->assertStringContainsString($expectedIssuer, $uri);
|
|||
|
|
$this->assertStringContainsString($expectedAccount, $uri);
|
|||
|
|
|
|||
|
|
// Compare with the library's own provisioning URI
|
|||
|
|
$libUri = TOTP::create($secret, clock: $this->clock)
|
|||
|
|
->withLabel($accountName)
|
|||
|
|
->withIssuer($issuer)
|
|||
|
|
->getProvisioningUri();
|
|||
|
|
$this->assertEquals($libUri, $uri);
|
|||
|
|
}
|
|||
|
|
}
|