Files
TicketAssistent/tests/Unit/AIClassifierServiceTest.php
SitiWeb c94d3f85e8 Add unit and feature tests for ticket processing and article management
- Implement Fake repositories and services for testing purposes.
- Create tests for Article API including creation, validation, and listing.
- Develop ProcessTicketJobFlowTest to validate ticket processing logic.
- Add QuickReplyAdminTest for creating and updating quick replies.
- Implement TicketAndArticleModelTest to ensure proper cascading deletes and credential encryption.
- Create TicketIngestionTest for ticket creation and job dispatching.
- Add TicketShowPageTest to verify rendering of quick replies and tool calls.
- Implement unit tests for ClassifierPromptBuilder, EmbeddingService, LlmJsonDecoder, QuickReplyResolver, SupportReplyService, TicketResultPayloadBuilder, TicketToolCallService, and ToolCallRequestValidator.
2026-04-30 02:10:15 +02:00

190 lines
5.7 KiB
PHP

<?php
namespace Tests\Unit;
use App\DTOs\ArticleCandidateDTO;
use App\Services\AIClassifierService;
use App\Services\AppSettingsService;
use App\Services\ClassifierPromptBuilder;
use App\Services\Llm\LlmClientInterface;
use App\Services\LlmJsonDecoder;
use App\Services\ToolCallRequestValidator;
use Tests\TestCase;
class AIClassifierServiceTest extends TestCase
{
public function test_it_returns_validated_domain_tool_call_from_llm_json(): void
{
$client = new class implements LlmClientInterface
{
public string $prompt = '';
public function embed(string $text): array
{
return [];
}
public function generate(string $prompt, array $options = []): string
{
$this->prompt = $prompt;
return json_encode([
'article_id' => 42,
'confidence' => 0.91,
'explanation' => 'Past bij domeininformatie.',
'tool_call' => [
'action' => 'domain_inf',
'parameters' => ['sld' => 'Example', 'tld' => 'NL'],
'reason' => 'Domeinstatus is nodig.',
],
]);
}
};
$service = new AIClassifierService(
$client,
$this->fakeSettings(),
new ClassifierPromptBuilder,
new LlmJsonDecoder,
new ToolCallRequestValidator
);
$result = $service->rank('Hoe staat example.nl ingesteld?', [
new ArticleCandidateDTO(
articleId: 42,
title: 'Domein controleren',
content: 'Controleer domeininformatie.',
distance: 0.12,
note: 'Gebruik domain_inf wanneer een volledig domein genoemd wordt.',
allowedActions: ['domain_inf'],
),
]);
$this->assertSame(42, $result->articleId);
$this->assertSame([
'action' => 'domain_inf',
'parameters' => ['sld' => 'example', 'tld' => 'nl'],
'reason' => 'Domeinstatus is nodig.',
], $result->toolCall);
$this->assertStringContainsString('Allowed actions: ["domain_inf"]', $client->prompt);
$this->assertStringContainsString('Internal note for support assistant', $client->prompt);
}
public function test_it_falls_back_when_ranking_disabled(): void
{
config()->set('services.llm.ranking_enabled', false);
$client = new class implements LlmClientInterface
{
public function embed(string $text): array
{
return [];
}
public function generate(string $prompt, array $options = []): string
{
return '{}';
}
};
$service = new AIClassifierService(
$client,
$this->fakeSettings(),
new ClassifierPromptBuilder,
new LlmJsonDecoder,
new ToolCallRequestValidator
);
$result = $service->rank('vraag', [$this->candidate(15)]);
$this->assertSame(15, $result->articleId);
$this->assertSame(0.2, $result->confidence);
}
public function test_it_falls_back_when_llm_json_is_invalid(): void
{
config()->set('services.llm.ranking_enabled', true);
$client = new class implements LlmClientInterface
{
public function embed(string $text): array
{
return [];
}
public function generate(string $prompt, array $options = []): string
{
return 'not-json';
}
};
$service = new AIClassifierService(
$client,
$this->fakeSettings(),
new ClassifierPromptBuilder,
new LlmJsonDecoder,
new ToolCallRequestValidator
);
$result = $service->rank('vraag', [$this->candidate(21)]);
$this->assertSame(21, $result->articleId);
$this->assertStringContainsString('invalid JSON', $result->explanation);
}
public function test_it_falls_back_when_article_id_not_in_candidates(): void
{
$client = new class implements LlmClientInterface
{
public function embed(string $text): array
{
return [];
}
public function generate(string $prompt, array $options = []): string
{
return json_encode([
'article_id' => 999,
'confidence' => 0.9,
'explanation' => 'x',
]);
}
};
$service = new AIClassifierService(
$client,
$this->fakeSettings(),
new ClassifierPromptBuilder,
new LlmJsonDecoder,
new ToolCallRequestValidator
);
$result = $service->rank('vraag', [$this->candidate(33)]);
$this->assertSame(33, $result->articleId);
$this->assertStringContainsString('schema invalid', $result->explanation);
}
private function fakeSettings(): AppSettingsService
{
return new class extends AppSettingsService
{
public function getPrompt(string $key, ?string $default = null): ?string
{
return 'Select best article.';
}
public function get(string $key, ?string $default = null): ?string
{
return $default;
}
};
}
private function candidate(int $id): ArticleCandidateDTO
{
return new ArticleCandidateDTO(
articleId: $id,
title: 'A',
content: 'B',
distance: 0.2
);
}
}