where('key', 'team')->firstOrFail(); $version = PlanVersion::query()->where('plan_family_id', $team->id)->firstOrFail(); $monthly = $version->prices()->where('term', 'monthly')->firstOrFail(); $page = $this->get('/')->assertOk(); // Whole euros, German grouping, non-breaking space — the exact string the // sheet renders, so a formatting change cannot slip past this test. $page->assertSee(number_format($monthly->amount_cents / 100, 0, ',', '.')."\u{00A0}€", false); $page->assertSee('bis '.$version->seats); // The included traffic is sold and metered but was simply never printed. $page->assertSee('Datenvolumen'); $page->assertSee($version->traffic_gb >= 1000 && $version->traffic_gb % 1000 === 0 ? ($version->traffic_gb / 1000).' TB' : $version->traffic_gb.' GB'); }); it('follows the catalogue when the owner changes a price', function () { // The point of the whole exercise: one place to change a price. DB::table('plan_prices') ->where('plan_version_id', PlanVersion::query() ->where('plan_family_id', PlanFamily::query()->where('key', 'team')->value('id')) ->value('id')) ->where('term', 'monthly') ->update(['amount_cents' => 21500]); $this->get('/') ->assertOk() ->assertSee("215\u{00A0}€", false) ->assertDontSee("179\u{00A0}€", false); }); it('keeps the front page up when the catalogue cannot be read', function () { // A second version of the same plan on sale at the same moment. The // catalogue refuses to guess which one is being sold — correctly, because // guessing would mean quoting a price at random. The marketing page must // survive that: an outage in the shop is not an outage of the company. Log::spy(); $team = PlanFamily::query()->where('key', 'team')->firstOrFail(); $original = PlanVersion::query()->where('plan_family_id', $team->id)->firstOrFail(); $clone = collect($original->getAttributes())->except('id')->all(); $clone['uuid'] = (string) Str::uuid(); $clone['version'] = $original->version + 1; DB::table('plan_versions')->insert($clone); $this->get('/') ->assertOk() ->assertSee('Preise derzeit auf Anfrage') // No number survives from the broken catalogue. ->assertDontSee("179\u{00A0}€", false); Log::shouldHaveReceived('error') ->withArgs(fn (string $message) => str_contains($message, 'plan catalogue')) ->once(); }); it('names the currency the catalogue is priced in, not the one the page was written in', function () { // The currency is configurable. A sheet that prints "€" while the checkout // charges francs is the same two-sources-of-truth mistake as a hard-coded // amount — only harder to spot, because the number looks right. config()->set('provisioning.currency', 'CHF'); DB::table('plan_prices')->update(['currency' => 'CHF']); $this->get('/') ->assertOk() ->assertSee("179\u{00A0}CHF", false) ->assertDontSee("179\u{00A0}€", false); }); it('drops a catalogue feature it has no wording for, rather than printing its key', function () { $version = PlanVersion::query() ->where('plan_family_id', PlanFamily::query()->where('key', 'team')->value('id')) ->firstOrFail(); DB::table('plan_versions')->where('id', $version->id)->update([ 'features' => json_encode([...$version->features, 'quantum_entanglement']), ]); $this->get('/') ->assertOk() ->assertDontSee('quantum_entanglement'); });