2026-03-19 19:36:17 +01:00
|
|
|
<?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);
|
|
|
|
|
}
|
refactor: consolidate JS toggle components, grid query parsers, and add tests
JS components: migrate app-custom-field-options-toggle, app-color-default-toggle,
and app-settings-telemetry to shared conditional-controls factory/utility,
removing duplicated syncControlState logic and init boilerplate.
grid.php: extract gridQueryCsvInput() to DRY up 3 CSV parsers, simplify
multi_csv handler via gridNormalizeLabelList, delegate order type to gridQueryEnum.
Tests: add 23 SearchQueryNormalizer tests (LIKE escaping, wildcards, Unicode)
and 7 additional Crypto edge-case tests (empty input, missing fields, special
chars, binary content, truncation).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-13 22:22:15 +02:00
|
|
|
|
|
|
|
|
public function testDecryptThrowsOnEmptyString(): void
|
|
|
|
|
{
|
|
|
|
|
$this->expectException(\RuntimeException::class);
|
|
|
|
|
Crypto::decryptString('');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function testDecryptThrowsOnMissingPayloadFields(): void
|
|
|
|
|
{
|
|
|
|
|
// Valid base64 wrapping valid JSON, but missing required fields.
|
|
|
|
|
$payload = base64_encode(json_encode(['v' => 1]));
|
|
|
|
|
|
|
|
|
|
$this->expectException(\RuntimeException::class);
|
|
|
|
|
$this->expectExceptionMessage('malformed');
|
|
|
|
|
Crypto::decryptString($payload);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function testRoundTripWithSpecialCharacters(): void
|
|
|
|
|
{
|
|
|
|
|
$values = [
|
|
|
|
|
'unicode: Ünterström ñ 日本語',
|
|
|
|
|
"newlines:\nand\ttabs",
|
|
|
|
|
'json-like: {"key": "value"}',
|
|
|
|
|
str_repeat('x', 10000),
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
foreach ($values as $plaintext) {
|
|
|
|
|
$encrypted = Crypto::encryptString($plaintext);
|
|
|
|
|
self::assertSame($plaintext, Crypto::decryptString($encrypted), "Round-trip failed for: " . substr($plaintext, 0, 40));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function testRoundTripWithBinaryLikeContent(): void
|
|
|
|
|
{
|
|
|
|
|
$plaintext = "null-bytes:\x00\x01\x02 and high-bytes:\xFE\xFF";
|
|
|
|
|
$encrypted = Crypto::encryptString($plaintext);
|
|
|
|
|
|
|
|
|
|
self::assertSame($plaintext, Crypto::decryptString($encrypted));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function testDecryptThrowsOnNonBase64Input(): void
|
|
|
|
|
{
|
|
|
|
|
$this->expectException(\RuntimeException::class);
|
|
|
|
|
Crypto::decryptString('not!valid@base64###');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function testDecryptThrowsOnTruncatedPayload(): void
|
|
|
|
|
{
|
|
|
|
|
$encrypted = Crypto::encryptString('secret');
|
|
|
|
|
$truncated = substr($encrypted, 0, (int) (strlen($encrypted) / 2));
|
|
|
|
|
|
|
|
|
|
$this->expectException(\RuntimeException::class);
|
|
|
|
|
Crypto::decryptString($truncated);
|
|
|
|
|
}
|
2026-03-19 19:36:17 +01:00
|
|
|
}
|