95 lines
3.5 KiB
PHP
95 lines
3.5 KiB
PHP
<?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);
|
||
});
|