create(['code' => 'fsn1']); // The capacity page quotes live server prices. A test suite that reaches // the internet is a test suite that fails when somebody else's site is // down, and it puts requests on that site on every run. Http::fake([ '*' => Http::response(['server' => []]), ]); }); /** A paid order with its contract frozen, standing at the reservation step. */ function parkedOrder(string $plan = 'start', string $datacenter = 'fsn1'): ProvisioningRun { $order = Order::factory()->withSubscription()->create([ 'plan' => $plan, 'datacenter' => $datacenter, 'status' => 'paid', ]); $pipeline = (array) config('provisioning.pipelines.customer'); return ProvisioningRun::factory()->create([ 'subject_type' => Order::class, 'subject_id' => $order->id, 'pipeline' => 'customer', 'current_step' => array_search(ReserveResources::class, $pipeline, true), 'status' => ProvisioningRun::STATUS_WAITING, ]); } it('parks a paid order instead of failing it when nothing has room', function () { // No host at all: the customer has paid and there is nowhere to put them. $run = parkedOrder(); $run->update(['status' => ProvisioningRun::STATUS_RUNNING]); $result = app(ReserveResources::class)->execute($run->fresh()); expect($result->type)->toBe('poll') ->and($result->reason)->toBe('awaiting_capacity') // poll(), not retry(): the wait is measured in days and must not eat // the run's attempt budget. ->and($result->afterSeconds)->toBe(ReserveResources::PARK_POLL_SECONDS); }); it('fails only once the order has waited longer than a machine can take', function () { // Long enough to buy, rack and onboard a server over a weekend; short // enough that a forgotten order surfaces instead of waiting for ever. $run = parkedOrder(); // forceFill, because created_at is not fillable — update() drops it // silently and the run stays a second old, which is a green test for a // deadline that never fires. $run->forceFill([ 'status' => ProvisioningRun::STATUS_RUNNING, 'created_at' => now()->subDays(ReserveResources::PARK_DAYS + 1), ])->save(); expect(app(ReserveResources::class)->execute($run->fresh())->type)->toBe('fail'); }); it('finds parked orders by where they stand, not by a string that casts to zero', function () { // current_step is an INDEX. Comparing it to 'reserve_resources' reads as // correct and matches step 0 of every pipeline instead — including a host // onboarding that is merely waiting on SSH. $parked = parkedOrder(); ProvisioningRun::factory()->create([ 'pipeline' => 'host', 'current_step' => 0, 'status' => ProvisioningRun::STATUS_WAITING, ]); $found = app(HostCapacity::class)->parked(); expect($found)->toHaveCount(1) ->and($found[0]['run']->id)->toBe($parked->id); }); it('recommends what to buy from the mix that is actually waiting', function () { // The BIGGEST parked package decides the minimum machine — an instance // lives on one host, so a server that cannot take the largest one leaves it // parked however much total space it has. parkedOrder('start'); parkedOrder('start'); parkedOrder('business'); $demand = app(HostCapacity::class)->queueDemand(); expect($demand['count'])->toBe(3) ->and($demand['largest'])->toBe(1050) // business ->and($demand['total'])->toBe(1290) // 2 × 120 + 1050 ->and($demand['by_plan'])->toHaveCount(2); }); it('sizes the queue from the contract the customer signed, not today catalogue', function () { // A plan edited after the sale must not change the machine we go and buy // for the customer who bought the old one. $run = parkedOrder('start'); App\Models\Subscription::query() ->where('order_id', $run->subject_id) ->update(['disk_gb' => 4000]); expect(app(HostCapacity::class)->queueDemand()['largest'])->toBe(4000); }); it('promises immediate delivery only where a host could actually take it', function () { Host::factory()->active()->create(['datacenter' => 'fsn1', 'total_gb' => 500, 'reserve_pct' => 0]); $capacity = app(HostCapacity::class); expect($capacity->deliveryFor(120))->toBe('immediate') // start fits ->and($capacity->deliveryFor(1050))->toBe('scheduled') // business does not // Nothing sized is nothing promised. ->and($capacity->deliveryFor(0))->toBe('scheduled'); }); it('stops promising immediate delivery once the room is committed', function () { // Thick booking: the instance holds its whole disk whether the customer has // put a byte in it or not, and the promise has to follow the booking. $host = Host::factory()->active()->create(['datacenter' => 'fsn1', 'total_gb' => 500, 'reserve_pct' => 0]); expect(app(HostCapacity::class)->deliveryFor(120))->toBe('immediate'); Instance::factory()->create(['host_id' => $host->id, 'status' => 'active', 'disk_gb' => 450]); expect(app(HostCapacity::class)->deliveryFor(120))->toBe('scheduled'); }); it('shows the delivery promise for every package on the console front page', function () { Host::factory()->active()->create(['datacenter' => 'fsn1', 'total_gb' => 500, 'reserve_pct' => 0]); Livewire::actingAs(operator('Owner'), 'operator') ->test(Overview::class) ->assertViewHas('delivery', fn (array $d) => count($d['plans']) > 0 && collect($d['plans'])->contains(fn (array $p) => $p['state'] === 'immediate') && collect($d['plans'])->contains(fn (array $p) => $p['state'] === 'scheduled')); }); it('lets the operator decide which parked order goes on which host', function () { $run = parkedOrder('start'); $small = Host::factory()->active()->create(['datacenter' => 'fsn1', 'name' => 'pve-a', 'total_gb' => 1000, 'reserve_pct' => 0]); $chosen = Host::factory()->active()->create(['datacenter' => 'fsn1', 'name' => 'pve-b', 'total_gb' => 1000, 'reserve_pct' => 0]); Livewire::actingAs(operator('Owner'), 'operator') ->test(Capacity::class) ->call('pin', $run->id, (string) $chosen->id); expect($run->fresh()->context('preferred_host_id'))->toBe($chosen->id); // And the step honours it — placeableIn() would have taken pve-a, which // sorts first by name and has just as much room. $run->update(['status' => ProvisioningRun::STATUS_RUNNING]); app(ReserveResources::class)->execute($run->fresh()); expect(Instance::query()->where('order_id', $run->subject_id)->first()?->host_id)->toBe($chosen->id) ->and($small->instances()->count())->toBe(0); }); it('waits rather than quietly placing a pinned order somewhere else', function () { // A pin is honoured or it waits. Placing it elsewhere because the chosen // host happened to be full answers a different question than the one the // operator answered — and they would find out from the finished instance. $run = parkedOrder('start'); $full = Host::factory()->active()->create(['datacenter' => 'fsn1', 'total_gb' => 100, 'reserve_pct' => 0]); Host::factory()->active()->create(['datacenter' => 'fsn1', 'total_gb' => 1000, 'reserve_pct' => 0]); Livewire::actingAs(operator('Owner'), 'operator') ->test(Capacity::class) ->call('pin', $run->id, (string) $full->id); $run->update(['status' => ProvisioningRun::STATUS_RUNNING]); $result = app(ReserveResources::class)->execute($run->fresh()); expect($result->type)->toBe('poll') // Named apart from 'awaiting_capacity': this one is a decision to // revisit, not a server to buy. ->and($result->reason)->toBe('awaiting_pinned_host') ->and(Instance::query()->where('order_id', $run->subject_id)->exists())->toBeFalse(); }); it('hands placement back when the assignment is cleared', function () { $run = parkedOrder('start'); $host = Host::factory()->active()->create(['datacenter' => 'fsn1', 'total_gb' => 1000, 'reserve_pct' => 0]); $page = Livewire::actingAs(operator('Owner'), 'operator')->test(Capacity::class); $page->call('pin', $run->id, (string) $host->id); $page->call('pin', $run->id, ''); expect($run->fresh()->context('preferred_host_id'))->toBeNull(); }); it('refuses to pin a run that is no longer waiting for a machine', function () { // A preference written onto a run that has moved on would sit in its // context for ever and take effect on a re-run months later. $run = parkedOrder('start'); $run->update(['status' => ProvisioningRun::STATUS_RUNNING]); $host = Host::factory()->active()->create(['datacenter' => 'fsn1', 'total_gb' => 1000, 'reserve_pct' => 0]); Livewire::actingAs(operator('Owner'), 'operator') ->test(Capacity::class) ->call('pin', $run->id, (string) $host->id); expect($run->fresh()->context('preferred_host_id'))->toBeNull(); }); it('rechecks exactly the runs the queue is showing', function () { $parked = parkedOrder(); $parked->update(['next_attempt_at' => now()->addHour()]); $other = ProvisioningRun::factory()->create([ 'pipeline' => 'host', 'current_step' => 0, 'status' => ProvisioningRun::STATUS_WAITING, 'next_attempt_at' => now()->addHour(), ]); Livewire::actingAs(operator('Owner'), 'operator') ->test(Capacity::class) ->call('recheck'); expect($parked->fresh()->next_attempt_at->isBefore(now()->addMinute()))->toBeTrue() ->and($other->fresh()->next_attempt_at->isAfter(now()->addMinutes(30)))->toBeTrue(); });