packages/sql-fixture/tests/Unit/Fixture/Choice/RowChoicesTest.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\ChoiceValueException;
12use SqlFixture\Fixture\Choice\RowChoices as Subject;
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\CaseSelection::class)]
25#[UsesClass(\SqlFixture\Fixture\Choice\ChoiceBindings::class)]
26#[UsesClass(ChoiceValueException::class)]
27#[UsesClass(\SqlFixture\Fixture\Choice\InverseRelations::class)]
28#[UsesClass(\SqlFixture\Fixture\Choice\ResolvedRow::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 RowChoicesTest extends TestCase
69{
70 public function testResolveSelectsPerRowWithoutChangingTheSourcePlan(): void
71 {
72 $a = Relation::manyToOne('c.id', 'a.id');
73 $b = Relation::manyToOne('c.id', 'b.id');
74 $choice = RelationChoice::on('c.kind')->when('a', $a)->when('b', $b);
75 $plan = new FixturePlan($choice);
76 $row = (new Subject())->resolve($plan, 'c', ['kind' => 'b'], null, new GenerationRun([]), Factory::create());
77 self::assertSame([$b], $row->plan->relations);
78 self::assertSame(['kind' => 'b'], $row->values);
79 self::assertSame([$choice], $plan->choices);
80 self::assertSame([], $row->plan->choices);
81 }
82
83 public function testResolveInfersInverseValuesAndKeepsOtherChoices(): void
84 {
85 $a = Relation::manyToOne('c.id', 'a.id');
86 $choice = RelationChoice::on('c.kind')->when('a', $a)->when('none');
87 $other = RelationChoice::on('x.kind')->when('none');
88 $plan = new FixturePlan($choice, $other);
89 $row = (new Subject())->resolve($plan, 'c', ['id' => 7], $a, new GenerationRun([]), Factory::create());
90 self::assertSame(['id' => 7, 'kind' => 'a'], $row->values);
91 self::assertSame($a, $row->arrivedBy);
92 self::assertSame([$other], $row->plan->choices);
93 }
94
95 public function testResolveKeepsExplicitNullAndClearsInactiveKeys(): void
96 {
97 $plan = new FixturePlan(RelationChoice::on('c.kind')->when('a', Relation::manyToOne('c.id', 'a.id'))->when(null));
98 $row = (new Subject())->resolve($plan, 'c', ['kind' => null], null, new GenerationRun([]), Factory::create());
99 self::assertSame(['kind' => null, 'id' => null], $row->values);
100 self::assertSame([], $row->plan->relations);
101 }
102
103
104 public function testResolveSkipsEarlierChoicesForOtherTables(): void
105 {
106 $other = RelationChoice::on('x.kind')->when('none');
107 $choice = RelationChoice::on('c.kind')->when('none');
108 $row = (new Subject())->resolve(new FixturePlan($other, $choice), 'c', [], null, new GenerationRun([]), Factory::create());
109 self::assertSame(['kind' => 'none'], $row->values);
110 self::assertSame([$other], $row->plan->choices);
111 }
112
113 public function testResolveReplacesAnEquivalentInverseCandidateWithTheSelectedEdge(): void
114 {
115 $first = Relation::manyToOne('c.id', 'a.id');
116 $second = Relation::manyToOne('c.id', 'a.id');
117 $choice = RelationChoice::on('c.kind')->when('first', $first)->when('second', $second);
118 $row = (new Subject())->resolve(new FixturePlan($choice), 'c', ['kind' => 'second'], $first, new GenerationRun([]), Factory::create());
119 self::assertSame($second, $row->arrivedBy);
120 self::assertSame([$second], $row->plan->relations);
121 }
122
123 public function testResolvePreservesAnUnconditionalArrival(): void
124 {
125 $arrival = Relation::oneToMany('users.id', 'c.user_id');
126 $plan = new FixturePlan($arrival, RelationChoice::on('c.kind')->when('none'));
127 $row = (new Subject())->resolve($plan, 'c', [], $arrival, new GenerationRun([]), Factory::create());
128 self::assertSame($arrival, $row->arrivedBy);
129 }
130}
131