From 234f9300798588a7cd501c3909f7b638029ebf09 Mon Sep 17 00:00:00 2001 From: Ollama Date: Sat, 14 Mar 2026 12:14:59 +0000 Subject: [PATCH] Fix strftime directives handling and tighten test assertions - Remove incorrect %C mapping (was mapping century to full year) - Add special handling for %C (century), %c (datetime), %n (newline), %t (tab), %x (date) - Add %h mapping (same as %b for abbreviated month) - Tighten edge-case test assertions to use assertSame/assertMatchesRegularExpression - Add tests for new directives: %C, %c, %n, %t, %x, %h --- Dockerfile.test | 3 ++ app/Libraries/Token_lib.php | 38 ++++++++++---- build/.phpunit.cache/test-results | 1 + build/logs/logfile.xml | 38 ++++++++++++++ build/logs/testdox.html | 87 +++++++++++++++++++++++++++++++ build/logs/testdox.txt | 31 +++++++++++ tests/Libraries/Token_libTest.php | 71 ++++++++++++++++++++----- 7 files changed, 248 insertions(+), 21 deletions(-) create mode 100644 Dockerfile.test create mode 100644 build/.phpunit.cache/test-results create mode 100644 build/logs/logfile.xml create mode 100644 build/logs/testdox.html create mode 100644 build/logs/testdox.txt diff --git a/Dockerfile.test b/Dockerfile.test new file mode 100644 index 000000000..3729f6ac9 --- /dev/null +++ b/Dockerfile.test @@ -0,0 +1,3 @@ +FROM php:8.4-cli +RUN apt-get update && apt-get install -y libicu-dev && docker-php-ext-install intl +WORKDIR /app \ No newline at end of file diff --git a/app/Libraries/Token_lib.php b/app/Libraries/Token_lib.php index cd5b72352..e260240e1 100644 --- a/app/Libraries/Token_lib.php +++ b/app/Libraries/Token_lib.php @@ -23,6 +23,7 @@ class Token_lib '%D' => 'MM/dd/yy', '%e' => 'd', '%F' => 'yyyy-MM-dd', + '%h' => 'MMM', '%j' => 'D', '%m' => 'MM', '%U' => 'w', @@ -43,7 +44,6 @@ class Token_lib '%X' => 'HH:mm:ss', '%z' => 'ZZZZZ', '%Z' => 'z', - '%C' => 'yyyy', '%g' => 'yy', '%G' => 'yyyy', '%u' => 'e', @@ -51,7 +51,7 @@ class Token_lib ]; private array $validStrftimeFormats = [ - 'a', 'A', 'b', 'B', 'c', 'C', 'd', 'D', 'e', 'F', 'g', 'G', + 'a', 'A', 'b', 'B', 'c', 'd', 'D', 'e', 'F', 'g', 'G', 'h', 'H', 'I', 'j', 'm', 'M', 'n', 'p', 'P', 'r', 'R', 'S', 't', 'T', 'u', 'U', 'V', 'w', 'W', 'x', 'X', 'y', 'Y', 'z', 'Z' ]; @@ -92,19 +92,39 @@ class Token_lib $dateTime = new DateTime(); return preg_replace_callback( - '/%([a-zA-Z%]|%%)?/', + '/%([a-zA-Z%])/', function ($match) use ($formatter, $dateTime) { - if ($match[0] === '%%') { - return '%'; - } + $formatChar = $match[1]; - $formatChar = $match[1] ?? ''; - if ($formatChar === '%') { return '%'; } - if ($formatChar === '' || !in_array($formatChar, $this->validStrftimeFormats, true)) { + if ($formatChar === 'n') { + return "\n"; + } + + if ($formatChar === 't') { + return "\t"; + } + + if ($formatChar === 'C') { + return str_pad((string) intdiv((int) $dateTime->format('Y'), 100), 2, '0', STR_PAD_LEFT); + } + + if ($formatChar === 'c') { + $formatter->setPattern('yyyy-MM-dd HH:mm:ss'); + $result = $formatter->format($dateTime); + return $result !== false ? $result : $match[0]; + } + + if ($formatChar === 'x') { + $formatter->setPattern('yyyy-MM-dd'); + $result = $formatter->format($dateTime); + return $result !== false ? $result : $match[0]; + } + + if (!in_array($formatChar, $this->validStrftimeFormats, true)) { return $match[0]; } diff --git a/build/.phpunit.cache/test-results b/build/.phpunit.cache/test-results new file mode 100644 index 000000000..f218fd058 --- /dev/null +++ b/build/.phpunit.cache/test-results @@ -0,0 +1 @@ +{"version":2,"defects":{"Token_libTest::testRenderHandlesSpecialCharacters":8,"Token_libTest::testRenderHandlesUnicode":8,"Token_libTest::testRenderHandlesNewLines":8,"Token_libTest::testRenderHandlesTabs":8,"Token_libTest::testRenderHandlesDateAtStart":8,"Token_libTest::testRenderHandlesSqlInjectionAttempt":8,"Token_libTest::testRenderHandlesVeryLongStringWithDate":8,"Token_libTest::testRenderHandlesMultipleDates":8,"Token_libTest::testRenderDoesNotReplaceInvalidFormatSpecifiers":7},"times":{"Token_libTest::testRenderReturnsInputStringWhenNoTokens":0.002,"Token_libTest::testRenderHandlesStringWithPercentNotInDateFormat":0.004,"Token_libTest::testRenderHandlesInvalidDateFormatPercentDashPercent":0.001,"Token_libTest::testRenderHandlesInvalidDateFormatPercentYPercentQPercentBad":0,"Token_libTest::testRenderHandlesStringWithPercentAPercent":0,"Token_libTest::testRenderHandlesExtremelyLongString":0,"Token_libTest::testRenderHandlesStringWithMultiplePercentSymbols":0,"Token_libTest::testRenderHandlesStringWithOnlyPercentSymbol":0,"Token_libTest::testRenderPreservesTextWithValidDateTokensAndNoOtherTokens":0,"Token_libTest::testRenderHandlesEmptyString":0,"Token_libTest::testScanExtractsTokens":0,"Token_libTest::testScanExtractsTokensWithLength":0,"Token_libTest::testScanReturnsEmptyArrayForNoTokens":0,"Token_libTest::testRenderHandlesConsecutivePercentSigns":0,"Token_libTest::testRenderHandlesEscapedPercentSigns":0,"Token_libTest::testRenderHandlesSpecialCharacters":0.005,"Token_libTest::testRenderHandlesUnicode":0,"Token_libTest::testRenderHandlesNewLines":0,"Token_libTest::testRenderHandlesTabs":0,"Token_libTest::testRenderHandlesUnclosedBraces":0,"Token_libTest::testRenderHandlesUnopenedBraces":0,"Token_libTest::testRenderHandlesDateAtStart":0,"Token_libTest::testRenderHandlesSqlInjectionAttempt":0,"Token_libTest::testRenderHandlesVeryLongStringWithDate":0,"Token_libTest::testRenderHandlesMultipleDates":0,"Token_libTest::testRenderHandlesValidYearFormat":0,"Token_libTest::testRenderHandlesValidMonthFormat":0,"Token_libTest::testRenderHandlesValidDayFormat":0,"Token_libTest::testRenderHandlesFullDateFormat":0,"Token_libTest::testRenderHandlesPercentB":0,"Token_libTest::testRenderHandlesPercentA":0,"Token_libTest::testRenderHandlesComplexPercentFormat":0,"Token_libTest::testRenderDoesNotReplaceInvalidFormatSpecifiers":0,"Token_libTest::testScanWorksWithMixedContent":0,"Token_libTest::testRenderReplacesTimezoneFormat":0,"Tests\\Libraries\\Token_libTest::testRenderReturnsInputStringWhenNoTokens":0.001,"Tests\\Libraries\\Token_libTest::testRenderHandlesStringWithPercentNotInDateFormat":0.004,"Tests\\Libraries\\Token_libTest::testRenderHandlesInvalidDateFormatPercentDashPercent":0.001,"Tests\\Libraries\\Token_libTest::testRenderHandlesInvalidDateFormatPercentYPercentQPercentBad":0,"Tests\\Libraries\\Token_libTest::testRenderHandlesStringWithPercentAPercent":0,"Tests\\Libraries\\Token_libTest::testRenderHandlesExtremelyLongString":0,"Tests\\Libraries\\Token_libTest::testRenderHandlesStringWithMultiplePercentSymbols":0,"Tests\\Libraries\\Token_libTest::testRenderHandlesStringWithOnlyPercentSymbol":0,"Tests\\Libraries\\Token_libTest::testRenderPreservesTextWithValidDateTokensAndNoOtherTokens":0,"Tests\\Libraries\\Token_libTest::testRenderHandlesEmptyString":0,"Tests\\Libraries\\Token_libTest::testScanExtractsTokens":0,"Tests\\Libraries\\Token_libTest::testScanExtractsTokensWithLength":0,"Tests\\Libraries\\Token_libTest::testScanReturnsEmptyArrayForNoTokens":0,"Tests\\Libraries\\Token_libTest::testRenderHandlesConsecutivePercentSigns":0,"Tests\\Libraries\\Token_libTest::testRenderHandlesEscapedPercentSigns":0,"Tests\\Libraries\\Token_libTest::testRenderHandlesUnclosedBraces":0,"Tests\\Libraries\\Token_libTest::testRenderHandlesUnopenedBraces":0,"Tests\\Libraries\\Token_libTest::testRenderHandlesVeryLongStringWithDate":0,"Tests\\Libraries\\Token_libTest::testRenderHandlesMultipleDates":0,"Tests\\Libraries\\Token_libTest::testRenderHandlesValidYearFormat":0,"Tests\\Libraries\\Token_libTest::testRenderHandlesValidMonthFormat":0,"Tests\\Libraries\\Token_libTest::testRenderHandlesValidDayFormat":0,"Tests\\Libraries\\Token_libTest::testRenderHandlesFullDateFormat":0,"Tests\\Libraries\\Token_libTest::testRenderHandlesPercentB":0,"Tests\\Libraries\\Token_libTest::testRenderHandlesPercentA":0,"Tests\\Libraries\\Token_libTest::testRenderHandlesComplexPercentFormat":0,"Tests\\Libraries\\Token_libTest::testRenderDoesNotReplaceInvalidFormatSpecifiers":0,"Tests\\Libraries\\Token_libTest::testRenderReplacesTimezoneFormat":0,"Tests\\Libraries\\Token_libTest::testScanWorksWithMixedContent":0}} \ No newline at end of file diff --git a/build/logs/logfile.xml b/build/logs/logfile.xml new file mode 100644 index 000000000..0620ddbf0 --- /dev/null +++ b/build/logs/logfile.xml @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/build/logs/testdox.html b/build/logs/testdox.html new file mode 100644 index 000000000..90bca5c26 --- /dev/null +++ b/build/logs/testdox.html @@ -0,0 +1,87 @@ + + + + + Test Documentation + + + +

Token_lib (Tests\Libraries\Token_lib)

+ + + \ No newline at end of file diff --git a/build/logs/testdox.txt b/build/logs/testdox.txt new file mode 100644 index 000000000..1fb7200d4 --- /dev/null +++ b/build/logs/testdox.txt @@ -0,0 +1,31 @@ +Token_lib (Tests\Libraries\Token_lib) + [x] Render returns input string when no tokens + [x] Render handles string with percent not in date format + [x] Render handles invalid date format percent dash percent + [x] Render handles invalid date format percent y percent q percent bad + [x] Render handles string with percent a percent + [x] Render handles extremely long string + [x] Render handles string with multiple percent symbols + [x] Render handles string with only percent symbol + [x] Render preserves text with valid date tokens and no other tokens + [x] Render handles empty string + [x] Scan extracts tokens + [x] Scan extracts tokens with length + [x] Scan returns empty array for no tokens + [x] Render handles consecutive percent signs + [x] Render handles escaped percent signs + [x] Render handles unclosed braces + [x] Render handles unopened braces + [x] Render handles very long string with date + [x] Render handles multiple dates + [x] Render handles valid year format + [x] Render handles valid month format + [x] Render handles valid day format + [x] Render handles full date format + [x] Render handles percent b + [x] Render handles percent a + [x] Render handles complex percent format + [x] Render does not replace invalid format specifiers + [x] Render replaces timezone format + [x] Scan works with mixed content + diff --git a/tests/Libraries/Token_libTest.php b/tests/Libraries/Token_libTest.php index 8a5a52f17..fac3d92f0 100644 --- a/tests/Libraries/Token_libTest.php +++ b/tests/Libraries/Token_libTest.php @@ -34,22 +34,21 @@ class Token_libTest extends CIUnitTestCase { $input = '%-%-%'; $result = $this->tokenLib->render($input, [], false); - $this->assertNotEmpty($result); - $this->assertNotEquals('', $result); + $this->assertSame('%-%-%', $result); } public function testRenderHandlesInvalidDateFormatPercentYPercentQPercentBad(): void { $input = '%Y-%q-%bad'; $result = $this->tokenLib->render($input, [], false); - $this->assertNotEmpty($result); + $this->assertMatchesRegularExpression('/\d{4}-%q-%bad/', $result); } public function testRenderHandlesStringWithPercentAPercent(): void { $input = '%a%'; $result = $this->tokenLib->render($input, [], false); - $this->assertNotEmpty($result); + $this->assertMatchesRegularExpression('/^[A-Za-z]{3}%$/', $result); } public function testRenderHandlesExtremelyLongString(): void @@ -112,44 +111,42 @@ class Token_libTest extends CIUnitTestCase { $input = 'Progress: 100%% complete'; $result = $this->tokenLib->render($input, [], false); - $this->assertNotEmpty($result); - $this->assertStringContainsString('complete', $result); + $this->assertSame('Progress: 100% complete', $result); } public function testRenderHandlesEscapedPercentSigns(): void { $input = 'Value: %%'; $result = $this->tokenLib->render($input, [], false); - $this->assertNotEmpty($result); + $this->assertSame('Value: %', $result); } public function testRenderHandlesUnclosedBraces(): void { $input = "Invoice {CO Date: %Y-%m-%d"; $result = $this->tokenLib->render($input, [], false); - $this->assertNotEmpty($result); + $this->assertMatchesRegularExpression('/Invoice \{CO Date: \d{4}-\d{2}-\d{2}/', $result); } public function testRenderHandlesUnopenedBraces(): void { $input = "Invoice CO} Date: %Y-%m-%d"; $result = $this->tokenLib->render($input, [], false); - $this->assertNotEmpty($result); + $this->assertMatchesRegularExpression('/Invoice CO\} Date: \d{4}-\d{2}-\d{2}/', $result); } public function testRenderHandlesVeryLongStringWithDate(): void { $input = str_repeat('buffer ', 500) . '%Y-%m-%d Invoice' . str_repeat('buffer ', 500); $result = $this->tokenLib->render($input, [], false); - $this->assertNotEmpty($result); - $this->assertStringContainsString('buffer', $result); + $this->assertMatchesRegularExpression('/buffer.*\d{4}-\d{2}-\d{2} Invoice.*buffer/', $result); } public function testRenderHandlesMultipleDates(): void { $input = '%Y-%m-%d Invoice - %Y-%m-%d'; $result = $this->tokenLib->render($input, [], false); - $this->assertNotEmpty($result); + $this->assertMatchesRegularExpression('/\d{4}-\d{2}-\d{2} Invoice - \d{4}-\d{2}-\d{2}/', $result); } public function testRenderHandlesValidYearFormat(): void @@ -264,4 +261,54 @@ class Token_libTest extends CIUnitTestCase $this->assertStringNotContainsString('%R', $result); $this->assertMatchesRegularExpression('/Time: \d{2}:\d{2}/', $result); } + + public function testRenderHandlesPercentC(): void + { + $input = 'Century: %C'; + $result = $this->tokenLib->render($input, [], false); + $this->assertNotEmpty($result); + $this->assertStringNotContainsString('%C', $result); + $this->assertMatchesRegularExpression('/Century: \d{2}/', $result); + } + + public function testRenderHandlesLowercasePercentC(): void + { + $input = 'DateTime: %c'; + $result = $this->tokenLib->render($input, [], false); + $this->assertNotEmpty($result); + $this->assertStringNotContainsString('%c', $result); + $this->assertMatchesRegularExpression('/DateTime: \d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}/', $result); + } + + public function testRenderHandlesPercentN(): void + { + $input = "Line1%nLine2"; + $result = $this->tokenLib->render($input, [], false); + $this->assertSame("Line1\nLine2", $result); + } + + public function testRenderHandlesLowercasePercentT(): void + { + $input = "Col1%tCol2"; + $result = $this->tokenLib->render($input, [], false); + $this->assertSame("Col1\tCol2", $result); + } + + public function testRenderHandlesPercentX(): void + { + $input = 'Date: %x'; + $result = $this->tokenLib->render($input, [], false); + $this->assertNotEmpty($result); + $this->assertStringNotContainsString('%x', $result); + $this->assertMatchesRegularExpression('/Date: \d{4}-\d{2}-\d{2}/', $result); + } + + public function testRenderHandlesPercentH(): void + { + $input = 'Month: %h'; + $result = $this->tokenLib->render($input, [], false); + $this->assertNotEmpty($result); + $this->assertStringNotContainsString('%h', $result); + $this->assertMatchesRegularExpression('/Month: [A-Za-z]{3}/', $result); + } } \ No newline at end of file