actingAs(Operator::factory()->role('Read-only')->create(), 'operator') ->get(route('admin.readiness')) ->assertForbidden(); }); it('lists every check, satisfied or not', function () { // Livewire::actingAs() needs the 'operator' guard spelled out — omitting it // signed the test in on the 'web' guard instead and cost Task 11 a failing // test for exactly this reason. Livewire::actingAs(Operator::factory()->role('Owner')->create(), 'operator') ->test(ReadinessPage::class) ->assertSee(__('readiness.billing.company_details')) ->assertSee(__('readiness.provisioning.usable_host')); }); it('says what breaks, not only that something is missing', function () { Livewire::actingAs(Operator::factory()->role('Owner')->create(), 'operator') ->test(ReadinessPage::class) ->assertSee(__('readiness.delivery.mailer_not_log_breaks')); }); it('runs an active check on demand and shows its answer', function () { // Die drei Prüfungen aus Task 9 sind nur dann etwas wert, wenn jemand sie // auslösen kann. Sie laufen NICHT bei jedem Seitenaufruf mit: eine davon // schreibt einen TXT-Eintrag in die echte Zone. // // Http::fake() steht hier unbedingt, nicht weil dieser eine Testpfad das // Netzwerk erreicht (DnsTokenCheck bricht schon vorher an blank($token) // || blank($zone) ab), sondern weil JEDER Test, der runCheck() anfasst, // dieselbe Absicherung tragen soll — ein Testpfad, der heute früh // ausscheidet, ist keine Garantie, dass er es morgen noch tut. Http::fake(); Livewire::actingAs(Operator::factory()->role('Owner')->create(), 'operator') ->test(ReadinessPage::class) ->call('runCheck', 'provisioning.dns_token') ->assertSet('results.provisioning.dns_token.ok', false); }); /** * Der „Prüfen"-Knopf darf der Prüfung nicht widersprechen. * * StripeCheck::run() rechnet die Kontoart des Schlüssels aus, die Seite hat * sie verworfen und nur `ok`/`reason` gerendert — ein Live-Schlüssel im * Testplatz antwortet bei Stripe einwandfrei, und die Seite las darüber * „Geprüft: in Ordnung", während die Plakette oben „Testbetrieb" sagte. */ it('names the account a checked key belongs to, and says when it is the wrong one', function () { Http::fake(['*' => Http::response(['id' => 'acct_1'], 200)]); OperatingMode::set(OperatingMode::Test); app(SecretVault::class)->put('stripe.secret', 'sk_live_REALMONEY', Operator::factory()->create(), OperatingMode::Test); Livewire::actingAs(Operator::factory()->role('Owner')->create(), 'operator') ->test(ReadinessPage::class) ->call('runCheck', 'billing.stripe_secret') ->assertSee(__('readiness.check_key_mode', ['mode' => __('readiness.mode.live')])) ->assertSee(__('readiness.check_key_mode_conflict', ['mode' => __('readiness.mode.test')])); }); it('refuses to run a check that is not on the list', function () { // Ein Schlüssel aus dem Browser. Ohne diese Prüfung wäre das ein Weg, // beliebige Klassen aufzurufen. Http::fake(); Livewire::actingAs(Operator::factory()->role('Owner')->create(), 'operator') ->test(ReadinessPage::class) ->call('runCheck', 'etwas.erfundenes') ->assertSet('results', []); }); /** * Der Wächter dieser Spec. * * Ein künftig hinzugefügtes Zugangsdatum darf nicht still an der Übersicht * vorbeigehen. Genau das war der Befund vom 2026-07-30: eine grüne Testsuite * bei einer Installation, die keinen Host durchinstallieren konnte, weil * niemand die Liste geführt hat. */ it('covers every entry in the secret registry', function () { $covered = collect(Readiness::all())->pluck('key')->implode(' '); foreach (array_keys(SecretVault::REGISTRY) as $secret) { // `stripe.secret` → irgendein Prüfschlüssel, der `stripe_secret` enthält. $needle = str_replace('.', '_', $secret); // NICHT expect($covered)->toContain($needle, $message) — Pest's // toContain() ist variadic, ein zweites Argument ist ein zweiter // GEFORDERTER Substring, keine eigene Fehlermeldung. Genau diese Falle // dokumentiert tests/Feature/ModalHeightTest.php bereits selbst // ("asserted that the footer contains its own filename, which is why // this failed on a file that was correct") — der Brief-Code für diesen // Test tappte hinein: er scheiterte auf JEDEM Eintrag, auch auf // 'stripe_secret', das längst abgedeckt ist. str_contains() + toBeTrue() // mit Nachricht liefert die Prüfung UND den lesbaren Hinweis. expect(str_contains($covered, $needle))->toBeTrue( "Der Tresor-Eintrag [{$secret}] taucht auf der Bereitschaftsseite nicht auf." ); } }); /** * Fix-Runde, Befund 1: der Auflöser (checkUrl()) ist nur so gut wie seine * Quelle. Neun Prüfschlüssel trugen `tab: 'integrations'` — kein Mitglied von * Integrations::TABS — und landeten deshalb alle auf dem Standard-Reiter * ('services'), unabhängig davon, wo ihr Feld tatsächlich liegt. Dieser Test * hält JEDEN Prüfschlüssel gegen die eine Stelle, an der sein Feld wirklich * steht — nicht nur die neun, die diesmal falsch waren — damit der nächste * falsche Wert genauso durchrutscht wie diese. */ it('sends every check to the tab or page where its field actually lives', function () { $expectedTab = [ 'billing.stripe_secret' => 'services', 'billing.webhook_secret' => 'env', 'billing.company_details' => 'company', 'billing.invoice_series' => 'company', 'billing.catalogue_synced' => 'services', 'onboarding.ssh_private_key' => 'platform', 'onboarding.secrets_key' => 'env', 'onboarding.vpn_config_key' => 'env', 'onboarding.wg_hub' => 'platform', 'onboarding.datacenter' => 'datacenters', 'provisioning.dns_token' => 'services', 'provisioning.dns_zone' => 'services', 'provisioning.traefik_path' => 'services', 'provisioning.usable_host' => 'hosts', 'provisioning.vm_template' => 'plans', 'provisioning.monitoring_token' => 'services', 'delivery.mailer_not_log' => 'mail', 'delivery.mailbox' => 'mail', 'delivery.mail_templates' => 'templates', 'delivery.inbound_mail_password' => 'services', 'operation.scheduler' => 'env', 'operation.queue_provisioning' => 'env', ]; foreach (Readiness::all() as $check) { // NICHT expect($expectedTab)->toHaveKey($check->key, $message) — genau // dieselbe Falle wie bei toContain() weiter oben: toHaveKey()s zweites // Argument ist der erwartete WERT des Schlüssels, keine Fehlermeldung. expect(array_key_exists($check->key, $expectedTab))->toBeTrue( "Neuer Prüfschlüssel [{$check->key}] hat keinen erwarteten tab-Wert in diesem Test — bitte nachtragen." ); expect($check->tab)->toBe( $expectedTab[$check->key], "{$check->key} zeigt auf '{$check->tab}', sein Feld liegt aber unter '{$expectedTab[$check->key]}'." ); } }); /** * Fix-Runde, Befund 2: mount() lässt hosts.manage ODER secrets.manage durch, * runCheck() verlangt aber secrets.manage allein. Ein Admin (nur * hosts.manage) erreicht die Seite und sah bislang auch den Knopf — der beim * Drücken 403 geworfen hätte. Ein sichtbarer Knopf, der beim Drücken * scheitert, ist schlimmer als gar keiner. */ it('hides the check button from an operator who could not press it anyway', function () { Livewire::actingAs(Operator::factory()->role('Admin')->create(), 'operator') ->test(ReadinessPage::class) ->assertDontSee(__('readiness.run_check')); }); /** * Fix-Runde, Befund 3: die eine Eigenschaft, wegen der diese ganze * Konstruktion existiert — die vier RUNNABLE-Prüfungen laufen NUR auf * Knopfdruck, weil DnsTokenCheck einen echten TXT-Eintrag in die * Produktivzone schreibt — war bislang ungeprüft. */ it('sends nothing over the network on a plain page load', function () { Http::fake(); Livewire::actingAs(Operator::factory()->role('Owner')->create(), 'operator') ->test(ReadinessPage::class); Http::assertNothingSent(); });