Skip to content

Commit 666f9aa

Browse files
authored
Merge pull request #88 from Ref34t/enhance-aiclient-isconfigured
Enhance AiClient::isConfigured() to support provider ID and class name
2 parents a4a82c4 + 9f6a0d9 commit 666f9aa

File tree

2 files changed

+143
-3
lines changed

2 files changed

+143
-3
lines changed

src/AiClient.php

Lines changed: 33 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
use WordPress\AiClient\ProviderImplementations\Google\GoogleProvider;
1010
use WordPress\AiClient\ProviderImplementations\OpenAi\OpenAiProvider;
1111
use WordPress\AiClient\Providers\Contracts\ProviderAvailabilityInterface;
12+
use WordPress\AiClient\Providers\Contracts\ProviderInterface;
1213
use WordPress\AiClient\Providers\Http\HttpTransporterFactory;
1314
use WordPress\AiClient\Providers\Models\Contracts\ModelInterface;
1415
use WordPress\AiClient\Providers\Models\DTO\ModelConfig;
@@ -114,14 +115,43 @@ public static function defaultRegistry(): ProviderRegistry
114115
/**
115116
* Checks if a provider is configured and available for use.
116117
*
118+
* Supports multiple input formats for developer convenience:
119+
* - ProviderAvailabilityInterface: Direct availability check
120+
* - string (provider ID): e.g., AiClient::isConfigured('openai')
121+
* - string (class name): e.g., AiClient::isConfigured(OpenAiProvider::class)
122+
*
123+
* When using string input, this method leverages the ProviderRegistry's centralized
124+
* dependency management, ensuring HttpTransporter and authentication are properly
125+
* injected into availability instances.
126+
*
117127
* @since 0.1.0
128+
* @since n.e.x.t Now supports being passed a provider ID or class name.
118129
*
119-
* @param ProviderAvailabilityInterface $availability The provider availability instance to check.
130+
* @param ProviderAvailabilityInterface|string|class-string<ProviderInterface> $availabilityOrIdOrClassName
131+
* The provider availability instance, provider ID, or provider class name.
120132
* @return bool True if the provider is configured and available, false otherwise.
121133
*/
122-
public static function isConfigured(ProviderAvailabilityInterface $availability): bool
134+
public static function isConfigured($availabilityOrIdOrClassName): bool
123135
{
124-
return $availability->isConfigured();
136+
// Handle direct ProviderAvailabilityInterface (backward compatibility)
137+
if ($availabilityOrIdOrClassName instanceof ProviderAvailabilityInterface) {
138+
return $availabilityOrIdOrClassName->isConfigured();
139+
}
140+
141+
// Handle string input (provider ID or class name) via registry
142+
if (is_string($availabilityOrIdOrClassName)) {
143+
return self::defaultRegistry()->isProviderConfigured($availabilityOrIdOrClassName);
144+
}
145+
146+
throw new \InvalidArgumentException(
147+
'Parameter must be a ProviderAvailabilityInterface instance, provider ID string, or provider class name. ' .
148+
sprintf(
149+
'Received: %s',
150+
is_object($availabilityOrIdOrClassName)
151+
? get_class($availabilityOrIdOrClassName)
152+
: gettype($availabilityOrIdOrClassName)
153+
)
154+
);
125155
}
126156

127157
/**

tests/unit/AiClientTest.php

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
use WordPress\AiClient\AiClient;
1010
use WordPress\AiClient\Messages\DTO\MessagePart;
1111
use WordPress\AiClient\Messages\DTO\UserMessage;
12+
use WordPress\AiClient\ProviderImplementations\OpenAi\OpenAiProvider;
1213
use WordPress\AiClient\Providers\Contracts\ProviderAvailabilityInterface;
1314
use WordPress\AiClient\Providers\Models\DTO\ModelConfig;
1415
use WordPress\AiClient\Providers\ProviderRegistry;
@@ -246,6 +247,115 @@ public function testIsConfiguredReturnsFalseWhenProviderIsNotConfigured(): void
246247
$this->assertFalse($result);
247248
}
248249

250+
/**
251+
* Tests isConfigured method with provider ID string leverages default registry.
252+
*/
253+
public function testIsConfiguredWithProviderIdString(): void
254+
{
255+
// This test will use the actual default registry since we can't easily mock static methods
256+
// The default registry should have providers registered, so we test the delegation path
257+
$result = AiClient::isConfigured('openai');
258+
259+
// The result will be false because no actual API keys are configured in tests,
260+
// but the important thing is that no exception is thrown and the registry delegation works
261+
$this->assertIsBool($result);
262+
}
263+
264+
/**
265+
* Tests isConfigured method with provider class name leverages default registry.
266+
*/
267+
public function testIsConfiguredWithProviderClassName(): void
268+
{
269+
// This test will use the actual default registry since we can't easily mock static methods
270+
// The default registry should have providers registered, so we test the delegation path
271+
$result = AiClient::isConfigured(OpenAiProvider::class);
272+
273+
// The result will be false because no actual API keys are configured in tests,
274+
// but the important thing is that no exception is thrown and the registry delegation works
275+
$this->assertIsBool($result);
276+
}
277+
278+
/**
279+
* Tests isConfigured method throws exception for invalid parameter types.
280+
*/
281+
public function testIsConfiguredThrowsExceptionForInvalidParameterTypes(): void
282+
{
283+
$this->expectException(\InvalidArgumentException::class);
284+
$this->expectExceptionMessage(
285+
'Parameter must be a ProviderAvailabilityInterface instance, provider ID string, or provider class name. ' .
286+
'Received: integer'
287+
);
288+
289+
AiClient::isConfigured(123);
290+
}
291+
292+
/**
293+
* Data provider for invalid isConfigured parameter types.
294+
*
295+
* @return array<string, array{mixed, string}>
296+
*/
297+
public function invalidIsConfiguredParameterTypesProvider(): array
298+
{
299+
return [
300+
'integer parameter' => [123, 'integer'],
301+
'array parameter' => [['invalid_array'], 'array'],
302+
'object parameter' => [new \stdClass(), 'stdClass'],
303+
'boolean parameter' => [true, 'boolean'],
304+
'null parameter' => [null, 'NULL'],
305+
];
306+
}
307+
308+
/**
309+
* Tests that isConfigured rejects all invalid parameter types consistently.
310+
*
311+
* @dataProvider invalidIsConfiguredParameterTypesProvider
312+
* @param mixed $invalidParam
313+
*/
314+
public function testIsConfiguredRejectsInvalidParameterTypes($invalidParam, string $expectedType): void
315+
{
316+
try {
317+
AiClient::isConfigured($invalidParam);
318+
$this->fail("Expected InvalidArgumentException for isConfigured with $expectedType");
319+
} catch (\InvalidArgumentException $e) {
320+
$this->assertStringContainsString(
321+
'Parameter must be a ProviderAvailabilityInterface instance, provider ID string, ' .
322+
'or provider class name.',
323+
$e->getMessage(),
324+
"isConfigured should reject invalid parameter type: $expectedType"
325+
);
326+
$this->assertStringContainsString(
327+
"Received: $expectedType",
328+
$e->getMessage(),
329+
"isConfigured should include received type in error message"
330+
);
331+
}
332+
}
333+
334+
/**
335+
* Tests backward compatibility - isConfigured still works with ProviderAvailabilityInterface.
336+
*/
337+
public function testIsConfiguredBackwardCompatibility(): void
338+
{
339+
// Test that the original interface-based approach still works exactly as before
340+
$mockAvailability = $this->createMock(ProviderAvailabilityInterface::class);
341+
$mockAvailability->expects($this->once())
342+
->method('isConfigured')
343+
->willReturn(true);
344+
345+
// Should work without registry parameter
346+
$result = AiClient::isConfigured($mockAvailability);
347+
$this->assertTrue($result);
348+
349+
// Should work in all cases with interface input
350+
$mockAvailability2 = $this->createMock(ProviderAvailabilityInterface::class);
351+
$mockAvailability2->expects($this->once())
352+
->method('isConfigured')
353+
->willReturn(false);
354+
355+
$result2 = AiClient::isConfigured($mockAvailability2);
356+
$this->assertFalse($result2);
357+
}
358+
249359
/**
250360
* Tests generateResult delegates to generateTextResult when model supports text generation.
251361
*/

0 commit comments

Comments
 (0)