From 3624d248aadc45fdb8d7125293932f22e3bd5170 Mon Sep 17 00:00:00 2001 From: Chris Huber Date: Tue, 19 May 2026 15:39:01 -0400 Subject: [PATCH 1/2] Add provider-supplied request authentication --- ...iderWithRequestAuthenticationInterface.php | 24 ++++++ src/Providers/ProviderRegistry.php | 47 +++++++----- tests/mocks/MockCustomAuthModel.php | 21 ++++++ tests/mocks/MockCustomAuthProvider.php | 73 +++++++++++++++++++ .../mocks/MockCustomRequestAuthentication.php | 30 ++++++++ tests/unit/Providers/ProviderRegistryTest.php | 40 ++++++++++ 6 files changed, 215 insertions(+), 20 deletions(-) create mode 100644 src/Providers/Contracts/ProviderWithRequestAuthenticationInterface.php create mode 100644 tests/mocks/MockCustomAuthModel.php create mode 100644 tests/mocks/MockCustomAuthProvider.php create mode 100644 tests/mocks/MockCustomRequestAuthentication.php diff --git a/src/Providers/Contracts/ProviderWithRequestAuthenticationInterface.php b/src/Providers/Contracts/ProviderWithRequestAuthenticationInterface.php new file mode 100644 index 00000000..07dd7a01 --- /dev/null +++ b/src/Providers/Contracts/ProviderWithRequestAuthenticationInterface.php @@ -0,0 +1,24 @@ +getAuthenticationMethod(); - if ($authenticationMethod === null) { - throw new InvalidArgumentException( - sprintf( - 'Provider %s does not expect any authentication, but got %s.', - $className, - get_class($requestAuthentication) - ) - ); - } + if (!is_subclass_of($className, ProviderWithRequestAuthenticationInterface::class)) { + $authenticationMethod = $className::metadata()->getAuthenticationMethod(); + if ($authenticationMethod === null) { + throw new InvalidArgumentException( + sprintf( + 'Provider %s does not expect any authentication, but got %s.', + $className, + get_class($requestAuthentication) + ) + ); + } - $expectedClass = $authenticationMethod->getImplementationClass(); - if (!$requestAuthentication instanceof $expectedClass) { - throw new InvalidArgumentException( - sprintf( - 'Provider %s expects authentication of type %s, but got %s.', - $className, - $expectedClass, - get_class($requestAuthentication) - ) - ); + $expectedClass = $authenticationMethod->getImplementationClass(); + if (!$requestAuthentication instanceof $expectedClass) { + throw new InvalidArgumentException( + sprintf( + 'Provider %s expects authentication of type %s, but got %s.', + $className, + $expectedClass, + get_class($requestAuthentication) + ) + ); + } } $availability = $className::availability(); @@ -521,6 +524,10 @@ private function createDefaultProviderRequestAuthentication( $providerId = $providerMetadata->getId(); $authenticationMethod = $providerMetadata->getAuthenticationMethod(); + if (is_subclass_of($className, ProviderWithRequestAuthenticationInterface::class)) { + return $className::requestAuthentication(); + } + if ($authenticationMethod === null) { return null; } diff --git a/tests/mocks/MockCustomAuthModel.php b/tests/mocks/MockCustomAuthModel.php new file mode 100644 index 00000000..37698b4b --- /dev/null +++ b/tests/mocks/MockCustomAuthModel.php @@ -0,0 +1,21 @@ +getModelMetadata($modelId); + $config = $modelConfig ?? new ModelConfig(); + + return new MockCustomAuthModel($modelMetadata, $config); + } + + /** + * Sets the request authentication for testing. + * + * @param RequestAuthenticationInterface|null $requestAuthentication The request authentication instance. + */ + public static function setRequestAuthentication(?RequestAuthenticationInterface $requestAuthentication): void + { + static::$requestAuthentication = $requestAuthentication; + } + + /** + * Resets static state for testing. + */ + public static function reset(): void + { + parent::reset(); + static::$requestAuthentication = null; + } +} diff --git a/tests/mocks/MockCustomRequestAuthentication.php b/tests/mocks/MockCustomRequestAuthentication.php new file mode 100644 index 00000000..8d82cdd3 --- /dev/null +++ b/tests/mocks/MockCustomRequestAuthentication.php @@ -0,0 +1,30 @@ +withHeader('X-Mock-Auth', 'custom'); + } + + /** + * {@inheritDoc} + */ + public static function getJsonSchema(): array + { + return []; + } +} diff --git a/tests/unit/Providers/ProviderRegistryTest.php b/tests/unit/Providers/ProviderRegistryTest.php index ff8197e6..672633f2 100644 --- a/tests/unit/Providers/ProviderRegistryTest.php +++ b/tests/unit/Providers/ProviderRegistryTest.php @@ -14,6 +14,8 @@ use WordPress\AiClient\Providers\Models\DTO\ModelRequirements; use WordPress\AiClient\Providers\Models\Enums\CapabilityEnum; use WordPress\AiClient\Providers\ProviderRegistry; +use WordPress\AiClient\Tests\mocks\MockCustomAuthProvider; +use WordPress\AiClient\Tests\mocks\MockCustomRequestAuthentication; use WordPress\AiClient\Tests\mocks\MockHttpTransporter; use WordPress\AiClient\Tests\mocks\MockModel; use WordPress\AiClient\Tests\mocks\MockModelMetadataDirectory; @@ -32,12 +34,14 @@ protected function setUp(): void { parent::setUp(); $this->registry = new ProviderRegistry(); + MockCustomAuthProvider::reset(); MockProvider::reset(); // Reset static state of mock provider before each test. } protected function tearDown(): void { MockProvider::reset(); // Reset static state of mock provider after each test. + MockCustomAuthProvider::reset(); parent::tearDown(); } @@ -376,6 +380,42 @@ public function testGetProviderRequestAuthenticationReturnsDefault(): void $this->assertNull($retrievedAuth); } + /** + * Tests that provider-supplied request authentication is used when available. + * + * @return void + */ + public function testRegisterProviderUsesProviderSuppliedRequestAuthentication(): void + { + $requestAuthentication = new MockCustomRequestAuthentication(); + MockCustomAuthProvider::setRequestAuthentication($requestAuthentication); + + $this->registry->registerProvider(MockCustomAuthProvider::class); + + $this->assertSame( + $requestAuthentication, + $this->registry->getProviderRequestAuthentication('mock-custom-auth') + ); + } + + /** + * Tests that provider-supplied request authentication is bound to models. + * + * @return void + */ + public function testRegisterProviderBindsProviderSuppliedRequestAuthenticationToModels(): void + { + $requestAuthentication = new MockCustomRequestAuthentication(); + MockCustomAuthProvider::setRequestAuthentication($requestAuthentication); + + $this->registry->registerProvider(MockCustomAuthProvider::class); + + $model = $this->registry->getProviderModel('mock-custom-auth', 'mock-text-model'); + + $this->assertInstanceOf(MockModel::class, $model); + $this->assertSame($requestAuthentication, $model->getRequestAuthentication()); + } + /** * Tests the internal getEnvVarName method using reflection. * From 7afce4c09f7e1fbcfc8781074b5dff1748df5bde Mon Sep 17 00:00:00 2001 From: Chris Huber Date: Sun, 31 May 2026 17:39:09 -0400 Subject: [PATCH 2/2] Add custom provider auth registry coverage --- tests/unit/Providers/ProviderRegistryTest.php | 35 +++++++++++++++++-- 1 file changed, 32 insertions(+), 3 deletions(-) diff --git a/tests/unit/Providers/ProviderRegistryTest.php b/tests/unit/Providers/ProviderRegistryTest.php index 672633f2..af3b5a48 100644 --- a/tests/unit/Providers/ProviderRegistryTest.php +++ b/tests/unit/Providers/ProviderRegistryTest.php @@ -416,6 +416,29 @@ public function testRegisterProviderBindsProviderSuppliedRequestAuthenticationTo $this->assertSame($requestAuthentication, $model->getRequestAuthentication()); } + /** + * Tests that explicit request authentication overrides provider-supplied authentication. + * + * @return void + */ + public function testSetProviderRequestAuthenticationOverridesProviderSuppliedRequestAuthentication(): void + { + MockCustomAuthProvider::setRequestAuthentication(new MockCustomRequestAuthentication()); + + $this->registry->registerProvider(MockCustomAuthProvider::class); + + $requestAuthentication = new MockCustomRequestAuthentication(); + $this->registry->setProviderRequestAuthentication('mock-custom-auth', $requestAuthentication); + + $model = $this->registry->getProviderModel('mock-custom-auth', 'mock-text-model'); + + $this->assertSame( + $requestAuthentication, + $this->registry->getProviderRequestAuthentication('mock-custom-auth') + ); + $this->assertSame($requestAuthentication, $model->getRequestAuthentication()); + } + /** * Tests the internal getEnvVarName method using reflection. * @@ -428,7 +451,9 @@ public function testRegisterProviderBindsProviderSuppliedRequestAuthenticationTo public function testGetEnvVarName(string $providerId, string $field, string $expected): void { $method = new \ReflectionMethod(ProviderRegistry::class, 'getEnvVarName'); - $method->setAccessible(true); + if (PHP_VERSION_ID < 80100) { + $method->setAccessible(true); + } $result = $method->invoke($this->registry, $providerId, $field); // Invoke on instance @@ -464,7 +489,9 @@ public function testCreateDefaultProviderRequestAuthenticationWithEnvVar(): void $this->registry->registerProvider(MockProvider::class); $method = new \ReflectionMethod(ProviderRegistry::class, 'createDefaultProviderRequestAuthentication'); - $method->setAccessible(true); + if (PHP_VERSION_ID < 80100) { + $method->setAccessible(true); + } $auth = $method->invoke($this->registry, MockProvider::class); @@ -488,7 +515,9 @@ public function testCreateDefaultProviderRequestAuthenticationWithoutEnvVar(): v $this->registry->registerProvider(MockProvider::class); $method = new \ReflectionMethod(ProviderRegistry::class, 'createDefaultProviderRequestAuthentication'); - $method->setAccessible(true); + if (PHP_VERSION_ID < 80100) { + $method->setAccessible(true); + } $auth = $method->invoke($this->registry, MockProvider::class);