packages/sql-fixture/tests/Unit/Fixture/Choice/CaseSelectionTest.php
1<?php
2
3declare(strict_types=1);
4
5namespace Tests\Unit\Fixture\Choice;
6
7use Faker\Factory;
8use PHPUnit\Framework\Attributes\CoversClass;
9use PHPUnit\Framework\Attributes\UsesClass;
10use PHPUnit\Framework\TestCase;
11use SqlFixture\Fixture\Choice\CaseSelection as Subject;
12use SqlFixture\Fixture\Choice\ChoiceValueException;
13use SqlFixture\Fixture\GenerationRun;
14use SqlFixture\Fixture\RowSpec;
15use SqlFixture\Plan\Choice\ChoiceCase;
16use SqlFixture\Plan\Choice\ChoiceDefinitionException;
17use SqlFixture\Plan\ColumnRef;
18use SqlFixture\Plan\FixturePlan;
19use SqlFixture\Plan\Parsing\RelationCursor;
20use SqlFixture\Plan\Relation;
21use SqlFixture\Plan\RelationChoice;
22
23#[CoversClass(Subject::class)]
24#[UsesClass(\SqlFixture\Fixture\Choice\ChoiceBindings::class)]
25#[UsesClass(ChoiceValueException::class)]
26#[UsesClass(\SqlFixture\Fixture\Choice\InverseRelations::class)]
27#[UsesClass(\SqlFixture\Fixture\Choice\ResolvedRow::class)]
28#[UsesClass(\SqlFixture\Fixture\Choice\RowChoices::class)]
29#[UsesClass(GenerationRun::class)]
30#[UsesClass(\SqlFixture\Fixture\OverrideRows::class)]
31#[UsesClass(RowSpec::class)]
32#[UsesClass(\SqlFixture\Fixture\TableOverrides::class)]
33#[UsesClass(ChoiceCase::class)]
34#[UsesClass(ChoiceDefinitionException::class)]
35#[UsesClass(\SqlFixture\Plan\Choice\ChoiceLiteral::class)]
36#[UsesClass(\SqlFixture\Plan\Choice\ChoiceSyntax::class)]
37#[UsesClass(\SqlFixture\Plan\Choice\ChoiceValidation::class)]
38#[UsesClass(\SqlFixture\Plan\Choice\PlanContents::class)]
39#[UsesClass(ColumnRef::class)]
40#[UsesClass(\SqlFixture\Plan\Exception\CompositeArityMismatchException::class)]
41#[UsesClass(\SqlFixture\Plan\Exception\CyclicDependencyException::class)]
42#[UsesClass(\SqlFixture\Plan\Exception\DuplicateColumnBindingException::class)]
43#[UsesClass(\SqlFixture\Plan\Exception\EmptyPlanException::class)]
44#[UsesClass(\SqlFixture\Plan\Exception\EmptyTableNameException::class)]
45#[UsesClass(\SqlFixture\Plan\Exception\InvalidTableNameException::class)]
46#[UsesClass(\SqlFixture\Plan\Exception\MissingEndpointColumnsException::class)]
47#[UsesClass(\SqlFixture\Plan\Exception\UnbalancedBracketsException::class)]
48#[UsesClass(\SqlFixture\Plan\Exception\UnboundedSelfReferenceException::class)]
49#[UsesClass(\SqlFixture\Plan\Exception\UnexpectedPlanTokenException::class)]
50#[UsesClass(\SqlFixture\Plan\Exception\UnsupportedManyToManyException::class)]
51#[UsesClass(FixturePlan::class)]
52#[UsesClass(\SqlFixture\Plan\Parsing\PlanStatements::class)]
53#[UsesClass(RelationCursor::class)]
54#[UsesClass(\SqlFixture\Plan\Parsing\RelationReader::class)]
55#[UsesClass(\SqlFixture\Plan\PlanParser::class)]
56#[UsesClass(\SqlFixture\Plan\PlanPrinter::class)]
57#[UsesClass(\SqlFixture\Plan\PlanStructureException::class)]
58#[UsesClass(\SqlFixture\Plan\PlanSyntaxException::class)]
59#[UsesClass(\SqlFixture\Plan\Printing\PlanTables::class)]
60#[UsesClass(\SqlFixture\Plan\Printing\RelationGroups::class)]
61#[UsesClass(\SqlFixture\Plan\Printing\StatementPrinter::class)]
62#[UsesClass(Relation::class)]
63#[UsesClass(RelationChoice::class)]
64#[UsesClass(\SqlFixture\Plan\RelationKind::class)]
65#[UsesClass(\SqlFixture\Plan\RelationSide::class)]
66#[UsesClass(\SqlFixture\Plan\Validation\PlanValidation::class)]
67#[UsesClass(\SqlFixture\Plan\Validation\TableName::class)]
68final class CaseSelectionTest extends TestCase
69{
70 public function testSelectUsesStrictValues(): void
71 {
72 $choice = RelationChoice::on('c.kind')->when('1')->when(1)->when(false)->when(null);
73 $selector = new Subject();
74 self::assertSame($choice->cases[0], $selector->select($choice, ['kind' => '1'], null, Factory::create()));
75 self::assertSame($choice->cases[1], $selector->select($choice, ['kind' => 1], null, Factory::create()));
76 self::assertSame($choice->cases[2], $selector->select($choice, ['kind' => false], null, Factory::create()));
77 self::assertSame($choice->cases[3], $selector->select($choice, ['kind' => null], null, Factory::create()));
78 }
79
80 public function testSelectUsesFallbackOnlyForSuppliedValues(): void
81 {
82 $choice = RelationChoice::on('c.kind')->when('known')->otherwise();
83 $selector = new Subject();
84 self::assertSame($choice->fallback, $selector->select($choice, ['kind' => 'other'], null, Factory::create()));
85 self::assertSame($choice->cases[0], $selector->select($choice, [], null, Factory::create()));
86 }
87
88 public function testSelectRejectsUnknownValues(): void
89 {
90 $this->expectException(ChoiceValueException::class);
91 (new Subject())->select(RelationChoice::on('c.kind')->when('known'), ['kind' => 'other'], null, Factory::create());
92 }
93
94 public function testSelectInfersTheCaseFromAnInverseRelation(): void
95 {
96 $a = Relation::manyToOne('c.id', 'a.id');
97 $b = Relation::manyToOne('c.id', 'b.id');
98 $choice = RelationChoice::on('c.kind')->when('a', $a)->when('b', $b);
99 self::assertSame($choice->cases[1], (new Subject())->select($choice, [], $b, Factory::create()));
100 }
101
102 public function testSelectRejectsAnInverseFallbackWithoutAValue(): void
103 {
104 $a = Relation::manyToOne('c.id', 'a.id');
105 $choice = RelationChoice::on('c.kind')->when('none')->otherwise($a);
106 $this->expectException(ChoiceValueException::class);
107 (new Subject())->select($choice, [], $a, Factory::create());
108 }
109
110 public function testCheckArrivalRejectsAConflictingExplicitCase(): void
111 {
112 $a = Relation::manyToOne('c.id', 'a.id');
113 $choice = RelationChoice::on('c.kind')->when('a', $a)->when('none');
114 $this->expectException(ChoiceValueException::class);
115 (new Subject())->checkArrival($choice, $choice->cases[1], $a);
116 }
117
118 public function testMatchingArrivalUsesBothEndpointsAndTheOperator(): void
119 {
120 $a = Relation::manyToOne('c.id', 'a.id');
121 $case = new ChoiceCase('a', [$a]);
122 self::assertSame($a, (new Subject())->matchingArrival($case, Relation::manyToOne('c.id', 'a.id')));
123 self::assertNull((new Subject())->matchingArrival($case, Relation::manyToOne('c.other', 'a.id')));
124 self::assertNull((new Subject())->matchingArrival($case, Relation::manyToOne('c.id', 'b.id')));
125 self::assertNull((new Subject())->matchingArrival($case, Relation::oneToOne('a.id', 'c.id')));
126 }
127
128
129 public function testSelectDoesNotConstrainAnUnrelatedIncomingRelation(): void
130 {
131 $choice = RelationChoice::on('c.kind')->when('a', Relation::manyToOne('c.a_id', 'a.id'));
132 $arrival = Relation::oneToMany('users.id', 'c.user_id');
133 self::assertSame($choice->cases[0], (new Subject())->select($choice, [], $arrival, Factory::create()));
134 }
135}
136