CluPilotCloud/tests/Feature/Admin/CatalogueSyncTest.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();
});