CluPilotCloud/tests/Feature
nexxo 3b5dee8f0d Ein Ausweg wird nur angeboten, wenn er auch buchbar ist
DowngradeCheck baute seine Empfehlung aus packsToCover(), und die rechnet
bloss. BookAddon lehnt aus ZWEI Gruenden ab - der Deckel bei drei Bloecken
und der Ausschluss von Enterprise -, und die Empfehlung kannte keinen davon.
Solange ein Block 100 GB brachte, war das unerreichbar; seit er 20 GB bringt,
landet jede Ueberschreitung ueber 60 GB dort.

Business -> Team mit 600 GB belegt bot "5 x Zusatzspeicher buchen (+100 GB)"
an. Das Modal klemmte still auf drei, den vierten haette BookAddon abgelehnt,
und der Kunde stand nach 45 Euro im Monat genau dort, wo er vorher stand - auf
der Karte, die sein Abo billiger machen sollte. Enterprise -> Business empfahl
Bloecke, die es fuer dieses Paket ueberhaupt nicht gibt.

AddonCatalogue::bookableQuantity() antwortet jetzt auf beide Gruende. Es gab
einem Enterprise-Vertrag "3" auf die Frage, die sein eigener Docblock stellt.

DowngradeCheck stellt diese eine Frage und klemmt daran: `packs` ist die
gebrauchte Zahl nur, wenn der Vertrag sie auch buchen darf, sonst null - und
dann traegt `short`, was nach allen buchbaren Bloecken uebrig bliebe, gemessen
an DEREN Restmenge statt am Deckel, damit ein Kunde mit einem Block nicht mehr
zu loeschen bekommt als noetig. Kein halbes Angebot: eine Dauerbuchung, die
den Wechsel trotzdem nicht freigibt, ist kein Ausweg, sondern der ausgegraute
Knopf mit Preisschild.

packsToCover() bleibt reine Arithmetik, mit einem Kommentar, der sagt warum:
die kaufmaennische Grenze steht in AddonCatalogue, und ein Klemmen an dieser
Stelle zoege eine Vertragsabfrage in jeden Kontingent-Schritt und ins Portal,
die beide keine Verkaufsfrage stellen.

Der Knopf haengt schon an `packs > 0` und verschwindet von selbst; der Satz
wechselt auf downgrade_escape.capped, der die verbleibende Luecke nennt und
nicht den Grund - "hoechstens drei Bloecke" waere im Enterprise-Fall falsch,
wo es gar keine gibt.

Zwei bestehende Tests hingen an 600 und 800 GB aus der 100-GB-Zeit, beide
inzwischen nicht mehr deckbar; einer haette das Modal gesucht, das die Karte
zu Recht nicht mehr oeffnet. Auf 550 und 560 GB umgestellt, wo sie das pruefen,
wofuer sie geschrieben wurden.

Dazu ein Test, der belegt statt annimmt, warum das max(1, min(...)) in
ConfirmBookStorage stehen bleiben darf: ein Modal ist per openModal direkt
erreichbar, aber bookStoragePacks() legt weder am Deckel noch bei Enterprise
eine Bestellzeile an. Die bestehenden Tests deckten purchase() ab, nicht diese
Weiterleitung.

Voller Testlauf: 2405 bestanden. Pint sauber.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-01 15:40:26 +02:00
..
Admin Hostdetailseite: die Adresse steht neben den IPs und ist ein Klick zur Proxmox-Oberflaeche 2026-08-01 14:12:03 +02:00
Auth Accept terms instead of a start date, and fix the sender no server accepts 2026-07-30 15:22:35 +02:00
Billing Ein Ausweg wird nur angeboten, wenn er auch buchbar ist 2026-08-01 15:40:26 +02:00
Console Restart a machine, enforce the quota that was sold, end a route that ended 2026-07-29 18:28:28 +02:00
Host Hostnamen vergibt CluPilot: ein Name statt zweier, und der Zaehler ueberlebt das Loeschen 2026-08-01 13:45:43 +02:00
Mail Bereitschaft: das Absender-Postfach wird gefragt, ob es senden KANN 2026-07-31 20:35:58 +02:00
Portal Portal: Karte tauschen, offene Rechnungen begleichen, Hinweis bei Faelligkeit 2026-07-31 21:12:31 +02:00
Provisioning Fix-Runde 2: Warnung fuer nicht reparierte Hosts, case-sichere Vorabpruefung, wiederholbare Migration 2026-08-01 14:52:15 +02:00
Readiness Release v1.3.88 — die Vorlage baut sich selbst 2026-08-01 05:01:44 +02:00
BillingTest.php Refuse the sale instead of reaching Stripe without a key 2026-07-30 11:58:01 +02:00
BladeCommentsTest.php Sell the year as well as the month, and say what it saves 2026-07-30 15:44:35 +02:00
CartTest.php Zusatzspeicher: 20 GB fuer 15 Euro, hoechstens drei, nicht bei Enterprise 2026-08-01 15:09:02 +02:00
ChartLiveUpdateTest.php Release v1.3.91 — die Host-Seite zeigt Last statt Ausstattung 2026-08-01 10:24:21 +02:00
CheckoutPageTest.php Give the order a checkout page, and stop hiding what a year costs 2026-07-30 16:00:08 +02:00
CheckoutWithoutStripeKeyTest.php Pin the one question that must never throw 2026-07-30 16:58:22 +02:00
ComponentTest.php feat(portal): Fortify auth + Login/2FA/Dashboard + component kit 2026-07-25 01:20:25 +02:00
ConfirmInModalTest.php Replace native confirm() dialogs with the app's own modal pattern 2026-07-28 19:34:27 +02:00
ConnectionStateTest.php Recover silently from an expired session, and show a connection banner when offline 2026-07-28 21:36:21 +02:00
CustomDomainServingTest.php Fix nine defects in the provisioning pipelines 2026-07-30 01:34:55 +02:00
CustomDomainTest.php Close the domain page to packages that may not have one 2026-07-29 16:20:21 +02:00
CustomerTwoFactorTest.php Replace native confirm() dialogs with the app's own modal pattern 2026-07-28 19:34:27 +02:00
DashboardTest.php Stop mailing the initial admin password, hold it in the panel until noted 2026-07-28 23:19:20 +02:00
DeliveryPromiseTest.php Take the order, park it, and say when it will be delivered 2026-07-29 18:50:46 +02:00
DeploymentRunsAsTheAppUserTest.php Fix nine defects in the provisioning pipelines 2026-07-30 01:34:55 +02:00
DisplayTimezoneTest.php Show times on the operator's clock, keep storing them in UTC 2026-07-27 17:32:21 +02:00
DoubleOptInTest.php Require a confirmed address before an account can use anything 2026-07-28 23:43:20 +02:00
DowngradeTest.php Refuse the sale instead of reaching Stripe without a key 2026-07-30 11:58:01 +02:00
EditInModalTest.php R20: Begruendung im Modal, und der Waechter sieht jetzt auch Listenzeilen 2026-07-31 22:09:05 +02:00
EnsureCustomerActiveTest.php Satisfy Pint on the two new guard-boundary tests 2026-07-28 15:10:58 +02:00
ErrorPagesTest.php Ask the tunnel gateway where it actually listens 2026-07-27 14:43:23 +02:00
ExampleTest.php chore: bootstrap CluPilot control-plane (Laravel 13, Docker stack) 2026-07-25 00:21:30 +02:00
GrossPricingTest.php Quote what a person pays, and tab the settings page 2026-07-29 19:18:10 +02:00
HostStepTest.php Let an update install host packages, without handing out root 2026-07-29 10:27:56 +02:00
IconLayoutTest.php Ask the tunnel gateway where it actually listens 2026-07-27 14:43:23 +02:00
IdentitySeparationTest.php Write down that the console and the portal share no identity 2026-07-28 12:15:13 +02:00
ImpersonationTest.php Refuse a portal login for an address that already belongs to an operator 2026-07-28 14:42:16 +02:00
InstanceMetricsTest.php Measure what the template draws 2026-07-27 16:29:28 +02:00
LandingPriceSheetTest.php Quote what a person pays, and tab the settings page 2026-07-29 19:18:10 +02:00
MailSenderOwnershipTest.php Accept terms instead of a start date, and fix the sender no server accepts 2026-07-30 15:22:35 +02:00
MailTemplatesTest.php Actually issue the invoice when the money arrives 2026-07-29 03:25:56 +02:00
ModalHeightTest.php R24: a modal is never taller than the screen 2026-07-29 23:33:57 +02:00
MonitoringStatusSyncTest.php Move the console off /admin, give the status page its own address, and measure monitoring 2026-07-27 06:05:40 +02:00
NewDeviceWarningTest.php Recognise the devices an account signs in from, and warn about a new one 2026-07-28 23:28:34 +02:00
OfficialDomainsTest.php Re-apply what the last release commit silently reverted 2026-07-29 17:36:43 +02:00
OperatingModeTest.php Give the installation a test mode and a live mode 2026-07-30 10:41:23 +02:00
PasswordPolicyTest.php Passwortrichtlinie einstellbar, doppelte Anmelde-Mail behoben 2026-07-31 20:53:19 +02:00
PortalHostTest.php Answer on every name for the website, and send them all to one 2026-07-29 01:29:33 +02:00
PortalTabsTest.php Re-apply what the last release commit silently reverted 2026-07-29 17:36:43 +02:00
ProcessingAgreementTest.php Stop asking for a signature the law does not want, and lay out the tab 2026-07-30 17:30:55 +02:00
ProgressStepperTest.php Release v1.3.83 — Fortschritt zeigte Übersetzungsschlüssel 2026-08-01 02:08:28 +02:00
PublicSiteGateTest.php Stop a disabled operator from bypassing the hidden-site gate 2026-07-28 14:42:29 +02:00
ReadinessPageTest.php Hetzner-Cloud-DNS, Bereitschaftsseite aus sich heraus behebbar 2026-07-31 19:50:01 +02:00
ReleaseComparisonTest.php Stop the update agent dying on its own tag arithmetic 2026-07-29 00:00:17 +02:00
ReleaseVersionTest.php Move the console's identity out of the customer table 2026-07-28 10:31:43 +02:00
SeatsTest.php Replace native confirm() dialogs with the app's own modal pattern 2026-07-28 19:34:27 +02:00
SecretSlotMigrationTest.php Remember which Stripe account the catalogue was built in 2026-07-30 17:12:56 +02:00
SecretVaultModeTest.php Say which slot the value in force is really coming from 2026-07-30 17:01:33 +02:00
SelfServiceOrderTest.php Give the order a checkout page, and stop hiding what a year costs 2026-07-30 16:00:08 +02:00
ServerMarketTest.php Take the order, park it, and say when it will be delivered 2026-07-29 18:50:46 +02:00
SessionListTest.php Show where an account is signed in, and let it sign the other places out 2026-07-28 23:38:17 +02:00
SettingsTest.php Rebuild the settings page out of panels and rows 2026-07-30 17:38:57 +02:00
SignInMailOnceTest.php Passwortrichtlinie einstellbar, doppelte Anmelde-Mail behoben 2026-07-31 20:53:19 +02:00
SiteDesignSystemTest.php Write the processing agreement and its annex, and let it be downloaded 2026-07-30 17:12:30 +02:00
SparkGapTest.php Release v1.3.94 — die Kurven zeigen wieder, wie ausgelastet der Host ist 2026-08-01 11:56:10 +02:00
StatusHistoryTest.php Let an incident be deleted, and start measuring whether the hosts answer 2026-07-29 15:16:48 +02:00
StripeStrictModeTest.php Make source() respect strict entries like get() does 2026-07-30 11:21:07 +02:00
StripeWebhookSecretByModeTest.php Verify webhooks against the secret of the mode we are in 2026-07-30 12:13:55 +02:00
SupportRequestTest.php Editing in modals, an update button that is not gated on a stale reading, and a support page that is real 2026-07-27 17:55:49 +02:00
SwitchOperatingModeTest.php Fix round: name the slot on the card, and cover the modal that already worked 2026-07-30 15:32:16 +02:00
TrafficTest.php Let a failed measurement beat a green badge, and say which zone was missing 2026-07-31 15:26:27 +02:00
TranslationParityTest.php Ask the tunnel gateway where it actually listens 2026-07-27 14:43:23 +02:00
WelcomeTest.php Give customers two-factor, and stop every button on the settings page reacting at once 2026-07-27 08:48:34 +02:00