packages/sql-catalog/tests/Unit/Facade/ConfigurationTest.php
1<?php
2
3declare(strict_types=1);
4
5namespace Tests\Unit\Facade;
6
7use PHPUnit\Framework\Attributes\CoversClass;
8use PHPUnit\Framework\Attributes\DataProvider;
9use PHPUnit\Framework\Attributes\UsesClass;
10use PHPUnit\Framework\TestCase;
11use SqlCatalog\Core\Analysis\BuiltinCallModel;
12use SqlCatalog\Core\Analysis\FunctionModel\NamedModel;
13use SqlCatalog\Core\Analysis\FunctionModel\Registry;
14use SqlCatalog\Core\Evaluation\ArrayEntry;
15use SqlCatalog\Core\Evaluation\ArrayTerm;
16use SqlCatalog\Core\Evaluation\Domain;
17use SqlCatalog\Core\Evaluation\LiteralTerm;
18use SqlCatalog\Facade\Configuration;
19use SqlCatalog\Facade\ConfigurationSchema;
20use SqlCatalog\Facade\InvalidConfigurationException;
21use Tests\Fake\PairModel;
22
23#[CoversClass(Configuration::class)]
24#[UsesClass(ConfigurationSchema::class)]
25#[UsesClass(Registry::class)]
26#[UsesClass(NamedModel::class)]
27#[UsesClass(BuiltinCallModel::class)]
28#[UsesClass(InvalidConfigurationException::class)]
29#[UsesClass(Domain::class)]
30#[UsesClass(ArrayTerm::class)]
31#[UsesClass(ArrayEntry::class)]
32#[UsesClass(LiteralTerm::class)]
33final class ConfigurationTest extends TestCase
34{
35 public function testLoadReadsSettingsRelativeToTheFile(): void
36 {
37 $path = sys_get_temp_dir() . '/catalog-' . bin2hex(random_bytes(6)) . '.yaml';
38 $directory = realpath(dirname($path));
39 self::assertNotFalse($directory);
40 file_put_contents($path, "paths: [src]\noutput: report\nextensions: [pdo]\nreporter: html\nfunction-models:\n array_fill: Tests\\Fake\\PairModel\n");
41 try {
42 $settings = Configuration::load($path);
43 self::assertSame(['paths' => [$directory . '/src'], 'output' => [$directory . '/report'], 'extension' => ['pdo'], 'reporter' => ['html'], 'root' => [$directory]], $settings->options);
44 self::assertSame(['array_fill' => PairModel::class], $settings->functionModels);
45 self::assertSame(realpath($path), $settings->file);
46 } finally {
47 unlink($path);
48 }
49 }
50
51 public function testLoadAcceptsAnEmptyDocument(): void
52 {
53 $path = sys_get_temp_dir() . '/catalog-' . bin2hex(random_bytes(6)) . '.yaml';
54 $directory = realpath(dirname($path));
55 self::assertNotFalse($directory);
56 file_put_contents($path, '');
57 try {
58 self::assertSame(['root' => [$directory]], Configuration::load($path)->options);
59 } finally {
60 unlink($path);
61 }
62 }
63
64 public function testLoadRejectsMissingFiles(): void
65 {
66 $this->expectException(InvalidConfigurationException::class);
67 $this->expectExceptionMessage('Cannot read configuration');
68 Configuration::load('/definitely/missing/.catalog.yaml');
69 }
70
71 #[DataProvider('providerInvalidDocuments')]
72 public function testLoadRejectsInvalidDocuments(string $yaml): void
73 {
74 $path = sys_get_temp_dir() . '/catalog-' . bin2hex(random_bytes(6)) . '.yaml';
75 $directory = realpath(dirname($path));
76 self::assertNotFalse($directory);
77 file_put_contents($path, $yaml);
78 try {
79 $this->expectException(InvalidConfigurationException::class);
80 Configuration::load($path);
81 } finally {
82 unlink($path);
83 }
84 }
85
86 /**
87 * @return list<array{string}>
88 */
89 public static function providerInvalidDocuments(): array
90 {
91 return [['[]'], ['[broken'], ['<?php throw new RuntimeException("executed");'], ['!php/object \'O:8:"stdClass":0:{}\''], ["paths: [src]\npaths: [other]"]];
92 }
93
94 public function testApplyPreservesTheOriginalRegistryAndOverridesTheBuiltin(): void
95 {
96 $original = Registry::withBuiltins();
97 $configured = (new Configuration(functionModels: ['array_fill' => PairModel::class]))->apply($original);
98 $arguments = [Domain::literal(0), Domain::literal(10), Domain::literal('?')];
99 self::assertCount(2, $configured->evaluate('array_fill', $arguments)?->soleArray()->entries ?? []);
100 self::assertCount(1, $original->evaluate('array_fill', $arguments)?->soleArray()->entries ?? []);
101 }
102}
103