packages/sql-fixture/tests/Unit/Fixture/Choice/ChoiceBindingsTest.php
1<?php
2
3declare(strict_types=1);
4
5namespace Tests\Unit\Fixture\Choice;
6
7use PHPUnit\Framework\Attributes\CoversClass;
8use PHPUnit\Framework\Attributes\UsesClass;
9use PHPUnit\Framework\TestCase;
10use SqlFixture\Fixture\Choice\ChoiceBindings as Subject;
11use SqlFixture\Fixture\Choice\ChoiceValueException;
12use SqlFixture\Fixture\GenerationRun;
13use SqlFixture\Fixture\RowSpec;
14use SqlFixture\Plan\Choice\ChoiceCase;
15use SqlFixture\Plan\Choice\ChoiceDefinitionException;
16use SqlFixture\Plan\ColumnRef;
17use SqlFixture\Plan\FixturePlan;
18use SqlFixture\Plan\Parsing\RelationCursor;
19use SqlFixture\Plan\Relation;
20use SqlFixture\Plan\RelationChoice;
21
22#[CoversClass(Subject::class)]
23#[UsesClass(\SqlFixture\Fixture\Choice\CaseSelection::class)]
24#[UsesClass(ChoiceValueException::class)]
25#[UsesClass(\SqlFixture\Fixture\Choice\InverseRelations::class)]
26#[UsesClass(\SqlFixture\Fixture\Choice\ResolvedRow::class)]
27#[UsesClass(\SqlFixture\Fixture\Choice\RowChoices::class)]
28#[UsesClass(GenerationRun::class)]
29#[UsesClass(\SqlFixture\Fixture\OverrideRows::class)]
30#[UsesClass(RowSpec::class)]
31#[UsesClass(\SqlFixture\Fixture\TableOverrides::class)]
32#[UsesClass(ChoiceCase::class)]
33#[UsesClass(ChoiceDefinitionException::class)]
34#[UsesClass(\SqlFixture\Plan\Choice\ChoiceLiteral::class)]
35#[UsesClass(\SqlFixture\Plan\Choice\ChoiceSyntax::class)]
36#[UsesClass(\SqlFixture\Plan\Choice\ChoiceValidation::class)]
37#[UsesClass(\SqlFixture\Plan\Choice\PlanContents::class)]
38#[UsesClass(ColumnRef::class)]
39#[UsesClass(\SqlFixture\Plan\Exception\CompositeArityMismatchException::class)]
40#[UsesClass(\SqlFixture\Plan\Exception\CyclicDependencyException::class)]
41#[UsesClass(\SqlFixture\Plan\Exception\DuplicateColumnBindingException::class)]
42#[UsesClass(\SqlFixture\Plan\Exception\EmptyPlanException::class)]
43#[UsesClass(\SqlFixture\Plan\Exception\EmptyTableNameException::class)]
44#[UsesClass(\SqlFixture\Plan\Exception\InvalidTableNameException::class)]
45#[UsesClass(\SqlFixture\Plan\Exception\MissingEndpointColumnsException::class)]
46#[UsesClass(\SqlFixture\Plan\Exception\UnbalancedBracketsException::class)]
47#[UsesClass(\SqlFixture\Plan\Exception\UnboundedSelfReferenceException::class)]
48#[UsesClass(\SqlFixture\Plan\Exception\UnexpectedPlanTokenException::class)]
49#[UsesClass(\SqlFixture\Plan\Exception\UnsupportedManyToManyException::class)]
50#[UsesClass(FixturePlan::class)]
51#[UsesClass(\SqlFixture\Plan\Parsing\PlanStatements::class)]
52#[UsesClass(RelationCursor::class)]
53#[UsesClass(\SqlFixture\Plan\Parsing\RelationReader::class)]
54#[UsesClass(\SqlFixture\Plan\PlanParser::class)]
55#[UsesClass(\SqlFixture\Plan\PlanPrinter::class)]
56#[UsesClass(\SqlFixture\Plan\PlanStructureException::class)]
57#[UsesClass(\SqlFixture\Plan\PlanSyntaxException::class)]
58#[UsesClass(\SqlFixture\Plan\Printing\PlanTables::class)]
59#[UsesClass(\SqlFixture\Plan\Printing\RelationGroups::class)]
60#[UsesClass(\SqlFixture\Plan\Printing\StatementPrinter::class)]
61#[UsesClass(Relation::class)]
62#[UsesClass(RelationChoice::class)]
63#[UsesClass(\SqlFixture\Plan\RelationKind::class)]
64#[UsesClass(\SqlFixture\Plan\RelationSide::class)]
65#[UsesClass(\SqlFixture\Plan\Validation\PlanValidation::class)]
66#[UsesClass(\SqlFixture\Plan\Validation\TableName::class)]
67final class ChoiceBindingsTest extends TestCase
68{
69 public function testFixClearsOnlyInactiveKeys(): void
70 {
71 $a = Relation::manyToOne('c.a_id', 'a.id');
72 $b = Relation::manyToOne('c.b_id', 'b.id');
73 $choice = RelationChoice::on('c.kind')->when('a', $a)->when('b', $b);
74 self::assertSame(['a_id' => 3, 'b_id' => null], (new Subject())->fix($choice, $choice->cases[0], ['a_id' => 3], new GenerationRun([])));
75 }
76
77 public function testFixRejectsExplicitInactiveReferences(): void
78 {
79 $choice = RelationChoice::on('c.kind')->when('a', Relation::manyToOne('c.a_id', 'a.id'))->when('none');
80 $this->expectException(ChoiceValueException::class);
81 (new Subject())->fix($choice, $choice->cases[1], ['a_id' => 3], new GenerationRun([]));
82 }
83
84 public function testFixNullsAnAbsentOptionalParent(): void
85 {
86 $choice = RelationChoice::on('c.kind')->when('a', Relation::manyToOne('c.a_id', 'a.id', true));
87 self::assertSame(['a_id' => null], (new Subject())->fix($choice, $choice->cases[0], [], new GenerationRun([])));
88 self::assertSame(['a_id' => 7], (new Subject())->fix($choice, $choice->cases[0], ['a_id' => 7], new GenerationRun([])));
89 self::assertSame([], (new Subject())->fix($choice, $choice->cases[0], [], new GenerationRun(['a' => RowSpec::from('a', 1)])));
90 }
91
92 public function testChildColumnsExcludesKeysOnOtherTables(): void
93 {
94 $choice = RelationChoice::on('c.kind');
95 self::assertSame(['tenant', 'id'], (new Subject())->childColumns($choice, [Relation::manyToOne('c.(tenant, id)', 'a.(tenant, id)'), Relation::oneToMany('c.id', 'd.c_id')]));
96 }
97
98
99 public function testFixProcessesOptionalParentsAfterRequiredRelations(): void
100 {
101 $choice = RelationChoice::on('c.kind')->when('a', Relation::manyToOne('c.a_id', 'a.id'), Relation::manyToOne('c.b_id', 'b.id', true));
102 self::assertSame(['b_id' => null], (new Subject())->fix($choice, $choice->cases[0], [], new GenerationRun([])));
103 }
104
105 public function testChildColumnsDeduplicatesOverlappingCompositeKeysAsAList(): void
106 {
107 $relations = [Relation::manyToOne('c.(tenant, a_id)', 'a.(tenant, id)'), Relation::manyToOne('c.(tenant, b_id)', 'b.(tenant, id)')];
108 self::assertSame(['tenant', 'a_id', 'b_id'], (new Subject())->childColumns(RelationChoice::on('c.kind'), $relations));
109 }
110}
111