109 lines
4.5 KiB
PHP
109 lines
4.5 KiB
PHP
<?php
|
|
|
|
use App\Jobs\RunCatalogueSync;
|
|
use App\Livewire\Admin\ConfirmSyncCatalogue;
|
|
use App\Livewire\Admin\Integrations;
|
|
use App\Models\PlanFamily;
|
|
use App\Services\Billing\CatalogueSyncStatus;
|
|
use App\Support\OperatingMode;
|
|
use App\Support\StripeCatalogueMode;
|
|
use Illuminate\Support\Facades\Queue;
|
|
use Livewire\Livewire;
|
|
|
|
/**
|
|
* `stripe:sync-catalogue` konnte nur aus einer Shell laufen, obwohl die
|
|
* Bereitschaftsseite den fehlenden Abgleich als blockierend meldet. Jetzt läuft
|
|
* er aus der Konsole — aber NICHT synchron im Livewire-Aufruf: er spricht mit
|
|
* Stripe, legt Produkte und Preise an und braucht Sekunden bis Minuten.
|
|
*/
|
|
beforeEach(function () {
|
|
config()->set('admin_access.secrets_key', 'base64:'.base64_encode(random_bytes(32)));
|
|
});
|
|
|
|
it('queues the sync instead of running it inside the request', function () {
|
|
Queue::fake();
|
|
|
|
Livewire::actingAs(operator('Owner'), 'operator')
|
|
->test(Integrations::class)
|
|
->call('syncCatalogue', true)
|
|
->assertHasNoErrors();
|
|
|
|
Queue::assertPushed(RunCatalogueSync::class, fn ($job) => $job->dryRun === true);
|
|
});
|
|
|
|
it('confirms the live run in a modal, and only the live run', function () {
|
|
// Der Trockenlauf ändert nichts und braucht keine Rückfrage — genau
|
|
// deshalb ist er der erste Knopf. Der echte Lauf legt Objekte in einem
|
|
// Zahlungskonto an (R23).
|
|
Queue::fake();
|
|
|
|
Livewire::actingAs(operator('Owner'), 'operator')
|
|
->test(ConfirmSyncCatalogue::class)
|
|
->call('confirm')
|
|
->assertDispatched('catalogue-sync-confirmed');
|
|
});
|
|
|
|
it('records what the command printed, word for word', function () {
|
|
// Der Trockenlauf zählt, was FEHLEN würde. Sein Text ist das, was der
|
|
// Betreiber sonst im Terminal gelesen hätte — gekürzt oder umformuliert
|
|
// wäre er eine zweite Quelle für dieselbe Aussage.
|
|
(new RunCatalogueSync(dryRun: true, by: 'chef@example.test'))->handle();
|
|
|
|
$last = app(CatalogueSyncStatus::class)->last();
|
|
|
|
expect($last)->not->toBeNull()
|
|
->and($last['dry_run'])->toBeTrue()
|
|
->and($last['by'])->toBe('chef@example.test')
|
|
// Die Zeilen, die der Betreiber sonst im Terminal gelesen hätte: was
|
|
// angelegt WÜRDE, Zeile für Zeile, plus die Summe darunter.
|
|
->and($last['output'])->toContain('would be created')
|
|
->and($last['output'])->toContain('price start v1 monthly');
|
|
});
|
|
|
|
it('shows the stale-catalogue refusal verbatim rather than a summary of it', function () {
|
|
// Die teure Meldung: sie warnt davor, einen Katalog zu leeren, an dem
|
|
// laufende Verträge hängen. Wer sie zu „Abgleich fehlgeschlagen"
|
|
// zusammenfasst, nimmt dem Betreiber genau die Anweisung weg, die er
|
|
// braucht — und die Alternative ist, sie doch wieder im Terminal zu suchen.
|
|
// Die Migration legt den Grundkatalog selbst an — eine dieser Familien
|
|
// trägt jetzt eine ID aus dem anderen Konto.
|
|
PlanFamily::query()->firstOrFail()->update(['stripe_product_id' => 'prod_from_the_other_account']);
|
|
StripeCatalogueMode::record(OperatingMode::current()->isTest() ? OperatingMode::Live : OperatingMode::Test);
|
|
|
|
(new RunCatalogueSync(dryRun: true, by: 'chef@example.test'))->handle();
|
|
|
|
$last = app(CatalogueSyncStatus::class)->last();
|
|
|
|
expect($last['ok'])->toBeFalse()
|
|
->and($last['output'])->toContain('plan_families.stripe_product_id')
|
|
->and($last['output'])->toContain('Nothing was created.');
|
|
});
|
|
|
|
it('puts the last result on the page', function () {
|
|
app(CatalogueSyncStatus::class)->record(ok: true, dryRun: true, output: 'MARKER-42 object(s) would be created', by: 'chef@example.test');
|
|
|
|
Livewire::actingAs(operator('Owner'), 'operator')
|
|
->test(Integrations::class)
|
|
->assertSee('MARKER-42 object(s) would be created');
|
|
});
|
|
|
|
it('does not let an operator without the catalogue capability start a run', function () {
|
|
// Am Modal geprüft, nicht an der Seite: ein Modal ist OHNE die
|
|
// Route-Middleware der Seite erreichbar (siehe EditPlanFamily), also ist
|
|
// das hier die Grenze, die wirklich hält. Die Seite selbst weist Support
|
|
// schon in mount() ab — das ist der Test darunter.
|
|
Queue::fake();
|
|
|
|
Livewire::actingAs(operator('Support'), 'operator')
|
|
->test(ConfirmSyncCatalogue::class)
|
|
->assertForbidden();
|
|
|
|
Queue::assertNothingPushed();
|
|
});
|
|
|
|
it('does not even show the page to an operator who cannot reach it', function () {
|
|
Livewire::actingAs(operator('Support'), 'operator')
|
|
->test(Integrations::class)
|
|
->assertForbidden();
|
|
});
|