112 lines
4.1 KiB
TypeScript
112 lines
4.1 KiB
TypeScript
import { expect, it } from 'vitest';
|
|
import { render } from 'svelte/server';
|
|
import type {
|
|
RebalanceCandidate,
|
|
RebalancePreview,
|
|
RebalanceRun
|
|
} from '../../src/lib/rebalancing.js';
|
|
import RebalancePreviewView from '../../src/lib/ui/components/capacity/RebalancePreviewView.svelte';
|
|
import RebalanceRunView from '../../src/lib/ui/components/capacity/RebalanceRunView.svelte';
|
|
|
|
const at = '2026-10-08T12:00:00Z';
|
|
const move: RebalanceCandidate = {
|
|
server: 't5_zombie_theater_1',
|
|
instanceId: 'instance-a',
|
|
generation: 1,
|
|
sourceHostId: 'source-a',
|
|
sourceHostName: 'Source A',
|
|
targetHostId: 'target-b',
|
|
targetHostName: 'Target B',
|
|
improvement: 0.1
|
|
};
|
|
const preview: RebalancePreview = {
|
|
id: 'preview-a',
|
|
createdAt: at,
|
|
expiresAt: '2026-10-08T12:05:00Z',
|
|
settings: { hostIds: ['source-a', 'target-b'], game: 'all', maxMoves: 5 },
|
|
moves: [move],
|
|
hosts: [
|
|
{
|
|
id: 'source-a',
|
|
name: 'Source A',
|
|
before: { cpuPercent: 75, memoryPercent: 50, instances: 6 },
|
|
after: { cpuPercent: 62.5, memoryPercent: 42, instances: 5 }
|
|
}
|
|
],
|
|
exclusions: [{ server: 't6_zm_transit_1', reason: 'Lobby has players.' }],
|
|
warnings: ['Occupied lobbies stay in place.']
|
|
};
|
|
const run: RebalanceRun = {
|
|
id: 'run-a',
|
|
createdAt: at,
|
|
updatedAt: at,
|
|
settings: preview.settings,
|
|
state: 'running',
|
|
message: 'Moving one lobby.',
|
|
moves: [
|
|
{
|
|
...move,
|
|
id: 'move-a',
|
|
state: 'moving',
|
|
operationId: 'operation-a',
|
|
message: 'Waiting for destination health.',
|
|
updatedAt: at
|
|
}
|
|
]
|
|
};
|
|
const noop = () => {};
|
|
|
|
it('shows current and projected resources, the move destination, and exclusion reasons before starting', () => {
|
|
const body = render(RebalancePreviewView, {
|
|
props: { preview, expired: false, onstart: noop }
|
|
}).body;
|
|
expect(body).toContain('6 → 5');
|
|
expect(body).toContain('75% → 62.5%');
|
|
expect(body).toContain('50% → 42%');
|
|
expect(body).toContain('Source A → Target B');
|
|
expect(body).toContain('Lobby has players.');
|
|
expect(body).toContain('Occupied lobbies stay in place.');
|
|
expect(body).toContain('aria-label="Projected host distribution" tabindex="0"');
|
|
expect(body).not.toMatch(/<button[^>]*disabled/);
|
|
});
|
|
|
|
it.each(['expired', 'empty', 'blocked'] as const)('prevents starting a %s preview', (condition) => {
|
|
const body = render(RebalancePreviewView, {
|
|
props: {
|
|
preview: condition === 'empty' ? { ...preview, moves: [] } : preview,
|
|
expired: condition === 'expired',
|
|
disabled: condition === 'blocked',
|
|
onstart: noop
|
|
}
|
|
}).body;
|
|
expect(body).toMatch(/<button[^>]*disabled/);
|
|
if (condition === 'expired') expect(body).toContain('This preview has expired.');
|
|
if (condition === 'empty') expect(body).toContain('No eligible moves would improve');
|
|
});
|
|
|
|
it('keeps run progress tied to its provisioning record and explains stopping semantics', () => {
|
|
const body = render(RebalanceRunView, { props: { run, canManage: true, onstop: noop } }).body;
|
|
expect(body).toContain('0 of 1 moves completed');
|
|
expect(body).toContain('Waiting for destination health.');
|
|
expect(body).toContain(
|
|
'href="/provisioning/operation-a?returnTo=%2Fcapacity%3Frebalance%3D1%26rebalanceRun%3Drun-a"'
|
|
);
|
|
expect(body).toContain('Stop remaining moves');
|
|
expect(body.replace(/\s+/g, ' ')).toContain(
|
|
'A move already in progress continues through health verification.'
|
|
);
|
|
});
|
|
|
|
it('hides stop from viewers and terminal or already-stopping runs', () => {
|
|
expect(
|
|
render(RebalanceRunView, { props: { run, canManage: false, onstop: noop } }).body
|
|
).not.toContain('Stop remaining moves');
|
|
for (const state of ['stopping', 'done', 'stopped', 'failed'] as const) {
|
|
const body = render(RebalanceRunView, {
|
|
props: { run: { ...run, state }, canManage: true, onstop: noop }
|
|
}).body;
|
|
expect(body).not.toContain('Stop remaining moves');
|
|
expect(body).toContain('View provisioning');
|
|
}
|
|
});
|