saloonphp / saloon

🤠 Build beautiful API integrations and SDKs with Saloon
https://docs.saloon.dev
MIT License
2.03k stars 105 forks source link

Tests using Fixtures with custom Authenticators record incorrect response #413

Open Welfordian opened 3 months ago

Welfordian commented 3 months ago

Whilst testing my Saloon API integration with Pest, I stumbled upon the following message: Saloon was unable to guess a mock response for your request, consider using a wildcard url mock or a connector mock..

After a few minutes of debugging, I narrowed it down to the custom authenticator which makes another request to get the authentication token.

When using a fixture & the custom authenticator, Saloon appears to see the request to the authentication endpoint as the response from the parent request using the authentication.

Simply removing the defaultAuth method from App\Http\Integrations\HTTPDump\Requests\DumpRequest will cause the test to succeed.

This is Saloon v2, but the documentation seems to have a similar method for v3: https://docs.saloon.dev/conclusion/how-to-guides/per-request-authentication

Test below.

// tests/Feature/Integrations/HTTPDump/HTTPDumpConnector.php

it('helps debug saloon fixtures', function () {
    Saloon::fake([
        \App\Http\Integrations\HTTPDump\Requests\DumpRequest::class => MockResponse::fixture(
            'http_dump'
        ),
    ]);

    $httpDump = new \App\Http\Integrations\HTTPDump\HTTPDumpConnector();

    $response = $httpDump->send(
        new \App\Http\Integrations\HTTPDump\Requests\DumpRequest(
            '622a1f16-51ba-4d0b-a2ea-0b44d0176aef'
        )
    );

    expect($response->status())->toBe(204);
});
// app/Http/Integrations/HTTPDump/HTTPDumpConnector.php

<?php

namespace App\Http\Integrations\HTTPDump;

use Saloon\Http\Connector;
use Saloon\Traits\Plugins\AcceptsJson;

class HTTPDumpConnector extends Connector
{
    use AcceptsJson;

    public function resolveBaseUrl(): string
    {
        return 'https://httpdump.app/dumps';
    }
}
// app/Http/Integrations/HTTPDump/Requests/DumpRequest.php

<?php

namespace App\Http\Integrations\HTTPDump\Requests;

use App\Http\Integrations\HTTPDump\Auth\TestAuth;
use Saloon\Contracts\Authenticator;
use Saloon\Contracts\Body\HasBody;
use Saloon\Enums\Method;
use Saloon\Http\Request;
use Saloon\Traits\Body\HasJsonBody;

class DumpRequest extends Request implements HasBody
{
    use HasJsonBody;

    protected Method $method = Method::POST;

    public function __construct(
        protected readonly string $uuid
    ) {

    }

    public function resolveEndpoint(): string
    {
        return '/'.$this->uuid;
    }

    protected function defaultAuth(): ?Authenticator
    {
        return new TestAuth();
    }
}
// app/Http/Integrations/HTTPDump/Auth/TestAuth.php

<?php

namespace App\Http\Integrations\HTTPDump\Auth;

use App\Http\Integrations\HTTPDump\Requests\AuthRequest;
use Saloon\Contracts\Authenticator;
use Saloon\Contracts\PendingRequest;

readonly class TestAuth implements Authenticator
{
    public function set(PendingRequest $pendingRequest): void
    {
        if ($pendingRequest->getRequest() instanceof AuthRequest) {
            return;
        }

        $response = $pendingRequest
            ->getConnector()
            ->send(
                new AuthRequest()
            );

        $pendingRequest->headers()->add('Authorization', 'Bearer '.$response->json('access_token'));
    }
}
// app/Http/Integrations/HTTPDump/Requests/AuthRequest.php

<?php

namespace App\Http\Integrations\HTTPDump\Requests;

use Saloon\Enums\Method;
use Saloon\Http\Request;

class AuthRequest extends Request
{
    protected Method $method = Method::POST;

    public function resolveEndpoint(): string
    {
        return '/622a1f16-51ba-4d0b-a2ea-0b44d0176aef';
    }
}
Welfordian commented 3 months ago

Seems that even adding a mocked response for the authentication request does not resolve this issue.