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