Skip to content

Commit 2f55bcb

Browse files
Add more tests
1 parent b70f8c3 commit 2f55bcb

File tree

9 files changed

+1355
-255
lines changed

9 files changed

+1355
-255
lines changed

tests/Unit/MartinGeorgiev/Doctrine/DBAL/Types/BaseRangeTestCase.php

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66

77
use Doctrine\DBAL\Platforms\AbstractPlatform;
88
use MartinGeorgiev\Doctrine\DBAL\Types\BaseRangeType;
9+
use MartinGeorgiev\Doctrine\DBAL\Types\Exceptions\InvalidRangeForDatabaseException;
10+
use MartinGeorgiev\Doctrine\DBAL\Types\Exceptions\InvalidRangeForPHPException;
911
use MartinGeorgiev\Doctrine\DBAL\Types\ValueObject\Range;
1012
use PHPUnit\Framework\Attributes\DataProvider;
1113
use PHPUnit\Framework\Attributes\Test;
@@ -93,6 +95,41 @@ public function can_handle_postgres_empty_range(): void
9395
self::assertTrue($result->isEmpty());
9496
}
9597

98+
#[Test]
99+
public function can_handle_empty_string_from_sql(): void
100+
{
101+
$result = $this->fixture->convertToPHPValue('', $this->platform);
102+
103+
self::assertNull($result);
104+
}
105+
106+
#[Test]
107+
public function throws_exception_for_invalid_php_value_type(): void
108+
{
109+
$this->expectException(InvalidRangeForDatabaseException::class);
110+
$this->expectExceptionMessage('Invalid type for range');
111+
112+
$this->fixture->convertToDatabaseValue('invalid', $this->platform); // @phpstan-ignore-line argument.type
113+
}
114+
115+
#[Test]
116+
public function throws_exception_for_invalid_sql_value_type(): void
117+
{
118+
$this->expectException(InvalidRangeForPHPException::class);
119+
$this->expectExceptionMessage('Invalid database value type for range conversion');
120+
121+
$this->fixture->convertToPHPValue([1, 2], $this->platform); // @phpstan-ignore-line argument.type
122+
}
123+
124+
#[Test]
125+
public function throws_exception_for_invalid_range_format(): void
126+
{
127+
$this->expectException(InvalidRangeForPHPException::class);
128+
$this->expectExceptionMessage('Invalid range format from database');
129+
130+
$this->fixture->convertToPHPValue('{1,2}', $this->platform);
131+
}
132+
96133
/**
97134
* Each array contains [phpValue, postgresValue] pairs.
98135
*/
Lines changed: 288 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,288 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Tests\Unit\MartinGeorgiev\Doctrine\DBAL\Types\ValueObject;
6+
7+
use MartinGeorgiev\Doctrine\DBAL\Types\ValueObject\Range;
8+
use PHPUnit\Framework\Attributes\DataProvider;
9+
use PHPUnit\Framework\Attributes\Test;
10+
use PHPUnit\Framework\TestCase;
11+
12+
abstract class BaseRangeTestCase extends TestCase
13+
{
14+
#[Test]
15+
public function can_create_simple_range(): void
16+
{
17+
$range = $this->createSimpleRange();
18+
$expectedString = $this->getExpectedSimpleRangeString();
19+
20+
self::assertEquals($expectedString, (string) $range);
21+
self::assertFalse($range->isEmpty());
22+
}
23+
24+
#[Test]
25+
public function can_create_empty_range(): void
26+
{
27+
$range = $this->createEmptyRange();
28+
29+
self::assertEquals('empty', (string) $range);
30+
self::assertTrue($range->isEmpty());
31+
}
32+
33+
#[Test]
34+
public function can_create_infinite_range(): void
35+
{
36+
$range = $this->createInfiniteRange();
37+
38+
self::assertEquals('(,)', (string) $range);
39+
self::assertFalse($range->isEmpty());
40+
}
41+
42+
#[Test]
43+
public function can_create_inclusive_range(): void
44+
{
45+
$range = $this->createInclusiveRange();
46+
$expectedString = $this->getExpectedInclusiveRangeString();
47+
48+
self::assertEquals($expectedString, (string) $range);
49+
self::assertFalse($range->isEmpty());
50+
}
51+
52+
#[Test]
53+
#[DataProvider('provideContainsTestCases')]
54+
public function can_check_contains(Range $range, mixed $value, bool $expected): void
55+
{
56+
self::assertEquals($expected, $range->contains($value));
57+
}
58+
59+
#[Test]
60+
#[DataProvider('provideFromStringTestCases')]
61+
public function can_parse_from_string(string $input, Range $expectedRange): void
62+
{
63+
$range = $this->parseFromString($input);
64+
$this->assertRangeEquals($expectedRange, $range);
65+
}
66+
67+
#[Test]
68+
public function can_handle_boundary_conditions(): void
69+
{
70+
$range = $this->createBoundaryTestRange();
71+
$testCases = $this->getBoundaryTestCases();
72+
73+
$this->assertBoundaryConditions($range, $testCases);
74+
}
75+
76+
#[Test]
77+
public function can_handle_comparison_via_is_empty(): void
78+
{
79+
$testCases = $this->getComparisonTestCases();
80+
81+
foreach ($testCases as $description => $testCase) {
82+
self::assertEquals(
83+
$testCase['expectedEmpty'],
84+
$testCase['range']->isEmpty(),
85+
'Comparison test failed: '.$description
86+
);
87+
}
88+
}
89+
90+
/**
91+
* Create a simple range for basic testing.
92+
*/
93+
abstract protected function createSimpleRange(): Range;
94+
95+
/**
96+
* Get expected string representation of simple range.
97+
*/
98+
abstract protected function getExpectedSimpleRangeString(): string;
99+
100+
/**
101+
* Create an empty range.
102+
*/
103+
abstract protected function createEmptyRange(): Range;
104+
105+
/**
106+
* Create an infinite range.
107+
*/
108+
abstract protected function createInfiniteRange(): Range;
109+
110+
/**
111+
* Create an inclusive range for testing.
112+
*/
113+
abstract protected function createInclusiveRange(): Range;
114+
115+
/**
116+
* Get expected string representation of inclusive range.
117+
*/
118+
abstract protected function getExpectedInclusiveRangeString(): string;
119+
120+
/**
121+
* Parse range from string.
122+
*/
123+
abstract protected function parseFromString(string $input): Range;
124+
125+
/**
126+
* Create range for boundary testing.
127+
*/
128+
abstract protected function createBoundaryTestRange(): Range;
129+
130+
/**
131+
* Get boundary test cases.
132+
*
133+
* @return array<string, array{value: mixed, expected: bool}>
134+
*/
135+
abstract protected function getBoundaryTestCases(): array;
136+
137+
/**
138+
* Get comparison test cases.
139+
*
140+
* @return array<string, array{range: Range, expectedEmpty: bool}>
141+
*/
142+
abstract protected function getComparisonTestCases(): array;
143+
144+
/**
145+
* @return \Generator<string, array{Range, mixed, bool}>
146+
*/
147+
abstract public static function provideContainsTestCases(): \Generator;
148+
149+
/**
150+
* @return \Generator<string, array{string, Range}>
151+
*/
152+
abstract public static function provideFromStringTestCases(): \Generator;
153+
154+
/**
155+
* Assert that a range equals another range by comparing string representation and isEmpty state.
156+
*/
157+
protected function assertRangeEquals(Range $expected, Range $actual, string $message = ''): void
158+
{
159+
self::assertEquals($expected->__toString(), $actual->__toString(), $message.' (string representation)');
160+
self::assertEquals($expected->isEmpty(), $actual->isEmpty(), $message.' (isEmpty state)');
161+
}
162+
163+
/**
164+
* Assert that a range contains all the given values.
165+
*
166+
* @param array<mixed> $values
167+
*/
168+
protected function assertRangeContainsAll(Range $range, array $values, string $message = ''): void
169+
{
170+
foreach ($values as $value) {
171+
self::assertTrue(
172+
$range->contains($value),
173+
$message.' - Range should contain value: '.\var_export($value, true)
174+
);
175+
}
176+
}
177+
178+
/**
179+
* Assert that a range does not contain any of the given values.
180+
*
181+
* @param array<mixed> $values
182+
*/
183+
protected function assertRangeContainsNone(Range $range, array $values, string $message = ''): void
184+
{
185+
foreach ($values as $value) {
186+
self::assertFalse(
187+
$range->contains($value),
188+
$message.' - Range should not contain value: '.\var_export($value, true)
189+
);
190+
}
191+
}
192+
193+
/**
194+
* Assert that a range has the expected string representation.
195+
*/
196+
protected function assertRangeStringEquals(string $expected, Range $range, string $message = ''): void
197+
{
198+
self::assertEquals($expected, (string) $range, $message);
199+
}
200+
201+
/**
202+
* Assert that a range is empty.
203+
*/
204+
protected function assertRangeIsEmpty(Range $range, string $message = ''): void
205+
{
206+
self::assertTrue($range->isEmpty(), $message.' - Range should be empty');
207+
self::assertEquals('empty', (string) $range, $message.' - Empty range should have "empty" string representation');
208+
}
209+
210+
/**
211+
* Assert that a range is not empty.
212+
*/
213+
protected function assertRangeIsNotEmpty(Range $range, string $message = ''): void
214+
{
215+
self::assertFalse($range->isEmpty(), $message.' - Range should not be empty');
216+
self::assertNotEquals('empty', (string) $range, $message.' - Non-empty range should not have "empty" string representation');
217+
}
218+
219+
/**
220+
* Test boundary conditions for a range with known bounds.
221+
*
222+
* @param array<string, array{value: mixed, expected: bool}> $testCases
223+
*/
224+
protected function assertBoundaryConditions(Range $range, array $testCases, string $message = ''): void
225+
{
226+
foreach ($testCases as $description => $testCase) {
227+
self::assertEquals(
228+
$testCase['expected'],
229+
$range->contains($testCase['value']),
230+
$message.' - Boundary test failed: '.$description
231+
);
232+
}
233+
}
234+
235+
/**
236+
* Generate common boundary test cases for a range [lower, upper).
237+
*
238+
* @return array<string, array{value: mixed, expected: bool}>
239+
*/
240+
protected function generateBoundaryTestCases(
241+
mixed $lower,
242+
mixed $upper,
243+
mixed $belowLower,
244+
mixed $aboveUpper,
245+
mixed $middle
246+
): array {
247+
return [
248+
'contains lower bound (inclusive)' => ['value' => $lower, 'expected' => true],
249+
'does not contain value below range' => ['value' => $belowLower, 'expected' => false],
250+
'does not contain upper bound (exclusive)' => ['value' => $upper, 'expected' => false],
251+
'does not contain value above range' => ['value' => $aboveUpper, 'expected' => false],
252+
'contains middle value' => ['value' => $middle, 'expected' => true],
253+
'does not contain null' => ['value' => null, 'expected' => false],
254+
];
255+
}
256+
257+
/**
258+
* Test that a range correctly handles equal bounds with different bracket combinations.
259+
*
260+
* @param callable $rangeFactory Function that creates a range: fn($lower, $upper, $lowerInc, $upperInc) => Range
261+
*/
262+
protected function assertEqualBoundsHandling(callable $rangeFactory, mixed $value): void
263+
{
264+
$inclusiveEqual = $rangeFactory($value, $value, true, true);
265+
self::assertFalse($inclusiveEqual->isEmpty(), 'Equal bounds with inclusive brackets should not be empty');
266+
self::assertTrue($inclusiveEqual->contains($value), 'Equal bounds with inclusive brackets should contain the value');
267+
268+
$exclusiveExclusive = $rangeFactory($value, $value, false, false);
269+
self::assertTrue($exclusiveExclusive->isEmpty(), 'Equal bounds with exclusive brackets should be empty');
270+
271+
$inclusiveExclusive = $rangeFactory($value, $value, true, false);
272+
self::assertTrue($inclusiveExclusive->isEmpty(), 'Equal bounds with mixed brackets should be empty');
273+
274+
$exclusiveInclusive = $rangeFactory($value, $value, false, true);
275+
self::assertTrue($exclusiveInclusive->isEmpty(), 'Equal bounds with mixed brackets should be empty');
276+
}
277+
278+
/**
279+
* Test that a range correctly handles reverse bounds (lower > upper).
280+
*
281+
* @param callable $rangeFactory Function that creates a range: fn($lower, $upper, $lowerInc, $upperInc) => Range
282+
*/
283+
protected function assertReverseBoundsHandling(callable $rangeFactory, mixed $lower, mixed $upper): void
284+
{
285+
$reverseRange = $rangeFactory($lower, $upper, true, false);
286+
self::assertTrue($reverseRange->isEmpty(), 'Range with lower > upper should be empty');
287+
}
288+
}

0 commit comments

Comments
 (0)