packages/sql-fixture/tests/Unit/Plan/RelationChoiceTest.php

1<?php
2
3declare(strict_types=1);
4
5namespace Tests\Unit\Plan;
6
7use PHPUnit\Framework\Attributes\CoversClass;
8use PHPUnit\Framework\Attributes\UsesClass;
9use PHPUnit\Framework\TestCase;
10use SqlFixture\Fixture\Choice\ChoiceValueException;
11use SqlFixture\Fixture\GenerationRun;
12use SqlFixture\Fixture\RowSpec;
13use SqlFixture\Plan\Choice\ChoiceCase;
14use SqlFixture\Plan\Choice\ChoiceDefinitionException;
15use SqlFixture\Plan\ColumnRef;
16use SqlFixture\Plan\FixturePlan;
17use SqlFixture\Plan\Parsing\RelationCursor;
18use SqlFixture\Plan\Relation;
19use SqlFixture\Plan\RelationChoice as Subject;
20
21#[CoversClass(Subject::class)]
22#[UsesClass(\SqlFixture\Fixture\Choice\CaseSelection::class)]
23#[UsesClass(\SqlFixture\Fixture\Choice\ChoiceBindings::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(\SqlFixture\Plan\RelationKind::class)]
63#[UsesClass(\SqlFixture\Plan\RelationSide::class)]
64#[UsesClass(\SqlFixture\Plan\Validation\PlanValidation::class)]
65#[UsesClass(\SqlFixture\Plan\Validation\TableName::class)]
66final class RelationChoiceTest extends TestCase
67{
68    public function testOnAcceptsAReferenceOrString(): void
69    {
70        $ref = ColumnRef::of('comments', 'kind');
71        self::assertSame($ref, Subject::on($ref)->discriminator);
72        self::assertEquals($ref, Subject::on('comments.kind')->discriminator);
73    }
74
75    public function testWhenPreservesTypesAndDoesNotChangeTheOriginal(): void
76    {
77        $base = Subject::on('comments.kind');
78        $relation = Relation::manyToOne('comments.target_id', 'posts.id');
79        $choice = $base->when('1', $relation)->when(1)->when(true)->when(null);
80        self::assertSame([], $base->cases);
81        self::assertSame(['1', 1, true, null], array_column($choice->cases, 'value'));
82        self::assertSame([$relation], $choice->cases[0]->relations);
83    }
84
85    public function testOtherwiseKeepsExplicitCasesAndTheirOrder(): void
86    {
87        $base = Subject::on('comments.kind')->when('none');
88        $relation = Relation::manyToOne('comments.target_id', 'posts.id');
89        $choice = $base->otherwise($relation);
90        self::assertNull($base->fallback);
91        self::assertSame($base->cases, $choice->cases);
92        self::assertSame([$relation], $choice->fallback?->relations);
93    }
94
95    public function testBranchesIncludesTheFallbackLast(): void
96    {
97        $choice = Subject::on('comments.kind')->when('post');
98        self::assertSame($choice->cases, $choice->branches());
99        $complete = $choice->otherwise();
100        self::assertSame([$complete->cases[0], $complete->fallback], $complete->branches());
101    }
102
103    public function testRelationsFlattensCasesIncludingTheFallback(): void
104    {
105        $post = Relation::manyToOne('comments.target_id', 'posts.id');
106        $video = Relation::manyToOne('comments.target_id', 'videos.id');
107        self::assertSame([$post, $video], Subject::on('comments.kind')->when('post', $post)->otherwise($video)->relations());
108    }
109
110
111    public function testWhenAndOtherwiseNormalizeNamedVariadicRelations(): void
112    {
113        $relation = Relation::manyToOne('c.id', 'a.id');
114        $choice = Subject::on('c.kind')->when('a', ...['parent' => $relation])->when('b')->otherwise(...['parent' => $relation]);
115        self::assertSame([$relation], $choice->cases[0]->relations);
116        self::assertSame([$relation], $choice->fallback?->relations);
117        self::assertSame([$choice->cases[0], $choice->cases[1], $choice->fallback], $choice->branches());
118    }
119
120    public function testRelationsRetainsMultipleRequirementsWithinOneBranch(): void
121    {
122        $a = Relation::manyToOne('c.a_id', 'a.id');
123        $b = Relation::manyToOne('c.b_id', 'b.id');
124        self::assertSame([$a, $b], Subject::on('c.kind')->when('both', $a, $b)->relations());
125    }
126}
127