Files
redmine/redMCP/bin/test-query-normalizer.php
2026-04-25 04:12:01 +00:00

143 lines
5.7 KiB
PHP
Executable File

#!/usr/bin/env php
<?php
declare(strict_types=1);
use RedMCP\ListQueryNormalizer;
require __DIR__ . '/../vendor/autoload.php';
final class QueryNormalizerTest
{
private int $assertions = 0;
public function run(): void
{
$this->testDefaultPagingAndRawOverride();
$this->testIssueFriendlyFilters();
$this->testSortShortcutsAndStructuredSort();
$this->testDatePresetsAndRanges();
$this->testUserParams();
$this->testSearchParams();
fwrite(STDOUT, "OK {$this->assertions} assertions\n");
}
private function testDefaultPagingAndRawOverride(): void
{
$params = ListQueryNormalizer::listParams([
'limit' => 500,
'page' => 3,
'params' => ['limit' => 75],
]);
$this->assertSame(75, $params['limit'], 'raw params override normalized limit');
$this->assertSame(200, $params['offset'], 'page offset uses clamped normalized limit before raw override');
}
private function testIssueFriendlyFilters(): void
{
$filters = ListQueryNormalizer::issueFilters([
'project_id' => 'customer-service',
'status' => 'open',
'assigned_to_id' => 25,
'updated' => 'since 2026-04-01',
'filters' => ['assigned_to_id' => 'me'],
]);
$this->assertSame('customer-service', $filters['project_id'], 'project_id is copied');
$this->assertSame('open', $filters['status_id'], 'open status alias maps to Redmine open status');
$this->assertSame('me', $filters['assigned_to_id'], 'raw filters override normalized filters');
$this->assertSame('>=2026-04-01', $filters['updated_on'], 'since date maps to Redmine lower bound');
}
private function testSortShortcutsAndStructuredSort(): void
{
$newest = ListQueryNormalizer::listParams(['sort' => 'newest']);
$this->assertSame('updated_on:desc', $newest['sort'], 'newest shortcut sorts by updated_on descending');
$priority = ListQueryNormalizer::listParams(['sort' => 'priority']);
$this->assertSame('priority:desc,updated_on:desc', $priority['sort'], 'priority shortcut includes updated_on tie-breaker');
$structured = ListQueryNormalizer::listParams([
'sort' => [
['field' => 'created_on', 'direction' => 'desc'],
['field' => 'id', 'direction' => 'asc'],
],
]);
$this->assertSame('created_on:desc,id:asc', $structured['sort'], 'structured sort converts to Redmine sort string');
}
private function testDatePresetsAndRanges(): void
{
$clock = new DateTimeImmutable('2026-04-25 12:00:00', new DateTimeZone('UTC'));
$today = ListQueryNormalizer::issueFilters(['created' => 'today'], $clock);
$this->assertSame('2026-04-25', $today['created_on'], 'today maps to exact date');
$range = ListQueryNormalizer::issueFilters(['created' => '2026-04-01..2026-04-25'], $clock);
$this->assertSame('><2026-04-01|2026-04-25', $range['created_on'], 'range string maps to Redmine between syntax');
$objectRange = ListQueryNormalizer::issueFilters(['due' => ['from' => '2026-05-01', 'to' => '2026-05-31']], $clock);
$this->assertSame('><2026-05-01|2026-05-31', $objectRange['due_date'], 'object range maps to due_date');
$lastSeven = ListQueryNormalizer::issueFilters(['updated' => 'last 7 days'], $clock);
$this->assertSame('><2026-04-19|2026-04-25', $lastSeven['updated_on'], 'last N days includes today');
$freeText = ListQueryNormalizer::issueFilters(['created' => 'April 2 2026'], $clock);
$this->assertSame('2026-04-02', $freeText['created_on'], 'simple free text date parses');
}
private function testSearchParams(): void
{
$params = ListQueryNormalizer::searchParams([
'project_id' => 'customer-service',
'all_words' => true,
'titles_only' => false,
'open_issues' => true,
'sort' => 'oldest',
'page' => 2,
'params' => ['offset' => 5],
]);
$this->assertSame('customer-service', $params['project_id'], 'search project_id is copied');
$this->assertSame('1', $params['all_words'], 'true search flags map to Redmine 1');
$this->assertSame('0', $params['titles_only'], 'false search flags map to Redmine 0');
$this->assertSame('1', $params['open_issues'], 'open_issues flag maps to Redmine 1');
$this->assertSame('created_on:asc', $params['sort'], 'oldest shortcut maps to created_on ascending');
$this->assertSame(5, $params['offset'], 'raw search params override normalized paging');
}
private function testUserParams(): void
{
$params = ListQueryNormalizer::userParams([
'status' => 'active',
'name' => 'danny',
'group_id' => 4,
'sort' => 'oldest',
]);
$this->assertSame(1, $params['status'], 'active user status maps to Redmine active status id');
$this->assertSame('danny', $params['name'], 'user name filter is copied');
$this->assertSame(4, $params['group_id'], 'user group filter is copied');
$this->assertSame('created_on:asc', $params['sort'], 'user list accepts sort shortcuts');
}
/**
* @param mixed $expected
* @param mixed $actual
*/
private function assertSame($expected, $actual, string $message): void
{
$this->assertions++;
if ($expected === $actual) {
return;
}
fwrite(STDERR, "FAIL: {$message}\nExpected: " . var_export($expected, true) . "\nActual: " . var_export($actual, true) . "\n");
exit(1);
}
}
(new QueryNormalizerTest())->run();