diff --git a/src/AutomataMatcher.php b/src/AutomataMatcher.php index e9674bd..ccfb4af 100644 --- a/src/AutomataMatcher.php +++ b/src/AutomataMatcher.php @@ -161,6 +161,12 @@ private static function parseToken( */ public function match(string $path): ?Rule { + if (str_starts_with($path, '/') || str_ends_with($path, '/')) { + throw new \InvalidArgumentException( + "path should be a relative path to a file, thus it cannot start or end with a /" + ); + } + $path = explode('/', $path); $index = $this->start->match($path); return $index >= 0 diff --git a/src/SimpleMatcher.php b/src/SimpleMatcher.php index 57d4c55..3abd662 100644 --- a/src/SimpleMatcher.php +++ b/src/SimpleMatcher.php @@ -35,6 +35,12 @@ public function __construct(iterable $rules) */ public function match(string $path): ?Rule { + if (str_starts_with($path, '/') || str_ends_with($path, '/')) { + throw new \InvalidArgumentException( + "path should be a relative path to a file, thus it cannot start or end with a /" + ); + } + foreach ($this->rules as [$matcher, $rule]) { if ($matcher($path)) { return $rule; diff --git a/tests/AutomataMatcherTest.php b/tests/AutomataMatcherTest.php index 9a3b807..433ba53 100644 --- a/tests/AutomataMatcherTest.php +++ b/tests/AutomataMatcherTest.php @@ -2,8 +2,11 @@ namespace Kellegous\CodeOwners; +use Exception; use PHPUnit\Framework\TestCase; +require_once __DIR__ . '/TestProvider.php'; + /** * @covers AutomataMatcher */ @@ -15,49 +18,30 @@ final class AutomataMatcherTest extends TestCase */ public static function getMatchTests(): iterable { - $tests = include __DIR__ . '/matcher_tests.php'; - foreach ($tests as $desc => ['lines' => $lines, 'expected' => $expected]) { - $matcher = self::matcherWith($lines); - foreach ($expected as $path => $line) { - yield "{$desc} w/ {$path}" => [ - $matcher, - $path, - $line - ]; - } - } + return TestProvider::forMatchTests( + fn(iterable $rules) => AutomataMatcher::build($rules) + ); } /** - * @param string[] $lines - * @return AutomataMatcher + * @return iterable * @throws ParseException */ - private static function matcherWith(array $lines): AutomataMatcher + public static function getExampleTests(): iterable { - return AutomataMatcher::build( - Owners::fromString( - implode(PHP_EOL, $lines) - )->getRules() + return TestProvider::forExampleTests( + fn(iterable $rules) => AutomataMatcher::build($rules) ); } /** - * @return iterable - * @throws ParseException + * @return iterable */ - public static function getExampleTests(): iterable + public static function getBadInputTests(): iterable { - $owners = Owners::fromFile(__DIR__ . '/CODEOWNERS.example'); - $matcher = AutomataMatcher::build($owners->getRules()); - $tests = include __DIR__ . '/example_tests.php'; - foreach ($tests as $path => $line) { - yield $path => [ - $matcher, - $path, - $line - ]; - } + return TestProvider::forBadInputTests( + fn(iterable $rules) => AutomataMatcher::build($rules) + ); } /** @@ -99,4 +83,21 @@ public function testExampleMatch( : null; self::assertSame($expected_line, $line); } + + /** + * @param AutomataMatcher $matcher + * @param string $path + * @param Exception $exception + * @return void + * + * @dataProvider getBadInputTests + */ + public function testBadInput( + RuleMatcher $matcher, + string $path, + Exception $exception + ): void { + self::expectExceptionObject($exception); + $matcher->match($path); + } } diff --git a/tests/CODEOWNERS.example b/tests/CODEOWNERS.example index 10c8981..b1a2865 100644 --- a/tests/CODEOWNERS.example +++ b/tests/CODEOWNERS.example @@ -54,4 +54,4 @@ a/**/b @doctocat # In this example nobody owns the files # This line should be ignored by the parser. -/src \ No newline at end of file +/src diff --git a/tests/PatternTest.php b/tests/PatternTest.php index 36bc28c..090833e 100644 --- a/tests/PatternTest.php +++ b/tests/PatternTest.php @@ -46,4 +46,4 @@ public function testPatternMatch( $matcher($path) ); } -} \ No newline at end of file +} diff --git a/tests/SimpleMatcherTest.php b/tests/SimpleMatcherTest.php index 6d5c8a6..61e6133 100644 --- a/tests/SimpleMatcherTest.php +++ b/tests/SimpleMatcherTest.php @@ -2,8 +2,11 @@ namespace Kellegous\CodeOwners; +use Exception; use PHPUnit\Framework\TestCase; +require_once __DIR__ . '/TestProvider.php'; + /** * @covers SimpleMatcher */ @@ -15,49 +18,30 @@ class SimpleMatcherTest extends TestCase */ public static function getMatchTests(): iterable { - $tests = include __DIR__ . '/matcher_tests.php'; - foreach ($tests as $desc => ['lines' => $lines, 'expected' => $expected]) { - $matcher = self::matcherWith($lines); - foreach ($expected as $path => $line) { - yield "{$desc} w/ {$path}" => [ - $matcher, - $path, - $line - ]; - } - } + return TestProvider::forMatchTests( + fn(iterable $rules) => new SimpleMatcher($rules) + ); } /** - * @param string[] $lines - * @return SimpleMatcher + * @return iterable * @throws ParseException */ - private static function matcherWith(array $lines): SimpleMatcher + public static function getExampleTests(): iterable { - return new SimpleMatcher( - Owners::fromString( - implode(PHP_EOL, $lines) - )->getRules() + return TestProvider::forExampleTests( + fn(iterable $rules) => new SimpleMatcher($rules) ); } /** - * @return iterable - * @throws ParseException + * @return iterable */ - public static function getExampleTests(): iterable + public static function getBadInputTests(): iterable { - $owners = Owners::fromFile(__DIR__ . '/CODEOWNERS.example'); - $matcher = new SimpleMatcher($owners->getRules()); - $tests = include __DIR__ . '/example_tests.php'; - foreach ($tests as $path => $line) { - yield $path => [ - $matcher, - $path, - $line - ]; - } + return TestProvider::forBadInputTests( + fn(iterable $rules) => new SimpleMatcher($rules) + ); } /** @@ -99,4 +83,21 @@ public function testExampleMatch( : null; self::assertSame($expected_line, $line); } + + /** + * @param RuleMatcher $matcher + * @param string $path + * @param Exception $exception + * @return void + * + * @dataProvider getBadInputTests + */ + public function testBadInput( + RuleMatcher $matcher, + string $path, + Exception $exception + ): void { + self::expectExceptionObject($exception); + $matcher->match($path); + } } \ No newline at end of file diff --git a/tests/TestProvider.php b/tests/TestProvider.php new file mode 100644 index 0000000..1788112 --- /dev/null +++ b/tests/TestProvider.php @@ -0,0 +1,91 @@ +):T $create_matcher + * @return iterable + * @throws ParseException + */ + public static function forMatchTests( + Closure $create_matcher + ): iterable { + $tests = include __DIR__ . '/matcher_tests.php'; + foreach ($tests as $desc => ['lines' => $lines, 'expected' => $expected]) { + $rules = Owners::fromString( + implode(PHP_EOL, $lines) + )->getRules(); + $matcher = $create_matcher($rules); + foreach ($expected as $path => $line) { + yield "{$desc} w/ {$path}" => [ + $matcher, + $path, + $line + ]; + } + } + } + + /** + * @template T extends RuleMatcher + * @param Closure(iterable):T $create_matcher + * @return iterable + * @throws ParseException + */ + public static function forExampleTests( + Closure $create_matcher + ): iterable { + $owners = Owners::fromFile(__DIR__ . '/CODEOWNERS.example'); + $matcher = $create_matcher($owners->getRules()); + $tests = include __DIR__ . '/example_tests.php'; + foreach ($tests as $path => $line) { + yield $path => [ + $matcher, + $path, + $line + ]; + } + } + + /** + * @template T extends RuleMatcher + * @param Closure(iterable):T $create_matcher + * @return iterable + */ + public static function forBadInputTests( + Closure $create_matcher + ): iterable { + $matcher = $create_matcher([]); + yield 'leading /' => [ + $matcher, + '/foo/bar', + new InvalidArgumentException( + "path should be a relative path to a file, thus it cannot start or end with a /" + ) + ]; + yield 'trailing /' => [ + $matcher, + 'foo/bar/', + new InvalidArgumentException( + "path should be a relative path to a file, thus it cannot start or end with a /" + ) + ]; + } +} diff --git a/tests/matcher_tests.php b/tests/matcher_tests.php index 45c171d..f44c799 100644 --- a/tests/matcher_tests.php +++ b/tests/matcher_tests.php @@ -89,8 +89,8 @@ 'lines' => ['/*a/'], 'expected' => [ '1a' => null, - '2a/b/' => 1, - '2a/c/d/' => 1, + '2a/b' => 1, + '2a/c/d' => 1, ] ], '/a*/' => [ @@ -154,5 +154,4 @@ 'b/d/e' => null, ] ], - -]; \ No newline at end of file +];