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'; $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); } }