Deduplicate crypto gateway tests
This commit is contained in:
@@ -5,13 +5,12 @@ namespace MintyPHP\Tests\Service\Auth;
|
||||
use MintyPHP\Service\Auth\AuthCryptoGateway;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
class AuthCryptoGatewayTest extends TestCase
|
||||
final class AuthCryptoGatewayTest extends TestCase
|
||||
{
|
||||
private AuthCryptoGateway $gateway;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
// Define APP_CRYPTO_KEY once for the entire test run (valid 64-char hex = 32 bytes).
|
||||
if (!defined('APP_CRYPTO_KEY')) {
|
||||
define('APP_CRYPTO_KEY', str_repeat('ab', 32));
|
||||
}
|
||||
@@ -19,108 +18,17 @@ class AuthCryptoGatewayTest extends TestCase
|
||||
$this->gateway = new AuthCryptoGateway();
|
||||
}
|
||||
|
||||
// --- isConfigured ---
|
||||
|
||||
public function testIsConfiguredReturnsTrueWhenKeyIsSet(): void
|
||||
{
|
||||
$this->assertTrue($this->gateway->isConfigured());
|
||||
self::assertTrue($this->gateway->isConfigured());
|
||||
}
|
||||
|
||||
// --- encryptString / decryptString round-trip ---
|
||||
|
||||
public function testEncryptDecryptRoundTrip(): void
|
||||
public function testEncryptDecryptDelegatesToCrypto(): void
|
||||
{
|
||||
$plaintext = 'my-secret-sso-value';
|
||||
$plaintext = 'auth-secret';
|
||||
$encrypted = $this->gateway->encryptString($plaintext);
|
||||
|
||||
$this->assertNotSame($plaintext, $encrypted, 'Encrypted value must differ from plaintext');
|
||||
$this->assertNotEmpty($encrypted);
|
||||
|
||||
$decrypted = $this->gateway->decryptString($encrypted);
|
||||
$this->assertSame($plaintext, $decrypted);
|
||||
}
|
||||
|
||||
public function testEncryptEmptyStringProducesMalformedPayloadOnDecrypt(): void
|
||||
{
|
||||
// AES-256-GCM with empty plaintext yields empty ciphertext,
|
||||
// which Crypto::decryptString rejects as malformed — expected behavior.
|
||||
$encrypted = $this->gateway->encryptString('');
|
||||
|
||||
$this->expectException(\RuntimeException::class);
|
||||
$this->expectExceptionMessage('malformed');
|
||||
$this->gateway->decryptString($encrypted);
|
||||
}
|
||||
|
||||
public function testEncryptDecryptRoundTripWithUnicode(): void
|
||||
{
|
||||
$plaintext = 'Ünïcödé-tëst 🔑';
|
||||
$encrypted = $this->gateway->encryptString($plaintext);
|
||||
$decrypted = $this->gateway->decryptString($encrypted);
|
||||
|
||||
$this->assertSame($plaintext, $decrypted);
|
||||
}
|
||||
|
||||
public function testEncryptDecryptRoundTripWithLongString(): void
|
||||
{
|
||||
$plaintext = str_repeat('long-secret-', 100);
|
||||
$encrypted = $this->gateway->encryptString($plaintext);
|
||||
$decrypted = $this->gateway->decryptString($encrypted);
|
||||
|
||||
$this->assertSame($plaintext, $decrypted);
|
||||
}
|
||||
|
||||
// --- encryptString produces unique ciphertext ---
|
||||
|
||||
public function testEncryptProducesDifferentCiphertextEachTime(): void
|
||||
{
|
||||
$plaintext = 'same-input';
|
||||
$encrypted1 = $this->gateway->encryptString($plaintext);
|
||||
$encrypted2 = $this->gateway->encryptString($plaintext);
|
||||
|
||||
$this->assertNotSame($encrypted1, $encrypted2, 'Each encryption should use a unique IV');
|
||||
}
|
||||
|
||||
// --- decryptString error handling ---
|
||||
|
||||
public function testDecryptThrowsOnInvalidBase64(): void
|
||||
{
|
||||
$this->expectException(\RuntimeException::class);
|
||||
$this->gateway->decryptString('not-valid-base64!!!');
|
||||
}
|
||||
|
||||
public function testDecryptThrowsOnCorruptedPayload(): void
|
||||
{
|
||||
$this->expectException(\RuntimeException::class);
|
||||
$this->gateway->decryptString(base64_encode('not-json'));
|
||||
}
|
||||
|
||||
public function testDecryptThrowsOnTamperedCiphertext(): void
|
||||
{
|
||||
$encrypted = $this->gateway->encryptString('test-value');
|
||||
|
||||
// Tamper with the base64 payload — change one character in the middle
|
||||
$tampered = substr($encrypted, 0, 10) . 'X' . substr($encrypted, 11);
|
||||
|
||||
$this->expectException(\RuntimeException::class);
|
||||
$this->gateway->decryptString($tampered);
|
||||
}
|
||||
|
||||
// --- Delegation to Crypto class (GR-SEC-005) ---
|
||||
|
||||
public function testEncryptStringReturnsSameFormatAsCryptoClass(): void
|
||||
{
|
||||
$encrypted = $this->gateway->encryptString('test');
|
||||
|
||||
// The Crypto class wraps JSON in base64. Verify the outer structure.
|
||||
$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']);
|
||||
self::assertNotSame($plaintext, $encrypted);
|
||||
self::assertSame($plaintext, $this->gateway->decryptString($encrypted));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,107 +6,27 @@ use MintyPHP\Service\Settings\SettingsCryptoGateway;
|
||||
use MintyPHP\Service\Settings\SettingsCryptoGatewayInterface;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
class SettingsCryptoGatewayTest extends TestCase
|
||||
final 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);
|
||||
self::assertInstanceOf(SettingsCryptoGatewayInterface::class, new SettingsCryptoGateway());
|
||||
}
|
||||
|
||||
// --- encryptString / decryptString round-trip ---
|
||||
|
||||
public function testEncryptDecryptRoundTrip(): void
|
||||
public function testEncryptDecryptDelegatesToCrypto(): void
|
||||
{
|
||||
$plaintext = 'client-secret-for-sso';
|
||||
$encrypted = $this->gateway->encryptString($plaintext);
|
||||
$gateway = new SettingsCryptoGateway();
|
||||
$plaintext = 'settings-secret';
|
||||
$encrypted = $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);
|
||||
self::assertNotSame($plaintext, $encrypted);
|
||||
self::assertSame($plaintext, $gateway->decryptString($encrypted));
|
||||
}
|
||||
}
|
||||
|
||||
70
tests/Unit/Support/CryptoTest.php
Normal file
70
tests/Unit/Support/CryptoTest.php
Normal file
@@ -0,0 +1,70 @@
|
||||
<?php
|
||||
|
||||
namespace MintyPHP\Tests\Unit\Support;
|
||||
|
||||
use MintyPHP\Support\Crypto;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
final class CryptoTest extends TestCase
|
||||
{
|
||||
protected function setUp(): void
|
||||
{
|
||||
if (!defined('APP_CRYPTO_KEY')) {
|
||||
define('APP_CRYPTO_KEY', str_repeat('ab', 32));
|
||||
}
|
||||
}
|
||||
|
||||
public function testIsConfiguredReturnsTrueWhenKeyIsSet(): void
|
||||
{
|
||||
self::assertTrue(Crypto::isConfigured());
|
||||
}
|
||||
|
||||
public function testEncryptDecryptRoundTrip(): void
|
||||
{
|
||||
$plaintext = 'client-secret-for-sso';
|
||||
$encrypted = Crypto::encryptString($plaintext);
|
||||
|
||||
self::assertNotSame($plaintext, $encrypted);
|
||||
self::assertSame($plaintext, Crypto::decryptString($encrypted));
|
||||
}
|
||||
|
||||
public function testEncryptProducesDifferentCiphertextEachTime(): void
|
||||
{
|
||||
$plaintext = 'same-settings-value';
|
||||
|
||||
self::assertNotSame(
|
||||
Crypto::encryptString($plaintext),
|
||||
Crypto::encryptString($plaintext)
|
||||
);
|
||||
}
|
||||
|
||||
public function testDecryptThrowsOnInvalidOrTamperedPayload(): void
|
||||
{
|
||||
$encrypted = Crypto::encryptString('secret');
|
||||
$tampered = substr($encrypted, 0, 5) . 'Z' . substr($encrypted, 6);
|
||||
|
||||
try {
|
||||
Crypto::decryptString('garbage-data');
|
||||
self::fail('Expected invalid payload to throw.');
|
||||
} catch (\RuntimeException) {
|
||||
}
|
||||
|
||||
try {
|
||||
Crypto::decryptString(base64_encode('{}'));
|
||||
self::fail('Expected malformed payload to throw.');
|
||||
} catch (\RuntimeException) {
|
||||
}
|
||||
|
||||
$this->expectException(\RuntimeException::class);
|
||||
Crypto::decryptString($tampered);
|
||||
}
|
||||
|
||||
public function testEncryptEmptyStringProducesMalformedPayloadOnDecrypt(): void
|
||||
{
|
||||
$encrypted = Crypto::encryptString('');
|
||||
|
||||
$this->expectException(\RuntimeException::class);
|
||||
$this->expectExceptionMessage('malformed');
|
||||
Crypto::decryptString($encrypted);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user