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