diff --git a/src/platform/tests/Bridge/OpenAi/Embeddings/ModelClientTest.php b/src/platform/tests/Bridge/OpenAi/Embeddings/ModelClientTest.php new file mode 100644 index 000000000..33c4924ef --- /dev/null +++ b/src/platform/tests/Bridge/OpenAi/Embeddings/ModelClientTest.php @@ -0,0 +1,114 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\AI\Platform\Tests\Bridge\OpenAi\Embeddings; + +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\Attributes\TestWith; +use PHPUnit\Framework\Attributes\UsesClass; +use PHPUnit\Framework\TestCase; +use Symfony\AI\Platform\Bridge\OpenAi\Embeddings; +use Symfony\AI\Platform\Bridge\OpenAi\Embeddings\ModelClient; +use Symfony\AI\Platform\Exception\InvalidArgumentException; +use Symfony\Component\HttpClient\MockHttpClient; +use Symfony\Component\HttpClient\Response\MockResponse; +use Symfony\Contracts\HttpClient\ResponseInterface as HttpResponse; + +/** + * @author Oskar Stark + */ +#[CoversClass(ModelClient::class)] +#[UsesClass(Embeddings::class)] +#[Small] +final class ModelClientTest extends TestCase +{ + public function testItThrowsExceptionWhenApiKeyIsEmpty() + { + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage('The API key must not be empty.'); + + new ModelClient(new MockHttpClient(), ''); + } + + #[TestWith(['api-key-without-prefix'])] + #[TestWith(['pk-api-key'])] + #[TestWith(['SK-api-key'])] + #[TestWith(['skapikey'])] + #[TestWith(['sk api-key'])] + #[TestWith(['sk'])] + public function testItThrowsExceptionWhenApiKeyDoesNotStartWithSk(string $invalidApiKey) + { + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage('The API key must start with "sk-".'); + + new ModelClient(new MockHttpClient(), $invalidApiKey); + } + + public function testItAcceptsValidApiKey() + { + $modelClient = new ModelClient(new MockHttpClient(), 'sk-valid-api-key'); + + $this->assertInstanceOf(ModelClient::class, $modelClient); + } + + public function testItIsSupportingTheCorrectModel() + { + $modelClient = new ModelClient(new MockHttpClient(), 'sk-api-key'); + + $this->assertTrue($modelClient->supports(new Embeddings())); + } + + public function testItIsExecutingTheCorrectRequest() + { + $resultCallback = static function (string $method, string $url, array $options): HttpResponse { + self::assertSame('POST', $method); + self::assertSame('https://api.openai.com/v1/embeddings', $url); + self::assertSame('Authorization: Bearer sk-api-key', $options['normalized_headers']['authorization'][0]); + self::assertSame('{"model":"text-embedding-3-small","input":"test text"}', $options['body']); + + return new MockResponse(); + }; + $httpClient = new MockHttpClient([$resultCallback]); + $modelClient = new ModelClient($httpClient, 'sk-api-key'); + $modelClient->request(new Embeddings(), 'test text', []); + } + + public function testItIsExecutingTheCorrectRequestWithCustomOptions() + { + $resultCallback = static function (string $method, string $url, array $options): HttpResponse { + self::assertSame('POST', $method); + self::assertSame('https://api.openai.com/v1/embeddings', $url); + self::assertSame('Authorization: Bearer sk-api-key', $options['normalized_headers']['authorization'][0]); + self::assertSame('{"dimensions":256,"model":"text-embedding-3-large","input":"test text"}', $options['body']); + + return new MockResponse(); + }; + $httpClient = new MockHttpClient([$resultCallback]); + $modelClient = new ModelClient($httpClient, 'sk-api-key'); + $modelClient->request(new Embeddings(Embeddings::TEXT_3_LARGE), 'test text', ['dimensions' => 256]); + } + + public function testItIsExecutingTheCorrectRequestWithArrayInput() + { + $resultCallback = static function (string $method, string $url, array $options): HttpResponse { + self::assertSame('POST', $method); + self::assertSame('https://api.openai.com/v1/embeddings', $url); + self::assertSame('Authorization: Bearer sk-api-key', $options['normalized_headers']['authorization'][0]); + self::assertSame('{"model":"text-embedding-3-small","input":["text1","text2","text3"]}', $options['body']); + + return new MockResponse(); + }; + $httpClient = new MockHttpClient([$resultCallback]); + $modelClient = new ModelClient($httpClient, 'sk-api-key'); + $modelClient->request(new Embeddings(), ['text1', 'text2', 'text3'], []); + } +} diff --git a/src/platform/tests/Bridge/OpenAi/EmbeddingsTest.php b/src/platform/tests/Bridge/OpenAi/EmbeddingsTest.php new file mode 100644 index 000000000..9bc6e5a02 --- /dev/null +++ b/src/platform/tests/Bridge/OpenAi/EmbeddingsTest.php @@ -0,0 +1,48 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\AI\Platform\Tests\Bridge\OpenAi; + +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\TestCase; +use Symfony\AI\Platform\Bridge\OpenAi\Embeddings; + +/** + * @author Oskar Stark + */ +#[CoversClass(Embeddings::class)] +#[Small] +final class EmbeddingsTest extends TestCase +{ + public function testItCreatesEmbeddingsWithDefaultSettings() + { + $embeddings = new Embeddings(); + + $this->assertSame(Embeddings::TEXT_3_SMALL, $embeddings->getName()); + $this->assertSame([], $embeddings->getOptions()); + } + + public function testItCreatesEmbeddingsWithCustomSettings() + { + $embeddings = new Embeddings(Embeddings::TEXT_3_LARGE, ['dimensions' => 256]); + + $this->assertSame(Embeddings::TEXT_3_LARGE, $embeddings->getName()); + $this->assertSame(['dimensions' => 256], $embeddings->getOptions()); + } + + public function testItCreatesEmbeddingsWithAdaModel() + { + $embeddings = new Embeddings(Embeddings::TEXT_ADA_002); + + $this->assertSame(Embeddings::TEXT_ADA_002, $embeddings->getName()); + } +} diff --git a/src/platform/tests/Bridge/OpenAi/Gpt/ModelClientTest.php b/src/platform/tests/Bridge/OpenAi/Gpt/ModelClientTest.php new file mode 100644 index 000000000..f72581adf --- /dev/null +++ b/src/platform/tests/Bridge/OpenAi/Gpt/ModelClientTest.php @@ -0,0 +1,116 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\AI\Platform\Tests\Bridge\OpenAi\Gpt; + +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\Attributes\TestWith; +use PHPUnit\Framework\Attributes\UsesClass; +use PHPUnit\Framework\TestCase; +use Symfony\AI\Platform\Bridge\OpenAi\Gpt; +use Symfony\AI\Platform\Bridge\OpenAi\Gpt\ModelClient; +use Symfony\AI\Platform\Exception\InvalidArgumentException; +use Symfony\Component\HttpClient\EventSourceHttpClient; +use Symfony\Component\HttpClient\MockHttpClient; +use Symfony\Component\HttpClient\Response\MockResponse; +use Symfony\Contracts\HttpClient\ResponseInterface as HttpResponse; + +/** + * @author Oskar Stark + */ +#[CoversClass(ModelClient::class)] +#[UsesClass(Gpt::class)] +#[Small] +final class ModelClientTest extends TestCase +{ + public function testItThrowsExceptionWhenApiKeyIsEmpty() + { + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage('The API key must not be empty.'); + + new ModelClient(new MockHttpClient(), ''); + } + + #[TestWith(['api-key-without-prefix'])] + #[TestWith(['pk-api-key'])] + #[TestWith(['SK-api-key'])] + #[TestWith(['skapikey'])] + #[TestWith(['sk api-key'])] + #[TestWith(['sk'])] + public function testItThrowsExceptionWhenApiKeyDoesNotStartWithSk(string $invalidApiKey) + { + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage('The API key must start with "sk-".'); + + new ModelClient(new MockHttpClient(), $invalidApiKey); + } + + public function testItAcceptsValidApiKey() + { + $modelClient = new ModelClient(new MockHttpClient(), 'sk-valid-api-key'); + + $this->assertInstanceOf(ModelClient::class, $modelClient); + } + + public function testItWrapsHttpClientInEventSourceHttpClient() + { + $httpClient = new MockHttpClient(); + $modelClient = new ModelClient($httpClient, 'sk-valid-api-key'); + + $this->assertInstanceOf(ModelClient::class, $modelClient); + } + + public function testItAcceptsEventSourceHttpClientDirectly() + { + $httpClient = new EventSourceHttpClient(new MockHttpClient()); + $modelClient = new ModelClient($httpClient, 'sk-valid-api-key'); + + $this->assertInstanceOf(ModelClient::class, $modelClient); + } + + public function testItIsSupportingTheCorrectModel() + { + $modelClient = new ModelClient(new MockHttpClient(), 'sk-api-key'); + + $this->assertTrue($modelClient->supports(new Gpt())); + } + + public function testItIsExecutingTheCorrectRequest() + { + $resultCallback = static function (string $method, string $url, array $options): HttpResponse { + self::assertSame('POST', $method); + self::assertSame('https://api.openai.com/v1/chat/completions', $url); + self::assertSame('Authorization: Bearer sk-api-key', $options['normalized_headers']['authorization'][0]); + self::assertSame('{"temperature":1,"model":"gpt-4o","messages":[{"role":"user","content":"test message"}]}', $options['body']); + + return new MockResponse(); + }; + $httpClient = new MockHttpClient([$resultCallback]); + $modelClient = new ModelClient($httpClient, 'sk-api-key'); + $modelClient->request(new Gpt(), ['model' => 'gpt-4o', 'messages' => [['role' => 'user', 'content' => 'test message']]], ['temperature' => 1]); + } + + public function testItIsExecutingTheCorrectRequestWithArrayPayload() + { + $resultCallback = static function (string $method, string $url, array $options): HttpResponse { + self::assertSame('POST', $method); + self::assertSame('https://api.openai.com/v1/chat/completions', $url); + self::assertSame('Authorization: Bearer sk-api-key', $options['normalized_headers']['authorization'][0]); + self::assertSame('{"temperature":0.7,"model":"gpt-4o","messages":[{"role":"user","content":"Hello"}]}', $options['body']); + + return new MockResponse(); + }; + $httpClient = new MockHttpClient([$resultCallback]); + $modelClient = new ModelClient($httpClient, 'sk-api-key'); + $modelClient->request(new Gpt(), ['model' => 'gpt-4o', 'messages' => [['role' => 'user', 'content' => 'Hello']]], ['temperature' => 0.7]); + } +} diff --git a/src/platform/tests/Bridge/OpenAi/GptTest.php b/src/platform/tests/Bridge/OpenAi/GptTest.php new file mode 100644 index 000000000..4caeb6f11 --- /dev/null +++ b/src/platform/tests/Bridge/OpenAi/GptTest.php @@ -0,0 +1,41 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\AI\Platform\Tests\Bridge\OpenAi; + +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\TestCase; +use Symfony\AI\Platform\Bridge\OpenAi\Gpt; + +/** + * @author Oskar Stark + */ +#[CoversClass(Gpt::class)] +#[Small] +final class GptTest extends TestCase +{ + public function testItCreatesGptWithDefaultSettings() + { + $gpt = new Gpt(); + + $this->assertSame(Gpt::GPT_4O, $gpt->getName()); + $this->assertSame(['temperature' => 1.0], $gpt->getOptions()); + } + + public function testItCreatesGptWithCustomSettings() + { + $gpt = new Gpt(Gpt::GPT_4_TURBO, ['temperature' => 0.5, 'max_tokens' => 1000]); + + $this->assertSame(Gpt::GPT_4_TURBO, $gpt->getName()); + $this->assertSame(['temperature' => 0.5, 'max_tokens' => 1000], $gpt->getOptions()); + } +} diff --git a/src/platform/tests/Bridge/OpenAi/PlatformFactoryTest.php b/src/platform/tests/Bridge/OpenAi/PlatformFactoryTest.php new file mode 100644 index 000000000..87eae9b51 --- /dev/null +++ b/src/platform/tests/Bridge/OpenAi/PlatformFactoryTest.php @@ -0,0 +1,51 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\AI\Platform\Tests\Bridge\OpenAi; + +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\TestCase; +use Symfony\AI\Platform\Bridge\OpenAi\PlatformFactory; +use Symfony\AI\Platform\Platform; +use Symfony\Component\HttpClient\EventSourceHttpClient; +use Symfony\Component\HttpClient\MockHttpClient; + +/** + * @author Oskar Stark + */ +#[CoversClass(PlatformFactory::class)] +#[Small] +final class PlatformFactoryTest extends TestCase +{ + public function testItCreatesPlatformWithDefaultSettings() + { + $platform = PlatformFactory::create('sk-test-api-key'); + + $this->assertInstanceOf(Platform::class, $platform); + } + + public function testItCreatesPlatformWithCustomHttpClient() + { + $httpClient = new MockHttpClient(); + $platform = PlatformFactory::create('sk-test-api-key', $httpClient); + + $this->assertInstanceOf(Platform::class, $platform); + } + + public function testItCreatesPlatformWithEventSourceHttpClient() + { + $httpClient = new EventSourceHttpClient(new MockHttpClient()); + $platform = PlatformFactory::create('sk-test-api-key', $httpClient); + + $this->assertInstanceOf(Platform::class, $platform); + } +} diff --git a/src/platform/tests/Bridge/OpenAi/WhisperTest.php b/src/platform/tests/Bridge/OpenAi/WhisperTest.php new file mode 100644 index 000000000..428efe057 --- /dev/null +++ b/src/platform/tests/Bridge/OpenAi/WhisperTest.php @@ -0,0 +1,41 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\AI\Platform\Tests\Bridge\OpenAi; + +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\TestCase; +use Symfony\AI\Platform\Bridge\OpenAi\Whisper; + +/** + * @author Oskar Stark + */ +#[CoversClass(Whisper::class)] +#[Small] +final class WhisperTest extends TestCase +{ + public function testItCreatesWhisperWithDefaultSettings() + { + $whisper = new Whisper(); + + $this->assertSame(Whisper::WHISPER_1, $whisper->getName()); + $this->assertSame([], $whisper->getOptions()); + } + + public function testItCreatesWhisperWithCustomSettings() + { + $whisper = new Whisper(Whisper::WHISPER_1, ['language' => 'en', 'response_format' => 'json']); + + $this->assertSame(Whisper::WHISPER_1, $whisper->getName()); + $this->assertSame(['language' => 'en', 'response_format' => 'json'], $whisper->getOptions()); + } +}