Files
api/services/nginx/app/tests/Api/ReferenceSuggestionsApiTest.php
T
Jeppe Bundgaard 6d4066be1c Add unit tests for InvoicingPeriodDraftOverlay and reference suggestion logic, including fake DB integration and aggregation methods
- Implemented `InvoicingPeriodDraftOverlayTest` with coverage for blocking and permitting invoicing actions based on draft states, transactions, and metadata.
- Created `ReferenceSuggestionsApiTest` to validate ranked and filtered suggestions across bookings, orders, and vehicles with varied match relevance, context, and frequency.
- Added `order_reference_suggestions_service` class, including query methods, normalization utilities, and aggregation logic for reference suggestions.
- Enhanced query handling in `InvoicingPeriodDraftOverlayFakeDb` to validate SQL constraints and column cache resets in overlapping invoicing contexts.
2026-05-11 18:18:08 +02:00

209 lines
7.8 KiB
PHP

<?php
declare(strict_types=1);
usesApiSuite();
function reference_suggestion_by_reference(array $suggestions, string $reference): ?array
{
foreach ($suggestions as $suggestion) {
if (($suggestion['reference'] ?? null) === $reference) {
return $suggestion;
}
}
return null;
}
it('returns ranked POS reference suggestions from bookings, orders, and customer vehicles', function (): void {
api_test_covers('GET /orders/reference-suggestions', 'happy');
$department = api_fixtures()->createDepartment();
$customer = api_fixtures()->createUser(['display_name' => 'Reference Suggestion Customer']);
$cashier = api_fixtures()->createUser(['display_name' => 'Reference Suggestion Cashier']);
api_fixtures()->createOrderBooking([
'customer_number' => $customer['customer_number'],
'department' => $department['id'],
'reference' => 'REF-BOOKING',
'datetime' => '2026-05-13 09:00:00',
'created_at' => '2026-05-01 08:00:00',
'reg_1' => 'BOOK1',
]);
api_fixtures()->createOrder([
'customer_id' => $customer['customer_number'],
'cashier_id' => $cashier['id'],
'department_id' => $department['id'],
'reference' => 'REF-HISTORY',
'created_at' => '2026-05-10 10:00:00',
'reg_1' => 'HIST1',
]);
api_fixtures()->createVehicle([
'customer_id' => $customer['customer_number'],
'type' => 53,
'reg' => 'VEH1',
'reference' => 'REF-VEHICLE',
'created_at' => '2026-05-08 12:00:00',
]);
api_fixtures()->createOrderBooking([
'customer_number' => $customer['customer_number'],
'department' => $department['id'],
'reference' => 'REF-SHARED',
'datetime' => '2026-05-15 11:00:00',
'reg_1' => 'SHARED1',
]);
api_fixtures()->createOrder([
'customer_id' => $customer['customer_number'],
'cashier_id' => $cashier['id'],
'department_id' => $department['id'],
'reference' => 'REF-SHARED',
'created_at' => '2026-05-12 10:00:00',
'reg_1' => 'SHARED1',
]);
api_fixtures()->createVehicle([
'customer_id' => $customer['customer_number'],
'type' => 53,
'reg' => 'SHARED1',
'reference' => 'REF-SHARED',
'created_at' => '2026-05-09 12:00:00',
]);
$deletedOrder = api_fixtures()->createOrder([
'customer_id' => $customer['customer_number'],
'cashier_id' => $cashier['id'],
'department_id' => $department['id'],
'reference' => 'REF-DELETED',
]);
api_test_runtime()->db()
->query("UPDATE orders SET deleted_at = '2026-05-10 12:00:00' WHERE id = " . (int)$deletedOrder['id']);
api_fixtures()->createOrder([
'customer_id' => $customer['customer_number'],
'cashier_id' => $cashier['id'],
'department_id' => $department['id'],
'reference' => '',
]);
$session = api_fixtures()->createUserSession([
'list_orders',
'department_access_' . $department['id'],
]);
$response = api_client()->get('/orders/reference-suggestions?' . http_build_query([
'search' => 'REF',
'department_id' => $department['id'],
'customer_id' => $customer['customer_number'],
'reg_1' => 'SHARED1',
'limit' => 10,
]), $session['headers']);
$response
->assertStatus(200)
->assertEnvelope()
->assertSuccess();
$suggestions = $response->data();
expect($suggestions)->toBeArray();
$booking = reference_suggestion_by_reference($suggestions, 'REF-BOOKING');
$history = reference_suggestion_by_reference($suggestions, 'REF-HISTORY');
$vehicle = reference_suggestion_by_reference($suggestions, 'REF-VEHICLE');
$shared = reference_suggestion_by_reference($suggestions, 'REF-SHARED');
expect($booking['source'] ?? null)->toBe('booking');
expect($booking['source_created_at'] ?? null)->toBe('2026-05-13 09:00:00');
expect($history['source'] ?? null)->toBe('order');
expect($vehicle['source'] ?? null)->toBe('vehicle');
expect($shared['source'] ?? null)->toBe('booking');
expect($shared['section'] ?? null)->toBe('this_vehicle');
expect($booking['section'] ?? null)->toBe('other_customer_vehicle');
expect($history['section'] ?? null)->toBe('other_customer_vehicle');
expect($vehicle['section'] ?? null)->toBe('other_customer_vehicle');
expect($shared['usage_count'] ?? null)->toBe(3);
expect($shared['last_used_at'] ?? null)->toBe('2026-05-15 11:00:00');
expect(reference_suggestion_by_reference($suggestions, 'REF-DELETED'))->toBeNull();
expect(reference_suggestion_by_reference($suggestions, ''))->toBeNull();
});
it('orders reference suggestions by match relevance before context and frequency', function (): void {
$department = api_fixtures()->createDepartment();
$customer = api_fixtures()->createUser(['display_name' => 'Reference Ranking Customer']);
$cashier = api_fixtures()->createUser(['display_name' => 'Reference Ranking Cashier']);
api_fixtures()->createOrder([
'customer_id' => $customer['customer_number'],
'cashier_id' => $cashier['id'],
'department_id' => $department['id'],
'reference' => 'ABC',
'created_at' => '2026-05-01 08:00:00',
'reg_1' => 'OTHER1',
]);
api_fixtures()->createOrderBooking([
'customer_number' => $customer['customer_number'],
'department' => $department['id'],
'reference' => 'ABC-PREFIX',
'datetime' => '2026-05-16 08:00:00',
'reg_1' => 'MATCH1',
]);
api_fixtures()->createOrder([
'customer_id' => $customer['customer_number'],
'cashier_id' => $cashier['id'],
'department_id' => $department['id'],
'reference' => 'X-ABC-CONTAINS',
'created_at' => '2026-05-17 08:00:00',
'reg_1' => 'MATCH1',
]);
$session = api_fixtures()->createUserSession([
'list_orders',
'department_access_' . $department['id'],
]);
$response = api_client()->get('/orders/reference-suggestions?' . http_build_query([
'search' => 'ABC',
'department_id' => $department['id'],
'customer_id' => $customer['customer_number'],
'reg_1' => 'MATCH1',
]), $session['headers']);
$response
->assertStatus(200)
->assertEnvelope()
->assertSuccess();
$references = array_map(
static fn(array $suggestion): string => (string)($suggestion['reference'] ?? ''),
is_array($response->data()) ? $response->data() : []
);
expect(array_slice($references, 0, 3))->toBe(['ABC', 'ABC-PREFIX', 'X-ABC-CONTAINS']);
});
it('enforces authentication, list permission, and department access for reference suggestions', function (): void {
api_test_covers('GET /orders/reference-suggestions', 'auth');
$department = api_fixtures()->createDepartment();
api_client()->get('/orders/reference-suggestions?department_id=' . $department['id'])
->assertStatus(400)
->assertEnvelope()
->assertSuccess(false)
->assertMessage('Invalid session');
$missingListPermission = api_fixtures()->createUserSession([
'department_access_' . $department['id'],
]);
api_client()->get('/orders/reference-suggestions?department_id=' . $department['id'], $missingListPermission['headers'])
->assertStatus(403)
->assertEnvelope()
->assertSuccess(false)
->assertMissingPermissions(['list_orders']);
$missingDepartmentAccess = api_fixtures()->createUserSession(['list_orders']);
api_client()->get('/orders/reference-suggestions?department_id=' . $department['id'], $missingDepartmentAccess['headers'])
->assertStatus(403)
->assertEnvelope()
->assertSuccess(false)
->assertMissingPermissions(['department_access_' . $department['id']]);
});