Files
clinicpro/tests/Shared/Captcha/AltchaServiceTest.php
T
hamed ec184dfcc8 Add AST cache files for AltchaService, API documentation, and AltchaService tests
- Created JSON representation of AltchaService class and its methods, including imports and relationships.
- Added documentation for the Captcha API, detailing endpoints and responses.
- Introduced test cases for AltchaService, covering various functionalities and edge cases.
2026-07-10 11:42:23 +03:30

107 lines
3.9 KiB
PHP

<?php
namespace App\Tests\Shared\Captcha;
use AltchaOrg\Altcha\V1\Altcha;
use AltchaOrg\Altcha\V1\Hasher\Algorithm;
use App\Config\Repository\SiteConfigRepository;
use App\Shared\Captcha\AltchaService;
use PHPUnit\Framework\TestCase;
use Symfony\Component\Cache\Adapter\ArrayAdapter;
/**
* ALTCHA service: signed-challenge issuing, solution verification, one-time replay guard.
*/
class AltchaServiceTest extends TestCase
{
private const KEY = 'test-hmac-key-please-change';
private function service(bool $enabled = true, int $maxNumber = 2000, int $expire = 300, ?string $override = null): AltchaService
{
$configRepo = $this->createStub(SiteConfigRepository::class);
$configRepo->method('get')->willReturn($override); // null → fall back to env flag
return new AltchaService(self::KEY, $enabled, $maxNumber, $expire, new ArrayAdapter(), $configRepo);
}
/**
* Solve a service-issued challenge the way the browser widget would, and
* return the base64 payload the client sends back.
*/
private function solvedPayload(array $challenge): string
{
$solver = new Altcha(self::KEY);
$solution = $solver->solveChallenge(
$challenge['challenge'],
$challenge['salt'],
Algorithm::SHA256,
(int) $challenge['maxnumber'],
);
self::assertNotNull($solution, 'challenge must be solvable');
return base64_encode(json_encode([
'algorithm' => $challenge['algorithm'],
'challenge' => $challenge['challenge'],
'number' => $solution->number,
'salt' => $challenge['salt'],
'signature' => $challenge['signature'],
]));
}
public function testCreateChallengeShape(): void
{
$c = $this->service()->createChallenge();
self::assertSame('SHA-256', $c['algorithm']);
self::assertArrayHasKey('challenge', $c);
self::assertArrayHasKey('salt', $c);
self::assertArrayHasKey('signature', $c);
self::assertSame(2000, $c['maxnumber']);
}
public function testValidSolutionVerifies(): void
{
$svc = $this->service();
$payload = $this->solvedPayload($svc->createChallenge());
self::assertTrue($svc->verifySolution($payload));
}
public function testReplayIsRejected(): void
{
$svc = $this->service();
$payload = $this->solvedPayload($svc->createChallenge());
self::assertTrue($svc->verifySolution($payload), 'first use accepted');
self::assertFalse($svc->verifySolution($payload), 'second use (replay) rejected');
}
public function testTamperedSignatureRejected(): void
{
$svc = $this->service();
$challenge = $svc->createChallenge();
$challenge['signature'] = str_repeat('0', strlen($challenge['signature']));
self::assertFalse($svc->verifySolution($this->solvedPayload($challenge)));
}
public function testEmptyAndGarbagePayloadsRejected(): void
{
$svc = $this->service();
self::assertFalse($svc->verifySolution(''));
self::assertFalse($svc->verifySolution('not-base64-!@#'));
self::assertFalse($svc->verifySolution(base64_encode('{"foo":"bar"}')));
}
public function testEnabledFlag(): void
{
self::assertTrue($this->service(true)->enabled());
self::assertFalse($this->service(false)->enabled());
}
public function testAdminOverrideBeatsEnv(): void
{
// env off ولی override پنل روشن → روشن
self::assertTrue($this->service(false, override: '1')->enabled());
// env on ولی override پنل خاموش → خاموش
self::assertFalse($this->service(true, override: '0')->enabled());
// override خالی/null → به env برمی‌گردد
self::assertTrue($this->service(true, override: null)->enabled());
}
}