Skip to content

Commit 9495e98

Browse files
chore: improve test coverage and standardize style of unit tests in ORM\Query\AST\Functions namespace (#405)
1 parent 143c6a8 commit 9495e98

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

60 files changed

+416
-263
lines changed

src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/BaseVariadicFunction.php

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,10 @@ private function feedParserWithNodesForNodeMappingPattern(Parser $parser, string
8181
// When nodeIndex=2, we're about to add the 3rd argument (total: 3)
8282
$foundMoreNodesThanMappingExpected = ($nodeIndex + 1) > $this->getMaxArgumentCount();
8383
if ($foundMoreNodesThanMappingExpected) {
84+
if ($this->getMinArgumentCount() === $this->getMaxArgumentCount()) {
85+
throw InvalidArgumentForVariadicFunctionException::exactCount($this->getFunctionName(), $this->getMinArgumentCount());
86+
}
87+
8488
throw InvalidArgumentForVariadicFunctionException::between($this->getFunctionName(), $this->getMinArgumentCount(), $this->getMaxArgumentCount());
8589
}
8690

@@ -114,6 +118,10 @@ protected function validateArguments(Node ...$arguments): void
114118
$maxArgumentCount = $this->getMaxArgumentCount();
115119
$argumentCount = \count($arguments);
116120

121+
if ($minArgumentCount === $maxArgumentCount && $argumentCount !== $minArgumentCount) {
122+
throw InvalidArgumentForVariadicFunctionException::exactCount($this->getFunctionName(), $this->getMinArgumentCount());
123+
}
124+
117125
if ($argumentCount < $minArgumentCount) {
118126
throw InvalidArgumentForVariadicFunctionException::atLeast($this->getFunctionName(), $this->getMinArgumentCount());
119127
}

tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/AllTest.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,14 +19,14 @@ protected function getStringFunctions(): array
1919
protected function getExpectedSqlStatements(): array
2020
{
2121
return [
22-
'SELECT c0_.id AS id_0 FROM ContainsArrays c0_ WHERE c0_.id > ALL(c0_.textArray)',
22+
'all elements match condition' => 'SELECT c0_.id AS id_0 FROM ContainsArrays c0_ WHERE c0_.id > ALL(c0_.textArray)',
2323
];
2424
}
2525

2626
protected function getDqlStatements(): array
2727
{
2828
return [
29-
\sprintf('SELECT e.id FROM %s e WHERE e.id > ALL_OF(e.textArray)', ContainsArrays::class),
29+
'all elements match condition' => \sprintf('SELECT e.id FROM %s e WHERE e.id > ALL_OF(e.textArray)', ContainsArrays::class),
3030
];
3131
}
3232
}

tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/ArrayPositionTest.php

Lines changed: 24 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
use MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\ArrayPosition;
99
use MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\BaseVariadicFunction;
1010
use MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\Exception\InvalidArgumentForVariadicFunctionException;
11+
use PHPUnit\Framework\Attributes\DataProvider;
1112
use PHPUnit\Framework\Attributes\Test;
1213

1314
class ArrayPositionTest extends BaseVariadicFunctionTestCase
@@ -31,6 +32,9 @@ protected function getExpectedSqlStatements(): array
3132
'finds numeric element' => 'SELECT array_position(c0_.integerArray, 42) AS sclr_0 FROM ContainsArrays c0_',
3233
'finds element using parameter' => 'SELECT array_position(c0_.textArray, ?) AS sclr_0 FROM ContainsArrays c0_',
3334
'with start position' => "SELECT array_position(c0_.textArray, 'new-value', 2) AS sclr_0 FROM ContainsArrays c0_",
35+
'with zero start position' => "SELECT array_position(c0_.textArray, 'value', 0) AS sclr_0 FROM ContainsArrays c0_",
36+
'with negative start position' => "SELECT array_position(c0_.textArray, 'value', -1) AS sclr_0 FROM ContainsArrays c0_",
37+
'with arithmetic expression as start position' => "SELECT array_position(c0_.textArray, 'value', 1 + 1) AS sclr_0 FROM ContainsArrays c0_",
3438
];
3539
}
3640

@@ -41,26 +45,36 @@ protected function getDqlStatements(): array
4145
'finds numeric element' => \sprintf('SELECT ARRAY_POSITION(e.integerArray, 42) FROM %s e', ContainsArrays::class),
4246
'finds element using parameter' => \sprintf('SELECT ARRAY_POSITION(e.textArray, :dql_parameter) FROM %s e', ContainsArrays::class),
4347
'with start position' => \sprintf("SELECT ARRAY_POSITION(e.textArray, 'new-value', 2) FROM %s e", ContainsArrays::class),
48+
'with zero start position' => \sprintf("SELECT ARRAY_POSITION(e.textArray, 'value', 0) FROM %s e", ContainsArrays::class),
49+
'with negative start position' => \sprintf("SELECT ARRAY_POSITION(e.textArray, 'value', -1) FROM %s e", ContainsArrays::class),
50+
'with arithmetic expression as start position' => \sprintf("SELECT ARRAY_POSITION(e.textArray, 'value', 1+1) FROM %s e", ContainsArrays::class),
4451
];
4552
}
4653

54+
#[DataProvider('provideInvalidArgumentCountCases')]
4755
#[Test]
48-
public function too_few_arguments_throws_exception(): void
56+
public function throws_exception_for_invalid_argument_count(string $dql, string $expectedMessage): void
4957
{
5058
$this->expectException(InvalidArgumentForVariadicFunctionException::class);
51-
$this->expectExceptionMessage('array_position() requires at least 2 arguments');
59+
$this->expectExceptionMessage($expectedMessage);
5260

53-
$dql = \sprintf('SELECT ARRAY_POSITION(e.textArray) FROM %s e', ContainsArrays::class);
5461
$this->buildEntityManager()->createQuery($dql)->getSQL();
5562
}
5663

57-
#[Test]
58-
public function too_many_arguments_throws_exception(): void
64+
/**
65+
* @return array<string, array{string, string}>
66+
*/
67+
public static function provideInvalidArgumentCountCases(): array
5968
{
60-
$this->expectException(InvalidArgumentForVariadicFunctionException::class);
61-
$this->expectExceptionMessage('array_position() requires between 2 and 3 arguments');
62-
63-
$dql = \sprintf("SELECT ARRAY_POSITION(e.textArray, 0, 1, 'extra_arg') FROM %s e", ContainsArrays::class);
64-
$this->buildEntityManager()->createQuery($dql)->getSQL();
69+
return [
70+
'too few arguments' => [
71+
\sprintf('SELECT ARRAY_POSITION(e.textArray) FROM %s e', ContainsArrays::class),
72+
'array_position() requires at least 2 arguments',
73+
],
74+
'too many arguments' => [
75+
\sprintf("SELECT ARRAY_POSITION(e.textArray, 0, 1, 'extra_arg') FROM %s e", ContainsArrays::class),
76+
'array_position() requires between 2 and 3 arguments',
77+
],
78+
];
6579
}
6680
}

tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/ArrayToJsonTest.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ protected function getDqlStatements(): array
4242
}
4343

4444
#[Test]
45-
public function invalid_boolean_throws_exception(): void
45+
public function throws_exception_for_invalid_boolean_value(): void
4646
{
4747
$this->expectException(InvalidBooleanException::class);
4848
$this->expectExceptionMessage('Invalid boolean value "invalid" provided for array_to_json. Must be "true" or "false".');
@@ -52,7 +52,7 @@ public function invalid_boolean_throws_exception(): void
5252
}
5353

5454
#[Test]
55-
public function too_many_arguments_throws_exception(): void
55+
public function throws_exception_for_too_many_arguments(): void
5656
{
5757
$this->expectException(InvalidArgumentForVariadicFunctionException::class);
5858
$this->expectExceptionMessage('array_to_json() requires between 1 and 2 arguments');

tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/CeilTest.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,14 +19,14 @@ protected function getStringFunctions(): array
1919
protected function getExpectedSqlStatements(): array
2020
{
2121
return [
22-
'SELECT CEIL(c0_.decimal1) AS sclr_0 FROM ContainsDecimals c0_',
22+
'rounds decimal up to nearest integer' => 'SELECT CEIL(c0_.decimal1) AS sclr_0 FROM ContainsDecimals c0_',
2323
];
2424
}
2525

2626
protected function getDqlStatements(): array
2727
{
2828
return [
29-
\sprintf('SELECT CEIL(e.decimal1) FROM %s e', ContainsDecimals::class),
29+
'rounds decimal up to nearest integer' => \sprintf('SELECT CEIL(e.decimal1) FROM %s e', ContainsDecimals::class),
3030
];
3131
}
3232
}

tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/DateAddTest.php

Lines changed: 32 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
use MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\DateAdd;
1010
use MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\Exception\InvalidArgumentForVariadicFunctionException;
1111
use MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\Exception\InvalidTimezoneException;
12+
use PHPUnit\Framework\Attributes\DataProvider;
1213
use PHPUnit\Framework\Attributes\Test;
1314

1415
class DateAddTest extends BaseVariadicFunctionTestCase
@@ -28,50 +29,62 @@ protected function getStringFunctions(): array
2829
protected function getExpectedSqlStatements(): array
2930
{
3031
return [
31-
'adds 1 day with timezone' => "SELECT date_add(c0_.datetimetz1, '1 day', 'Europe/Sofia') AS sclr_0 FROM ContainsDates c0_",
32-
'adds 2 hours with timezone' => "SELECT date_add(c0_.datetimetz1, '2 hours', 'UTC') AS sclr_0 FROM ContainsDates c0_",
33-
'adds 3 days without timezone' => "SELECT date_add(c0_.datetimetz1, '3 days') AS sclr_0 FROM ContainsDates c0_",
34-
'adds with WHERE clause' => "SELECT c0_.datetimetz1 AS datetimetz1_0 FROM ContainsDates c0_ WHERE date_add(c0_.datetimetz1, '1 day') = '2023-01-02 00:00:00'",
32+
'with timezone (3 arguments)' => "SELECT date_add(c0_.datetimetz1, '1 day', 'Europe/Sofia') AS sclr_0 FROM ContainsDates c0_",
33+
'without timezone (2 arguments)' => "SELECT date_add(c0_.datetimetz1, '3 days') AS sclr_0 FROM ContainsDates c0_",
34+
'used in WHERE clause' => "SELECT c0_.datetimetz1 AS datetimetz1_0 FROM ContainsDates c0_ WHERE date_add(c0_.datetimetz1, '1 day') = '2023-01-02 00:00:00'",
3535
];
3636
}
3737

3838
protected function getDqlStatements(): array
3939
{
4040
return [
41-
'adds 1 day with timezone' => \sprintf("SELECT DATE_ADD(e.datetimetz1, '1 day', 'Europe/Sofia') FROM %s e", ContainsDates::class),
42-
'adds 2 hours with timezone' => \sprintf("SELECT DATE_ADD(e.datetimetz1, '2 hours', 'UTC') FROM %s e", ContainsDates::class),
43-
'adds 3 days without timezone' => \sprintf("SELECT DATE_ADD(e.datetimetz1, '3 days') FROM %s e", ContainsDates::class),
44-
'adds with WHERE clause' => \sprintf("SELECT e.datetimetz1 FROM %s e WHERE DATE_ADD(e.datetimetz1, '1 day') = '2023-01-02 00:00:00'", ContainsDates::class),
41+
'with timezone (3 arguments)' => \sprintf("SELECT DATE_ADD(e.datetimetz1, '1 day', 'Europe/Sofia') FROM %s e", ContainsDates::class),
42+
'without timezone (2 arguments)' => \sprintf("SELECT DATE_ADD(e.datetimetz1, '3 days') FROM %s e", ContainsDates::class),
43+
'used in WHERE clause' => \sprintf("SELECT e.datetimetz1 FROM %s e WHERE DATE_ADD(e.datetimetz1, '1 day') = '2023-01-02 00:00:00'", ContainsDates::class),
4544
];
4645
}
4746

4847
#[Test]
49-
public function invalid_timezone_throws_exception(): void
48+
public function throws_exception_for_too_few_arguments(): void
5049
{
51-
$this->expectException(InvalidTimezoneException::class);
52-
$this->expectExceptionMessage('Invalid timezone "Invalid/Timezone" provided for date_add');
50+
$this->expectException(InvalidArgumentForVariadicFunctionException::class);
51+
$this->expectExceptionMessage('date_add() requires at least 2 arguments');
5352

54-
$dql = \sprintf("SELECT DATE_ADD(e.datetimetz1, '1 day', 'Invalid/Timezone') FROM %s e", ContainsDates::class);
53+
$dql = \sprintf('SELECT DATE_ADD(e.datetimetz1) FROM %s e', ContainsDates::class);
5554
$this->buildEntityManager()->createQuery($dql)->getSQL();
5655
}
5756

5857
#[Test]
59-
public function too_few_arguments_throws_exception(): void
58+
public function throws_exception_for_too_many_arguments(): void
6059
{
6160
$this->expectException(InvalidArgumentForVariadicFunctionException::class);
62-
$this->expectExceptionMessage('date_add() requires at least 2 arguments');
61+
$this->expectExceptionMessage('date_add() requires between 2 and 3 arguments');
6362

64-
$dql = \sprintf('SELECT DATE_ADD(e.datetimetz1) FROM %s e', ContainsDates::class);
63+
$dql = \sprintf("SELECT DATE_ADD(e.datetimetz1, '1 day', 'Europe/Sofia', 'extra_arg') FROM %s e", ContainsDates::class);
6564
$this->buildEntityManager()->createQuery($dql)->getSQL();
6665
}
6766

67+
#[DataProvider('provideInvalidTimezoneValues')]
6868
#[Test]
69-
public function too_many_arguments_throws_exception(): void
69+
public function throws_exception_for_invalid_timezone(string $invalidTimezone): void
7070
{
71-
$this->expectException(InvalidArgumentForVariadicFunctionException::class);
72-
$this->expectExceptionMessage('date_add() requires between 2 and 3 arguments');
71+
$this->expectException(InvalidTimezoneException::class);
72+
$this->expectExceptionMessage(\sprintf('Invalid timezone "%s" provided for date_add. Must be a valid PHP timezone identifier.', $invalidTimezone));
7373

74-
$dql = \sprintf("SELECT DATE_ADD(e.datetimetz1, '1 day', 'Europe/Sofia', 'extra_arg') FROM %s e", ContainsDates::class);
74+
$dql = \sprintf("SELECT DATE_ADD(e.datetimetz1, '1 day', '%s') FROM %s e", $invalidTimezone, ContainsDates::class);
7575
$this->buildEntityManager()->createQuery($dql)->getSQL();
7676
}
77+
78+
/**
79+
* @return array<string, array{string}>
80+
*/
81+
public static function provideInvalidTimezoneValues(): array
82+
{
83+
return [
84+
'empty string' => [''],
85+
'whitespace only' => [' '],
86+
'numeric value' => ['123'],
87+
'invalid timezone' => ['Invalid/Timezone'],
88+
];
89+
}
7790
}

tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/DateExtractTest.php

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -19,22 +19,22 @@ protected function getStringFunctions(): array
1919
protected function getExpectedSqlStatements(): array
2020
{
2121
return [
22-
"SELECT EXTRACT('DAY' FROM c0_.date1) AS sclr_0 FROM ContainsDates c0_",
23-
"SELECT EXTRACT('MONTH' FROM c0_.date1) AS sclr_0 FROM ContainsDates c0_",
24-
"SELECT EXTRACT('YEAR' FROM c0_.date1) AS sclr_0 FROM ContainsDates c0_",
25-
"SELECT c0_.date1 AS date1_0 FROM ContainsDates c0_ WHERE EXTRACT('DAY' FROM c0_.date1) = 7",
26-
"SELECT c0_.date1 AS date1_0 FROM ContainsDates c0_ WHERE EXTRACT('MONTH' FROM c0_.date1) = 12",
22+
'extracts day from date' => "SELECT EXTRACT('DAY' FROM c0_.date1) AS sclr_0 FROM ContainsDates c0_",
23+
'extracts month from date' => "SELECT EXTRACT('MONTH' FROM c0_.date1) AS sclr_0 FROM ContainsDates c0_",
24+
'extracts year from date' => "SELECT EXTRACT('YEAR' FROM c0_.date1) AS sclr_0 FROM ContainsDates c0_",
25+
'filters by extracted day' => "SELECT c0_.date1 AS date1_0 FROM ContainsDates c0_ WHERE EXTRACT('DAY' FROM c0_.date1) = 7",
26+
'filters by extracted month' => "SELECT c0_.date1 AS date1_0 FROM ContainsDates c0_ WHERE EXTRACT('MONTH' FROM c0_.date1) = 12",
2727
];
2828
}
2929

3030
protected function getDqlStatements(): array
3131
{
3232
return [
33-
\sprintf("SELECT DATE_EXTRACT('DAY', e.date1) FROM %s e", ContainsDates::class),
34-
\sprintf("SELECT DATE_EXTRACT('MONTH', e.date1) FROM %s e", ContainsDates::class),
35-
\sprintf("SELECT DATE_EXTRACT('YEAR', e.date1) FROM %s e", ContainsDates::class),
36-
\sprintf("SELECT e.date1 FROM %s e WHERE DATE_EXTRACT('DAY', e.date1) = 7", ContainsDates::class),
37-
\sprintf("SELECT e.date1 FROM %s e WHERE DATE_EXTRACT('MONTH', e.date1) = 12", ContainsDates::class),
33+
'extracts day from date' => \sprintf("SELECT DATE_EXTRACT('DAY', e.date1) FROM %s e", ContainsDates::class),
34+
'extracts month from date' => \sprintf("SELECT DATE_EXTRACT('MONTH', e.date1) FROM %s e", ContainsDates::class),
35+
'extracts year from date' => \sprintf("SELECT DATE_EXTRACT('YEAR', e.date1) FROM %s e", ContainsDates::class),
36+
'filters by extracted day' => \sprintf("SELECT e.date1 FROM %s e WHERE DATE_EXTRACT('DAY', e.date1) = 7", ContainsDates::class),
37+
'filters by extracted month' => \sprintf("SELECT e.date1 FROM %s e WHERE DATE_EXTRACT('MONTH', e.date1) = 12", ContainsDates::class),
3838
];
3939
}
4040
}

tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/DateOverlapsTest.php

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -19,29 +19,29 @@ protected function getStringFunctions(): array
1919
protected function getExpectedSqlStatements(): array
2020
{
2121
return [
22-
"SELECT c0_.date1 AS date1_0 FROM ContainsDates c0_ WHERE (c0_.date1, c0_.date2) OVERLAPS ('2001-12-21', '2001-12-25') = 0",
23-
"SELECT c0_.date1 AS date1_0 FROM ContainsDates c0_ WHERE (c0_.date1, c0_.date2) OVERLAPS ('2001-12-21', '2001-12-25') = 1",
24-
"SELECT c0_.date1 AS date1_0 FROM ContainsDates c0_ WHERE (c0_.date1, COALESCE(c0_.date2, CURRENT_DATE)) OVERLAPS ('2001-12-21', '2001-12-25') = 1",
25-
"SELECT c0_.date1 AS date1_0 FROM ContainsDates c0_ WHERE (c0_.date1, COALESCE(c0_.date2, CURRENT_DATE)) OVERLAPS ('2001-12-21', '2001-12-25') = 0",
22+
'checks non-overlapping date ranges' => "SELECT c0_.date1 AS date1_0 FROM ContainsDates c0_ WHERE (c0_.date1, c0_.date2) OVERLAPS ('2001-12-21', '2001-12-25') = 0",
23+
'checks overlapping date ranges' => "SELECT c0_.date1 AS date1_0 FROM ContainsDates c0_ WHERE (c0_.date1, c0_.date2) OVERLAPS ('2001-12-21', '2001-12-25') = 1",
24+
'checks overlapping with null handling true' => "SELECT c0_.date1 AS date1_0 FROM ContainsDates c0_ WHERE (c0_.date1, COALESCE(c0_.date2, CURRENT_DATE)) OVERLAPS ('2001-12-21', '2001-12-25') = 1",
25+
'checks overlapping with null handling false' => "SELECT c0_.date1 AS date1_0 FROM ContainsDates c0_ WHERE (c0_.date1, COALESCE(c0_.date2, CURRENT_DATE)) OVERLAPS ('2001-12-21', '2001-12-25') = 0",
2626
];
2727
}
2828

2929
protected function getDqlStatements(): array
3030
{
3131
return [
32-
\sprintf(
32+
'checks non-overlapping date ranges' => \sprintf(
3333
"SELECT e.date1 FROM %s e WHERE DATE_OVERLAPS(e.date1, e.date2, '2001-12-21', '2001-12-25') = 0",
3434
ContainsDates::class
3535
),
36-
\sprintf(
36+
'checks overlapping date ranges' => \sprintf(
3737
"SELECT e.date1 FROM %s e WHERE DATE_OVERLAPS(e.date1, e.date2, '2001-12-21', '2001-12-25') = TRUE",
3838
ContainsDates::class
3939
),
40-
\sprintf(
40+
'checks overlapping with null handling true' => \sprintf(
4141
"SELECT e.date1 FROM %s e WHERE DATE_OVERLAPS(e.date1, COALESCE(e.date2, CURRENT_DATE()), '2001-12-21', '2001-12-25') = 1",
4242
ContainsDates::class
4343
),
44-
\sprintf(
44+
'checks overlapping with null handling false' => \sprintf(
4545
"SELECT e.date1 FROM %s e WHERE DATE_OVERLAPS(e.date1, COALESCE(e.date2, CURRENT_DATE()), '2001-12-21', '2001-12-25') = FALSE",
4646
ContainsDates::class
4747
),

0 commit comments

Comments
 (0)