app(HttpStripeClient::class)->createPrice('prod_x', 100, 'EUR', 'month')) ->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(); // A portal user is matched to its customer by email, not by a foreign // key — see the existing Billing feature tests (e.g. StorageAllowanceTest). $user = User::factory()->create(['email' => $customer->email]); // 'team' is a real, sellable plan (seeded by the plan-catalogue migration) // that ranks above the customer's implicit 'start' plan — a genuine // upgrade the purchase() guards would otherwise accept. A made-up key // would be refused by the existing plan-validity check regardless of // whether the Stripe guard works at all, and would prove nothing. // // assertHasErrors(), not assertHasNoErrors(): the guard reports through // addError('purchase', …) — the same page-blocking pattern order.blade.php // already uses for "cannot proceed" — and Livewire's test harness folds // any non-empty error bag into the call's result. Asserting no errors // here would contradict the very behaviour this test exists to prove. Livewire::actingAs($user) ->test(Billing::class) ->call('purchase', 'upgrade', 'team') ->assertHasErrors('purchase'); // Der Punkt: nach der Zahlung wäre es zu spät. Es darf gar nichts entstehen. expect(Order::count())->toBe(0); }); it('says why instead of failing silently', function () { OperatingMode::set(OperatingMode::Test); $customer = Customer::factory()->create(); $user = User::factory()->create(['email' => $customer->email]); Livewire::actingAs($user) ->test(Billing::class) ->call('purchase', 'upgrade', 'team') ->assertSee(__('billing.stripe_not_configured')); });