packages/sql-faker/tests/Unit/Compiler/Lemon/LemonConditionTest.php
1<?php
2
3declare(strict_types=1);
4
5namespace Tests\Unit\SqlFaker\Compiler\Lemon;
6
7use PHPUnit\Framework\Attributes\CoversClass;
8use PHPUnit\Framework\Attributes\Small;
9use PHPUnit\Framework\TestCase;
10use RuntimeException;
11use SqlFaker\Compiler\Lemon\LemonCondition;
12
13#[CoversClass(LemonCondition::class)]
14#[Small]
15final class LemonConditionTest extends TestCase
16{
17 public function testEvaluateUsesDeclaredNamesAndLemonOperatorGrouping(): void
18 {
19 $condition = new LemonCondition(['YES']);
20 self::assertTrue($condition->evaluate('YES'));
21 self::assertTrue($condition->evaluate('YES || YES'));
22 self::assertFalse($condition->evaluate('NO || NO'));
23 self::assertFalse($condition->evaluate('NO'));
24 self::assertTrue($condition->evaluate('!NO && YES'));
25 self::assertFalse($condition->evaluate('NO && YES || YES'));
26 self::assertTrue($condition->evaluate('(NO && YES) || YES'));
27 self::assertTrue($condition->evaluate('YES || NO && NO'));
28 self::assertFalse($condition->evaluate('!(YES || NO)'));
29 }
30
31 public function testEvaluateRejectsUnparsedTrailingText(): void
32 {
33 $this->expectException(RuntimeException::class);
34 $this->expectExceptionMessage('Unexpected Lemon condition token: )');
35 (new LemonCondition())->evaluate('YES)');
36 }
37
38 public function testEvaluateRejectsEmptyConditions(): void
39 {
40 $this->expectException(RuntimeException::class);
41 $this->expectExceptionMessage('Empty Lemon preprocessor condition.');
42 (new LemonCondition())->evaluate(' ');
43 }
44
45 public function testExpressionStopsAtTheMatchingParenthesis(): void
46 {
47 $offset = 0;
48 self::assertTrue((new LemonCondition(['YES']))->expression(['YES', '||', 'NO', ')'], $offset));
49 self::assertSame(3, $offset);
50 }
51
52 public function testExpressionRejectsUnknownOperators(): void
53 {
54 $offset = 0;
55 $this->expectException(RuntimeException::class);
56 $this->expectExceptionMessage('Invalid Lemon condition operator: +');
57 (new LemonCondition())->expression(['YES', '+', 'NO'], $offset);
58 }
59
60 public function testTermReadsNestedNegationAndAdvancesOverItsClosingParenthesis(): void
61 {
62 $offset = 0;
63 self::assertTrue((new LemonCondition(['YES']))->term(['!', '(', '!', 'YES', ')'], $offset));
64 self::assertSame(5, $offset);
65 }
66
67 public function testTermRejectsAnUnclosedGroup(): void
68 {
69 $offset = 0;
70 $this->expectException(RuntimeException::class);
71 $this->expectExceptionMessage('Unclosed Lemon preprocessor condition.');
72 (new LemonCondition())->term(['(', 'NO'], $offset);
73 }
74
75 public function testTermRejectsNonIdentifierInput(): void
76 {
77 $offset = 0;
78 $this->expectException(RuntimeException::class);
79 $this->expectExceptionMessage('Invalid Lemon preprocessor term: 0');
80 (new LemonCondition())->term(['0'], $offset);
81 }
82}
83