Add unit tests for invoicing period pagination, normalization, and filtering logic
- Implemented `InvoicingPeriodPaginationTest` for testing period pagination modes, normalization of options, search functionality, and visibility filters. - Added comprehensive tests to validate scenarios such as active period views, exact counts, and customer-card level search. - Improved cURL timeout settings with `CURLOPT_CONNECTTIMEOUT` and `CURLOPT_TIMEOUT` adjustments. - Introduced and documented helper classes/methods for local caching, pagination response structure, and customer name retrieval.
This commit is contained in:
@@ -0,0 +1,388 @@
|
||||
<?php
|
||||
|
||||
app_require('classes/response.php');
|
||||
app_require('routes/InvoicingPeriodRoute.php');
|
||||
|
||||
use classes\response;
|
||||
use routes\InvoicingPeriodRoute;
|
||||
|
||||
function invoicing_period_pagination_invoke(string $method, array $args = []): mixed
|
||||
{
|
||||
$reflection = new ReflectionClass(InvoicingPeriodRoute::class);
|
||||
$target = $reflection->getMethod($method);
|
||||
$target->setAccessible(true);
|
||||
return $target->invokeArgs(null, $args);
|
||||
}
|
||||
|
||||
function invoicing_period_customer_card(
|
||||
int $customerNumber,
|
||||
string $customerName,
|
||||
array $transactions,
|
||||
bool $requiresAction = false,
|
||||
array $extra = []
|
||||
): array {
|
||||
return array_merge([
|
||||
'id' => $customerNumber,
|
||||
'customer_number' => $customerNumber,
|
||||
'customer_name' => $customerName,
|
||||
'requires_action' => $requiresAction,
|
||||
'transactions' => $transactions,
|
||||
'meta' => [],
|
||||
'queue' => [
|
||||
'has_active_job' => false,
|
||||
'statuses' => [],
|
||||
'invoice_collection_ids' => [],
|
||||
'is_action_blocked' => false,
|
||||
],
|
||||
'draft' => [
|
||||
'has_valid_draft' => false,
|
||||
'invoice_collection_ids' => [],
|
||||
'is_action_blocked' => false,
|
||||
],
|
||||
], $extra);
|
||||
}
|
||||
|
||||
function invoicing_period_transaction(array $overrides = []): array
|
||||
{
|
||||
return array_merge([
|
||||
'id' => 9001,
|
||||
'date' => '2026-04-10 12:00:00',
|
||||
'amount' => 125.5,
|
||||
'booked' => false,
|
||||
'department_id' => 1,
|
||||
'customer_number' => 1001,
|
||||
'reference' => 'REF-9001',
|
||||
'po' => 'PO-9001',
|
||||
'notes' => 'Gate note',
|
||||
'reg_1' => 'AB12345',
|
||||
'reg_2' => '',
|
||||
'reg_3' => '',
|
||||
'excluded' => false,
|
||||
'invoice_collection_id' => 3001,
|
||||
'queue_status' => null,
|
||||
'queue_job_id' => null,
|
||||
], $overrides);
|
||||
}
|
||||
|
||||
it('detects paginated period mode only when pagination parameters are present', function (): void {
|
||||
global $response;
|
||||
|
||||
$previousResponse = $GLOBALS['response'] ?? null;
|
||||
$previousGet = $_GET;
|
||||
$previousMethod = $_SERVER['REQUEST_METHOD'] ?? null;
|
||||
$response = new response();
|
||||
|
||||
try {
|
||||
$_SERVER['REQUEST_METHOD'] = 'GET';
|
||||
$_GET = [
|
||||
'dateFrom' => '2026-04-01',
|
||||
'dateTo' => '2026-04-30',
|
||||
];
|
||||
|
||||
expect(invoicing_period_pagination_invoke('getPeriodPaginationOptionsFromRequest'))->toBeNull();
|
||||
|
||||
$_GET['page'] = '2';
|
||||
expect(invoicing_period_pagination_invoke('getPeriodPaginationOptionsFromRequest'))->toMatchArray([
|
||||
'periodView' => 'all',
|
||||
'page' => 2,
|
||||
'limit' => 100,
|
||||
'search' => '',
|
||||
'includeRequiresAction' => true,
|
||||
'includeBooked' => true,
|
||||
]);
|
||||
} finally {
|
||||
$_GET = $previousGet;
|
||||
if ($previousMethod === null) {
|
||||
unset($_SERVER['REQUEST_METHOD']);
|
||||
} else {
|
||||
$_SERVER['REQUEST_METHOD'] = $previousMethod;
|
||||
}
|
||||
if ($previousResponse === null) {
|
||||
unset($GLOBALS['response']);
|
||||
} else {
|
||||
$response = $previousResponse;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
it('normalizes period pagination options and clamps invalid page and limit values', function (): void {
|
||||
$options = invoicing_period_pagination_invoke('normalizePeriodPaginationOptions', [[
|
||||
'periodView' => 'not-a-view',
|
||||
'page' => '-4',
|
||||
'limit' => '500',
|
||||
'search' => ' Nordic ',
|
||||
'includeRequiresAction' => '0',
|
||||
'includeBooked' => 'false',
|
||||
]]);
|
||||
|
||||
expect($options)->toBe([
|
||||
'periodView' => 'all',
|
||||
'page' => 1,
|
||||
'limit' => 500,
|
||||
'search' => 'Nordic',
|
||||
'includeRequiresAction' => false,
|
||||
'includeBooked' => false,
|
||||
]);
|
||||
|
||||
expect(invoicing_period_pagination_invoke('normalizePeriodPaginationOptions', [[
|
||||
'limit' => '900',
|
||||
]]))->toMatchArray([
|
||||
'limit' => 500,
|
||||
]);
|
||||
|
||||
expect(invoicing_period_pagination_invoke('normalizePeriodPaginationOptions', [[
|
||||
'limit' => 'all',
|
||||
]]))->toMatchArray([
|
||||
'limit' => 'all',
|
||||
]);
|
||||
|
||||
expect(invoicing_period_pagination_invoke('normalizePeriodPaginationOptions', [[
|
||||
'periodView' => 'invoice_per_order',
|
||||
'page' => '3',
|
||||
'limit' => '0',
|
||||
]]))->toMatchArray([
|
||||
'periodView' => 'invoice_per_order',
|
||||
'page' => 3,
|
||||
'limit' => 100,
|
||||
]);
|
||||
});
|
||||
|
||||
it('slices only the active period view and keeps exact full-result type counts', function (): void {
|
||||
$period = [
|
||||
'dateFrom' => '2026-04-01 00:00:00',
|
||||
'dateTo' => '2026-04-30 23:59:59',
|
||||
'types' => [
|
||||
'all' => [
|
||||
invoicing_period_customer_card(1001, 'Alpha Transport', [
|
||||
invoicing_period_transaction(['id' => 11, 'customer_number' => 1001, 'amount' => 50]),
|
||||
], false),
|
||||
invoicing_period_customer_card(1002, 'Beta Transport', [
|
||||
invoicing_period_transaction([
|
||||
'id' => 12,
|
||||
'customer_number' => 1002,
|
||||
'amount' => 75,
|
||||
'booked' => true,
|
||||
'excluded' => true,
|
||||
'reference' => 'REF-BETA',
|
||||
'po' => 'PO-BETA',
|
||||
'reg_1' => 'BB22222',
|
||||
]),
|
||||
], true),
|
||||
invoicing_period_customer_card(1003, 'Gamma Transport', [
|
||||
invoicing_period_transaction([
|
||||
'id' => 13,
|
||||
'customer_number' => 1003,
|
||||
'amount' => 125,
|
||||
'booked' => true,
|
||||
]),
|
||||
], false, [
|
||||
'draft' => [
|
||||
'has_valid_draft' => true,
|
||||
'invoice_collection_ids' => [3013],
|
||||
'is_action_blocked' => true,
|
||||
],
|
||||
]),
|
||||
],
|
||||
'fixed_pricing' => [
|
||||
invoicing_period_customer_card(1002, 'Beta Transport', [], true, [
|
||||
'meta' => [
|
||||
'fixed_pricing' => [
|
||||
'price' => 999,
|
||||
],
|
||||
],
|
||||
]),
|
||||
],
|
||||
'invoice_per_order' => [
|
||||
invoicing_period_customer_card(1001, 'Alpha Transport', [], true, [
|
||||
'flags' => [
|
||||
['source' => 'manual', 'status' => 'active'],
|
||||
],
|
||||
]),
|
||||
],
|
||||
],
|
||||
];
|
||||
|
||||
$result = invoicing_period_pagination_invoke('applyPeriodPagination', [$period, [
|
||||
'periodView' => 'all',
|
||||
'page' => 2,
|
||||
'limit' => 1,
|
||||
'search' => '',
|
||||
'includeRequiresAction' => true,
|
||||
'includeBooked' => true,
|
||||
]]);
|
||||
|
||||
expect($result['pagination'])->toMatchArray([
|
||||
'page' => 2,
|
||||
'per_page' => 1,
|
||||
'total' => 3,
|
||||
'search' => '',
|
||||
]);
|
||||
|
||||
expect($result['period']['types']['all'])->toHaveCount(1);
|
||||
expect($result['period']['types']['all'][0])->toMatchArray([
|
||||
'customer_number' => 1002,
|
||||
'customer_name' => 'Beta Transport',
|
||||
'requires_action' => true,
|
||||
'meta' => [
|
||||
'fixed_pricing' => [
|
||||
'price' => 999,
|
||||
],
|
||||
],
|
||||
]);
|
||||
expect($result['period']['types']['all'][0]['transactions'][0])->toMatchArray([
|
||||
'id' => 12,
|
||||
'amount' => 75,
|
||||
'booked' => true,
|
||||
'excluded' => true,
|
||||
'reference' => 'REF-BETA',
|
||||
'po' => 'PO-BETA',
|
||||
'reg_1' => 'BB22222',
|
||||
]);
|
||||
expect($result['period']['types']['fixed_pricing'])->toBe([]);
|
||||
expect($result['period']['type_counts']['all'])->toBe([
|
||||
'requires_action' => 1,
|
||||
'draft' => 1,
|
||||
'manual_flags' => 0,
|
||||
'automatic_flags' => 0,
|
||||
'completed' => 1,
|
||||
'total' => 3,
|
||||
]);
|
||||
expect($result['period']['type_totals']['all'])->toBe([
|
||||
'total' => 1174.0,
|
||||
'booked' => 125.0,
|
||||
'not_booked' => 1049.0,
|
||||
]);
|
||||
expect($result['period']['type_totals']['fixed_pricing'])->toBe([
|
||||
'total' => 999.0,
|
||||
'booked' => 0.0,
|
||||
'not_booked' => 999.0,
|
||||
]);
|
||||
expect($result['period']['type_counts']['invoice_per_order']['manual_flags'])->toBe(1);
|
||||
});
|
||||
|
||||
it('returns the entire active period view when the limit is all', function (): void {
|
||||
$period = [
|
||||
'dateFrom' => '2026-04-01 00:00:00',
|
||||
'dateTo' => '2026-04-30 23:59:59',
|
||||
'types' => [
|
||||
'all' => [
|
||||
invoicing_period_customer_card(1101, 'Alpha', [
|
||||
invoicing_period_transaction(['id' => 31, 'customer_number' => 1101]),
|
||||
]),
|
||||
invoicing_period_customer_card(1102, 'Beta', [
|
||||
invoicing_period_transaction(['id' => 32, 'customer_number' => 1102]),
|
||||
]),
|
||||
invoicing_period_customer_card(1103, 'Gamma', [
|
||||
invoicing_period_transaction(['id' => 33, 'customer_number' => 1103]),
|
||||
]),
|
||||
],
|
||||
],
|
||||
];
|
||||
|
||||
$result = invoicing_period_pagination_invoke('applyPeriodPagination', [$period, [
|
||||
'periodView' => 'all',
|
||||
'page' => 3,
|
||||
'limit' => 'all',
|
||||
'search' => '',
|
||||
'includeRequiresAction' => true,
|
||||
'includeBooked' => true,
|
||||
]]);
|
||||
|
||||
expect($result['pagination'])->toMatchArray([
|
||||
'page' => 1,
|
||||
'per_page' => 'all',
|
||||
'total' => 3,
|
||||
]);
|
||||
expect(array_column($result['period']['types']['all'], 'customer_number'))->toBe([1101, 1102, 1103]);
|
||||
});
|
||||
|
||||
it('searches customer fields and order fields at the customer-card level', function (): void {
|
||||
$period = [
|
||||
'dateFrom' => '2026-04-01 00:00:00',
|
||||
'dateTo' => '2026-04-30 23:59:59',
|
||||
'types' => [
|
||||
'all' => [
|
||||
invoicing_period_customer_card(2001, 'Solaris Fleet', [
|
||||
invoicing_period_transaction([
|
||||
'id' => 21,
|
||||
'customer_number' => 2001,
|
||||
'reference' => 'REF-KEEP',
|
||||
'po' => 'PO-KEEP',
|
||||
]),
|
||||
]),
|
||||
invoicing_period_customer_card(2002, 'Nordic Logistics', [
|
||||
invoicing_period_transaction([
|
||||
'id' => 22,
|
||||
'customer_number' => 2002,
|
||||
'reference' => 'MISS',
|
||||
'po' => 'PO-777',
|
||||
'notes' => 'Driver waits at gate',
|
||||
'reg_1' => 'CD33333',
|
||||
]),
|
||||
invoicing_period_transaction([
|
||||
'id' => 23,
|
||||
'customer_number' => 2002,
|
||||
'reference' => 'SECOND-LINE',
|
||||
]),
|
||||
]),
|
||||
],
|
||||
],
|
||||
];
|
||||
|
||||
$result = invoicing_period_pagination_invoke('applyPeriodPagination', [$period, [
|
||||
'periodView' => 'all',
|
||||
'page' => 1,
|
||||
'limit' => 25,
|
||||
'search' => 'po-777',
|
||||
'includeRequiresAction' => true,
|
||||
'includeBooked' => true,
|
||||
]]);
|
||||
|
||||
expect($result['pagination']['total'])->toBe(1);
|
||||
expect($result['period']['types']['all'])->toHaveCount(1);
|
||||
expect($result['period']['types']['all'][0]['customer_number'])->toBe(2002);
|
||||
expect($result['period']['types']['all'][0]['transactions'])->toHaveCount(2);
|
||||
|
||||
$registrationMatch = invoicing_period_pagination_invoke('applyPeriodPagination', [$period, [
|
||||
'periodView' => 'all',
|
||||
'page' => 1,
|
||||
'limit' => 25,
|
||||
'search' => 'cd33333',
|
||||
'includeRequiresAction' => true,
|
||||
'includeBooked' => true,
|
||||
]]);
|
||||
expect($registrationMatch['period']['types']['all'][0]['customer_name'])->toBe('Nordic Logistics');
|
||||
});
|
||||
|
||||
it('applies requires-action and booked visibility filters before counting and slicing', function (): void {
|
||||
$period = [
|
||||
'dateFrom' => '2026-04-01 00:00:00',
|
||||
'dateTo' => '2026-04-30 23:59:59',
|
||||
'types' => [
|
||||
'all' => [
|
||||
invoicing_period_customer_card(3001, 'Needs Action', [
|
||||
invoicing_period_transaction(['customer_number' => 3001, 'booked' => false]),
|
||||
], true),
|
||||
invoicing_period_customer_card(3002, 'Already Booked', [
|
||||
invoicing_period_transaction(['customer_number' => 3002, 'booked' => true]),
|
||||
], false),
|
||||
invoicing_period_customer_card(3003, 'Still Open', [
|
||||
invoicing_period_transaction(['customer_number' => 3003, 'booked' => false]),
|
||||
], false),
|
||||
],
|
||||
],
|
||||
];
|
||||
|
||||
$result = invoicing_period_pagination_invoke('applyPeriodPagination', [$period, [
|
||||
'periodView' => 'all',
|
||||
'page' => 1,
|
||||
'limit' => 25,
|
||||
'search' => '',
|
||||
'includeRequiresAction' => false,
|
||||
'includeBooked' => false,
|
||||
]]);
|
||||
|
||||
expect($result['pagination']['total'])->toBe(1);
|
||||
expect($result['period']['type_counts']['all']['total'])->toBe(1);
|
||||
expect($result['period']['types']['all'][0]['customer_number'])->toBe(3003);
|
||||
});
|
||||
Reference in New Issue
Block a user