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

1<?php
2
3declare(strict_types=1);
4
5namespace Tests\Unit\Plan;
6
7use PHPUnit\Framework\Attributes\CoversClass;
8use PHPUnit\Framework\Attributes\Test;
9use PHPUnit\Framework\Attributes\UsesClass;
10use PHPUnit\Framework\TestCase;
11use SqlFixture\Plan\ColumnRef;
12use SqlFixture\Plan\PlanSyntaxException;
13
14#[CoversClass(PlanSyntaxException::class)]
15#[UsesClass(ColumnRef::class)]
16#[UsesClass(\SqlFixture\Plan\FixturePlan::class)]
17#[UsesClass(\SqlFixture\Plan\PlanParser::class)]
18#[UsesClass(\SqlFixture\Plan\PlanPrinter::class)]
19#[UsesClass(\SqlFixture\Plan\Relation::class)]
20#[UsesClass(\SqlFixture\Plan\RelationKind::class)]
21#[UsesClass(\SqlFixture\Plan\RelationSide::class)]
22#[UsesClass(\SqlFixture\Plan\Parsing\PlanStatements::class)]
23#[UsesClass(\SqlFixture\Plan\Parsing\RelationCursor::class)]
24#[UsesClass(\SqlFixture\Plan\Parsing\RelationReader::class)]
25#[UsesClass(\SqlFixture\Plan\PlanStructureException::class)]
26#[UsesClass(\SqlFixture\Plan\Printing\PlanTables::class)]
27#[UsesClass(\SqlFixture\Plan\Printing\RelationGroups::class)]
28#[UsesClass(\SqlFixture\Plan\Printing\StatementPrinter::class)]
29#[UsesClass(\SqlFixture\Plan\Validation\PlanValidation::class)]
30#[UsesClass(\SqlFixture\Plan\Validation\TableName::class)]
31#[UsesClass(\SqlFixture\Plan\Exception\EmptyPlanException::class)]
32#[UsesClass(\SqlFixture\Plan\Exception\EmptyTableNameException::class)]
33#[UsesClass(\SqlFixture\Plan\Exception\MissingEndpointColumnsException::class)]
34#[UsesClass(\SqlFixture\Plan\Exception\InvalidTableNameException::class)]
35#[UsesClass(\SqlFixture\Plan\Exception\UnbalancedBracketsException::class)]
36#[UsesClass(\SqlFixture\Plan\Exception\UnexpectedPlanTokenException::class)]
37#[UsesClass(\SqlFixture\Plan\Exception\UnsupportedManyToManyException::class)]
38#[UsesClass(\SqlFixture\Plan\Exception\CompositeArityMismatchException::class)]
39#[UsesClass(\SqlFixture\Plan\Choice\ChoiceValidation::class)]
40#[UsesClass(\SqlFixture\Plan\Choice\PlanContents::class)]
41final class PlanSyntaxExceptionTest extends TestCase
42{
43    #[Test]
44    public function testEmptyPlanAsksForATable(): void
45    {
46        self::assertSame(
47            'A fixture plan must name at least one table.',
48            (new \SqlFixture\Plan\Exception\EmptyPlanException())->getMessage()
49        );
50    }
51
52    #[Test]
53    public function testEmptyTableNameAsksForATable(): void
54    {
55        self::assertSame(
56            'A relation endpoint must name a table.',
57            (new \SqlFixture\Plan\Exception\EmptyTableNameException())->getMessage()
58        );
59    }
60
61    #[Test]
62    public function testNoColumnsNamesTheTable(): void
63    {
64        self::assertSame(
65            'The endpoint for table order names no columns.',
66            (new \SqlFixture\Plan\Exception\MissingEndpointColumnsException('order'))->getMessage()
67        );
68    }
69
70    #[Test]
71    public function testUnexpectedReportsTheOffsetAndWhatWasWanted(): void
72    {
73        $message = (new \SqlFixture\Plan\Exception\UnexpectedPlanTokenException('order.id ! x.y', 9, "one of '<', '>' or '-'"))->getMessage();
74
75        self::assertSame(
76            "Cannot parse the fixture plan at offset 9: expected one of '<', '>' or '-'. "
77            . 'Plan: order.id ! x.y',
78            $message
79        );
80    }
81
82    #[Test]
83    public function testManyToManyUnsupportedManyToManyPointsAtTheExplicitForm(): void
84    {
85        $message = (new \SqlFixture\Plan\Exception\UnsupportedManyToManyException('order.id <> product.id'))->getMessage();
86
87        self::assertSame(
88            'The <> operator is not supported, because a fixture has to put rows in the '
89            . 'junction table and so must name it. Write the two halves instead, for example '
90            . '"order.id < order_detail.order_id, order_detail.product_id > product.id". '
91            . 'Plan: order.id <> product.id',
92            $message
93        );
94    }
95
96    #[Test]
97    public function testCompositeArityMismatchReportsBothCounts(): void
98    {
99        $message = (new \SqlFixture\Plan\Exception\CompositeArityMismatchException(
100            ColumnRef::of('order', 'shop_id', 'no'),
101            ColumnRef::of('order_detail', 'order_no')
102        ))->getMessage();
103
104        self::assertSame(
105            'The relation order.(shop_id, no) ... order_detail.order_no names 2 columns on '
106            . 'one side and 1 on the other.',
107            $message
108        );
109    }
110
111    #[Test]
112    public function testNotATableNamePointsAtFrom(): void
113    {
114        $message = (new \SqlFixture\Plan\Exception\InvalidTableNameException('order.id < order_detail.order_id'))->getMessage();
115
116        self::assertSame(
117            'A FixturePlan part must be a Relation or a plain table name, but '
118            . '"order.id < order_detail.order_id" is neither. To build a plan from relation '
119            . 'syntax, use FixturePlan::from().',
120            $message
121        );
122    }
123
124    #[Test]
125    public function testUnbalancedBracketsNamesThePlan(): void
126    {
127        self::assertSame(
128            'The fixture plan closes a bracket it never opened. Plan: a.id < b.a_id]',
129            (new \SqlFixture\Plan\Exception\UnbalancedBracketsException('a.id < b.a_id]'))->getMessage()
130        );
131    }
132}
133