Improve operational UX, recovery flows, and action safety
Implement the approved UX review across Fleet, Betas, Library, deployments, and Settings. Add reviewed promotion and reconciliation plans, atomic queued-removal cancellation, recoverable beta drafts and launch inputs, accessible controls, and mobile improvements. Validated with 473 tests, a clean typecheck, production build, and isolated desktop/mobile browser walkthroughs. Include the review and implementation coverage notes.
This commit is contained in:
@@ -48,6 +48,60 @@ afterEach(async () => {
|
||||
vi.unstubAllEnvs();
|
||||
});
|
||||
|
||||
describe('queued removal cancellation', () => {
|
||||
it('lets cancellation win while the worker awaits a player count, and never removes the server', async () => {
|
||||
const { createServer, destroyServer, processDestructions } = await import('../../src/lib/server/fleet/create.js');
|
||||
const { getDeployment } = await import('../../src/lib/server/fleet/deployments.js');
|
||||
const { POST } = await import('../../src/routes/api/deployments/[id]/cancel/+server.js');
|
||||
const { rconGetPlayerCount } = await import('../../src/lib/server/integrations/rcon.js');
|
||||
const { komodoDestroyDeployment } = await import('../../src/lib/server/integrations/komodo.js');
|
||||
await createServer(input);
|
||||
let resolveCount!: (players: number) => void;
|
||||
vi.mocked(rconGetPlayerCount).mockImplementationOnce(() => new Promise(resolve => { resolveCount = resolve; }));
|
||||
const queued = destroyServer(serverName);
|
||||
const work = processDestructions();
|
||||
await vi.waitFor(() => expect(resolveCount).toBeTypeOf('function'));
|
||||
expect((await POST({ params: { id: queued.deploymentId! } })).status).toBe(200);
|
||||
resolveCount(0);
|
||||
await work;
|
||||
expect(komodoDestroyDeployment).not.toHaveBeenCalled();
|
||||
expect(getDeployment(queued.deploymentId!)?.deployment).toMatchObject({ state: 'cancelled', server_exists: 1, stage: 'Removal cancelled' });
|
||||
expect(getDeployment(queued.deploymentId!)?.canCancelRemoval).toBe(false);
|
||||
expect((await POST({ params: { id: queued.deploymentId! } })).status).toBe(409);
|
||||
});
|
||||
|
||||
it('does not overwrite cancellation when the in-flight player check rejects', async () => {
|
||||
const { createServer, destroyServer, processDestructions } = await import('../../src/lib/server/fleet/create.js');
|
||||
const { cancelRemoval, getDeployment } = await import('../../src/lib/server/fleet/deployments.js');
|
||||
const { rconGetPlayerCount } = await import('../../src/lib/server/integrations/rcon.js');
|
||||
await createServer(input);
|
||||
let rejectCount!: (error: Error) => void;
|
||||
vi.mocked(rconGetPlayerCount).mockImplementationOnce(() => new Promise((_resolve, reject) => { rejectCount = reject; }));
|
||||
const queued = destroyServer(serverName);
|
||||
const work = processDestructions();
|
||||
await vi.waitFor(() => expect(rejectCount).toBeTypeOf('function'));
|
||||
expect(cancelRemoval(queued.deploymentId!)).toBe(true);
|
||||
rejectCount(new Error('RCON unavailable'));
|
||||
await work;
|
||||
expect(getDeployment(queued.deploymentId!)?.deployment).toMatchObject({ state: 'cancelled', error: null });
|
||||
});
|
||||
|
||||
it('refuses cancellation after an atomic removal claim and for cleanup checkpoints', async () => {
|
||||
const { createServer, destroyServer } = await import('../../src/lib/server/fleet/create.js');
|
||||
const { cancelRemoval, claimRemoval, canCancelRemoval, deploymentStage, REMOVING_SERVER } = await import('../../src/lib/server/fleet/deployments.js');
|
||||
const { db } = await import('../../src/lib/server/db/index.js');
|
||||
await createServer(input);
|
||||
const queued = destroyServer(serverName);
|
||||
expect(claimRemoval(queued.deploymentId!)).toBe(true);
|
||||
expect(cancelRemoval(queued.deploymentId!)).toBe(false);
|
||||
expect(claimRemoval(queued.deploymentId!)).toBe(false);
|
||||
deploymentStage(queued.deploymentId!, REMOVING_SERVER);
|
||||
db().prepare("UPDATE server_deployments SET state = 'waiting' WHERE id = ?").run(queued.deploymentId);
|
||||
expect(canCancelRemoval(queued.deploymentId!)).toBe(false);
|
||||
expect(cancelRemoval(queued.deploymentId!)).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('destruction cleanup recovery', () => {
|
||||
it('removes T6 servers with beta test runs and their dependent evidence', async () => {
|
||||
const { createServer, destroyServer, processDestructions } = await import('../../src/lib/server/fleet/create.js');
|
||||
|
||||
Reference in New Issue
Block a user