Expose php artisan etsy:mcp as JSON-RPC stdio next to laravel-boost, reuse the stored HickLab3D shop connection, create listings as drafts only, and skip Etsy v3 conversation send because that API does not exist.
151 lines
7.3 KiB
PHP
151 lines
7.3 KiB
PHP
<?php
|
|
|
|
namespace Tests\Feature;
|
|
|
|
use App\Models\EtsyAppCredential;
|
|
use App\Models\EtsyConnection;
|
|
use App\Models\User;
|
|
use Illuminate\Foundation\Testing\LazilyRefreshDatabase;
|
|
use Illuminate\Support\Facades\Http;
|
|
use Tests\TestCase;
|
|
|
|
class EtsySettingsTest extends TestCase
|
|
{
|
|
use LazilyRefreshDatabase;
|
|
|
|
public function test_only_administrators_can_view_and_change_etsy_credentials(): void
|
|
{
|
|
$this->get(route('settings.index'))->assertRedirect(route('login'));
|
|
|
|
$user = User::factory()->create();
|
|
$this->actingAs($user)->get(route('settings.index'))->assertForbidden();
|
|
$this->actingAs($user)->put(route('settings.etsy.update'), [
|
|
'keystring' => 'key', 'shared_secret' => 'secret',
|
|
])->assertForbidden();
|
|
$this->assertDatabaseCount('etsy_app_credentials', 0);
|
|
}
|
|
|
|
public function test_administrator_saves_encrypted_credentials_for_oauth_without_exposing_them(): void
|
|
{
|
|
config()->set('services.etsy.keystring', 'old-server-key');
|
|
config()->set('services.etsy.shared_secret', 'old-server-secret');
|
|
$admin = User::factory()->create(['role' => 'admin']);
|
|
|
|
$this->actingAs($admin)->put(route('settings.etsy.update'), [
|
|
'keystring' => 'saved-key', 'shared_secret' => 'saved-secret',
|
|
])->assertRedirect(route('settings.index'))->assertSessionHas('status');
|
|
|
|
$credentials = EtsyAppCredential::firstOrFail();
|
|
$this->assertSame('saved-key', $credentials->keystring);
|
|
$this->assertSame('saved-secret', $credentials->shared_secret);
|
|
$this->assertNotSame('saved-key', $credentials->getRawOriginal('keystring'));
|
|
$this->assertNotSame('saved-secret', $credentials->getRawOriginal('shared_secret'));
|
|
$this->assertArrayNotHasKey('shared_secret', $credentials->toArray());
|
|
|
|
$this->actingAs($admin)->get(route('settings.index'))
|
|
->assertOk()->assertSee('App credentials saved in dashboard.')
|
|
->assertDontSee('saved-key')->assertDontSee('saved-secret');
|
|
$this->actingAs($admin)->post(route('etsy.connect'))
|
|
->assertRedirectContains('client_id=saved-key');
|
|
}
|
|
|
|
public function test_oauth_api_requests_use_the_saved_shared_secret(): void
|
|
{
|
|
Http::preventStrayRequests();
|
|
Http::fake([
|
|
'https://api.etsy.com/v3/public/oauth/token' => Http::response(['access_token' => '123.token', 'refresh_token' => '123.refresh', 'expires_in' => 3600]),
|
|
'https://api.etsy.com/v3/application/users/123/shops' => Http::response(['shop_id' => 42, 'shop_name' => 'HickLab3D']),
|
|
]);
|
|
config()->set('services.etsy.keystring', 'old-server-key');
|
|
config()->set('services.etsy.shared_secret', 'old-server-secret');
|
|
$admin = User::factory()->create(['role' => 'admin']);
|
|
$this->actingAs($admin)->put(route('settings.etsy.update'), [
|
|
'keystring' => 'saved-key', 'shared_secret' => 'saved-secret',
|
|
])->assertRedirect(route('settings.index'));
|
|
|
|
$this->actingAs($admin)->withSession(['etsy_oauth' => [
|
|
'state' => 'expected', 'verifier' => 'verifier', 'started' => time(),
|
|
]])->get(route('etsy.callback', ['state' => 'expected', 'code' => 'code']))
|
|
->assertRedirect(route('etsy.index'));
|
|
|
|
Http::assertSent(fn ($request) => $request->url() === 'https://api.etsy.com/v3/application/users/123/shops'
|
|
&& $request->hasHeader('x-api-key', 'saved-key:saved-secret'));
|
|
}
|
|
|
|
public function test_initial_credentials_require_both_values(): void
|
|
{
|
|
$admin = User::factory()->create(['role' => 'admin']);
|
|
|
|
$this->actingAs($admin)->put(route('settings.etsy.update'), [
|
|
'keystring' => 'key',
|
|
])->assertSessionHasErrors('shared_secret');
|
|
$this->assertDatabaseCount('etsy_app_credentials', 0);
|
|
}
|
|
|
|
public function test_blank_fields_keep_saved_credentials_and_new_key_disconnects_old_shop(): void
|
|
{
|
|
$admin = User::factory()->create(['role' => 'admin']);
|
|
$this->actingAs($admin)->put(route('settings.etsy.update'), [
|
|
'keystring' => 'first-key', 'shared_secret' => 'first-secret',
|
|
])->assertRedirect(route('settings.index'));
|
|
EtsyConnection::create([
|
|
'shop_id' => 42,
|
|
'shop_name' => 'HickLab3D',
|
|
'access_token' => '123.token',
|
|
'refresh_token' => '123.refresh',
|
|
'expires_at' => now()->addHour(),
|
|
]);
|
|
|
|
$this->actingAs($admin)->put(route('settings.etsy.update'), [
|
|
'keystring' => '', 'shared_secret' => '',
|
|
])->assertRedirect(route('settings.index'));
|
|
$this->assertSame('first-key', EtsyAppCredential::firstOrFail()->keystring);
|
|
$this->assertDatabaseCount('etsy_connections', 1);
|
|
|
|
$this->actingAs($admin)->put(route('settings.etsy.update'), [
|
|
'keystring' => 'second-key', 'shared_secret' => '',
|
|
])->assertRedirect(route('settings.index'))->assertSessionHas('status');
|
|
$this->assertSame('second-key', EtsyAppCredential::firstOrFail()->keystring);
|
|
$this->assertSame('first-secret', EtsyAppCredential::firstOrFail()->shared_secret);
|
|
$this->assertDatabaseCount('etsy_connections', 0);
|
|
}
|
|
|
|
public function test_shop_name_and_oauth_callback_are_saved_with_server_credentials(): void
|
|
{
|
|
config()->set('services.etsy.keystring', 'server-key');
|
|
config()->set('services.etsy.shared_secret', 'server-secret');
|
|
config()->set('services.etsy.redirect_uri', 'https://old.example.test/etsy/callback');
|
|
$admin = User::factory()->create(['role' => 'admin']);
|
|
|
|
$this->actingAs($admin)->put(route('settings.etsy.update'), [
|
|
'shop_name' => 'HickLab3D',
|
|
'redirect_uri' => 'https://new.example.test/etsy/callback',
|
|
])->assertRedirect(route('settings.index'));
|
|
|
|
$credentials = EtsyAppCredential::firstOrFail();
|
|
$this->assertSame('server-key', $credentials->keystring);
|
|
$this->assertSame('server-secret', $credentials->shared_secret);
|
|
$this->assertSame('HickLab3D', $credentials->shop_name);
|
|
$this->assertSame('https://new.example.test/etsy/callback', $credentials->redirect_uri);
|
|
$this->assertNotSame($credentials->redirect_uri, $credentials->getRawOriginal('redirect_uri'));
|
|
$this->actingAs($admin)->post(route('etsy.connect'))
|
|
->assertRedirectContains('https%3A%2F%2Fnew.example.test%2Fetsy%2Fcallback');
|
|
$this->actingAs($admin)->get(route('settings.index'))
|
|
->assertOk()->assertDontSee('server-secret');
|
|
}
|
|
|
|
public function test_oauth_connect_requests_shop_management_scopes(): void
|
|
{
|
|
config()->set('services.etsy', ['keystring' => 'key', 'shared_secret' => 'secret', 'redirect_uri' => 'https://example.test/etsy/callback']);
|
|
$admin = User::factory()->create(['role' => 'admin']);
|
|
|
|
$location = $this->actingAs($admin)->post(route('etsy.connect'))->headers->get('Location');
|
|
foreach (['listings_r', 'listings_w', 'transactions_r', 'transactions_w', 'shops_r'] as $scope) {
|
|
$this->assertStringContainsString($scope, $location);
|
|
}
|
|
$this->assertStringNotContainsString('listings_d', $location);
|
|
$this->assertStringNotContainsString('shops_w', $location);
|
|
$this->assertStringNotContainsString('conversations_', $location);
|
|
}
|
|
}
|