Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
195 changes: 195 additions & 0 deletions modules/backend/tests/widgets/FilterWidgetTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
use Backend\Widgets\Filter;
use ApplicationException;
use Backend\Models\User;
use Carbon\Carbon;
use ReflectionMethod;

class FilterWidgetTest extends PluginTestCase
{
Expand Down Expand Up @@ -140,4 +142,197 @@ protected function restrictedFilterFixture(bool $singlePermission = false)
]
]);
}

/**
* Test that when only start date is provided, end date defaults to 2037-12-31 23:59:59
* This ensures the date is within MySQL TIMESTAMP limits (max: 2038-01-19 03:14:07)
*/
public function testDateRangeFilterWithOnlyStartDate()
{
$filter = new Filter(null, [
'model' => new User,
'arrayName' => 'array',
'scopes' => [
'date_range' => [
'type' => 'daterange',
'label' => 'Date Range'
]
]
]);

$reflection = new ReflectionMethod($filter, 'datesFromAjax');
$reflection->setAccessible(true);

// Simulate AJAX data with only start date (end date is empty)
$ajaxDates = ['2026-01-01 00:00:00', ''];
$result = $reflection->invoke($filter, $ajaxDates);

$this->assertCount(2, $result);
$this->assertInstanceOf(Carbon::class, $result[0]);
$this->assertInstanceOf(Carbon::class, $result[1]);

// Start date should be as provided
$this->assertEquals('2026-01-01 00:00:00', $result[0]->format('Y-m-d H:i:s'));

// End date should default to 2037-12-31 23:59:59 (not 2999-12-31)
$this->assertEquals('2037-12-31 23:59:59', $result[1]->format('Y-m-d H:i:s'));

// Verify it's within MySQL TIMESTAMP limits
$mysqlMaxTimestamp = Carbon::createFromTimestamp(2147483647); // 2038-01-19 03:14:07
$this->assertTrue($result[1]->lt($mysqlMaxTimestamp), 'End date must be less than MySQL TIMESTAMP maximum');
}

/**
* Test that when only end date is provided, start date defaults to 0000-01-01 00:00:00
*/
public function testDateRangeFilterWithOnlyEndDate()
{
$filter = new Filter(null, [
'model' => new User,
'arrayName' => 'array',
'scopes' => [
'date_range' => [
'type' => 'daterange',
'label' => 'Date Range'
]
]
]);

$reflection = new ReflectionMethod($filter, 'datesFromAjax');
$reflection->setAccessible(true);

// Simulate AJAX data with only end date (start date is empty)
$ajaxDates = ['', '2026-12-31 23:59:59'];
$result = $reflection->invoke($filter, $ajaxDates);

$this->assertCount(2, $result);
$this->assertInstanceOf(Carbon::class, $result[0]);
$this->assertInstanceOf(Carbon::class, $result[1]);

// Start date should default to 0000-01-01 00:00:00
$this->assertEquals('0000-01-01 00:00:00', $result[0]->format('Y-m-d H:i:s'));

// End date should be as provided
$this->assertEquals('2026-12-31 23:59:59', $result[1]->format('Y-m-d H:i:s'));
}

/**
* Test that when both dates are provided, they are used as-is
*/
public function testDateRangeFilterWithBothDates()
{
$filter = new Filter(null, [
'model' => new User,
'arrayName' => 'array',
'scopes' => [
'date_range' => [
'type' => 'daterange',
'label' => 'Date Range'
]
]
]);

$reflection = new ReflectionMethod($filter, 'datesFromAjax');
$reflection->setAccessible(true);

// Simulate AJAX data with both dates provided
$ajaxDates = ['2026-01-01 00:00:00', '2026-12-31 23:59:59'];
$result = $reflection->invoke($filter, $ajaxDates);

$this->assertCount(2, $result);
$this->assertInstanceOf(Carbon::class, $result[0]);
$this->assertInstanceOf(Carbon::class, $result[1]);

$this->assertEquals('2026-01-01 00:00:00', $result[0]->format('Y-m-d H:i:s'));
$this->assertEquals('2026-12-31 23:59:59', $result[1]->format('Y-m-d H:i:s'));
}

/**
* Test that the default end date (2037-12-31 23:59:59) is within MySQL TIMESTAMP limits
*/
public function testDefaultEndDateWithinMySQLTimestampLimits()
{
$filter = new Filter(null, [
'model' => new User,
'arrayName' => 'array',
'scopes' => [
'date_range' => [
'type' => 'daterange',
'label' => 'Date Range'
]
]
]);

$reflection = new ReflectionMethod($filter, 'datesFromAjax');
$reflection->setAccessible(true);

// Test with empty end date
$ajaxDates = ['2026-01-01 00:00:00', ''];
$result = $reflection->invoke($filter, $ajaxDates);

$defaultEndDate = $result[1];

// MySQL TIMESTAMP maximum: 2038-01-19 03:14:07 (Unix timestamp: 2147483647)
$mysqlMaxTimestamp = Carbon::createFromTimestamp(2147483647);

// Verify default end date is less than MySQL maximum
$this->assertTrue(
$defaultEndDate->lt($mysqlMaxTimestamp),
'Default end date (2037-12-31 23:59:59) must be less than MySQL TIMESTAMP maximum (2038-01-19 03:14:07)'
);

// Verify it's exactly what we expect
$this->assertEquals('2037-12-31 23:59:59', $defaultEndDate->format('Y-m-d H:i:s'));
}

/**
* Test that null input returns empty array
*/
public function testDateRangeFilterWithNullInput()
{
$filter = new Filter(null, [
'model' => new User,
'arrayName' => 'array',
'scopes' => [
'date_range' => [
'type' => 'daterange',
'label' => 'Date Range'
]
]
]);

$reflection = new ReflectionMethod($filter, 'datesFromAjax');
$reflection->setAccessible(true);

$result = $reflection->invoke($filter, null);
$this->assertIsArray($result);
$this->assertEmpty($result);
}

/**
* Test that invalid date format returns empty array
*/
public function testDateRangeFilterWithInvalidDateFormat()
{
$filter = new Filter(null, [
'model' => new User,
'arrayName' => 'array',
'scopes' => [
'date_range' => [
'type' => 'daterange',
'label' => 'Date Range'
]
]
]);

$reflection = new ReflectionMethod($filter, 'datesFromAjax');
$reflection->setAccessible(true);

// Invalid date format
$ajaxDates = ['invalid-date', '2026-12-31'];
$result = $reflection->invoke($filter, $ajaxDates);

$this->assertIsArray($result);
$this->assertEmpty($result);
}
}
4 changes: 2 additions & 2 deletions modules/backend/widgets/Filter.php
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ public function renderScopeElement($scope)
$params['after'] = null;
}

if (strcasecmp($before, '2999-12-31 23:59:59') < 0) {
if (strcasecmp($before, '2037-12-31 23:59:59') < 0) {
$params['beforeStr'] = Backend::dateTime($scope->value[1], ['formatAlias' => 'dateMin']);
$params['before'] = $before;
}
Expand Down Expand Up @@ -1072,7 +1072,7 @@ protected function datesFromAjax($ajaxDates)
if ($i == 0) {
$dates[] = Carbon::createFromFormat('Y-m-d H:i:s', '0000-01-01 00:00:00');
} else {
$dates[] = Carbon::createFromFormat('Y-m-d H:i:s', '2999-12-31 23:59:59');
$dates[] = Carbon::createFromFormat('Y-m-d H:i:s', '2037-12-31 23:59:59');
}
} else {
$dates = [];
Expand Down