Skip to content

Commit

Permalink
openrouter api calls are mocked for tests (#21)
Browse files Browse the repository at this point in the history
* openrouter api calls are mocked for tests

* v2 is modified as v3 for github actions
  • Loading branch information
moe-mizrak authored Sep 17, 2024
1 parent a0ac154 commit 4ec4ac1
Show file tree
Hide file tree
Showing 6 changed files with 125 additions and 19 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ jobs:
run: vendor/bin/phpunit --log-junit phpunit-report.xml

- name: Upload PHPUnit Report
uses: actions/upload-artifact@v2
uses: actions/upload-artifact@v3
with:
name: PHPUnit Test Report
path: phpunit-report.xml
2 changes: 1 addition & 1 deletion phpunit.xml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
</testsuites>
<php>
<env name="APP_ENV" value="testing"/>
<env name="OPENROUTER_API_KEY" value="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"/>
<env name="OPENROUTER_API_KEY" value=""/>
<env name="OPENROUTER_API_ENDPOINT" value="https://openrouter.ai/api/v1/"/>
</php>
</phpunit>
7 changes: 0 additions & 7 deletions src/OpenRouterAPI.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@

namespace MoeMizrak\LaravelOpenrouter;

use GuzzleHttp\Client;

/**
* This abstract class forms the response from OpenRouter
*
Expand All @@ -12,9 +10,4 @@
*/
abstract class OpenRouterAPI
{
/**
* OpenRouterAPI constructor.
* @param Client $client
*/
public function __construct(protected Client $client) {}
}
9 changes: 5 additions & 4 deletions src/OpenRouterRequest.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

namespace MoeMizrak\LaravelOpenrouter;

use GuzzleHttp\ClientInterface;
use GuzzleHttp\Exception\GuzzleException;
use GuzzleHttp\Promise\PromiseInterface;
use JsonException;
Expand Down Expand Up @@ -60,7 +61,7 @@ public function chatRequest(ChatData $chatData): ErrorData|ResponseData
];

// Send POST request to the OpenRouter API chat completion endpoint and get the response.
$response = $this->client->request(
$response = app(ClientInterface::class)->request(
'POST',
$chatCompletionPath,
$options
Expand Down Expand Up @@ -102,7 +103,7 @@ public function chatStreamRequest(ChatData $chatData): PromiseInterface
];

// Send POST request to the OpenRouter API chat completion endpoint and get the streaming response.
$promise = $this->client->requestAsync(
$promise = app(ClientInterface::class)->requestAsync(
'POST',
$chatCompletionPath,
$options
Expand Down Expand Up @@ -202,7 +203,7 @@ public function costRequest(string $generationId): CostResponseData
$costPath = 'generation?id=' . $generationId;

// Send GET request to the OpenRouter API generation endpoint and get the response.
$response = $this->client->request(
$response = app(ClientInterface::class)->request(
'GET',
$costPath
);
Expand All @@ -223,7 +224,7 @@ public function limitRequest(): LimitResponseData
$limitPath = 'auth/key';

// Send GET request to the OpenRouter API limit endpoint and get the response.
$response = $this->client->request(
$response = app(ClientInterface::class)->request(
'GET',
$limitPath
);
Expand Down
9 changes: 6 additions & 3 deletions src/OpenRouterServiceProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
namespace MoeMizrak\LaravelOpenrouter;

use GuzzleHttp\Client;
use GuzzleHttp\ClientInterface;
use GuzzleHttp\HandlerStack;
use GuzzleRetry\GuzzleRetryMiddleware;
use Illuminate\Foundation\AliasLoader;
Expand Down Expand Up @@ -37,10 +38,12 @@ public function register(): void
{
$this->configure();

$this->app->singleton(ClientInterface::class, function () {
return $this->configureClient();
});

$this->app->bind('laravel-openrouter', function () {
return new OpenRouterRequest(
$this->configureClient()
);
return new OpenRouterRequest();
});

$this->app->bind(OpenRouterRequest::class, function () {
Expand Down
115 changes: 112 additions & 3 deletions tests/OpenRouterAPITest.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@

namespace MoeMizrak\LaravelOpenrouter\Tests;

use GuzzleHttp\ClientInterface;
use GuzzleHttp\Psr7\Response;
use Illuminate\Support\Arr;
use Mockery\MockInterface;
use MoeMizrak\LaravelOpenrouter\DTO\ChatData;
use MoeMizrak\LaravelOpenrouter\DTO\CostResponseData;
use MoeMizrak\LaravelOpenrouter\DTO\ImageContentPartData;
Expand All @@ -13,6 +16,7 @@
use MoeMizrak\LaravelOpenrouter\DTO\ResponseData;
use MoeMizrak\LaravelOpenrouter\DTO\ResponseFormatData;
use MoeMizrak\LaravelOpenrouter\DTO\TextContentData;
use MoeMizrak\LaravelOpenrouter\DTO\UsageData;
use MoeMizrak\LaravelOpenrouter\Exceptions\XorValidationException;
use MoeMizrak\LaravelOpenrouter\Facades\LaravelOpenRouter;
use MoeMizrak\LaravelOpenrouter\OpenRouterRequest;
Expand Down Expand Up @@ -47,6 +51,87 @@ public function setUp(): void
$this->api = $this->app->make(OpenRouterRequest::class);
}

private function mockBasicBody(): array
{
return [
'id' => 'gen-QcWgjEtiEDNHgomV2jjoQpCZlkRZ',
'model' => $this->model,
'object' => 'chat.completion',
'created' => 1718888436,
'choices' => [
[
'index' => 0,
'message' => [
'role' => RoleType::ASSISTANT,
'content' => 'Some random content',
],
'finish_reason' => 'stop',
],
],
'usage' => new UsageData([
'prompt_tokens' => 23,
'completion_tokens' => 100,
'total_tokens' => 123,
]),
];
}

private function mockBasicCostBody(): array
{
return [
'data' => [
'id' => "gen-QcWgjEtiEDNHgomV2jjoQpCZlkRZ",
'model' => $this->model,
'total_cost' => 0,
'streamed' => true,
'origin' => "https://github.com/moe-mizrak/laravel-openrouter",
'cancelled' => false,
'finish_reason' => null, // Nullable field
'generation_time' => 0,
'created_at' => "2024-09-17T18:33:11.957775+00:00",
'provider_name' => "HuggingFace",
'tokens_prompt' => 24,
'tokens_completion' => 87,
'native_tokens_prompt' => 27,
'native_tokens_completion' => 102,
'num_media_prompt' => null, // Nullable field
'num_media_completion' => null, // Nullable field
'app_id' => 1777723,
'latency' => 829,
'moderation_latency' => null, // Nullable field
'upstream_id' => null, // Nullable field
'usage' => 0,
],
];
}

private function mockBasicLimitBody(): array
{
return [
'data' => [
'label' => 'sk-or-v1-7a3...1f9',
'usage' => 0,
'limit' => 1,
'is_free_tier' => true,
'limit_remaining' => 12,
'rate_limit' => [
'requests' => 10,
'interval' => '10s',
],
],
];
}

private function mockOpenRouter(array $mockBody): void
{
$mockResponse = (new Response(200, [], json_encode($mockBody)));
$this->mock(ClientInterface::class, function (MockInterface $mock) use ($mockResponse) {
$mock->shouldReceive('request')
->once()
->andReturn($mockResponse);
});
}

/**
* General assertions required for testing instead of replicating the same code.
*
Expand Down Expand Up @@ -80,6 +165,7 @@ public function it_makes_a_basic_chat_completion_open_route_api_request()
'model' => $this->model,
'max_tokens' => $this->maxTokens,
]);
$this->mockOpenRouter($this->mockBasicBody());

/* EXECUTE */
$response = $this->api->chatRequest($chatData);
Expand Down Expand Up @@ -107,6 +193,7 @@ public function it_makes_a_basic_chat_completion_open_route_api_request_with_his
'model' => $this->model,
'max_tokens' => $this->maxTokens,
]);
$this->mockOpenRouter($this->mockBasicBody());
$oldResponse = $this->api->chatRequest($chatData);
$historicalMessage = new MessageData([
'role' => RoleType::ASSISTANT,
Expand All @@ -124,6 +211,9 @@ public function it_makes_a_basic_chat_completion_open_route_api_request_with_his
'model' => $this->model,
'max_tokens' => $this->maxTokens,
]);
$mockBody = $this->mockBasicBody();
$mockBody['choices'][0]['message.content'] = 'You are Moe the AI Necromancer, a friendly and knowledgeable assistant designed to help answer questions and engage in stimulating conversations. I specialize in a wide range of topics, including necromancy, AI, and many other subjects. How can I assist you today?';
$this->mockOpenRouter($mockBody);

/* EXECUTE */
$response = $this->api->chatRequest($chatData);
Expand All @@ -141,6 +231,7 @@ public function it_makes_a_basic_chat_completion_open_route_api_request_with_his
public function it_makes_a_basic_chat_completion_stream_request()
{
/* SETUP */
$this->markTestSkipped('Test skipped until stream request is mocked');
$chatData = new ChatData([
'messages' => [
$this->messageData,
Expand Down Expand Up @@ -196,6 +287,9 @@ public function it_makes_a_basic_prompt_chat_completion_open_route_api_request()
'model' => $this->model,
'max_tokens' => $this->maxTokens,
]);
$mockBody = $this->mockBasicBody();
$mockBody['choices'][0]['text'] = 'Some mocked text';
$this->mockOpenRouter($mockBody);

/* EXECUTE */
$response = $this->api->chatRequest($chatData);
Expand Down Expand Up @@ -262,6 +356,7 @@ public function it_successfully_sends_text_content_in_messages_in_the_open_route
'model' => $this->model,
'max_tokens' => $this->maxTokens,
]);
$this->mockOpenRouter($this->mockBasicBody());

/* EXECUTE */
$response = $this->api->chatRequest($chatData);
Expand Down Expand Up @@ -304,6 +399,7 @@ public function it_successfully_sends_image_and_text_content_in_messages_in_the_
'model' => $this->model,
'max_tokens' => $this->maxTokens,
]);
$this->mockOpenRouter($this->mockBasicBody());

/* EXECUTE */
$response = $this->api->chatRequest($chatData);
Expand Down Expand Up @@ -342,6 +438,7 @@ public function it_successfully_sends_multiple_text_content_in_messages_in_the_o
'model' => $this->model,
'max_tokens' => $this->maxTokens,
]);
$this->mockOpenRouter($this->mockBasicBody());

/* EXECUTE */
$response = $this->api->chatRequest($chatData);
Expand Down Expand Up @@ -398,6 +495,7 @@ public function it_makes_a_basic_chat_completion_open_route_api_request_with_res
'max_tokens' => $this->maxTokens,
'response_format' => $responseFormatData,
]);
$this->mockOpenRouter($this->mockBasicBody());

/* EXECUTE */
$response = $this->api->chatRequest($chatData);
Expand Down Expand Up @@ -428,6 +526,9 @@ public function it_makes_a_basic_chat_completion_open_route_api_request_with_sto
'max_tokens' => $this->maxTokens,
'stop' => $stop,
]);
$mockBody = $this->mockBasicBody();
$mockBody['choices'][0]['finish_reason'] = 'bugs';
$this->mockOpenRouter($mockBody);

/* EXECUTE */
$response = $this->api->chatRequest($chatData);
Expand All @@ -452,9 +553,11 @@ public function it_makes_cost_request_with_generation_id()
'model' => $this->model,
'max_tokens' => $this->maxTokens,
]);
$this->mockOpenRouter($this->mockBasicBody());
$chatResponse = $this->api->chatRequest($chatData);
$generationId = $chatResponse->id;
sleep(3); // Pauses the script for 3 seconds just to make sure $generationId is generated
$this->mockOpenRouter($this->mockBasicCostBody());

/* EXECUTE */
$response = $this->api->costRequest($generationId);
Expand All @@ -467,7 +570,6 @@ public function it_makes_cost_request_with_generation_id()
$this->assertNotNull($response->origin);
$this->assertNotNull($response->streamed);
$this->assertNotNull($response->cancelled);
$this->assertNotNull($response->finish_reason);
$this->assertNotNull($response->generation_time);
$this->assertNotNull($response->created_at);
$this->assertNotNull($response->provider_name);
Expand All @@ -477,7 +579,6 @@ public function it_makes_cost_request_with_generation_id()
$this->assertNotNull($response->native_tokens_completion);
$this->assertNotNull($response->app_id);
$this->assertNotNull($response->latency);
$this->assertNotNull($response->upstream_id);
$this->assertNotNull($response->usage);
}

Expand Down Expand Up @@ -509,6 +610,7 @@ public function it_makes_chat_completion_api_request_with_llm_parameters()
'repetition_penalty' => $repetitionPenalty,
'seed' => $seed,
]);
$this->mockOpenRouter($this->mockBasicBody());

/* EXECUTE */
$response = $this->api->chatRequest($chatData);
Expand Down Expand Up @@ -545,6 +647,7 @@ public function it_makes_chat_completion_api_request_with_open_router_specific_p
'route' => $route,
'provider' => $provider,
]);
$this->mockOpenRouter($this->mockBasicBody());

/* EXECUTE */
$response = $this->api->chatRequest($chatData);
Expand All @@ -559,7 +662,6 @@ public function it_makes_chat_completion_api_request_with_open_router_specific_p
$this->assertNotNull($response->usage->completion_tokens);
$this->assertNotNull($response->usage->total_tokens);
$this->assertNotNull($response->choices);
$this->assertNotNull(Arr::get($response->choices[0], 'finish_reason'));
$this->assertEquals(RoleType::ASSISTANT, Arr::get($response->choices[0], 'message.role'));
$this->assertNotNull(Arr::get($response->choices[0], 'message.content'));
}
Expand Down Expand Up @@ -590,6 +692,9 @@ public function it_makes_chat_completion_api_request_with_fallback_to_second_mod
'route' => $route,
'provider' => $provider,
]);
$mockBody = $this->mockBasicBody();
$mockBody['model'] = $modelGryphe;
$this->mockOpenRouter($mockBody);

/* EXECUTE */
$response = $this->api->chatRequest($chatData);
Expand Down Expand Up @@ -692,6 +797,9 @@ public function it_throws_validation_exception_when_NOT_ALLOWED_value_is_sent_fo
*/
public function it_makes_a_limit_open_route_api_request_and_gets_rate_limit_and_credit_left_on_api_key()
{
/* SETUP */
$this->mockOpenRouter($this->mockBasicLimitBody());

/* EXECUTE */
$response = $this->api->limitRequest();

Expand All @@ -718,6 +826,7 @@ public function it_makes_a_open_route_api_request_by_using_facade()
'model' => $this->model,
'max_tokens' => $this->maxTokens,
]);
$this->mockOpenRouter($this->mockBasicBody());

/* EXECUTE */
$response = LaravelOpenRouter::chatRequest($chatData);
Expand Down

0 comments on commit 4ec4ac1

Please sign in to comment.