packages/sql-faker/tests/Unit/Generation/Plan/LexemeConstraintTest.php
1<?php
2
3declare(strict_types=1);
4
5namespace Tests\Unit\Generation\Plan;
6
7use PHPUnit\Framework\Attributes\CoversClass;
8use PHPUnit\Framework\Attributes\UsesClass;
9use PHPUnit\Framework\TestCase;
10use SqlFaker\Generation\Plan\LexemeConstraint;
11use SqlFaker\Generation\Value\IntegerDomain;
12
13#[CoversClass(LexemeConstraint::class)]
14#[UsesClass(IntegerDomain::class)]
15final class LexemeConstraintTest extends TestCase
16{
17 public function testOneOfChoicesRemainReachableAndIntersectionRemovesOnlyForbiddenSpellings(): void
18 {
19 $names = LexemeConstraint::oneOf('users', 'orders', 'users');
20 self::assertSame('users', $names->choose(static fn (int $count): int => 0));
21 self::assertSame('orders', $names->choose(static fn (int $count): int => $count - 1));
22 $intersection = $names->intersect(LexemeConstraint::oneOf('orders', 'products'));
23 self::assertSame('orders', $intersection->choose(static fn (int $count): int => 0));
24 self::assertFalse($intersection->accepts('users'));
25 self::assertTrue($names->accepts('users'));
26 }
27
28 public function testIntegersBoundsAndFiniteSetsIntersectWithoutSampling(): void
29 {
30 $range = LexemeConstraint::integers(10, 99)->intersect(LexemeConstraint::integers(20, 30));
31 self::assertSame('20', $range->choose(static fn (int $count): int => 0));
32 self::assertSame('30', $range->choose(static fn (int $count): int => $count - 1));
33 self::assertFalse($range->accepts('31'));
34 self::assertFalse($range->accepts("'20'"));
35 $finite = $range->intersect(LexemeConstraint::oneOf('19', '20', '31'));
36 self::assertSame('20', $finite->choose(static fn (int $count): int => 0));
37 }
38
39
40 public function testChooseRetainsTheWholeIntegerIntervalIncludingItsBounds(): void
41 {
42 $range = LexemeConstraint::integers(0, PHP_INT_MAX);
43 self::assertSame('0', $range->choose(static fn (int $count): int => 0));
44 self::assertSame((string) PHP_INT_MAX, $range->choose(static fn (int $count): int => $count - 1));
45 }
46
47 public function testAcceptsUsesLexicalSpellingsRatherThanNumericCoercion(): void
48 {
49 $range = LexemeConstraint::integers(10, 20);
50 self::assertTrue($range->accepts('10'));
51 self::assertTrue($range->accepts('20'));
52 self::assertFalse($range->accepts('9'));
53 self::assertFalse($range->accepts('21'));
54 self::assertFalse($range->accepts('-10'));
55 self::assertFalse($range->accepts('+10'));
56 self::assertFalse($range->accepts("'10'"));
57 self::assertFalse($range->accepts('1e1'));
58 self::assertFalse($range->accepts('10.0'));
59 self::assertFalse($range->accepts(' 10'));
60
61 }
62
63 public function testIntersectIsIndependentOfWhichSideHoldsTheFiniteSet(): void
64 {
65 $range = LexemeConstraint::integers(10, 20);
66 $set = LexemeConstraint::oneOf('9', '10', '15', '21');
67 self::assertEquals($range->intersect($set), $set->intersect($range));
68 self::assertFalse($range->intersect($set)->accepts('11'));
69 self::assertTrue($range->intersect($set)->accepts('15'));
70 }
71
72
73 public function testOneOfDeduplicatesValuesWithoutLeavingChoiceIndexGaps(): void
74 {
75 $names = LexemeConstraint::oneOf('users', 'users', 'orders');
76 self::assertSame('orders', $names->choose(static fn (int $count): int => $count - 1));
77 }
78
79 public function testIntegersSupportsSingletonIntervalsIncludingZero(): void
80 {
81 $zero = LexemeConstraint::integers(0, 0);
82 self::assertSame('0', $zero->choose(static fn (int $count): int => $count - 1));
83 self::assertTrue($zero->accepts('0'));
84 self::assertSame('42', LexemeConstraint::integers(42, 42)->choose(static fn (int $count): int => 0));
85 }
86
87 public function testChooseDefaultIntegerDomainIncludesZero(): void
88 {
89 $domain = new LexemeConstraint([]);
90 self::assertSame('0', $domain->choose(static fn (int $count): int => 0));
91 self::assertSame((string) PHP_INT_MAX, $domain->choose(static fn (int $count): int => $count - 1));
92 }
93
94}
95