assertNull(config('ai.provider')); $this->assertNull(config('ai.ollama')); $this->assertIsString(config('ai.openai.base_url')); $this->assertIsArray(config('ai.openai.models')); $this->assertArrayHasKey('fast', config('ai.openai.models')); $this->assertArrayHasKey('balanced', config('ai.openai.models')); $this->assertArrayHasKey('advanced', config('ai.openai.models')); $this->assertIsInt(config('ai.openai.timeout')); } public function test_ollama_reachable_and_unavailable_health_states_are_reported(): void { Http::fakeSequence() ->push(['data' => [['id' => 'qwen']]]) ->push([], 503); $this->assertTrue($this->ollama()->health()['connected']); $this->assertFalse($this->ollama()->health()['connected']); } public function test_invalid_model_fails_without_silent_fallback(): void { Http::fake(['*/chat/completions' => Http::response(['error' => 'model not found'], 404)]); $this->expectException(RuntimeException::class); $this->expectExceptionMessage('404'); $this->ollama()->chat('hello'); } public function test_provider_timeout_is_not_silently_swallowed(): void { Http::fake(fn () => throw new ConnectionException('timeout')); $this->expectException(ConnectionException::class); $this->ollama()->chat('hello'); } public function test_openai_compatible_assist_and_invalid_credentials_are_handled(): void { $provider = $this->online(); Http::fake(['*/chat/completions' => Http::response(['choices' => [['message' => ['content' => '{"result":"ok"}']]]])]); $this->assertSame('ok', $provider->assist('rewrite', 'content', [])['proposal']['result']); Http::fake(['*/models' => Http::response(['error' => 'unauthorized'], 401)]); $this->assertFalse($provider->health()['connected']); } private function ollama(): OpenAiCompatibleProvider { return new OpenAiCompatibleProvider(new AiEndpointPolicy(customProviderHosts: []), [ 'id' => 'ollama', 'provider' => 'ollama', 'mode' => 'local', 'base_url' => 'http://127.0.0.1:11434/v1', 'api_key' => '', 'default_model' => 'qwen', 'timeout_seconds' => 10, ]); } private function online(): OpenAiCompatibleProvider { return new OpenAiCompatibleProvider(new AiEndpointPolicy( resolver: fn (string $host): array => ['93.184.216.34'], customProviderHosts: ['ai.example.test'], ), [ 'id' => 'online', 'provider' => 'openai_compatible', 'mode' => 'online', 'base_url' => 'https://ai.example.test/v1', 'api_key' => 'secret', 'default_model' => 'model-a', 'timeout_seconds' => 10, ]); } }