provider = new AnthropicProvider('test-api-key'); } public function test_builds_request_correctly_with_system_message(): void { $messages = [ ['role' => 'system', 'content' => 'You are a helpful assistant'], ['role' => 'user', 'content' => 'Hello'] ]; $options = [ 'model' => 'claude-sonnet-4', 'temperature' => 0.7, 'max_tokens' => 2000 ]; $reflection = new \ReflectionClass($this->provider); $method = $reflection->getMethod('buildRequest'); $method->setAccessible(true); $result = $method->invoke($this->provider, $messages, $options); $this->assertEquals('claude-sonnet-4', $result['model']); $this->assertEquals(0.7, $result['temperature']); $this->assertEquals(2000, $result['max_tokens']); $this->assertEquals('You are a helpful assistant', $result['system']); $this->assertCount(1, $result['messages']); // System message extracted $this->assertEquals('user', $result['messages'][0]['role']); } public function test_normalizes_response_correctly(): void { $rawResponse = [ 'id' => 'msg_123', 'model' => 'claude-sonnet-4', 'content' => [ [ 'type' => 'text', 'text' => 'Hello! How can I assist you today?' ] ], 'role' => 'assistant', 'stop_reason' => 'end_turn', 'usage' => [ 'input_tokens' => 15, 'output_tokens' => 25 ] ]; $normalized = $this->provider->normalizeResponse($rawResponse); $this->assertEquals('msg_123', $normalized['id']); $this->assertEquals('claude-sonnet-4', $normalized['model']); $this->assertEquals('Hello! How can I assist you today?', $normalized['content']); $this->assertEquals('assistant', $normalized['role']); $this->assertEquals('end_turn', $normalized['finish_reason']); $this->assertEquals(15, $normalized['usage']['prompt_tokens']); $this->assertEquals(25, $normalized['usage']['completion_tokens']); $this->assertEquals(40, $normalized['usage']['total_tokens']); } public function test_calculates_cost_correctly(): void { // Create pricing in database ModelPricing::create([ 'provider' => 'anthropic', 'model' => 'claude-sonnet-4', 'input_price_per_million' => 3.00, 'output_price_per_million' => 15.00, 'is_active' => true, 'effective_from' => now() ]); Cache::flush(); $cost = $this->provider->calculateCost(1000, 500, 'claude-sonnet-4'); // Expected: (1000/1M * 3.00) + (500/1M * 15.00) = 0.003 + 0.0075 = 0.0105 $this->assertEquals(0.0105, $cost); } public function test_handles_api_errors(): void { Http::fake([ 'https://api.anthropic.com/*' => Http::response(['error' => 'Invalid API key'], 401) ]); $this->expectException(\App\Exceptions\ProviderException::class); $this->expectExceptionMessage('Invalid API key'); $this->provider->chatCompletion([ ['role' => 'user', 'content' => 'test'] ]); } public function test_get_supported_models(): void { $models = $this->provider->getSupportedModels(); $this->assertIsArray($models); $this->assertContains('claude-opus-4', $models); $this->assertContains('claude-sonnet-4', $models); $this->assertContains('claude-haiku-4', $models); } public function test_handles_multiple_content_blocks(): void { $rawResponse = [ 'id' => 'msg_456', 'model' => 'claude-sonnet-4', 'content' => [ ['type' => 'text', 'text' => 'First part. '], ['type' => 'text', 'text' => 'Second part.'] ], 'role' => 'assistant', 'stop_reason' => 'end_turn', 'usage' => ['input_tokens' => 10, 'output_tokens' => 20] ]; $normalized = $this->provider->normalizeResponse($rawResponse); $this->assertEquals('First part. Second part.', $normalized['content']); } }