Files

175 lines
6.9 KiB
TypeScript

import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import { readFile, mkdir } from 'node:fs/promises';
import { existsSync } from 'node:fs';
import { join } from 'node:path';
import { newFixture, type Fixture } from '../helpers/fixture.js';
import { activateFiles } from '../helpers/staged-files.js';
let fixture: Fixture;
beforeEach(async () => {
vi.resetModules();
fixture = await newFixture(['t5_zombie_theater_1']);
delete process.env.RCON_PASSWORD;
});
afterEach(async () => {
await fixture.cleanup();
});
const BASELINE_CFG = [
'// GENERAL',
'set g_inactivity "190"',
'set sv_voice "1"',
'',
'set sv_maprotation "map zombie_theater"'
].join('\n');
async function seedFleet(): Promise<void> {
const { seedServerRegistry } = await import('../../src/lib/server/fleet/servers.js');
await seedServerRegistry();
// Pre-create the configs directory so the config write path finds a
// hospitable server tree.
await mkdir(join(fixture.t5, 't5_zombie_theater_1', 'configs'), { recursive: true });
}
describe('config baseline + overlay flow', () => {
it('composes baseline + overlay for activation and queues restoration of the pre-beta state', async () => {
await seedFleet();
const { createScript, getLatestVersion } =
await import('../../src/lib/server/fleet/scripts.js');
const { openBeta, rollbackBeta } = await import('../../src/lib/server/fleet/beta.js');
// 1. Upload baseline for game=t5.
const { script: baselineScript } = createScript({
name: 't5 baseline',
kind: 'baseline',
scopeType: 'game',
scopeValue: 't5',
content: Buffer.from(BASELINE_CFG, 'utf8')
});
const bVer = getLatestVersion(baselineScript.id)!;
// 2. Upload an overlay for game=t5.
const { script: overlayScript } = createScript({
name: 'disable afk',
kind: 'config',
scopeType: 'game',
scopeValue: 't5',
content: Buffer.from('set g_inactivity "0"', 'utf8')
});
const oVer = getLatestVersion(overlayScript.id)!;
// 3. Bundle both into one beta.
const { betaId } = await openBeta({
scripts: [
{ scriptId: baselineScript.id, scriptVersionId: bVer.id },
{ scriptId: overlayScript.id, scriptVersionId: oVer.id }
],
servers: ['t5_zombie_theater_1'],
waitForHealth: false,
waitForMatch: false
});
// The live filename is untouched until activation.
const cfgPath = join(fixture.t5, 't5_zombie_theater_1', 'configs', 'dedicated_sp.cfg');
expect(existsSync(cfgPath)).toBe(false);
await activateFiles('beta', betaId);
expect(existsSync(cfgPath)).toBe(true);
const composed = (await readFile(cfgPath)).toString('utf8');
expect(composed).toContain('set g_inactivity "0"');
expect(composed).not.toContain('set g_inactivity "190"');
// Baseline lines that weren't overridden are preserved.
expect(composed).toContain('set sv_voice "1"');
// 5. Rollback restores the pre-beta state. There was no cfg on disk
// beforehand, so rollback should just remove the beta-written file.
const rollback = await rollbackBeta(betaId);
expect(await readFile(cfgPath, 'utf8')).toBe(composed);
await activateFiles('campaign', rollback.deploymentId!);
expect(existsSync(cfgPath)).toBe(false);
});
it('requires an existing baseline before overlays can compose', async () => {
await seedFleet();
const { createScript, getLatestVersion } =
await import('../../src/lib/server/fleet/scripts.js');
const { openBeta } = await import('../../src/lib/server/fleet/beta.js');
const { db } = await import('../../src/lib/server/db/index.js');
const { script: overlayScript } = createScript({
name: 'lonely overlay',
kind: 'config',
scopeType: 'game',
scopeValue: 't5',
content: Buffer.from('set g_inactivity "0"', 'utf8')
});
const oVer = getLatestVersion(overlayScript.id)!;
// openBeta returns success at the SQL level but the deploy step
// marks the server 'failed' with a "no baseline" error because no
// baseline is available.
const { betaId } = await openBeta({
scripts: [{ scriptId: overlayScript.id, scriptVersionId: oVer.id }],
servers: ['t5_zombie_theater_1'],
waitForHealth: false,
waitForMatch: false
});
await activateFiles('beta', betaId);
const row = db()
.prepare(`SELECT stage, error FROM beta_servers WHERE beta_id = ? AND server = ?`)
.get(betaId, 't5_zombie_theater_1') as { stage: string; error: string | null };
expect(row.stage).toBe('failed');
expect(row.error ?? '').toMatch(/no baseline available/);
});
});
describe('plugin kind writes to plugin destination', () => {
it('deploys a plugin .dll directly to its target filename', async () => {
await seedFleet();
// Pre-create the plugins dir so the write can succeed.
await mkdir(join(fixture.t5, 't5_zombie_theater_1', 'runtime/plutonium/plugins'), {
recursive: true
});
const { createScript, getLatestVersion } =
await import('../../src/lib/server/fleet/scripts.js');
const { openBeta } = await import('../../src/lib/server/fleet/beta.js');
const { script } = createScript({
name: 'demo plugin',
kind: 'plugin',
releaseVersion: 'v3.1.0',
targetRelPath: 'demo.dll',
scopeType: 'game',
scopeValue: 't5',
content: Buffer.from([0x00, 0x01, 0x02, 0x03])
});
const v = getLatestVersion(script.id)!;
const { betaId } = await openBeta({
scripts: [{ scriptId: script.id, scriptVersionId: v.id }],
servers: ['t5_zombie_theater_1'],
waitForHealth: false,
waitForMatch: false
});
const pluginPath = join(
fixture.t5,
't5_zombie_theater_1',
'runtime/plutonium/plugins',
'demo.dll'
);
expect(existsSync(pluginPath)).toBe(false);
await activateFiles('beta', betaId);
expect(existsSync(pluginPath)).toBe(true);
const bytes = await readFile(pluginPath);
expect(bytes.equals(Buffer.from([0x00, 0x01, 0x02, 0x03]))).toBe(true);
expect(betaId).toBeGreaterThan(0);
const { GET } = await import('../../src/routes/api/betas/[id]/+server.js');
const detail = await (await GET({ params: { id: String(betaId) } })).json();
expect(detail.scripts[0].version.release_version).toBe('v3.1.0');
});
});