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()); } }