Pin the one question that must never throw

isConfigured() had no coverage at all: replacing its body with main's
`filled($this->secret())` left all 1795 tests green, while at runtime the
same mutation throws StripeNotConfigured. Six callers ask that question
precisely to avoid an exception — CheckoutController:87 would answer a
customer with a 500 instead of a sentence.

The merge makes it urgent rather than merely untidy: git auto-merges
HttpStripeClient.php without a conflict marker, so nothing forces anyone
to look at the one line where the two branches disagree.

Proved red by that exact mutation before it went in, then reverted:

  ⨯ it answers the configured question instead of throwing it
    StripeNotConfigured: No Stripe secret is stored for the [test] operating mode.
    at app/Services/Stripe/HttpStripeClient.php:376

Http::fake() + assertNothingSent(): the question is asked on every
checkout and may only read the vault.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
feature/betriebsmodus
nexxo 2026-07-30 16:58:22 +02:00
parent 49d528dbea
commit 507c976035
1 changed files with 40 additions and 0 deletions

View File

@ -3,10 +3,13 @@
use App\Exceptions\StripeNotConfigured;
use App\Livewire\Billing;
use App\Models\Customer;
use App\Models\Operator;
use App\Models\Order;
use App\Models\User;
use App\Services\Secrets\SecretVault;
use App\Services\Stripe\HttpStripeClient;
use App\Support\OperatingMode;
use Illuminate\Support\Facades\Http;
use Livewire\Livewire;
/**
@ -23,6 +26,43 @@ it('refuses to reach stripe without a key for the active mode', function () {
->toThrow(StripeNotConfigured::class);
});
/**
* Die andere Hälfte derselben Entscheidung, und der Riegel vor dem Merge:
* `secret()` WIRFT, `isConfigured()` WIRFT NICHT.
*
* Sechs Aufrufer stellen genau diese Frage, um KEINE Ausnahme zu bekommen
* CheckoutController:87 (der Kunde liest sonst eine 500 statt eines Satzes),
* SyncStripeCatalogue, RepriceStripeSubscriptions, SyncStripeAddonItems,
* MoveStripeSubscriptionPrice. `main` trägt an dieser Stelle noch
* `return filled($this->secret());`, und der Trockenlauf des Merges führt die
* Datei OHNE Konfliktmarkierung zusammen: niemand wird gezwungen hinzusehen.
* Mit `main`s Rumpf und dem werfenden `secret()` dieses Zweigs wird aus der
* Frage eine Ausnahme und ohne diesen Test bleiben dabei 1795 Tests grün
* (nachgewiesen im Schluss-Review durch genau diese Mutation).
*/
it('answers the configured question instead of throwing it', function () {
Http::fake();
OperatingMode::set(OperatingMode::Test);
// Nicht toBeFalse() allein: eine geworfene Ausnahme wäre kein „false",
// sondern gar keine Antwort — und genau das ist der Rückschritt.
expect(app(HttpStripeClient::class)->isConfigured())->toBeFalse();
// Die Frage darf auch nichts kosten: sie wird auf jedem Kassenaufruf
// gestellt und liest nur den Tresor.
Http::assertNothingSent();
});
it('says configured once the slot of the active mode is filled', function () {
Http::fake();
OperatingMode::set(OperatingMode::Test);
app(SecretVault::class)->put('stripe.secret', 'sk_test_x', Operator::factory()->create(), OperatingMode::Test);
expect(app(HttpStripeClient::class)->isConfigured())->toBeTrue();
Http::assertNothingSent();
});
it('does not create an order when the key is missing', function () {
OperatingMode::set(OperatingMode::Test);
$customer = Customer::factory()->create();