packages/sql-catalog/tests/Unit/Core/Source/SourceScannerTest.php
1<?php
2
3declare(strict_types=1);
4
5namespace Tests\Unit\Core\Source;
6
7use PHPUnit\Framework\Attributes\CoversClass;
8use PHPUnit\Framework\Attributes\UsesClass;
9use PHPUnit\Framework\TestCase;
10use SqlCatalog\Core\Source\SourceFile;
11use SqlCatalog\Core\Source\SourceScanException;
12use SqlCatalog\Core\Source\SourceScanner;
13
14#[CoversClass(SourceScanner::class)]
15#[UsesClass(SourceFile::class)]
16#[UsesClass(SourceScanException::class)]
17final class SourceScannerTest extends TestCase
18{
19 public function testScanFindsThePhpFilesUnderADirectory(): void
20 {
21 $root = dirname(__DIR__, 4);
22 $files = (new SourceScanner($root))->scan([$root . '/src']);
23 $paths = array_map(static fn (SourceFile $file): string => $file->path, $files);
24 self::assertContains('src/Facade/Analyzer.php', $paths);
25 self::assertStringStartsWith('<?php', $files[0]->code);
26 }
27
28 public function testScanReportsPathsInAStableOrder(): void
29 {
30 $root = dirname(__DIR__, 4);
31 $scanner = new SourceScanner($root);
32 self::assertSame(
33 array_map(static fn (SourceFile $file): string => $file->path, $scanner->scan([$root . '/src'])),
34 array_map(static fn (SourceFile $file): string => $file->path, $scanner->scan([$root . '/src'])),
35 );
36 }
37
38 public function testScanOneReadsASingleFile(): void
39 {
40 $root = dirname(__DIR__, 4);
41 $files = (new SourceScanner($root))->scanOne($root . '/src/Facade/Analyzer.php');
42 self::assertCount(1, $files);
43 self::assertSame('src/Facade/Analyzer.php', $files[0]->path);
44 }
45
46 public function testScanOneRefusesAPathThatIsNotThere(): void
47 {
48 $this->expectException(SourceScanException::class);
49 (new SourceScanner('.'))->scanOne('/definitely/not/here');
50 }
51
52 public function testWalkSkipsTheDirectoriesAScanNeverWants(): void
53 {
54 $root = dirname(__DIR__, 4);
55 $paths = (new SourceScanner($root))->walk($root . '/src');
56 self::assertNotEmpty($paths);
57 self::assertSame([], array_values(array_filter(
58 $paths,
59 static fn (string $path): bool => str_contains($path, '/vendor/'),
60 )));
61 }
62
63 public function testReadNamesTheFileRelativeToTheRoot(): void
64 {
65 $root = dirname(__DIR__, 4);
66 self::assertSame('src/Facade/Analyzer.php', (new SourceScanner($root))->read($root . '/src/Facade/Analyzer.php')?->path);
67 }
68
69 public function testReadSkipsAnExcludedFile(): void
70 {
71 $root = dirname(__DIR__, 4);
72 self::assertNull((new SourceScanner($root, ['src/*']))->read($root . '/src/Facade/Analyzer.php'));
73 }
74
75 public function testIsExcludedAcceptsAPatternOrADirectory(): void
76 {
77 $scanner = new SourceScanner('.', ['vendor', 'src/Generated/*.php']);
78 self::assertTrue($scanner->isExcluded('vendor/a.php'));
79 self::assertTrue($scanner->isExcluded('src/Generated/a.php'));
80 self::assertFalse($scanner->isExcluded('src/a.php'));
81 }
82
83 public function testRelativeLeavesAPathOutsideTheRootAlone(): void
84 {
85 self::assertSame('/definitely/not/here', (new SourceScanner('.'))->relative('/definitely/not/here'));
86 }
87}
88