183 lines
6.5 KiB
TypeScript
183 lines
6.5 KiB
TypeScript
import { expect, it, vi } from 'vitest';
|
|
import { render } from 'svelte/server';
|
|
import { writable } from 'svelte/store';
|
|
import type { CapacityData, CapacityHost, CapacityHistory } from '../../src/lib/capacity.js';
|
|
import CapacityPage from '../../src/routes/capacity/+page.svelte';
|
|
import CapacityHosts from '../../src/lib/ui/components/capacity/CapacityHosts.svelte';
|
|
import CapacityChart from '../../src/lib/ui/components/capacity/CapacityChart.svelte';
|
|
|
|
vi.mock('$app/state', () => ({
|
|
page: { url: new URL('http://localhost/capacity?host=ec-1&game=t5') }
|
|
}));
|
|
vi.mock('$app/stores', () => ({ page: writable({ url: new URL('http://localhost/capacity') }) }));
|
|
vi.mock('$app/navigation', () => ({
|
|
beforeNavigate: vi.fn(),
|
|
afterNavigate: vi.fn(),
|
|
goto: vi.fn()
|
|
}));
|
|
|
|
const allowance = {
|
|
cpuCores: 0.5,
|
|
memoryBytes: 1024 ** 3,
|
|
source: 'host' as const,
|
|
samples: 20,
|
|
observedAt: '2026-10-06T12:00:00Z'
|
|
};
|
|
const host = (changes: Partial<CapacityHost> = {}): CapacityHost => ({
|
|
id: 'ec-1',
|
|
name: 'ec-1',
|
|
connected: true,
|
|
readiness: 'ready',
|
|
updatedAt: '2026-10-06T12:00:00Z',
|
|
eligible: true,
|
|
games: ['t5', 't6'],
|
|
reasons: [],
|
|
cpuCoresAvailable: 2,
|
|
memoryBytesAvailable: 4 * 1024 ** 3,
|
|
portsAvailable: 1,
|
|
totalPorts: 10,
|
|
estimatedServers: 1,
|
|
limits: { cpu: 4, memory: 4, ports: 1 },
|
|
limitingResource: 'Ports',
|
|
calibration: { running: allowance, startup: allowance },
|
|
...changes
|
|
});
|
|
const history: CapacityHistory = {
|
|
range: '24h',
|
|
startAt: 1000,
|
|
endAt: 5000,
|
|
bucketMs: 1000,
|
|
points: [
|
|
{
|
|
at: 1000,
|
|
cpuCoresAvailable: 2,
|
|
memoryBytesAvailable: 1024 ** 3,
|
|
reportingHosts: 1,
|
|
expectedHosts: 1
|
|
},
|
|
{
|
|
at: 2000,
|
|
cpuCoresAvailable: null,
|
|
memoryBytesAvailable: null,
|
|
reportingHosts: 0,
|
|
expectedHosts: 1
|
|
},
|
|
{
|
|
at: 3000,
|
|
cpuCoresAvailable: 3,
|
|
memoryBytesAvailable: 2 * 1024 ** 3,
|
|
reportingHosts: 1,
|
|
expectedHosts: 1
|
|
}
|
|
]
|
|
};
|
|
const data = (): CapacityData => ({
|
|
query: { range: '24h', game: 't5', hostId: 'ec-1' },
|
|
history,
|
|
current: {
|
|
updatedAt: '2026-10-06T12:00:00Z',
|
|
hosts: [host()],
|
|
hostOptions: [{ id: 'ec-1', name: 'ec-1' }],
|
|
warnings: [],
|
|
summary: {
|
|
estimatedServers: 1,
|
|
cpuCoresAvailable: 2,
|
|
memoryBytesAvailable: 4 * 1024 ** 3,
|
|
portsAvailable: 1,
|
|
eligibleHosts: 1,
|
|
totalHosts: 1,
|
|
startupSlots: 3,
|
|
startupLimit: 3,
|
|
activeStartups: 0,
|
|
queued: 0
|
|
}
|
|
}
|
|
});
|
|
|
|
it('keeps current estimates and host comparison before history with scoped history controls', () => {
|
|
const body = render(CapacityPage, { props: { data: data() } }).body;
|
|
expect(body.indexOf('id="capacity-now-title"')).toBeLessThan(
|
|
body.indexOf('id="host-capacity-title"')
|
|
);
|
|
expect(body.indexOf('id="host-capacity-title"')).toBeLessThan(
|
|
body.indexOf('id="capacity-history-title"')
|
|
);
|
|
expect(body.indexOf('aria-label="History range"')).toBeGreaterThan(
|
|
body.indexOf('id="capacity-history-title"')
|
|
);
|
|
expect(body).toContain('range=7d&game=t5&host=ec-1');
|
|
expect(body).toContain('3 of 3 available');
|
|
expect(body).toContain('No queued servers');
|
|
expect(body).not.toContain('href="/provisioning"');
|
|
expect(body).not.toContain('Manage hosts');
|
|
expect(body).toContain('Updates every 30 seconds');
|
|
expect(body).not.toContain('>Refresh<');
|
|
});
|
|
|
|
it('keeps fleet-wide queue scope explicit and preserves unavailable estimates', () => {
|
|
const report = data();
|
|
report.current.summary.queued = 2;
|
|
report.current.summary.estimatedServers = null;
|
|
const body = render(CapacityPage, { props: { data: report } }).body;
|
|
expect(body).toContain('Capacity estimate unavailable');
|
|
expect(body).toContain('href="/provisioning"');
|
|
expect(body.replace(/\s+/g, ' ')).toContain('2 servers queued');
|
|
expect(body).toContain('Fleet-wide');
|
|
});
|
|
|
|
it('keeps ineligible hosts and their reasons visible while collapsing detailed assumptions', () => {
|
|
const body = render(CapacityHosts, {
|
|
props: {
|
|
returnTo: '/capacity',
|
|
hosts: [
|
|
host(),
|
|
host({
|
|
id: 'missing-files',
|
|
name: 'Missing files',
|
|
eligible: false,
|
|
estimatedServers: 0,
|
|
limitingResource: null,
|
|
reasons: ['Latest game files are not ready.']
|
|
}),
|
|
host({
|
|
id: 'offline',
|
|
name: 'Offline host',
|
|
connected: false,
|
|
eligible: false,
|
|
estimatedServers: 0,
|
|
cpuCoresAvailable: null,
|
|
memoryBytesAvailable: null,
|
|
limitingResource: null,
|
|
reasons: ['Waiting for a fresh host report.']
|
|
})
|
|
]
|
|
}
|
|
}).body;
|
|
expect(body).toContain('Additional servers</th>');
|
|
expect(body).toContain('Not eligible');
|
|
expect(body).toContain('Latest game files are not ready.');
|
|
expect(body).toContain('Offline');
|
|
expect(body).toContain('>—</td>');
|
|
expect(body).not.toContain('>Ready<');
|
|
expect(body).toMatch(/<tr hidden[^>]*id="capacity-host-ec-1"/);
|
|
expect(body).toContain('aria-expanded="false"');
|
|
expect(body).toContain('Running server');
|
|
expect(body).toContain('aria-label="Host capacity comparison" tabindex="0"');
|
|
});
|
|
|
|
it('preserves chart gaps, keyboard inspection and reporting coverage in the compact chart', () => {
|
|
const body = render(CapacityChart, { props: { history, scope: 'All hosts' } }).body;
|
|
expect(body.match(/class="trend /g)).toHaveLength(2);
|
|
expect(body).toContain('role="slider"');
|
|
expect(body).toContain('1 of 1 hosts reporting');
|
|
expect(body).toContain('Reporting hosts');
|
|
expect(body).toContain('The game filter applies to current launch estimates only.');
|
|
expect(body).toContain('About this chart');
|
|
expect(body).not.toMatch(/<details[^>]*open/);
|
|
const empty = render(CapacityChart, {
|
|
props: { history: { ...history, points: [history.points[1]] }, scope: 'All hosts' }
|
|
}).body;
|
|
expect(empty).toContain('Waiting for capacity history');
|
|
expect(empty).toContain('No complete report');
|
|
});
|