142 lines
6.5 KiB
PHP
142 lines
6.5 KiB
PHP
<?php
|
|
|
|
use App\Models\InvoiceSeries;
|
|
use App\Models\Operator;
|
|
use App\Models\PlanFamily;
|
|
use App\Models\PlanPrice;
|
|
use App\Services\Secrets\SecretVault;
|
|
use App\Support\OperatingMode;
|
|
use App\Support\Readiness;
|
|
use App\Support\Readiness\Check;
|
|
use App\Support\Settings;
|
|
|
|
/**
|
|
* Die Bereitschaftsprüfung berichtet, was fehlt — und sagt dazu, was
|
|
* kaputtgeht. Ein Feldname allein ist keine Auskunft: „vat_id fehlt" sagt
|
|
* nichts, „der Kunde bekommt keinen Beleg" sagt alles.
|
|
*/
|
|
function checkFor(string $key): ?Check
|
|
{
|
|
return collect(Readiness::all())->firstWhere('key', $key);
|
|
}
|
|
|
|
it('reports the stripe key of the active mode as missing', function () {
|
|
OperatingMode::set(OperatingMode::Test);
|
|
|
|
expect(checkFor('billing.stripe_secret')->satisfied)->toBeFalse();
|
|
expect(checkFor('billing.stripe_secret')->severity)->toBe(Check::SEVERITY_BLOCKING);
|
|
});
|
|
|
|
it('reports it as satisfied once the key of that mode is stored', function () {
|
|
OperatingMode::set(OperatingMode::Test);
|
|
app(SecretVault::class)->put('stripe.secret', 'sk_test_x', Operator::factory()->create(), OperatingMode::Test);
|
|
|
|
expect(checkFor('billing.stripe_secret')->satisfied)->toBeTrue();
|
|
});
|
|
|
|
it('does not accept a live key as proof while the test mode is active', function () {
|
|
// Sonst meldete die Seite Bereitschaft für einen Modus, in dem die Kasse
|
|
// sperrt — die Seite widerspräche der Sperre aus Task 5.
|
|
OperatingMode::set(OperatingMode::Test);
|
|
app(SecretVault::class)->put('stripe.secret', 'sk_live_x', Operator::factory()->create(), OperatingMode::Live);
|
|
|
|
expect(checkFor('billing.stripe_secret')->satisfied)->toBeFalse();
|
|
});
|
|
|
|
/**
|
|
* Ein Schlüssel im falschen Platz ist ein blockierender Befund.
|
|
*
|
|
* Gemessen im Schluss-Review: `put('stripe.secret', 'sk_live_REALMONEY')` im
|
|
* Testbetrieb wird angenommen, `get()` liefert ihn, die Bereitschaftsseite
|
|
* meldete „erfüllt" — und oben auf derselben Seite steht die Plakette
|
|
* „Testbetrieb". Das ist derselbe Ausgang, gegen den die strikte Ausnahme im
|
|
* Tresor gebaut wurde („bucht echtes Geld ab, während die Konsole Testbetrieb
|
|
* anzeigt"), nur über den eingetippten statt den automatischen Weg.
|
|
*
|
|
* Am Präfix ohne Netzverkehr entscheidbar — OperatingMode::ofStripeKey(),
|
|
* dieselbe Stelle, die auch die Migration und StripeCheck fragen.
|
|
*/
|
|
it('refuses a live key sitting in the test slot', function () {
|
|
OperatingMode::set(OperatingMode::Test);
|
|
app(SecretVault::class)->put('stripe.secret', 'sk_live_REALMONEY', Operator::factory()->create(), OperatingMode::Test);
|
|
|
|
expect(checkFor('billing.stripe_secret')->satisfied)->toBeFalse();
|
|
expect(checkFor('billing.stripe_secret')->severity)->toBe(Check::SEVERITY_BLOCKING);
|
|
});
|
|
|
|
it('refuses a test key sitting in the live slot', function () {
|
|
// Die andere Richtung kostet kein Geld, sondern nimmt keins ein: der Kunde
|
|
// zahlt in Stripes Testkonto, und die Bestellung sieht bezahlt aus.
|
|
OperatingMode::set(OperatingMode::Live);
|
|
app(SecretVault::class)->put('stripe.secret', 'sk_test_notreal', Operator::factory()->create(), OperatingMode::Live);
|
|
|
|
expect(checkFor('billing.stripe_secret')->satisfied)->toBeFalse();
|
|
});
|
|
|
|
it('accepts the restricted key of the active mode', function () {
|
|
// rk_ ist ein echter Schlüssel, kein Sonderfall — die Erkennung darf ihn
|
|
// nicht für einen Widerspruch halten, nur weil er nicht sk_ heißt.
|
|
OperatingMode::set(OperatingMode::Test);
|
|
app(SecretVault::class)->put('stripe.secret', 'rk_test_readonly', Operator::factory()->create(), OperatingMode::Test);
|
|
|
|
expect(checkFor('billing.stripe_secret')->satisfied)->toBeTrue();
|
|
});
|
|
|
|
it('says what the wrong key does, not that a field is empty', function () {
|
|
OperatingMode::set(OperatingMode::Test);
|
|
app(SecretVault::class)->put('stripe.secret', 'sk_live_REALMONEY', Operator::factory()->create(), OperatingMode::Test);
|
|
|
|
// Der Satz für „gar kein Schlüssel" wäre hier eine Lüge: einer liegt da,
|
|
// er ist der falsche, und die Folge ist eine ganz andere.
|
|
expect(checkFor('billing.stripe_secret')->breaks)
|
|
->not->toBe(__('readiness.billing.stripe_secret_breaks'));
|
|
});
|
|
|
|
it('reports incomplete company details without repeating the list', function () {
|
|
Settings::forget('company.name');
|
|
|
|
expect(checkFor('billing.company_details')->satisfied)->toBeFalse();
|
|
});
|
|
|
|
it('names what breaks, not just what is missing', function () {
|
|
expect(checkFor('billing.company_details')->breaks)->not->toBe('');
|
|
});
|
|
|
|
it('says the installation is not ready while something blocking is open', function () {
|
|
expect(Readiness::isReady())->toBeFalse();
|
|
expect(Readiness::blocking())->not->toBeEmpty();
|
|
});
|
|
|
|
it('does not report the invoice series ready when the required series is switched off', function () {
|
|
// IssueInvoice draws its series with `where('kind', ...)->where('active', true)`
|
|
// (IssueInvoice.php:373 and :444) — a deactivated 'invoice' row is as
|
|
// invisible to it as a missing one. Counting distinct kinds without the
|
|
// same `active` filter would report readiness for an installation where
|
|
// not a single invoice can draw a number.
|
|
InvoiceSeries::query()->where('kind', 'invoice')->update(['active' => false]);
|
|
|
|
expect(checkFor('billing.invoice_series')->satisfied)->toBeFalse();
|
|
});
|
|
|
|
it('does not raise a permanent alarm for a draft version stripe never touches', function () {
|
|
// stripe:sync-catalogue only mirrors PUBLISHED versions (its own header
|
|
// comment: "Only PUBLISHED versions are synced. A draft has promised
|
|
// nothing..."). A draft is an entirely ordinary state — there is a
|
|
// dedicated modal to create one — so a query that does not also filter on
|
|
// `published_at` would keep this check permanently unsatisfied on any
|
|
// installation that has ever drafted a next version: an alarm that is
|
|
// always on and therefore never read.
|
|
PlanPrice::query()->update(['stripe_price_id' => 'price_already_synced']);
|
|
|
|
$family = PlanFamily::query()->create(['key' => 'draftonly', 'name' => 'Draft Only', 'tier' => 9]);
|
|
$draft = $family->versions()->create([
|
|
'version' => 1, 'quota_gb' => 10, 'traffic_gb' => 100, 'seats' => 1, 'ram_mb' => 1024,
|
|
'cores' => 1, 'disk_gb' => 20, 'performance' => 'standard', 'features' => [],
|
|
'available_from' => now(),
|
|
// published_at left null: a draft, exactly what stripe:sync-catalogue skips.
|
|
]);
|
|
$draft->prices()->create(['term' => 'monthly', 'amount_cents' => 1000, 'currency' => 'EUR']);
|
|
|
|
expect(checkFor('billing.catalogue_synced')->satisfied)->toBeTrue();
|
|
});
|