181 lines
7.7 KiB
PHP
181 lines
7.7 KiB
PHP
<?php
|
|
|
|
use App\Models\Customer;
|
|
use App\Models\Host;
|
|
use App\Models\Instance;
|
|
use App\Models\ProvisioningRun;
|
|
use App\Models\Subscription;
|
|
use App\Livewire\Admin\Instances as AdminInstances;
|
|
use App\Livewire\Admin\Overview;
|
|
use App\Livewire\Admin\Revenue;
|
|
use Illuminate\Support\Facades\DB;
|
|
use Livewire\Livewire;
|
|
|
|
/**
|
|
* The console reports what is in the database, or nothing.
|
|
*
|
|
* Its front page, its instance list and its revenue page were hard-coded
|
|
* fiction: forty-two customers, €7,842 a month, hosts named pve-fsn-1..3, a
|
|
* twelve-month growth curve. It read like a running business and measured
|
|
* nothing. These tests exist so that cannot come back quietly.
|
|
*/
|
|
|
|
it('reports an empty estate as empty, not as a going concern', function () {
|
|
// The strongest statement of the old bug: with nothing in the database the
|
|
// console still claimed 42 customers, 39 instances, 4 hosts and €7,842 a
|
|
// month. The figures are asserted directly rather than searched for in the
|
|
// markup — "42" also occurs inside Livewire's own component ids.
|
|
//
|
|
// The mailer is pinned to a delivering one: phpunit.xml forces
|
|
// MAIL_MAILER=array so a test suite cannot send mail, and the console
|
|
// rightly warns that nothing is being delivered. That warning is about the
|
|
// ENVIRONMENT, not about the estate, and this test is about an empty estate.
|
|
config()->set('mail.default', 'smtp');
|
|
Livewire::actingAs(operator('Owner'), 'operator')
|
|
->test(Overview::class)
|
|
->assertViewHas('kpis', function (array $kpis) {
|
|
[$customers, $instances, $hosts, $mrr] = $kpis;
|
|
|
|
return $customers['value'] === '0'
|
|
&& $instances['value'] === '0'
|
|
&& $hosts['value'] === '0'
|
|
&& str_contains($mrr['value'], '0,00');
|
|
})
|
|
->assertViewHas('hostLoad', [])
|
|
->assertViewHas('runs', [])
|
|
->assertViewHas('notices', [])
|
|
->assertSee(__('admin.no_open_runs'))
|
|
->assertSee(__('admin.no_notices'));
|
|
});
|
|
|
|
it('counts what is actually there', function () {
|
|
$host = Host::factory()->create(['status' => 'active', 'total_gb' => 1000, 'last_seen_at' => now()]);
|
|
$customer = Customer::factory()->create(['status' => 'active']);
|
|
Instance::factory()->create([
|
|
'customer_id' => $customer->id, 'host_id' => $host->id,
|
|
'status' => 'active', 'quota_gb' => 250,
|
|
]);
|
|
|
|
Livewire::actingAs(operator('Owner'), 'operator')
|
|
->test(Overview::class)
|
|
->assertSee('1')
|
|
->assertSee($host->name);
|
|
});
|
|
|
|
it('reports host capacity the way placement counts it', function () {
|
|
// The figure on the dashboard has to be the figure placement acts on.
|
|
// Placement counts the VM disk allocation — not the smaller Nextcloud
|
|
// quota — against capacity AFTER the host's reserve. A dashboard doing its
|
|
// own arithmetic would call a host comfortable while orders were already
|
|
// being refused on it.
|
|
$host = Host::factory()->create([
|
|
'status' => 'active', 'total_gb' => 1000, 'reserve_pct' => 20, 'last_seen_at' => now(),
|
|
]);
|
|
$customer = Customer::factory()->create();
|
|
Instance::factory()->create([
|
|
'customer_id' => $customer->id, 'host_id' => $host->id,
|
|
'status' => 'active', 'quota_gb' => 250, 'disk_gb' => 600,
|
|
]);
|
|
|
|
expect($host->committedGb())->toBe(600)
|
|
->and($host->freeGb())->toBe(800);
|
|
|
|
Livewire::actingAs(operator('Owner'), 'operator')
|
|
->test(Overview::class)
|
|
// 600 of the 800 GB placement may actually use, not 250 of 1000.
|
|
->assertSee('600 / 800 GB')
|
|
->assertDontSee('250 / 1000 GB');
|
|
});
|
|
|
|
it('states monthly revenue off the contracts, with a yearly term divided', function () {
|
|
$customer = Customer::factory()->create();
|
|
|
|
Subscription::factory()->create([
|
|
'customer_id' => $customer->id,
|
|
'status' => 'active', 'term' => 'monthly', 'price_cents' => 17900, 'currency' => 'EUR',
|
|
]);
|
|
// A year paid up front is not twelve times the monthly revenue.
|
|
Subscription::factory()->create([
|
|
'customer_id' => $customer->id,
|
|
'status' => 'active', 'term' => 'yearly', 'price_cents' => 120000, 'currency' => 'EUR',
|
|
]);
|
|
// A cancelled contract bills nothing.
|
|
Subscription::factory()->create([
|
|
'customer_id' => $customer->id,
|
|
'status' => 'cancelled', 'term' => 'monthly', 'price_cents' => 99900, 'currency' => 'EUR',
|
|
]);
|
|
|
|
// 179,00 monthly + 1.200,00 a year ÷ 12 = 179,00 + 100,00 = 279,00
|
|
Livewire::actingAs(operator('Owner'), 'operator')
|
|
->test(Revenue::class)
|
|
->assertSee('279,00')
|
|
->assertDontSee('999,00');
|
|
});
|
|
|
|
it('raises a notice only when something is actually wrong', function () {
|
|
// See above: the test suite deliberately delivers no mail, and the console
|
|
// says so. Pinned here so "clean" means an estate with nothing wrong in it.
|
|
config()->set('mail.default', 'smtp');
|
|
|
|
$clean = Livewire::actingAs(operator('Owner'), 'operator')->test(Overview::class);
|
|
$clean->assertSee(__('admin.systems_ok'));
|
|
|
|
ProvisioningRun::factory()->create(['status' => ProvisioningRun::STATUS_FAILED]);
|
|
|
|
Livewire::actingAs(operator('Owner'), 'operator')
|
|
->test(Overview::class)
|
|
->assertSee(__('admin.notice.failed_runs', ['n' => 1]))
|
|
->assertDontSee(__('admin.systems_ok'));
|
|
});
|
|
|
|
it('gets the same committed storage whether or not the sum was preloaded', function () {
|
|
// Two code paths now answer one question. If they ever disagree, the
|
|
// dashboard and placement disagree — which is the bug this replaced.
|
|
$host = Host::factory()->create(['total_gb' => 1000, 'reserve_pct' => 10]);
|
|
$customer = Customer::factory()->create();
|
|
|
|
Instance::factory()->create(['customer_id' => $customer->id, 'host_id' => $host->id, 'status' => 'active', 'disk_gb' => 120]);
|
|
Instance::factory()->create(['customer_id' => $customer->id, 'host_id' => $host->id, 'status' => 'failed', 'vmid' => 900, 'disk_gb' => 80]);
|
|
// Failed before a VM existed — it holds nothing.
|
|
Instance::factory()->create(['customer_id' => $customer->id, 'host_id' => $host->id, 'status' => 'failed', 'vmid' => null, 'disk_gb' => 500]);
|
|
|
|
$onDemand = Host::query()->find($host->id)->committedGb();
|
|
$preloaded = Host::query()
|
|
->withSum(['instances as committed_disk_gb' => fn ($q) => $q->occupyingHost()], 'disk_gb')
|
|
->find($host->id)
|
|
->committedGb();
|
|
|
|
expect($onDemand)->toBe(200)->and($preloaded)->toBe($onDemand);
|
|
|
|
// A host with nothing on it arrives preloaded-but-null (SUM over no rows).
|
|
// It must read as zero without going back to the database — that is the
|
|
// case the preload exists for.
|
|
$bare = Host::factory()->create(['total_gb' => 500]);
|
|
$preloadedBare = Host::query()
|
|
->withSum(['instances as committed_disk_gb' => fn ($q) => $q->occupyingHost()], 'disk_gb')
|
|
->find($bare->id);
|
|
|
|
DB::enableQueryLog();
|
|
expect($preloadedBare->committedGb())->toBe(0);
|
|
expect(DB::getQueryLog())->toBeEmpty();
|
|
DB::disableQueryLog();
|
|
});
|
|
|
|
it('lists real instances and claims no version it does not record', function () {
|
|
$host = Host::factory()->create(['name' => 'pve-test-9']);
|
|
$customer = Customer::factory()->create(['name' => 'Beispielkunde GmbH']);
|
|
Instance::factory()->create([
|
|
'customer_id' => $customer->id, 'host_id' => $host->id,
|
|
'subdomain' => 'beispielkunde', 'vmid' => 4711, 'quota_gb' => 500, 'status' => 'active',
|
|
]);
|
|
|
|
Livewire::actingAs(operator('Owner'), 'operator')
|
|
->test(AdminInstances::class)
|
|
->assertSee('beispielkunde')
|
|
->assertSee('Beispielkunde GmbH')
|
|
->assertSee('pve-test-9')
|
|
->assertSee('4711')
|
|
// The Nextcloud version is not recorded anywhere, so it is not shown.
|
|
->assertDontSee('NC 31');
|
|
});
|