create(['code' => 'fsn1']); }); it('measures the largest single reservation the estate could take', function () { // The largest free HOST, not the sum. A package goes on one machine, so two // hosts with 300 GB each cannot take a 540 GB instance between them. Host::factory()->active()->create(['datacenter' => 'fsn1', 'total_gb' => 300, 'reserve_pct' => 0]); Host::factory()->active()->create(['datacenter' => 'fsn1', 'total_gb' => 300, 'reserve_pct' => 0]); expect(app(HostCapacity::class)->largestPlaceableGb('fsn1'))->toBe(300) ->and(app(HostCapacity::class)->canPlace('fsn1', 540))->toBeFalse(); }); it('counts what is already committed, not what is on the disk', function () { // Thick booking: an instance holds its whole disk_gb whether the customer // has put a byte in it or not. $host = Host::factory()->active()->create(['datacenter' => 'fsn1', 'total_gb' => 1000, 'reserve_pct' => 0]); Instance::factory()->create(['host_id' => $host->id, 'status' => 'active', 'disk_gb' => 700]); expect(app(HostCapacity::class)->largestPlaceableGb('fsn1'))->toBe(300); }); it('respects the reserve the operator set aside', function () { // reserve_pct is headroom for snapshots and for the host's own growth. A // capacity answer that spends it is not an answer. Host::factory()->active()->create(['datacenter' => 'fsn1', 'total_gb' => 1000, 'reserve_pct' => 20]); expect(app(HostCapacity::class)->largestPlaceableGb('fsn1'))->toBe(800); }); it('warns on the console about a package that can no longer be placed', function () { // The whole point of the feature: this is the sentence that has to appear // BEFORE somebody pays for the package it names. Host::factory()->active()->create(['datacenter' => 'fsn1', 'total_gb' => 400, 'reserve_pct' => 0]); $atRisk = app(HostCapacity::class)->unplaceablePlans(); expect($atRisk)->not->toBeEmpty(); $page = Livewire::actingAs(operator('Owner'), 'operator')->test(Overview::class); $page->assertViewHas('notices', fn (array $notices) => collect($notices) ->contains(fn (array $n) => str_contains($n['text'], 'Host'))); }); it('says nothing while everything still fits', function () { // A warning that is always there is furniture. This one has to mean // something the moment it appears. Host::factory()->active()->create(['datacenter' => 'fsn1', 'total_gb' => 20000, 'reserve_pct' => 0]); expect(app(HostCapacity::class)->unplaceablePlans())->toBeEmpty(); }); it('does not take the overview down when the catalogue cannot be read', function () { // A capacity warning does not get to be the thing that breaks the page an // operator opens when something is already wrong. // PlanCatalogue is final — deliberately, it is the single reader of the // price tables — so it cannot be subclassed. The call site does not // type-hint what comes back from the container, which is enough to put a // throwing stand-in in its place. app()->bind(App\Services\Billing\PlanCatalogue::class, fn () => new class { public function sellable(): array { throw new RuntimeException('overlapping availability windows'); } }); expect(app(HostCapacity::class)->unplaceablePlans())->toBe([]); Livewire::actingAs(operator('Owner'), 'operator')->test(Overview::class)->assertOk(); }); it('does not shout at an installation that has no host yet', function () { // Zero hosts is not a capacity problem — it is a machine nobody has // onboarded, and that has its own notice. Without this guard a fresh // console greeted the operator with one warning per sellable package // before they had done anything at all. App\Models\Host::query()->delete(); expect(app(HostCapacity::class)->unplaceablePlans())->toBe([]); });