From 507c9760353542f85cd56662ed4f43c03a0f88cc Mon Sep 17 00:00:00 2001 From: nexxo Date: Thu, 30 Jul 2026 16:58:22 +0200 Subject: [PATCH] Pin the one question that must never throw MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- .../Feature/CheckoutWithoutStripeKeyTest.php | 40 +++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/tests/Feature/CheckoutWithoutStripeKeyTest.php b/tests/Feature/CheckoutWithoutStripeKeyTest.php index ea49efe..d56558b 100644 --- a/tests/Feature/CheckoutWithoutStripeKeyTest.php +++ b/tests/Feature/CheckoutWithoutStripeKeyTest.php @@ -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();