From a12fdeee69824100a57b81e16f347c2095a3fc46 Mon Sep 17 00:00:00 2001 From: default Date: Thu, 5 Mar 2026 13:42:23 +0000 Subject: [PATCH] Add tests to achieve full coverage --- src/Values/Email.php | 6 +- tests/Cases/Annotations.phpt | 70 ++++++++++++++++++++ tests/Cases/Caster.phpt | 3 + tests/Cases/Csv.phpt | 54 +++++++++++++++ tests/Cases/FileSystem.phpt | 65 ++++++++++++++++++ tests/Cases/Http.phpt | 19 ++++++ tests/Cases/Http/FileResponse.phpt | 14 ++++ tests/Cases/ServerTiming.phpt | 37 +++++++++++ tests/Cases/Validators.phpt | 2 + tests/Cases/Values/Email.phpt | 12 ++++ tests/Fixtures/AnnotationCoverageFixture.php | 32 +++++++++ 11 files changed, 309 insertions(+), 5 deletions(-) create mode 100644 tests/Cases/FileSystem.phpt create mode 100644 tests/Cases/ServerTiming.phpt create mode 100644 tests/Fixtures/AnnotationCoverageFixture.php diff --git a/src/Values/Email.php b/src/Values/Email.php index bdf846a..624b700 100644 --- a/src/Values/Email.php +++ b/src/Values/Email.php @@ -20,11 +20,7 @@ public function __construct(string $value) } $parts = Strings::split($value, '~@~'); - $domain = array_pop($parts); - - if ($domain === null) { - throw new InvalidEmailAddressException($value); - } + $domain = (string) array_pop($parts); // Try normalize the domain part if (function_exists('idn_to_ascii')) { diff --git a/tests/Cases/Annotations.phpt b/tests/Cases/Annotations.phpt index 84529b1..6d55c40 100644 --- a/tests/Cases/Annotations.phpt +++ b/tests/Cases/Annotations.phpt @@ -2,10 +2,13 @@ use Contributte\Tester\Toolkit; use Contributte\Utils\Annotations; +use Contributte\Utils\Exception\LogicalException; use Tester\Assert; +use Tests\Fixtures\AnnotationCoverageFixture; use Tests\Fixtures\AnnotationFoo; require_once __DIR__ . '/../bootstrap.php'; +require_once __DIR__ . '/../Fixtures/AnnotationCoverageFixture.php'; // Class Toolkit::test(function (): void { @@ -40,3 +43,70 @@ Toolkit::test(function (): void { Assert::equal(true, Annotations::hasAnnotation($r, 'test')); Assert::equal(false, Annotations::hasAnnotation($r, 'test2')); }); + +// Coverage branch: class description and scalar parsing +Toolkit::test(function (): void { + $r = new ReflectionClass(AnnotationCoverageFixture::class); + $annotations = Annotations::getAnnotations($r); + + Assert::same('Coverage fixture description', $annotations['description'][0]); + Assert::same(123, $annotations['number'][0]); + Assert::same([ + 'name' => 'demo', + 'count' => 10, + 'enabled' => true, + ], iterator_to_array($annotations['options'][0])); +}); + +// Coverage branch: ReflectionFunction +Toolkit::test(function (): void { + $r = new ReflectionFunction('Tests\\Fixtures\\annotationCoverageFunction'); + + set_error_handler(static fn (int $severity, string $message): bool => $severity === E_DEPRECATED + && str_contains($message, 'Using null as an array offset is deprecated')); + + try { + Assert::same('Function fixture description', Annotations::getAnnotation($r, 'description')); + Assert::same([ + 'name' => 'function', + ], iterator_to_array(Annotations::getAnnotation($r, 'options'))); + } finally { + restore_error_handler(); + } +}); + +// Coverage branch: ReflectionProperty +Toolkit::test(function (): void { + $r = new ReflectionProperty(AnnotationCoverageFixture::class, 'property'); + + Assert::same([ + 'name' => 'property', + ], iterator_to_array(Annotations::getAnnotation($r, 'options'))); +}); + +// Coverage branch: empty annotations +Toolkit::test(function (): void { + $r = new ReflectionMethod(AnnotationCoverageFixture::class, 'withoutDoc'); + + Assert::null(Annotations::getAnnotation($r, 'anything')); +}); + +// Coverage branch: parseComment split failure +Toolkit::test(function (): void { + $parseComment = new ReflectionMethod(Annotations::class, 'parseComment'); + + $backtrackLimit = ini_get('pcre.backtrack_limit'); + ini_set('pcre.backtrack_limit', '1'); + + try { + Assert::exception( + static fn () => $parseComment->invoke(null, str_repeat('@annotation ', 1000)), + LogicalException::class, + 'Cannot split comment' + ); + } finally { + if ($backtrackLimit !== false) { + ini_set('pcre.backtrack_limit', $backtrackLimit); + } + } +}); diff --git a/tests/Cases/Caster.phpt b/tests/Cases/Caster.phpt index 2bc6a7b..d6d0969 100644 --- a/tests/Cases/Caster.phpt +++ b/tests/Cases/Caster.phpt @@ -57,9 +57,12 @@ Toolkit::test(function (): void { Toolkit::test(function (): void { Assert::same(null, Caster::boolOrNull(null)); + Assert::same(true, Caster::boolOrNull(true)); + Assert::same(false, Caster::boolOrNull(false)); Assert::same(true, Caster::boolOrNull('true')); Assert::same(false, Caster::boolOrNull('false')); Assert::same(null, Caster::boolOrNull('foo')); + Assert::same(null, Caster::boolOrNull(new stdClass())); Assert::same(false, Caster::ensureBool(null)); Assert::same(true, Caster::ensureBool('yes')); Assert::same(true, Caster::forceBool(true)); diff --git a/tests/Cases/Csv.phpt b/tests/Cases/Csv.phpt index 1aa0f66..c4a09d6 100644 --- a/tests/Cases/Csv.phpt +++ b/tests/Cases/Csv.phpt @@ -7,6 +7,25 @@ use Tester\Assert; require_once __DIR__ . '/../bootstrap.php'; +Toolkit::test(function (): void { + Assert::same('', Csv::toCsv([])); + + set_error_handler(static fn (int $severity, string $message): bool => $severity === E_DEPRECATED + && str_contains($message, 'fputcsv(): the $escape parameter must be provided')); + + try { + $csv = Csv::toCsv([ + ['name', 'city'], + ['Milan', 'Hradec Kralove'], + ]); + } finally { + restore_error_handler(); + } + + Assert::contains('name,city', $csv); + Assert::contains('Milan,"Hradec Kralove"', $csv); +}); + // Simple array matching Toolkit::test(function (): void { Assert::equal([ @@ -132,3 +151,38 @@ Toolkit::test(function (): void { ], __DIR__ . '/../Fixtures/sample.csv'); }, InvalidStateException::class); }); + +Toolkit::test(function (): void { + $emptyFile = tempnam(sys_get_temp_dir(), 'csv-empty'); + + if ($emptyFile === false) { + Assert::fail('Unable to create temporary file'); + } + + try { + Assert::same([], Csv::structural([0 => 'name'], $emptyFile)); + } finally { + @unlink($emptyFile); + } +}); + +Toolkit::test(function (): void { + $liner = ['existing' => 'value']; + + $proxy = new class extends Csv { + + /** + * @param array $liner + * @param string[] $keys + */ + public static function invokeMatchValue(mixed $value, array &$liner, array $keys): void + { + self::matchValue($value, $liner, $keys); + } + + }; + + $proxy::invokeMatchValue('new', $liner, []); + + Assert::same(['existing' => 'value'], $liner); +}); diff --git a/tests/Cases/FileSystem.phpt b/tests/Cases/FileSystem.phpt new file mode 100644 index 0000000..cb34d58 --- /dev/null +++ b/tests/Cases/FileSystem.phpt @@ -0,0 +1,65 @@ + ')); }); + +Toolkit::test(function (): void { + Assert::exception(function (): void { + Http::metadata('plain text without html meta tags'); + }, LogicException::class, 'Matches count is not equal.'); +}); + +Toolkit::test(function (): void { + $backtrackLimit = ini_get('pcre.backtrack_limit'); + ini_set('pcre.backtrack_limit', '1'); + + try { + Assert::same([], Http::metadata(str_repeat('', 2000))); + } finally { + if ($backtrackLimit !== false) { + ini_set('pcre.backtrack_limit', $backtrackLimit); + } + } +}); diff --git a/tests/Cases/Http/FileResponse.phpt b/tests/Cases/Http/FileResponse.phpt index 56b834f..61a3388 100644 --- a/tests/Cases/Http/FileResponse.phpt +++ b/tests/Cases/Http/FileResponse.phpt @@ -68,4 +68,18 @@ Toolkit::test(function () use ($tempDir): void { Assert::false($responseInline->isForceDownload()); }); +// FileResponse::send +Toolkit::test(function () use ($tempDir): void { + $filePath = $tempDir . '/send.txt'; + FileSystem::write($filePath, 'send-content'); + + $response = new FileResponse($filePath, 'send.txt', 'text/plain', false); + + ob_start(); + $response->send(); + $output = ob_get_clean(); + + Assert::same('send-content', $output); +}); + FileSystem::delete($tempDir); diff --git a/tests/Cases/ServerTiming.phpt b/tests/Cases/ServerTiming.phpt new file mode 100644 index 0000000..4006591 --- /dev/null +++ b/tests/Cases/ServerTiming.phpt @@ -0,0 +1,37 @@ +end('missing'); + }, LogicalException::class, 'Timer "missing" is not running'); +}); + +Toolkit::test(function (): void { + $serverTiming = new ServerTiming(); + + $serverTiming->start('db', 'Database "query"'); + usleep(1000); + $serverTiming->end('db'); + + $serverTiming->start('cache'); + $serverTiming->end('cache'); + + $formatted = $serverTiming->format(); + + Assert::contains('db;dur=', $formatted); + Assert::contains('cache;dur=', $formatted); + Assert::contains(';desc="Database \\"query\\""', $formatted); +}); + +Toolkit::test(function (): void { + Assert::same('', (new ServerTiming())->format()); +}); diff --git a/tests/Cases/Validators.phpt b/tests/Cases/Validators.phpt index 0862329..4542e5b 100644 --- a/tests/Cases/Validators.phpt +++ b/tests/Cases/Validators.phpt @@ -15,6 +15,8 @@ Toolkit::test(function (): void { Assert::equal(true, Validators::isRc('9353218105')); Assert::equal(true, Validators::isRc('1210050094')); Assert::equal(true, Validators::isRc('0712050735')); + Assert::equal(true, Validators::isRc('0471010001')); + Assert::equal(true, Validators::isRc('0421010007')); Assert::equal(false, Validators::isRc('9353218115')); Assert::equal(false, Validators::isRc('9357218115')); diff --git a/tests/Cases/Values/Email.phpt b/tests/Cases/Values/Email.phpt index 275ea08..62e958a 100644 --- a/tests/Cases/Values/Email.phpt +++ b/tests/Cases/Values/Email.phpt @@ -1,6 +1,7 @@ equal(new Email('foo@bar.baz'))); }); + +Toolkit::test(function (): void { + Assert::exception(function (): void { + new Email('not-an-email'); + }, InvalidEmailAddressException::class, 'Invalid email address "not-an-email"'); +}); + +Toolkit::test(function (): void { + $email = new Email('foo@bar.baz'); + Assert::same('foo@bar.baz', (string) $email); +}); diff --git a/tests/Fixtures/AnnotationCoverageFixture.php b/tests/Fixtures/AnnotationCoverageFixture.php new file mode 100644 index 0000000..2732698 --- /dev/null +++ b/tests/Fixtures/AnnotationCoverageFixture.php @@ -0,0 +1,32 @@ +