Files
api/services/nginx/app/tests/Unit/XLVask/XLVaskAutomationServiceTest.php
T

196 lines
8.2 KiB
PHP

<?php
use classes\xlvask_automation_service;
require_once WD . '/classes/xlvask_automation_service.php';
it('normalizes registrations for XL Vask automation signatures', function (): void {
expect(xlvask_automation_service::normalizeRegistrationForAutomation(' ec 21-233 '))
->toBe('EC21233');
});
it('builds stable XL Vask automation item signatures', function (): void {
$items = [
['product_id' => 20, 'quantity' => 1, 'price' => 275],
['product_id' => 10, 'quantity' => 2, 'price' => 649],
['product_id' => 20, 'quantity' => 1, 'price' => 0],
];
expect(xlvask_automation_service::itemSignaturePartsForAutomation($items))
->toBe([
'10:2:649',
'20:1:0',
'20:1:275',
]);
});
it('identifies strict price agreement matches by product, quantity, and total', function (): void {
$usageItems = [
['product_id' => 10, 'quantity' => 1, 'price' => 519],
['product_id' => 24, 'quantity' => 1, 'price' => 39],
['product_id' => 21, 'quantity' => 1, 'price' => 79],
];
$orderItems = [
['product_id' => 21, 'quantity' => 1, 'price' => 79],
['product_id' => 10, 'quantity' => 1, 'price' => 519],
['product_id' => 24, 'quantity' => 1, 'price' => 39],
];
expect(xlvask_automation_service::isExactItemMatchForAutomation($usageItems, $orderItems))
->toBeTrue();
});
it('rejects price agreement automation when product lines differ despite equal total', function (): void {
$usageItems = [
['product_id' => 10, 'quantity' => 1, 'price' => 519],
['product_id' => 24, 'quantity' => 1, 'price' => 39],
];
$orderItems = [
['product_id' => 10, 'quantity' => 1, 'price' => 519],
['product_id' => 50, 'quantity' => 1, 'price' => 39],
];
expect(xlvask_automation_service::itemsTotalForAutomation($usageItems))
->toBe(xlvask_automation_service::itemsTotalForAutomation($orderItems))
->and(xlvask_automation_service::isExactItemMatchForAutomation($usageItems, $orderItems))
->toBeFalse();
});
it('normalizes persisted XL Vask usage-log rows before helper hydration', function (): void {
$row = xlvask_automation_service::normalizeUsageLogRowForAutomation([
'id' => 47086,
'WashId' => 'cc1eabc1-b4e1-425b-ad7c-dc68f8c97ceb',
'WashItems' => '[{"OriginalProductName":"Bus","Count":1}]',
]);
expect($row)
->not->toHaveKey('id')
->and($row['WashItems'])->toBe([
[
'OriginalProductName' => 'Bus',
'Count' => 1,
],
]);
});
it('builds stable OpenAI cache keys for identical automation input', function (): void {
$prompt = 'Prompt';
$schemaName = 'xlvask_automation';
$schema = [
'required' => ['action'],
'properties' => [
'confidence' => ['type' => 'number'],
'action' => ['type' => 'string'],
],
];
$schemaWithDifferentKeyOrder = [
'properties' => [
'action' => ['type' => 'string'],
'confidence' => ['type' => 'number'],
],
'required' => ['action'],
];
$payloadA = [
'usage_log' => [
'registration' => 'AB12345',
'creation_allowed' => true,
],
'candidate_orders' => [
['id' => 10, 'items' => [['product_id' => 1, 'quantity' => 1, 'price' => 100]]],
],
];
$payloadB = [
'candidate_orders' => [
['items' => [['price' => 100, 'quantity' => 1, 'product_id' => 1]], 'id' => 10],
],
'usage_log' => [
'creation_allowed' => true,
'registration' => 'AB12345',
],
];
expect(xlvask_automation_service::openAiCacheKeyForAutomation($schemaName, $prompt, $payloadA, $schema, 0.1))
->toBe(xlvask_automation_service::openAiCacheKeyForAutomation($schemaName, $prompt, $payloadB, $schemaWithDifferentKeyOrder, 0.1));
});
it('changes OpenAI cache keys when automation eligibility input changes', function (): void {
$schema = ['type' => 'object'];
$newerWashPayload = ['usage_log' => ['creation_allowed' => false, 'age_bucket' => 'newer_than_6_hours']];
$olderWashPayload = ['usage_log' => ['creation_allowed' => true, 'age_bucket' => 'older_than_6_hours']];
expect(xlvask_automation_service::openAiCacheKeyForAutomation('xlvask_automation', 'Prompt', $newerWashPayload, $schema, 0.1))
->not->toBe(xlvask_automation_service::openAiCacheKeyForAutomation('xlvask_automation', 'Prompt', $olderWashPayload, $schema, 0.1));
});
it('declares a persistent OpenAI cache table for XL Vask automation', function (): void {
$bootstrapContent = file_get_contents(WD . '/classes/xlvask_usage_logs_schema_bootstrap.php');
expect($bootstrapContent)
->toContain('xlvask_automation_openai_cache')
->toContain('UNIQUE KEY `uniq_xlvask_openai_cache_key` (`cache_key`)');
});
it('declares cached amount summary columns for XL Vask usage logs', function (): void {
$bootstrapContent = file_get_contents(WD . '/classes/xlvask_usage_logs_schema_bootstrap.php');
expect($bootstrapContent)
->toContain('cached_total_net_amount')
->toContain('cached_primary_product_name')
->toContain('cached_amount_at');
});
it('keeps automatic XL Vask execution scoped to exact attachments', function (): void {
$serviceContent = file_get_contents(WD . '/classes/xlvask_automation_service.php');
expect($serviceContent)
->toContain('&& $this->isExactAttachSuggestionForContext($suggestion, $context)')
->toContain("\$candidateOrderJson = \$suggestion['candidate_order_json'] ?? null;")
->toContain("return 'Automatisk accepteret: Prisoverensstemmelse.';");
});
it('does not automatically create XL Vask orders', function (): void {
$serviceContent = file_get_contents(WD . '/classes/xlvask_automation_service.php');
expect($serviceContent)
->toContain('if ($action === self::ACTION_CREATE) {')
->toContain('return false;');
});
it('scores same-day orders with matching XL Vask products and extra add-ons as attach suggestions', function (): void {
$usageItems = [
['product_id' => 10, 'quantity' => 1, 'price' => 519, 'product' => ['name' => 'Forvogn']],
['product_id' => 24, 'quantity' => 1, 'price' => 39, 'product' => ['name' => 'Spot Free- Lastbil']],
['product_id' => 21, 'quantity' => 1, 'price' => 79, 'product' => ['name' => 'Undervognskyl pr. Enhed']],
['product_id' => 99, 'quantity' => 8, 'price' => 0, 'product' => ['name' => 'Halleje']],
];
$orderItems = [
['product_id' => 10, 'quantity' => 1, 'price' => 519, 'product' => ['name' => 'Forvogn']],
['product_id' => 50, 'quantity' => 1, 'price' => 319, 'product' => ['name' => 'Indvendig vask Forvogn']],
['product_id' => 21, 'quantity' => 1, 'price' => 79, 'product' => ['name' => 'Undervognskyl pr. Enhed']],
['product_id' => 24, 'quantity' => 1, 'price' => 39, 'product' => ['name' => 'Spot Free- Lastbil']],
];
$score = xlvask_automation_service::scoreItemMatchForAutomation($usageItems, $orderItems);
expect($score['source'])
->toBe('fuzzy')
->and($score['confidence'])->toBeGreaterThanOrEqual(0.70)
->and($score['confidence'])->toBeLessThan(0.92)
->and($score['reason'])->toContain('ekstra ydelser');
});
it('does not score an order with only the primary product as a matching add-on attachment', function (): void {
$usageItems = [
['product_id' => 10, 'quantity' => 1, 'price' => 519, 'product' => ['name' => 'Forvogn']],
['product_id' => 24, 'quantity' => 1, 'price' => 39, 'product' => ['name' => 'Spot Free- Lastbil']],
['product_id' => 21, 'quantity' => 1, 'price' => 79, 'product' => ['name' => 'Undervognskyl pr. Enhed']],
];
$orderItems = [
['product_id' => 10, 'quantity' => 1, 'price' => 519, 'product' => ['name' => 'Forvogn']],
['product_id' => 50, 'quantity' => 1, 'price' => 319, 'product' => ['name' => 'Indvendig vask Forvogn']],
];
expect(xlvask_automation_service::scoreItemMatchForAutomation($usageItems, $orderItems)['confidence'])
->toBe(0.0);
});