Files
InvoiceShelf/tests/Feature/Marketplace/MarketplacePairingTest.php
T
Darko Gjorgjijoski 5ef7804e60 refactor: adopt modular domain architecture (#747)
* refactor: stabilize model identities for domain migration

* refactor: extract module platform context

* refactor: assign models to domain contexts

* refactor: extract ai platform context

* refactor: extract storage platform context

* refactor: extract mail platform context

* refactor: extract pdf platform context

* refactor: extract operations platform context

* refactor: move installation into operations platform

* refactor: extract money domain context

* refactor: extract taxation domain context

* refactor: extract catalog domain context

* refactor: extract metadata domain context

* refactor: extract reporting domain context

* refactor: extract purchases domain context

* refactor: extract receivables domain context

* refactor: extract accounts domain context

* refactor: complete reporting statement boundary

* refactor: extract contacts domain context

* refactor: extract sales domain context

* refactor: remove legacy application layers

* fix: migrate legacy bouncer role identities
2026-08-05 17:40:03 +02:00

98 lines
4.3 KiB
PHP

<?php
use App\Domains\Accounts\Models\User;
use App\Platform\Modules\Models\MarketplaceCredential;
use Illuminate\Support\Facades\Artisan;
use Illuminate\Support\Facades\Crypt;
use Illuminate\Support\Facades\Http;
use Laravel\Sanctum\Sanctum;
use function Pest\Laravel\deleteJson;
use function Pest\Laravel\postJson;
beforeEach(function () {
Artisan::call('db:seed', ['--class' => 'DatabaseSeeder', '--force' => true]);
Artisan::call('db:seed', ['--class' => 'DemoSeeder', '--force' => true]);
Sanctum::actingAs(User::findOrFail(1), ['*']);
config()->set('invoiceshelf.base_url', 'https://marketplace.test');
});
it('starts pairing with installation compatibility metadata', function () {
Http::fake([
'https://marketplace.test/api/marketplace/v1/device/code' => Http::response([
'success' => true, 'device_code' => 'device-code', 'user_code' => 'ABCD1234',
'verification_uri' => 'https://marketplace.test/pair', 'expires_in' => 600, 'interval' => 5,
], 201),
]);
postJson('/api/v1/modules/pairing/start')
->assertCreated()
->assertJsonPath('user_code', 'ABCD1234');
Http::assertSent(function ($request): bool {
$data = $request->data();
return $request->url() === 'https://marketplace.test/api/marketplace/v1/device/code'
&& filled($data['installation_name'] ?? null)
&& isset($data['module_api_version'], $data['php_version'], $data['extensions'])
&& collect($data['extensions'])->every(
fn ($extension): bool => is_string($extension)
&& preg_match('/^ext-[a-z0-9][a-z0-9_-]*$/', $extension) === 1,
);
});
});
it('stores only the encrypted opaque installation token after pairing', function () {
Http::fake([
'https://marketplace.test/api/marketplace/v1/device/code' => Http::response([
'success' => true, 'device_code' => 'device-code', 'user_code' => 'ABCD1234',
'verification_uri' => 'https://marketplace.test/pair', 'expires_in' => 600, 'interval' => 5,
], 201),
'https://marketplace.test/api/marketplace/v1/device/token' => Http::response([
'success' => true, 'installation_token' => 'opaque-installation-token', 'installation' => ['id' => 17, 'name' => 'Local'],
]),
]);
postJson('/api/v1/modules/pairing/start')->assertCreated();
postJson('/api/v1/modules/pairing/poll')->assertOk()->assertJsonPath('status', 'paired');
$credential = MarketplaceCredential::query()->sole();
expect($credential->credential)->not->toContain('opaque-installation-token')
->and(Crypt::decryptString($credential->credential))->toBe('opaque-installation-token')
->and($credential->device_id)->toBe('17');
});
it('reports pending device approval without storing a credential', function () {
Http::fake([
'https://marketplace.test/api/marketplace/v1/device/code' => Http::response([
'success' => true, 'device_code' => 'device-code', 'user_code' => 'ABCD1234',
'verification_uri' => 'https://marketplace.test/pair', 'expires_in' => 600, 'interval' => 5,
], 201),
'https://marketplace.test/api/marketplace/v1/device/token' => Http::response([
'success' => false, 'error' => 'authorization_pending', 'interval' => 5,
], 428),
]);
postJson('/api/v1/modules/pairing/start')->assertCreated();
postJson('/api/v1/modules/pairing/poll')->assertOk()->assertJsonPath('status', 'pending');
expect(MarketplaceCredential::query()->doesntExist())->toBeTrue();
});
it('revokes the remote installation when disconnecting locally', function () {
MarketplaceCredential::query()->create([
'credential' => Crypt::encryptString('opaque-installation-token'),
'paired_at' => now(),
]);
Http::fake([
'https://marketplace.test/api/marketplace/v1/device' => Http::response(['success' => true]),
]);
deleteJson('/api/v1/modules/pairing')->assertOk()->assertJsonPath('success', true);
expect(MarketplaceCredential::query()->exists())->toBeFalse();
Http::assertSent(fn ($request) => $request->method() === 'DELETE'
&& $request->url() === 'https://marketplace.test/api/marketplace/v1/device'
&& $request->hasHeader('Authorization', 'Bearer opaque-installation-token'));
});