Files
breadcrumb-the-shire/tests/Service/Settings/SettingsCryptoGatewayTest.php
fs f6777113ec test(gateways): add 27 unit tests for 3 security-critical gateway classes
AuthCryptoGatewayTest (10 tests): encrypt/decrypt round-trip, unique IVs,
Crypto payload format verification (GR-SEC-005), error handling.
SettingsCryptoGatewayTest (9 tests): interface compliance, round-trip,
AES-256-GCM format, tampered ciphertext handling.
OidcHttpGatewayTest (8 tests): success, 401/500, network error, malformed JSON.

Plan amended: PermissionGateway removed (does not exist in codebase).
SC-002 amended: payload format verification accepted as Crypto delegation proof.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-13 13:57:33 +01:00

113 lines
3.5 KiB
PHP

<?php
namespace MintyPHP\Tests\Service\Settings;
use MintyPHP\Service\Settings\SettingsCryptoGateway;
use MintyPHP\Service\Settings\SettingsCryptoGatewayInterface;
use PHPUnit\Framework\TestCase;
class SettingsCryptoGatewayTest extends TestCase
{
private SettingsCryptoGateway $gateway;
protected function setUp(): void
{
if (!defined('APP_CRYPTO_KEY')) {
define('APP_CRYPTO_KEY', str_repeat('ab', 32));
}
$this->gateway = new SettingsCryptoGateway();
}
// --- Interface compliance ---
public function testImplementsSettingsCryptoGatewayInterface(): void
{
$this->assertInstanceOf(SettingsCryptoGatewayInterface::class, $this->gateway);
}
// --- encryptString / decryptString round-trip ---
public function testEncryptDecryptRoundTrip(): void
{
$plaintext = 'client-secret-for-sso';
$encrypted = $this->gateway->encryptString($plaintext);
$this->assertNotSame($plaintext, $encrypted);
$this->assertNotEmpty($encrypted);
$decrypted = $this->gateway->decryptString($encrypted);
$this->assertSame($plaintext, $decrypted);
}
public function testEncryptEmptyStringProducesMalformedPayloadOnDecrypt(): void
{
$encrypted = $this->gateway->encryptString('');
$this->expectException(\RuntimeException::class);
$this->expectExceptionMessage('malformed');
$this->gateway->decryptString($encrypted);
}
public function testEncryptDecryptRoundTripWithSpecialChars(): void
{
$plaintext = 'p@$$w0rd!&key=val<ue>';
$encrypted = $this->gateway->encryptString($plaintext);
$decrypted = $this->gateway->decryptString($encrypted);
$this->assertSame($plaintext, $decrypted);
}
// --- Unique ciphertext per encryption ---
public function testEncryptProducesDifferentCiphertextEachTime(): void
{
$plaintext = 'same-settings-value';
$encrypted1 = $this->gateway->encryptString($plaintext);
$encrypted2 = $this->gateway->encryptString($plaintext);
$this->assertNotSame($encrypted1, $encrypted2);
}
// --- Delegation to Crypto class (GR-SEC-005) ---
public function testEncryptStringOutputIsValidCryptoPayload(): void
{
$encrypted = $this->gateway->encryptString('test');
$decoded = base64_decode($encrypted, true);
$this->assertIsString($decoded);
$payload = json_decode($decoded, true);
$this->assertIsArray($payload);
$this->assertArrayHasKey('v', $payload);
$this->assertArrayHasKey('iv', $payload);
$this->assertArrayHasKey('tag', $payload);
$this->assertArrayHasKey('ct', $payload);
$this->assertSame(1, $payload['v']);
}
// --- Error handling ---
public function testDecryptThrowsOnInvalidPayload(): void
{
$this->expectException(\RuntimeException::class);
$this->gateway->decryptString('garbage-data');
}
public function testDecryptThrowsOnEmptyJsonPayload(): void
{
$this->expectException(\RuntimeException::class);
$this->gateway->decryptString(base64_encode('{}'));
}
public function testDecryptThrowsOnTamperedCiphertext(): void
{
$encrypted = $this->gateway->encryptString('secret');
$tampered = substr($encrypted, 0, 5) . 'Z' . substr($encrypted, 6);
$this->expectException(\RuntimeException::class);
$this->gateway->decryptString($tampered);
}
}