packages/sql-faker/tests/Unit/Generation/Plan/ProductionPatternTest.php
1<?php
2
3declare(strict_types=1);
4
5namespace Tests\Unit\Generation\Plan;
6
7use PHPUnit\Framework\Attributes\CoversClass;
8use PHPUnit\Framework\TestCase;
9use SqlFaker\Generation\Plan\ProductionPattern;
10
11#[CoversClass(ProductionPattern::class)]
12#[\PHPUnit\Framework\Attributes\UsesClass(\SqlFaker\Generation\Derivation\CompletionCosts::class)]
13#[\PHPUnit\Framework\Attributes\UsesClass(\SqlFaker\Generation\Derivation\DerivationNode::class)]
14final class ProductionPatternTest extends TestCase
15{
16 public function testContainingRequiresEveryNamedSymbol(): void
17 {
18 $pattern = ProductionPattern::containing('FOREIGN', 'KEY');
19
20 self::assertTrue($pattern->matches(['CONSTRAINT', 'FOREIGN', 'KEY', 'REFERENCES']));
21 self::assertFalse($pattern->matches(['CONSTRAINT', 'FOREIGN', 'REFERENCES']));
22 }
23
24 public function testExactlyMatchesOrderAndCardinality(): void
25 {
26 $pattern = ProductionPattern::exactly(first: 'CONSTRAINT', second: 'name');
27
28 self::assertTrue($pattern->matches(['CONSTRAINT', 'name']));
29 self::assertFalse($pattern->matches(['name', 'CONSTRAINT']));
30 self::assertFalse($pattern->matches(['CONSTRAINT', 'name', 'FOREIGN']));
31 }
32
33 public function testExactlyCanSelectAnEmptyProduction(): void
34 {
35 $pattern = ProductionPattern::exactly();
36
37 self::assertTrue($pattern->matches([]));
38 self::assertFalse($pattern->matches(['COMMA']));
39 }
40
41 public function testNonEmptyRejectsOnlyAnEmptyProduction(): void
42 {
43 $pattern = ProductionPattern::nonEmpty();
44
45 self::assertTrue($pattern->matches(['name']));
46 self::assertFalse($pattern->matches([]));
47 }
48
49 public function testMatchesAcceptsAnAlternativeContainingEveryNamedSymbol(): void
50 {
51 self::assertTrue(ProductionPattern::containing('FOREIGN', 'KEY')->matches(['CONSTRAINT', 'FOREIGN', 'KEY']));
52 }
53
54 public function testMatchesRejectsAnAlternativeMissingANamedSymbol(): void
55 {
56 self::assertFalse(ProductionPattern::containing('FOREIGN', 'KEY')->matches(['FOREIGN']));
57 }
58
59 public function testMatchesAcceptsOnlyTheAlternativeWrittenExactly(): void
60 {
61 self::assertTrue(ProductionPattern::exactly('cmdlist', 'ecmd')->matches(['cmdlist', 'ecmd']));
62 self::assertFalse(ProductionPattern::exactly('cmdlist', 'ecmd')->matches(['ecmd', 'cmdlist']));
63 }
64
65 public function testMatchesRefusesOnlyTheEmptyAlternative(): void
66 {
67 self::assertTrue(ProductionPattern::nonEmpty()->matches(['ecmd']));
68 self::assertFalse(ProductionPattern::nonEmpty()->matches([]));
69 }
70 public function testAtSelectsAnOrdinalIndependentlyOfItsSymbols(): void
71 {
72 $pattern = ProductionPattern::at(2);
73 self::assertTrue($pattern->matches(['T'], 2));
74 self::assertFalse($pattern->matches(['T'], 1));
75 self::assertFalse($pattern->matches(['T']));
76 }
77 public function testAllOfBooleanPatternsRetainEveryAllowedAlternative(): void
78 {
79 $pattern = ProductionPattern::allOf(
80 ProductionPattern::anyOf(ProductionPattern::containing('VALUES'), ProductionPattern::containing('SELECT')),
81 ProductionPattern::excluding(ProductionPattern::containing('WITH')),
82 );
83 self::assertTrue($pattern->matches(['VALUES', 'row']));
84 self::assertTrue($pattern->matches(['SELECT', 'expr']));
85 self::assertFalse($pattern->matches(['WITH', 'SELECT', 'expr']));
86 self::assertFalse($pattern->matches(['DELETE', 'table']));
87 }
88
89
90 public function testAnyOfAcceptsEitherFormWithoutAcceptingUnrelatedForms(): void
91 {
92 $pattern = ProductionPattern::anyOf(ProductionPattern::containing('VALUES'), ProductionPattern::containing('SELECT'));
93 self::assertTrue($pattern->matches(['VALUES', 'row']));
94 self::assertTrue($pattern->matches(['SELECT', 'expr']));
95 self::assertFalse($pattern->matches(['UPDATE', 'table']));
96 }
97
98 public function testExcludingCanExcludeAnOrdinalOrAnEmptyAlternative(): void
99 {
100 $pattern = ProductionPattern::excluding(ProductionPattern::at(1));
101 self::assertTrue($pattern->matches(['ID'], 0));
102 self::assertFalse($pattern->matches(['ID'], 1));
103 self::assertFalse(ProductionPattern::excluding(ProductionPattern::exactly())->matches([]));
104 }
105
106
107 public function testAnyOfKeepsTheThirdAlternativeAvailable(): void
108 {
109 $pattern = ProductionPattern::anyOf(
110 ProductionPattern::containing('INSERT'),
111 ProductionPattern::containing('UPDATE'),
112 ProductionPattern::containing('DELETE'),
113 );
114 self::assertTrue($pattern->matches(['DELETE', 'table']));
115 self::assertFalse($pattern->matches(['SELECT', 'table']));
116 }
117
118 public function testAllOfEnforcesEveryCondition(): void
119 {
120 $pattern = ProductionPattern::allOf(
121 ProductionPattern::nonEmpty(),
122 ProductionPattern::containing('INSERT'),
123 ProductionPattern::excluding(ProductionPattern::containing('WITH')),
124 );
125 self::assertFalse($pattern->matches(['WITH', 'INSERT']));
126 self::assertTrue($pattern->matches(['INSERT']));
127 }
128
129}
130