CluPilotCloud/tests/Feature/ServerMarketTest.php

95 lines
3.5 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

<?php
use App\Services\Provisioning\ServerMarket;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Http;
/**
* What a machine costs today, answered on the page that asks the question.
*
* Read-only by construction. This orders nothing: buying a server is a
* contract, and a bug in a capacity calculation must not be able to sign one.
*/
beforeEach(fn () => Cache::forget('server-market'));
/** One offer in the feed's own shape. */
function offer(array $overrides = []): array
{
return array_merge([
'cpu' => 'Ryzen 5 3600',
'ram_size' => 64,
'is_ecc' => true,
'hdd_hr' => ['2x SSD M.2 NVMe 1 TB'],
'serverDiskData' => ['nvme' => [1024, 1024], 'sata' => [], 'hdd' => []],
'datacenter' => 'FSN1-DC1',
'price' => 42.0,
], $overrides);
}
it('counts a mirrored pair once, not twice', function () {
// A machine's usable space is not the sum of its disks on a platform whose
// whole promise is "your data survives a disk dying".
Http::fake(['*' => Http::response(['server' => [offer()]])]);
expect(app(ServerMarket::class)->offers(500)[0]['flash_gb'])->toBe(1024);
});
it('leaves out machines that could not take the largest parked order', function () {
// 1.25× the largest single package: the mirror plus the host's own
// footprint. An offer that only just fits is not an offer.
Http::fake(['*' => Http::response(['server' => [offer()]])]);
expect(app(ServerMarket::class)->offers(900))->toBe([]);
});
it('leaves out spinning disks and non-ECC memory', function () {
// Both are product decisions, not preferences: one VM per customer puts
// every database on the same disks, and ECC is part of what the nightly
// backup promise rests on.
Http::fake(['*' => Http::response(['server' => [
offer(['is_ecc' => false]),
offer(['serverDiskData' => ['nvme' => [], 'sata' => [], 'hdd' => [4096, 4096]]]),
offer(['ram_size' => 16]),
]])]);
expect(app(ServerMarket::class)->offers(100))->toBe([]);
});
it('lists the cheapest first', function () {
Http::fake(['*' => Http::response(['server' => [
offer(['price' => 80.0]),
offer(['price' => 30.0]),
offer(['price' => 55.0]),
]])]);
expect(collect(app(ServerMarket::class)->offers(100))->pluck('price')->all())
->toBe([30.0, 55.0, 80.0]);
});
it('reads the disk description the feed actually sends', function () {
// The feed sends a LIST of disk descriptions, not a string. Casting that to
// string printed the word "Array" into the console and raised a warning
// that took the whole page down with it.
Http::fake(['*' => Http::response(['server' => [offer(['hdd_hr' => ['2x SSD M.2 NVMe 1 TB']])]])]);
expect(app(ServerMarket::class)->offers(100)[0]['disks'])->toBe('2x SSD M.2 NVMe 1 TB');
});
it('says nothing rather than failing when the list cannot be reached', function () {
// An outside list being down is not a reason for the capacity page to fail.
// It shows the queue either way; the prices are the convenience.
Http::fake(['*' => fn () => throw new RuntimeException('connection refused')]);
expect(app(ServerMarket::class)->offers(100))->toBe([]);
});
it('asks the provider once per quarter hour, not once per page view', function () {
Http::fake(['*' => Http::response(['server' => [offer()]])]);
app(ServerMarket::class)->offers(100);
app(ServerMarket::class)->offers(100);
app(ServerMarket::class)->offers(100);
Http::assertSentCount(1);
});