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>
This commit is contained in:
@@ -67,4 +67,58 @@ final class CryptoTest extends TestCase
|
||||
$this->expectExceptionMessage('malformed');
|
||||
Crypto::decryptString($encrypted);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
137
tests/Unit/Support/SearchQueryNormalizerTest.php
Normal file
137
tests/Unit/Support/SearchQueryNormalizerTest.php
Normal file
@@ -0,0 +1,137 @@
|
||||
<?php
|
||||
|
||||
namespace MintyPHP\Tests\Unit\Support;
|
||||
|
||||
use MintyPHP\Support\Search\SearchQueryNormalizer;
|
||||
use PHPUnit\Framework\Attributes\DataProvider;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
final class SearchQueryNormalizerTest extends TestCase
|
||||
{
|
||||
// ── normalizeLikeQuery ──
|
||||
|
||||
public function testNormalizeLikeQueryReturnsEmptyForBlankInput(): void
|
||||
{
|
||||
self::assertSame('', SearchQueryNormalizer::normalizeLikeQuery(''));
|
||||
self::assertSame('', SearchQueryNormalizer::normalizeLikeQuery(' '));
|
||||
}
|
||||
|
||||
public function testNormalizeLikeQueryWrapsWithWildcards(): void
|
||||
{
|
||||
self::assertSame('%hello%', SearchQueryNormalizer::normalizeLikeQuery('hello'));
|
||||
}
|
||||
|
||||
public function testNormalizeLikeQueryDoesNotDoubleWrapLeadingWildcard(): void
|
||||
{
|
||||
self::assertSame('%hello%', SearchQueryNormalizer::normalizeLikeQuery('*hello'));
|
||||
}
|
||||
|
||||
public function testNormalizeLikeQueryDoesNotDoubleWrapTrailingWildcard(): void
|
||||
{
|
||||
self::assertSame('%hello%', SearchQueryNormalizer::normalizeLikeQuery('hello*'));
|
||||
}
|
||||
|
||||
public function testNormalizeLikeQueryDoesNotDoubleWrapBothWildcards(): void
|
||||
{
|
||||
self::assertSame('%hello%', SearchQueryNormalizer::normalizeLikeQuery('*hello*'));
|
||||
}
|
||||
|
||||
public function testNormalizeLikeQueryEscapesBackslash(): void
|
||||
{
|
||||
self::assertSame('%foo\\\\bar%', SearchQueryNormalizer::normalizeLikeQuery('foo\\bar'));
|
||||
}
|
||||
|
||||
public function testNormalizeLikeQueryEscapesUnderscore(): void
|
||||
{
|
||||
self::assertSame('%foo\\_bar%', SearchQueryNormalizer::normalizeLikeQuery('foo_bar'));
|
||||
}
|
||||
|
||||
public function testNormalizeLikeQueryConvertsAsterisksToPercent(): void
|
||||
{
|
||||
self::assertSame('%foo%bar%', SearchQueryNormalizer::normalizeLikeQuery('foo*bar'));
|
||||
}
|
||||
|
||||
public function testNormalizeLikeQueryCombinesEscapingAndWildcards(): void
|
||||
{
|
||||
// Input: "a_b*c\d" → underscores escaped, * becomes %, backslash escaped, wrapped
|
||||
self::assertSame('%a\\_b%c\\\\d%', SearchQueryNormalizer::normalizeLikeQuery('a_b*c\\d'));
|
||||
}
|
||||
|
||||
public function testNormalizeLikeQueryTrimsWhitespace(): void
|
||||
{
|
||||
self::assertSame('%search%', SearchQueryNormalizer::normalizeLikeQuery(' search '));
|
||||
}
|
||||
|
||||
public function testNormalizeLikeQuerySingleCharacter(): void
|
||||
{
|
||||
self::assertSame('%x%', SearchQueryNormalizer::normalizeLikeQuery('x'));
|
||||
}
|
||||
|
||||
public function testNormalizeLikeQueryOnlyWildcard(): void
|
||||
{
|
||||
self::assertSame('%', SearchQueryNormalizer::normalizeLikeQuery('*'));
|
||||
}
|
||||
|
||||
public function testNormalizeLikeQueryMultipleConsecutiveWildcards(): void
|
||||
{
|
||||
self::assertSame('%a%%%b%', SearchQueryNormalizer::normalizeLikeQuery('a***b'));
|
||||
}
|
||||
|
||||
public function testNormalizeLikeQueryUnicodeInput(): void
|
||||
{
|
||||
self::assertSame('%Ünterström%', SearchQueryNormalizer::normalizeLikeQuery('Ünterström'));
|
||||
}
|
||||
|
||||
// ── normalizeScoreQuery ──
|
||||
|
||||
public function testNormalizeScoreQueryReturnsEmptyForBlankInput(): void
|
||||
{
|
||||
self::assertSame('', SearchQueryNormalizer::normalizeScoreQuery(''));
|
||||
self::assertSame('', SearchQueryNormalizer::normalizeScoreQuery(' '));
|
||||
}
|
||||
|
||||
public function testNormalizeScoreQueryStripsAsterisks(): void
|
||||
{
|
||||
self::assertSame('hello', SearchQueryNormalizer::normalizeScoreQuery('*hello*'));
|
||||
}
|
||||
|
||||
public function testNormalizeScoreQueryStripsPercent(): void
|
||||
{
|
||||
self::assertSame('hello', SearchQueryNormalizer::normalizeScoreQuery('%hello%'));
|
||||
}
|
||||
|
||||
public function testNormalizeScoreQueryStripsUnderscore(): void
|
||||
{
|
||||
self::assertSame('foobar', SearchQueryNormalizer::normalizeScoreQuery('foo_bar'));
|
||||
}
|
||||
|
||||
public function testNormalizeScoreQueryStripsAllWildcardTypes(): void
|
||||
{
|
||||
self::assertSame('abc', SearchQueryNormalizer::normalizeScoreQuery('*a%b_c*'));
|
||||
}
|
||||
|
||||
public function testNormalizeScoreQueryPreservesNormalText(): void
|
||||
{
|
||||
self::assertSame('search term', SearchQueryNormalizer::normalizeScoreQuery('search term'));
|
||||
}
|
||||
|
||||
public function testNormalizeScoreQueryTrimsWhitespace(): void
|
||||
{
|
||||
self::assertSame('query', SearchQueryNormalizer::normalizeScoreQuery(' query '));
|
||||
}
|
||||
|
||||
public function testNormalizeScoreQueryOnlyWildcards(): void
|
||||
{
|
||||
self::assertSame('', SearchQueryNormalizer::normalizeScoreQuery('*%_'));
|
||||
}
|
||||
|
||||
public function testNormalizeScoreQueryPreservesBackslash(): void
|
||||
{
|
||||
self::assertSame('a\\b', SearchQueryNormalizer::normalizeScoreQuery('a\\b'));
|
||||
}
|
||||
|
||||
public function testNormalizeScoreQueryUnicodeInput(): void
|
||||
{
|
||||
self::assertSame('Müller', SearchQueryNormalizer::normalizeScoreQuery('Müller'));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user