packages/sql-catalog/tests/Unit/Core/Catalog/ResolutionTest.php

1<?php
2
3declare(strict_types=1);
4
5namespace Tests\Unit\Core\Catalog;
6
7use PHPUnit\Framework\Attributes\CoversClass;
8use PHPUnit\Framework\Attributes\DataProvider;
9use PHPUnit\Framework\Attributes\UsesClass;
10use PHPUnit\Framework\TestCase;
11use SqlCatalog\Core\Catalog\Resolution;
12use SqlCatalog\Core\Text\LiteralText;
13use SqlCatalog\Core\Text\Origin;
14use SqlCatalog\Core\Text\TextHole;
15use SqlCatalog\Core\Text\TextPattern;
16use SqlCatalog\Core\Type\TypeShape;
17
18#[CoversClass(Resolution::class)]
19#[UsesClass(LiteralText::class)]
20#[UsesClass(TextHole::class)]
21#[UsesClass(TextPattern::class)]
22#[UsesClass(TypeShape::class)]
23final class ResolutionTest extends TestCase
24{
25    public function testOfAStatementWithoutGapsIsResolved(): void
26    {
27        self::assertSame(Resolution::Resolved, Resolution::of(TextPattern::fromText('SELECT 1')));
28    }
29
30    #[DataProvider('providerOf')]
31    public function testOf(Origin $origin, Resolution $expected): void
32    {
33        $pattern = TextPattern::fromText('SELECT ')
34            ->concat(TextPattern::fromHole(new TextHole($origin, TypeShape::unknown())));
35
36        self::assertSame($expected, Resolution::of($pattern));
37    }
38
39    /**
40     * @return list<array{Origin, Resolution}>
41     */
42    public static function providerOf(): array
43    {
44        return [
45            [Origin::External, Resolution::ExternalInput],
46            [Origin::Budget, Resolution::Incomplete],
47            [Origin::Parameter, Resolution::IncompleteModel],
48            [Origin::Property, Resolution::IncompleteModel],
49            [Origin::Call, Resolution::IncompleteModel],
50            [Origin::Loop, Resolution::IncompleteModel],
51            [Origin::Branch, Resolution::IncompleteModel],
52            [Origin::Unresolved, Resolution::IncompleteModel],
53        ];
54    }
55
56    public function testOfPrefersTheStoppedSearchOverTheInputItAlsoReached(): void
57    {
58        $pattern = TextPattern::fromHole(new TextHole(Origin::External, TypeShape::unknown()))
59            ->concat(TextPattern::fromHole(new TextHole(Origin::Budget, TypeShape::unknown())));
60
61        self::assertSame(Resolution::Incomplete, Resolution::of($pattern));
62    }
63
64    public function testIsResolvedOnlyWhenTheTextIsPinnedDown(): void
65    {
66        self::assertTrue(Resolution::Resolved->isResolved());
67        self::assertFalse(Resolution::ExternalInput->isResolved());
68    }
69
70    public function testIsClosedSeparatesAFinishedSearchFromAnAbandonedOne(): void
71    {
72        self::assertTrue(Resolution::Resolved->isClosed());
73        self::assertTrue(Resolution::ExternalInput->isClosed());
74        self::assertFalse(Resolution::IncompleteModel->isClosed());
75        self::assertFalse(Resolution::Incomplete->isClosed());
76    }
77
78    public function testDescribeExplainsEveryOutcome(): void
79    {
80        $descriptions = array_map(
81            static fn (Resolution $resolution): string => $resolution->describe(),
82            Resolution::cases(),
83        );
84
85        self::assertCount(count(Resolution::cases()), array_unique($descriptions));
86        self::assertStringContainsString('runtime input', Resolution::ExternalInput->describe());
87    }
88
89    /**
90     * @return list<array{Resolution, bool}>
91     */
92    public static function providerWasRead(): array
93    {
94        return [
95            [Resolution::Resolved, true],
96            [Resolution::ExternalInput, true],
97            [Resolution::IncompleteModel, true],
98            [Resolution::Incomplete, false],
99            [Resolution::NotAnalyzed, false],
100        ];
101    }
102
103    #[DataProvider('providerWasRead')]
104    public function testWasReadSaysWhetherAStatementWasReadFromTheCallAtAll(Resolution $resolution, bool $expected): void
105    {
106        self::assertSame($expected, $resolution->wasRead());
107    }
108}
109