Skip to content

Commit 6563601

Browse files
committed
Add anthropic test for the Chat capability
1 parent 2aed53b commit 6563601

File tree

1 file changed

+175
-0
lines changed

1 file changed

+175
-0
lines changed
Lines changed: 175 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,175 @@
1+
<?php
2+
3+
use Joomla\AI\Exception\AuthenticationException;
4+
use Joomla\AI\Exception\ProviderException;
5+
use Joomla\AI\Exception\RateLimitException;
6+
use Joomla\AI\Exception\UnserializableResponseException;
7+
use Joomla\AI\Provider\AnthropicProvider;
8+
use Joomla\Http\Http;
9+
use Joomla\Http\HttpFactory;
10+
use Joomla\Http\Response as HttpResponse;
11+
use PHPUnit\Framework\TestCase;
12+
13+
class ChatTest extends TestCase
14+
{
15+
public function testChatReturnsSuccessfulResponse(): void
16+
{
17+
$httpFactoryMock = $this->createMock(HttpFactory::class);
18+
$httpClientMock = $this->createMock(Http::class);
19+
$httpFactoryMock->method('getHttp')->with([])->willReturn($httpClientMock);
20+
21+
$chatResponse = $this->createJsonResponse([
22+
'id' => 'msg_123',
23+
'type' => 'message',
24+
'role' => 'assistant',
25+
'model' => 'claude-3-haiku-20240307',
26+
'content' => [
27+
['type' => 'text', 'text' => 'Hello from Claude!'],
28+
],
29+
'usage' => [
30+
'input_tokens' => 12,
31+
'output_tokens' => 9,
32+
],
33+
'stop_reason' => 'end_turn',
34+
'stop_sequence' => null,
35+
]);
36+
37+
$httpClientMock->expects($this->once())->method('post')->with(
38+
'https://api.anthropic.com/v1/messages',
39+
$this->callback(function ($payload) {
40+
$decoded = json_decode($payload, true);
41+
$this->assertSame([['role' => 'user', 'content' => 'Hello there']], $decoded['messages']);
42+
return true;
43+
}),
44+
$this->callback(function ($headers) {
45+
$this->assertArrayHasKey('x-api-key', $headers);
46+
$this->assertSame('test-api-key', $headers['x-api-key']);
47+
return true;
48+
}),
49+
null
50+
)->willReturn($chatResponse);
51+
52+
$provider = new AnthropicProvider(['api_key' => 'test-api-key'], $httpFactoryMock);
53+
$response = $provider->chat('Hello there');
54+
55+
$this->assertSame(200, $response->getStatusCode());
56+
$this->assertSame('Hello from Claude!', $response->getContent());
57+
58+
$metadata = $response->getMetadata();
59+
$this->assertSame('claude-3-haiku-20240307', $metadata['model']);
60+
$this->assertSame('end_turn', $metadata['stop_reason']);
61+
$this->assertSame('Anthropic', $response->getProvider());
62+
}
63+
64+
public function testChatStatusCodeReflectsMaxTokensStopReason(): void
65+
{
66+
$provider = $this->createProviderWithResponses([
67+
$this->createJsonResponse([
68+
'id' => 'msg_limit',
69+
'type' => 'message',
70+
'role' => 'assistant',
71+
'model' => 'claude-3-sonnet',
72+
'content' => [['type' => 'text', 'text' => 'Truncated reply']],
73+
'usage' => ['input_tokens' => 100, 'output_tokens' => 200],
74+
'stop_reason' => 'max_tokens',
75+
'stop_sequence' => null,
76+
]),
77+
]);
78+
79+
$response = $provider->chat('Continue until you hit the limit.');
80+
81+
$this->assertSame(429, $response->getStatusCode());
82+
$metadata = $response->getMetadata();
83+
$this->assertSame('max_tokens', $metadata['stop_reason']);
84+
}
85+
86+
public function testChatThrowsProviderExceptionWhenErrorReturned(): void
87+
{
88+
$provider = $this->createProviderWithResponses([
89+
$this->createJsonResponse([
90+
'error' => [
91+
'message' => 'Model overloaded',
92+
'type' => 'overloaded_error',
93+
],
94+
]),
95+
]);
96+
97+
$this->expectException(ProviderException::class);
98+
$this->expectExceptionMessage('Model overloaded');
99+
100+
$provider->chat('Hello?');
101+
}
102+
103+
public function testChatThrowsProviderExceptionOnHttpFailure(): void
104+
{
105+
$provider = $this->createProviderWithResponses([
106+
$this->createJsonResponse([
107+
'message' => 'Internal server error',
108+
], 500),
109+
]);
110+
111+
$this->expectException(ProviderException::class);
112+
$this->expectExceptionMessage('Internal server error');
113+
114+
$provider->chat('Trigger failure');
115+
}
116+
117+
public function testChatThrowsRateLimitExceptionWhenRateLimited(): void
118+
{
119+
$provider = $this->createProviderWithResponses([
120+
$this->createJsonResponse([
121+
'message' => 'Rate limit exceeded',
122+
], 429),
123+
]);
124+
125+
$this->expectException(RateLimitException::class);
126+
$this->expectExceptionMessage('Rate limit exceeded');
127+
128+
$provider->chat('Spam request');
129+
}
130+
131+
public function testChatThrowsAuthenticationExceptionWhenApiKeyMissing(): void
132+
{
133+
$httpFactoryMock = $this->createMock(HttpFactory::class);
134+
135+
$provider = new AnthropicProvider([], $httpFactoryMock);
136+
137+
$this->expectException(AuthenticationException::class);
138+
$this->expectExceptionMessage('Anthropic API key not configured. Set ANTHROPIC_API_KEY environment variable or provide api_key option.');
139+
140+
$provider->chat('Hi');
141+
}
142+
143+
public function testChatThrowsUnserializableResponseExceptionForInvalidJson(): void
144+
{
145+
$invalidJsonResponse = new HttpResponse('php://memory', 200, ['Content-Type' => 'application/json']);
146+
$invalidJsonResponse->getBody()->write('{invalid');
147+
148+
$provider = $this->createProviderWithResponses([$invalidJsonResponse]);
149+
150+
$this->expectException(UnserializableResponseException::class);
151+
$this->expectExceptionMessage('Syntax error');
152+
153+
$provider->chat('Return malformed payload');
154+
}
155+
156+
private function createProviderWithResponses(array $responses): AnthropicProvider
157+
{
158+
$httpFactoryMock = $this->createMock(HttpFactory::class);
159+
$httpClientMock = $this->createMock(Http::class);
160+
161+
$httpFactoryMock->method('getHttp')->with([])->willReturn($httpClientMock);
162+
163+
$httpClientMock->method('post')->willReturnOnConsecutiveCalls(...$responses);
164+
165+
return new AnthropicProvider(['api_key' => 'test-api-key'], $httpFactoryMock);
166+
}
167+
168+
private function createJsonResponse(array $payload, int $status = 200): HttpResponse
169+
{
170+
$response = new HttpResponse('php://memory', $status, ['Content-Type' => 'application/json']);
171+
$response->getBody()->write(json_encode($payload));
172+
173+
return $response;
174+
}
175+
}

0 commit comments

Comments
 (0)