Apply selected server maps automatically without config overlays
This commit is contained in:
@@ -0,0 +1,120 @@
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
import { existsSync } from 'node:fs';
|
||||
import { readFile, rm } from 'node:fs/promises';
|
||||
import { join } from 'node:path';
|
||||
import { newFixture, type Fixture } from '../helpers/fixture.js';
|
||||
|
||||
vi.mock('../../src/lib/server/integrations/komodo.js', () => ({
|
||||
komodoCreateDeployment: vi.fn(async () => ({ ok: true })),
|
||||
komodoDeploy: vi.fn(async () => ({ ok: true })),
|
||||
komodoDestroyDeployment: vi.fn(async () => ({ ok: true })),
|
||||
komodoDeleteDeployment: vi.fn(async () => ({ ok: true })),
|
||||
komodoInspectHealthy: vi.fn(),
|
||||
komodoEnsureTag: vi.fn(async (name: string) => ({ ok: true, id: name })),
|
||||
komodoUpdateTagsOnResource: vi.fn(async () => ({ ok: true }))
|
||||
}));
|
||||
vi.mock('../../src/lib/server/pipeline/worker.js', () => ({ kick: vi.fn(async () => {}) }));
|
||||
|
||||
let fixture: Fixture;
|
||||
const serverName = 't5_zombie_cosmodrome_1';
|
||||
const input = { gameMode: 'zombie' as const, mapSlug: 'cosmodrome', plutoKey: 'test-key' };
|
||||
|
||||
beforeEach(async () => {
|
||||
vi.resetModules();
|
||||
vi.clearAllMocks();
|
||||
fixture = await newFixture([]);
|
||||
vi.stubEnv('KOMODO_ADDRESS', 'http://komodo.test');
|
||||
vi.stubEnv('KOMODO_API_KEY', 'test');
|
||||
vi.stubEnv('KOMODO_API_SECRET', 'test');
|
||||
vi.stubEnv('KOMODO_TARGET_SERVER', 'test-host');
|
||||
vi.stubEnv('RCON_PASSWORD', 'test-rcon');
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await fixture.cleanup();
|
||||
vi.unstubAllEnvs();
|
||||
});
|
||||
|
||||
async function promotedConfig() {
|
||||
const { createScript, getLatestVersion } = await import('../../src/lib/server/fleet/scripts.js');
|
||||
const { db } = await import('../../src/lib/server/db/index.js');
|
||||
for (const artifact of [
|
||||
{ name: 'baseline', kind: 'baseline' as const, scopeType: 'game' as const, scopeValue: 't5',
|
||||
content: Buffer.from('set sv_voice "1"\nset sv_maprotation "map zombie_theater"\n') },
|
||||
{ name: 'map settings', kind: 'config' as const, scopeType: 'map' as const, scopeValue: 'zombie_cosmodrome',
|
||||
content: Buffer.from('set sv_maprotation "map zombie_moon"\nset g_inactivity "0"\n') },
|
||||
{ name: 'other map', kind: 'config' as const, scopeType: 'map' as const, scopeValue: 'zombie_theater',
|
||||
content: Buffer.from('set sv_voice "0"\n') }
|
||||
]) {
|
||||
const { script } = createScript(artifact);
|
||||
db().prepare('UPDATE scripts SET current_version_id = ? WHERE id = ?')
|
||||
.run(getLatestVersion(script.id)!.id, script.id);
|
||||
}
|
||||
}
|
||||
|
||||
describe('server creation map selection', () => {
|
||||
it('keeps Plutainer defaults without a baseline and overrides the map before rotation', async () => {
|
||||
const { createServer } = await import('../../src/lib/server/fleet/create.js');
|
||||
const { komodoCreateDeployment, komodoDeploy } = await import('../../src/lib/server/integrations/komodo.js');
|
||||
const { getServer } = await import('../../src/lib/server/fleet/servers.js');
|
||||
const result = await createServer(input);
|
||||
expect(result).toMatchObject({ ok: true, name: serverName });
|
||||
expect(existsSync(join(fixture.t5, serverName, 'configs', 'dedicated_sp.cfg'))).toBe(false);
|
||||
const spec = vi.mocked(komodoCreateDeployment).mock.calls[0][0];
|
||||
const env = Object.fromEntries(spec.environment.map(({ variable, value }) => [variable, value]));
|
||||
expect(env.PLUTAINER_MAP_ROTATE).toBe('false');
|
||||
expect(env.PLUTAINER_EXTRA_ARGS).toBe('+exec electric_cherry_map.cfg +map_rotate');
|
||||
expect(await readFile(join(fixture.t5, serverName, 'configs', 'electric_cherry_map.cfg'), 'utf8'))
|
||||
.toContain('set sv_maprotation "map zombie_cosmodrome"');
|
||||
expect(env.PLUTAINER_CONFIG_FILE).toBe('dedicated_sp.cfg');
|
||||
expect(env.PLUTAINER_SKIP_SEED).toBeUndefined();
|
||||
expect(komodoDeploy).toHaveBeenCalledWith(serverName);
|
||||
expect(getServer(serverName)?.map).toBe('zombie_cosmodrome');
|
||||
const { scanServerDrift, runDriftDeployment } = await import('../../src/lib/server/fleet/drift.js');
|
||||
expect(await scanServerDrift(serverName)).toEqual([]);
|
||||
const mapPath = join(fixture.t5, serverName, 'configs', 'electric_cherry_map.cfg');
|
||||
await rm(mapPath);
|
||||
expect((await runDriftDeployment({ servers: [serverName] })).totalRestored).toBe(1);
|
||||
expect(await readFile(mapPath, 'utf8')).toContain('set sv_maprotation "map zombie_cosmodrome"');
|
||||
expect(await scanServerDrift(serverName)).toEqual([]);
|
||||
});
|
||||
|
||||
it('writes promoted settings and selected map before first boot and tracks the file', async () => {
|
||||
await promotedConfig();
|
||||
const { createServer } = await import('../../src/lib/server/fleet/create.js');
|
||||
const { komodoDeploy } = await import('../../src/lib/server/integrations/komodo.js');
|
||||
const { getServerScript } = await import('../../src/lib/server/fleet/inventory.js');
|
||||
const { scanServerDrift } = await import('../../src/lib/server/fleet/drift.js');
|
||||
vi.mocked(komodoDeploy).mockImplementationOnce(async () => {
|
||||
const config = await readFile(join(fixture.t5, serverName, 'configs', 'dedicated_sp.cfg'), 'utf8');
|
||||
expect(config).toContain('set sv_maprotation "map zombie_cosmodrome"');
|
||||
expect(config).toContain('set sv_voice "1"');
|
||||
expect(config).toContain('set g_inactivity "0"');
|
||||
expect(config).not.toContain('map zombie_moon');
|
||||
expect(getServerScript(serverName, 'dedicated_sp.cfg')?.source).toBe('create');
|
||||
return { ok: true };
|
||||
});
|
||||
expect(await createServer(input)).toMatchObject({ ok: true });
|
||||
expect(await scanServerDrift(serverName)).toEqual([]);
|
||||
});
|
||||
|
||||
it('removes the generated config and inventory if deployment fails', async () => {
|
||||
await promotedConfig();
|
||||
const { createServer } = await import('../../src/lib/server/fleet/create.js');
|
||||
const { komodoDeploy } = await import('../../src/lib/server/integrations/komodo.js');
|
||||
const { getServer } = await import('../../src/lib/server/fleet/servers.js');
|
||||
const { getServerScript } = await import('../../src/lib/server/fleet/inventory.js');
|
||||
vi.mocked(komodoDeploy).mockResolvedValueOnce({ ok: false, kind: 'http-error', message: 'test failure' });
|
||||
expect(await createServer(input)).toMatchObject({ ok: false, kind: 'komodo' });
|
||||
expect(getServer(serverName)).toBeUndefined();
|
||||
expect(getServerScript(serverName, 'dedicated_sp.cfg')).toBeUndefined();
|
||||
expect(existsSync(join(fixture.t5, serverName))).toBe(false);
|
||||
});
|
||||
|
||||
it('rejects unknown maps before creating a deployment', async () => {
|
||||
const { createServer } = await import('../../src/lib/server/fleet/create.js');
|
||||
const { komodoCreateDeployment } = await import('../../src/lib/server/integrations/komodo.js');
|
||||
expect(await createServer({ ...input, mapSlug: 'unknown' })).toMatchObject({ ok: false, kind: 'unknown-map' });
|
||||
expect(komodoCreateDeployment).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user