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