Ports push_scripts.sh (1497 lines of bash) to a SvelteKit + SQLite web app that manages a fleet of T5 Plutonium servers running on Komodo. Same push and beta workflow as the shell script, with a live SSE pipeline view, a !rt-token-gated auth modal that hard-pauses deployments on stale tokens, per-file audit diffs, and a Komodo-container deployment target. - Push/beta engines port the exact filter, held-by-beta, conflict, and stale-backup rules from the shell script - SQLite (better-sqlite3) replaces script_metadata.json; a one-shot importer moves legacy state over on first boot - 16 vitest tests cover push add/update/skip, beta open→rollback, open→promote, conflict guards, sweep frozen/manual branches, and IW4M login/status parsing - docker/ + komodo/stack.toml deploy as a managed container on the same host as the game servers, /root/zombies bind-mounted at the same path the shell script used
73 lines
2.8 KiB
TypeScript
73 lines
2.8 KiB
TypeScript
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
import { newFixture, type Fixture } from '../helpers/fixture.js';
|
|
|
|
let fixture: Fixture;
|
|
|
|
beforeEach(async () => {
|
|
vi.resetModules();
|
|
fixture = await newFixture();
|
|
process.env.IW4M_ADDRESS = 'http://iw4m.test';
|
|
process.env.IW4M_CLIENT_ID = '1';
|
|
vi.stubGlobal('fetch', vi.fn());
|
|
});
|
|
|
|
afterEach(async () => {
|
|
await fixture.cleanup();
|
|
vi.unstubAllGlobals();
|
|
});
|
|
|
|
describe('loginWithToken', () => {
|
|
it('returns cookie + expiry on 2xx', async () => {
|
|
(globalThis.fetch as ReturnType<typeof vi.fn>).mockResolvedValueOnce(
|
|
new Response('', {
|
|
status: 200,
|
|
headers: { 'Set-Cookie': '.AspNetCore.Cookies=abc; path=/; httponly' }
|
|
})
|
|
);
|
|
const { loginWithToken } = await import('../../src/lib/server/integrations/iw4m.js');
|
|
const res = await loginWithToken('abcd');
|
|
expect(res.ok).toBe(true);
|
|
if (res.ok) {
|
|
expect(res.cookie).toContain('.AspNetCore.Cookies=abc');
|
|
expect(res.expiresAt).toBeGreaterThan(Date.now());
|
|
}
|
|
});
|
|
|
|
it('returns rejected on 401', async () => {
|
|
(globalThis.fetch as ReturnType<typeof vi.fn>).mockResolvedValueOnce(new Response('', { status: 401 }));
|
|
const { loginWithToken } = await import('../../src/lib/server/integrations/iw4m.js');
|
|
const res = await loginWithToken('abcd');
|
|
expect(res.ok).toBe(false);
|
|
if (!res.ok) expect(res.reason).toBe('rejected');
|
|
});
|
|
|
|
it('returns unreachable on network error', async () => {
|
|
(globalThis.fetch as ReturnType<typeof vi.fn>).mockRejectedValueOnce(new Error('ECONNREFUSED'));
|
|
const { loginWithToken } = await import('../../src/lib/server/integrations/iw4m.js');
|
|
const res = await loginWithToken('abcd');
|
|
expect(res.ok).toBe(false);
|
|
if (!res.ok) expect(res.reason).toBe('unreachable');
|
|
});
|
|
});
|
|
|
|
describe('fetchStatus', () => {
|
|
it('normalizes camelCase / PascalCase entries', async () => {
|
|
(globalThis.fetch as ReturnType<typeof vi.fn>).mockResolvedValueOnce(
|
|
new Response(
|
|
JSON.stringify([
|
|
{ id: 1, port: 3072, name: 'kino der toten', currentPlayers: 3 },
|
|
{ Id: 2, Port: 3073, Name: 'ascension', CurrentPlayers: 0 }
|
|
]),
|
|
{ status: 200, headers: { 'Content-Type': 'application/json' } }
|
|
)
|
|
);
|
|
const { fetchStatus } = await import('../../src/lib/server/integrations/iw4m.js');
|
|
const res = await fetchStatus();
|
|
expect(res.ok).toBe(true);
|
|
if (res.ok) {
|
|
expect(res.entries[0].currentPlayers).toBe(3);
|
|
expect(res.entries[1].name).toBe('ascension');
|
|
}
|
|
});
|
|
});
|