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