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