[verified] Harden Etsy MCP validation and scheduled token refresh
Deploy HickLab3D Dashboard / Deploy to production (push) Successful in 38s
Deploy HickLab3D Dashboard / Deploy to production (push) Successful in 38s
This commit is contained in:
@@ -4,6 +4,7 @@ namespace Tests\Feature;
|
||||
|
||||
use App\Models\EtsyAppCredential;
|
||||
use App\Models\EtsyConnection;
|
||||
use App\Services\Etsy;
|
||||
use Illuminate\Foundation\Testing\LazilyRefreshDatabase;
|
||||
use Illuminate\Support\Carbon;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
@@ -22,6 +23,16 @@ class EtsySyncScheduledJobsTest extends TestCase
|
||||
Http::assertNothingSent();
|
||||
}
|
||||
|
||||
public function test_bundled_refresh_does_not_duplicate_the_independently_scheduled_etsy_sync(): void
|
||||
{
|
||||
$this->connectShop();
|
||||
Http::fake();
|
||||
|
||||
$this->artisan('integrations:refresh')->assertExitCode(0);
|
||||
|
||||
Http::assertNotSent(fn ($request): bool => str_contains($request->url(), 'etsy.com'));
|
||||
}
|
||||
|
||||
public function test_etsy_sync_refreshes_receipts_and_listings(): void
|
||||
{
|
||||
$this->connectShop();
|
||||
@@ -38,6 +49,28 @@ class EtsySyncScheduledJobsTest extends TestCase
|
||||
Http::assertSentCount(2);
|
||||
}
|
||||
|
||||
public function test_etsy_sync_returns_failure_if_a_required_refresh_fails(): void
|
||||
{
|
||||
$this->connectShop();
|
||||
Http::fake([
|
||||
'*/receipts*' => Http::response([], 503),
|
||||
'*/listings*' => Http::response(['results' => []]),
|
||||
]);
|
||||
$this->artisan('etsy:sync')->assertExitCode(1);
|
||||
Http::assertSentCount(2);
|
||||
}
|
||||
|
||||
public function test_etsy_sync_reports_failure_instead_of_claiming_stale_cache_was_refreshed(): void
|
||||
{
|
||||
$this->connectShop();
|
||||
Http::fake([
|
||||
'*/receipts*' => Http::sequence()->push(['results' => []])->push([], 503),
|
||||
'*/listings*' => Http::sequence()->push(['results' => []])->push([], 503),
|
||||
]);
|
||||
$this->artisan('etsy:sync')->assertExitCode(0);
|
||||
$this->artisan('etsy:sync')->assertExitCode(1);
|
||||
}
|
||||
|
||||
public function test_etsy_refresh_token_skips_cleanly_when_shop_is_not_connected(): void
|
||||
{
|
||||
Http::fake(fn () => Http::response([], 500));
|
||||
@@ -78,6 +111,23 @@ class EtsySyncScheduledJobsTest extends TestCase
|
||||
Http::assertNothingSent();
|
||||
}
|
||||
|
||||
public function test_refreshes_before_expiry_and_does_not_reuse_a_stale_rotating_token(): void
|
||||
{
|
||||
$this->freezeTime();
|
||||
$connection = $this->connectShop(expiresAt: now()->addMinutes(8));
|
||||
$stale = EtsyConnection::findOrFail($connection->id);
|
||||
Http::preventStrayRequests();
|
||||
Http::fake(['*/oauth/token' => Http::response([
|
||||
'access_token' => 'rotated-access', 'refresh_token' => 'rotated-refresh', 'expires_in' => 3600,
|
||||
])]);
|
||||
|
||||
$etsy = app(Etsy::class);
|
||||
$this->assertSame('rotated-access', $etsy->refreshAccessToken($connection));
|
||||
$this->assertSame('rotated-access', $etsy->refreshAccessToken($stale));
|
||||
Http::assertSentCount(1);
|
||||
$this->assertSame('rotated-refresh', $connection->fresh()->refresh_token);
|
||||
}
|
||||
|
||||
private function connectShop(?Carbon $expiresAt = null): EtsyConnection
|
||||
{
|
||||
EtsyAppCredential::create([
|
||||
|
||||
Reference in New Issue
Block a user