Turnstile::SETTING_KEY], ['value' => json_encode([ 'enabled' => $enabled, 'site_key' => 'site', 'secret_key' => SignRSA::EncryptFunc('secret'), ])]); } public function test_disabled_passes_without_token(): void { $this->configure(false); Http::fake(); $this->assertNull(Turnstile::siteKey()); $this->assertTrue(Turnstile::verify(null, null)); Http::assertNothingSent(); } public function test_enabled_requires_valid_token(): void { $this->configure(true); Http::fake(['challenges.cloudflare.com/*' => Http::sequence() ->push(['success' => true]) ->push(['success' => false, 'error-codes' => ['timeout-or-duplicate']])]); $this->assertSame('site', Turnstile::siteKey()); $this->assertFalse(Turnstile::verify('', '1.1.1.1')); $this->assertTrue(Turnstile::verify('ok', '1.1.1.1')); $this->assertFalse(Turnstile::verify('reused', '1.1.1.1')); Http::assertSent(fn ($request) => $request['secret'] === 'secret' && $request['response'] === 'ok'); } public function test_save_keeps_masked_secret_and_rejects_enabling_without_keys(): void { $this->configure(false); Turnstile::save(['enabled' => true, 'site_key' => 'new-site', 'secret_key' => Turnstile::SECRET_MASK]); $this->assertSame('secret', SignRSA::DecryptFunc(Turnstile::stored()['secret_key'])); $this->assertSame(['enabled' => true, 'site_key' => 'new-site', 'secret_key' => Turnstile::SECRET_MASK], Turnstile::forAdmin()); $this->expectException(InvalidArgumentException::class); Turnstile::save(['secret_key' => '']); } public function test_admin_login_rejects_missing_token_when_enabled(): void { $this->configure(true); Http::fake(); $this->postJson('/api/v1/administrator/login', ['username' => 'x', 'password' => 'y']) ->assertStatus(422) ->assertJsonPath('error', true); Http::assertNothingSent(); } }