Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature | Record Requests In Telescope #24

Closed
wants to merge 17 commits into from
1 change: 1 addition & 0 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
},
"require-dev": {
"friendsofphp/php-cs-fixer": "^3.5",
"laravel/telescope": "^4.9",
"orchestra/testbench": "^v6.24.0",
"pestphp/pest": "^1.21"
},
Expand Down
2 changes: 2 additions & 0 deletions phpunit.xml
Original file line number Diff line number Diff line change
Expand Up @@ -18,5 +18,7 @@
<php>
<env name="APP_KEY" value="base64:g6rX3ygsFZiO446CYsmZVbPZBGavoH0sVbGXZbS03SI="/>
<env name="BCRYPT_ROUNDS" value="4"/>
<env name="DB_CONNECTION" value="sqlite"/>
Sammyjo20 marked this conversation as resolved.
Show resolved Hide resolved
<env name="DB_DATABASE" value=":memory:"/>
</php>
</phpunit>
81 changes: 81 additions & 0 deletions src/Listeners/RecordSaloonToTelescope.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
<?php

namespace Sammyjo20\SaloonLaravel\Listeners;

use Laravel\Telescope\Telescope;
use Laravel\Telescope\IncomingEntry;
use Sammyjo20\Saloon\Http\SaloonRequest;
use Sammyjo20\SaloonLaravel\Events\SentSaloonRequest;

class RecordSaloonToTelescope
{
/**
* Handle the event.
*
* @param SentSaloonRequest $event
* @return void
* @throws \Illuminate\Contracts\Container\BindingResolutionException
* @throws \ReflectionException
* @throws \Sammyjo20\Saloon\Exceptions\SaloonInvalidConnectorException
*/
public function handle(SentSaloonRequest $event): void
{
$request = $event->request;
$response = $event->response;

// We'll attempt to guess if the response is JSON.

$isJsonResponse = (bool)json_decode($response->body(), true);

// Let's now append any query parameters to the end of the URL, since Saloon's getFullRequestUrl
// does not provide it.

$fullUrl = $this->buildFullUrl($request);

// We'll now record the client request!

$telescope = app()->make(Telescope::class);

$connectorClass = get_class($request->getConnector());
$requestClass = get_class($request);

$entry = IncomingEntry::make([
'method' => $request->getMethod(),
'uri' => $fullUrl,
'headers' => $request->getHeaders(),
'payload' => $request->getData(),
'response_status' => $response->status(),
'response_headers' => $response->headers(),
'response' => $isJsonResponse ? $response->json() : $response->body(),
'saloon_connector' => $connectorClass,
'saloon_request' => $requestClass,
]);

// Create tags

$entry->tags([
'Saloon',
'SaloonConnector:' . $connectorClass,
'SaloonRequest:' . $requestClass,
]);

$telescope->recordClientRequest($entry);
}

/**
* Build the full URL with query parameters
*
* @param SaloonRequest $request
* @return string
* @throws \ReflectionException
* @throws \Sammyjo20\Saloon\Exceptions\SaloonInvalidConnectorException
*/
private function buildFullUrl(SaloonRequest $request): string
{
$url = $request->getFullRequestUrl();
$glue = str_contains($url, '?') ? '&' : '?';
$query = http_build_query($request->getQuery());

return empty($query) ? $url : ($url . $glue . $query);
}
}
73 changes: 73 additions & 0 deletions tests/Feature/TelescopeRecorderTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
<?php

use Laravel\Telescope\EntryType;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Event;
use Sammyjo20\Saloon\Http\MockResponse;
use Sammyjo20\Saloon\Clients\MockClient;
use Sammyjo20\SaloonLaravel\Events\SentSaloonRequest;
use Sammyjo20\SaloonLaravel\Listeners\RecordSaloonToTelescope;
use Sammyjo20\SaloonLaravel\Tests\Fixtures\Requests\PostRequest;
use Sammyjo20\SaloonLaravel\Tests\Fixtures\Requests\UserRequest;
use Sammyjo20\SaloonLaravel\Tests\Fixtures\Connectors\TestConnector;

beforeEach(function () {
Event::listen(SentSaloonRequest::class, RecordSaloonToTelescope::class);
});

test('when a request is made - it is reported to telescope', function () {
$request = new PostRequest;

$mockClient = new MockClient([
MockResponse::fromRequest($request),
]);

$request->send($mockClient);

$entries = $this->loadTelescopeEntries()->where('type', EntryType::CLIENT_REQUEST);

expect($entries)->toHaveCount(1);

$entry = $entries->sole();
$entryContent = $entry->content;

// Make sure all fields are present, and it's stored correctly.

expect($entryContent)->toBeArray();
expect($entryContent)->toHaveKey('method', 'POST');
expect($entryContent)->toHaveKey('uri', 'https://tests.saloon.dev/api/post?test_query=hello');
expect($entryContent)->toHaveKey('headers', ['X-Foo' => 'Bar']);
expect($entryContent)->toHaveKey('payload', ['name' => 'Sammy']);
expect($entryContent)->toHaveKey('response_status', 200);
expect($entryContent)->toHaveKey('response_headers', ['Content-Type' => [0 => 'application/json'], 'X-Foo' => [0 => 'Bar']]);
expect($entryContent)->toHaveKey('response', ['name' => 'Sammy']);
expect($entryContent)->toHaveKey('saloon_connector', TestConnector::class);
expect($entryContent)->toHaveKey('saloon_request', PostRequest::class);

// Now we need to check the tags

$tags = DB::table('telescope_entries_tags')->get();

expect($tags)->toHaveCount(3);

$saloonTag = $tags->where('tag', 'Saloon')->first();
$requestTag = $tags->where('tag', 'SaloonRequest:' . PostRequest::class)->first();
$connectorTag = $tags->where('tag', 'SaloonConnector:' . TestConnector::class)->first();

expect($saloonTag)->not()->toBeNull();
expect($requestTag)->not()->toBeNull();
expect($connectorTag)->not()->toBeNull();
});

test('it will format the response data as json if it can determine the response is JSON', function () {
$mockClient = new MockClient([
MockResponse::make('Plain Text Response', 200, ['Content-Type' => 'text/plain']),
]);

UserRequest::make()->send($mockClient);

$entry = $this->loadTelescopeEntries()->where('type', EntryType::CLIENT_REQUEST)->sole();
$entryContent = $entry->content;

expect($entryContent)->toHaveKey('response', 'Plain Text Response');
});
63 changes: 63 additions & 0 deletions tests/Fixtures/Requests/PostRequest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
<?php

namespace Sammyjo20\SaloonLaravel\Tests\Fixtures\Requests;

use Sammyjo20\Saloon\Constants\Saloon;
use Sammyjo20\Saloon\Http\SaloonRequest;
use Sammyjo20\Saloon\Traits\Plugins\HasJsonBody;
use Sammyjo20\SaloonLaravel\Tests\Fixtures\Connectors\TestConnector;

class PostRequest extends SaloonRequest
{
use HasJsonBody;

/**
* Define the method that the request will use.
*
* @var string|null
*/
protected ?string $method = Saloon::POST;

/**
* The connector.
*
* @var string|null
*/
protected ?string $connector = TestConnector::class;

/**
* Define the endpoint for the request.
*
* @return string
*/
public function defineEndpoint(): string
{
return '/post';
}

/**
* Default Data
*
* @return string[]
*/
public function defaultData(): array
{
return [
'name' => 'Sammy',
];
}

public function defaultHeaders(): array
{
return [
'X-Foo' => 'Bar',
];
}

public function defaultQuery(): array
{
return [
'test_query' => 'hello',
];
}
}
30 changes: 30 additions & 0 deletions tests/TestCase.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,13 @@

namespace Sammyjo20\SaloonLaravel\Tests;

use Laravel\Telescope\Telescope;
use Illuminate\Support\Collection;
use Laravel\Telescope\Storage\EntryModel;
use Sammyjo20\SaloonLaravel\Facades\Saloon;
use Laravel\Telescope\TelescopeServiceProvider;
use Orchestra\Testbench\TestCase as BaseTestCase;
use Laravel\Telescope\Contracts\EntriesRepository;
use Sammyjo20\SaloonLaravel\SaloonServiceProvider;

class TestCase extends BaseTestCase
Expand All @@ -12,9 +17,22 @@ public function getPackageProviders($app)
{
return [
SaloonServiceProvider::class,
TelescopeServiceProvider::class,
];
}

/**
* Define database migrations.
*
* @return void
*/
protected function defineDatabaseMigrations()
{
$this->loadMigrationsFrom(__DIR__ . '/../vendor/laravel/telescope/database/migrations');

$this->artisan('migrate');
}

/**
* Override application aliases.
*
Expand All @@ -28,4 +46,16 @@ protected function getPackageAliases($app)
'Saloon' => Saloon::class,
];
}

/**
* Load the Telescope entries.
*
* @return Collection
*/
protected function loadTelescopeEntries(): Collection
{
Telescope::store(app(EntriesRepository::class));

return EntryModel::all();
}
}