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)]
39final class PlanSyntaxExceptionTest extends TestCase
40{
41    #[Test]
42    public function testEmptyPlanAsksForATable(): void
43    {
44        self::assertSame(
45            'A fixture plan must name at least one table.',
46            (new \SqlFixture\Plan\Exception\EmptyPlanException())->getMessage()
47        );
48    }
49
50    #[Test]
51    public function testEmptyTableNameAsksForATable(): void
52    {
53        self::assertSame(
54            'A relation endpoint must name a table.',
55            (new \SqlFixture\Plan\Exception\EmptyTableNameException())->getMessage()
56        );
57    }
58
59    #[Test]
60    public function testNoColumnsNamesTheTable(): void
61    {
62        self::assertSame(
63            'The endpoint for table order names no columns.',
64            (new \SqlFixture\Plan\Exception\MissingEndpointColumnsException('order'))->getMessage()
65        );
66    }
67
68    #[Test]
69    public function testUnexpectedReportsTheOffsetAndWhatWasWanted(): void
70    {
71        $message = (new \SqlFixture\Plan\Exception\UnexpectedPlanTokenException('order.id ! x.y', 9, "one of '<', '>' or '-'"))->getMessage();
72
73        self::assertSame(
74            "Cannot parse the fixture plan at offset 9: expected one of '<', '>' or '-'. "
75            . 'Plan: order.id ! x.y',
76            $message
77        );
78    }
79
80    #[Test]
81    public function testManyToManyUnsupportedManyToManyPointsAtTheExplicitForm(): void
82    {
83        $message = (new \SqlFixture\Plan\Exception\UnsupportedManyToManyException('order.id <> product.id'))->getMessage();
84
85        self::assertSame(
86            'The <> operator is not supported, because a fixture has to put rows in the '
87            . 'junction table and so must name it. Write the two halves instead, for example '
88            . '"order.id < order_detail.order_id, order_detail.product_id > product.id". '
89            . 'Plan: order.id <> product.id',
90            $message
91        );
92    }
93
94    #[Test]
95    public function testCompositeArityMismatchReportsBothCounts(): void
96    {
97        $message = (new \SqlFixture\Plan\Exception\CompositeArityMismatchException(
98            ColumnRef::of('order', 'shop_id', 'no'),
99            ColumnRef::of('order_detail', 'order_no')
100        ))->getMessage();
101
102        self::assertSame(
103            'The relation order.(shop_id, no) ... order_detail.order_no names 2 columns on '
104            . 'one side and 1 on the other.',
105            $message
106        );
107    }
108
109    #[Test]
110    public function testNotATableNamePointsAtFrom(): void
111    {
112        $message = (new \SqlFixture\Plan\Exception\InvalidTableNameException('order.id < order_detail.order_id'))->getMessage();
113
114        self::assertSame(
115            'A FixturePlan part must be a Relation or a plain table name, but '
116            . '"order.id < order_detail.order_id" is neither. To build a plan from relation '
117            . 'syntax, use FixturePlan::from().',
118            $message
119        );
120    }
121
122    #[Test]
123    public function testUnbalancedBracketsNamesThePlan(): void
124    {
125        self::assertSame(
126            'The fixture plan closes a bracket it never opened. Plan: a.id < b.a_id]',
127            (new \SqlFixture\Plan\Exception\UnbalancedBracketsException('a.id < b.a_id]'))->getMessage()
128        );
129    }
130}
131