Use ordered SQLite migrations (Refs #75)
CI / checks (push) Successful in 5m6s

This commit is contained in:
2026-10-10 00:01:18 -04:00
parent a0663e43b8
commit 8d1bb69591
56 changed files with 1085 additions and 736 deletions
+3
View File
@@ -136,6 +136,7 @@ it.each(['dry-run', 'create-only'])('turns saved %s policies off once on upgrade
"UPDATE autoscaling_policies SET mode=?,settings=json_set(settings,'$.mode',?) WHERE id=?"
)
.run(mode, mode, saved.id);
db().pragma('user_version = 0');
closeDb();
const migrated = scaling.getScalingPolicy(saved.id)!;
expect(migrated).toMatchObject({
@@ -177,6 +178,7 @@ it.each([false, true])(
db()
.prepare('UPDATE server_deployments SET submitted_at=? WHERE id=?')
.run(new Date().toISOString(), id);
db().pragma('user_version = 0');
closeDb();
expect(scaling.getScalingPolicy(saved.id)?.mode).toBe('off');
expect(
@@ -211,6 +213,7 @@ it('removes saved cooldowns on upgrade without changing the empty hold or pausin
"UPDATE autoscaling_policies SET next_action_at=?,settings=json_set(settings,'$.cooldownMinutes',60) WHERE id=?"
)
.run(Date.now() + 3600_000, saved.id);
db().pragma('user_version = 0');
closeDb();
const restored = scaling.getScalingPolicy(saved.id)!;
expect(restored).toEqual(saved);
+1
View File
@@ -108,6 +108,7 @@ describe('per-server beta membership', () => {
const x = await seedBeta(false);
x.assign('first', x.scripts[0].id);
x.db().prepare('DELETE FROM beta_scripts WHERE beta_id = ?').run(x.betaId);
x.db().pragma('user_version = 0');
x.closeDb();
expect(x.memberships()).toEqual([
{
+1
View File
@@ -583,6 +583,7 @@ describe('durable beta test plans', () => {
.prepare('INSERT INTO beta_test_presets (name, plan) VALUES (?, ?)')
.run('Old plan', JSON.stringify(oldPlan));
db().prepare("DELETE FROM settings WHERE key = 'game_telemetry_v2'").run();
db().pragma('user_version = 0');
closeDb();
const { testView } = await import('../../src/lib/server/fleet/beta-tests.js');
const { getBeta } = await import('../../src/lib/server/fleet/beta.js');
+1
View File
@@ -69,6 +69,7 @@ describe('reviewed beta promotion', () => {
const legacy = new Database(fixture.dbPath);
try {
legacy.exec('ALTER TABLE deployment_files DROP COLUMN snapshot_pending');
legacy.pragma('user_version = 0');
} finally {
legacy.close();
}
-2
View File
@@ -18,8 +18,6 @@ afterEach(async () => {
async function host(id: string, createdAt = epoch - 7 * 86_400_000) {
const { db } = await import('../../src/lib/server/db/index.js');
const { ensureHostSchema } = await import('../../src/lib/server/hosts/schema.js');
ensureHostSchema(db());
db()
.prepare('INSERT INTO ec_hosts (id,name,created_at) VALUES (?,?,?)')
.run(id, id, new Date(createdAt).toISOString());
-2
View File
@@ -45,8 +45,6 @@ beforeEach(async () => {
vi.useFakeTimers({ toFake: ['Date'] });
vi.setSystemTime(epoch);
const { db } = await import('../../src/lib/server/db/index.js');
const { ensureGameFileSchema } = await import('../../src/lib/server/game-files/schema.js');
ensureGameFileSchema(db());
for (const game of ['t5', 't6']) {
db()
.prepare(
@@ -431,6 +431,7 @@ describe('collection cancellation', () => {
const detail = await launch(1);
const { db, closeDb } = await import('../../src/lib/server/db/index.js');
db().exec('ALTER TABLE server_launch_collections DROP COLUMN cancelled_at');
db().pragma('user_version = 0');
closeDb();
const { getCollection, cancelCollection } =
await import('../../src/lib/server/fleet/collections.js');
+1 -5
View File
@@ -34,13 +34,9 @@ it('indexes campaign cascades and beta evidence on fresh and reopened databases'
}
});
it('covers every foreign key child prefix, including lazy host and library schemas', async () => {
it('covers every foreign key child prefix, including host and library schemas', async () => {
const { db } = await import('../../src/lib/server/db/index.js');
const { ensureHostSchema } = await import('../../src/lib/server/hosts/schema.js');
const { ensureGameFileSchema } = await import('../../src/lib/server/game-files/schema.js');
const handle = db();
ensureHostSchema(handle);
ensureGameFileSchema(handle);
const tables = handle.prepare("SELECT name FROM sqlite_master WHERE type='table'").all() as {
name: string;
}[];
+140
View File
@@ -0,0 +1,140 @@
import Database from 'better-sqlite3';
import { afterEach, beforeEach, expect, it, vi } from 'vitest';
import { newFixture, type Fixture } from '../helpers/fixture.js';
let fixture: Fixture;
beforeEach(async () => {
vi.resetModules();
fixture = await newFixture([]);
});
afterEach(async () => {
vi.restoreAllMocks();
await fixture.cleanup();
});
it('initializes every schema on first open and runs no migration SQL on later opens', async () => {
const { db, closeDb } = await import('../../src/lib/server/db/index.js');
const { databaseVersion } = await import('../../src/lib/server/db/migrations.js');
const handle = db();
expect(handle.pragma('user_version', { simple: true })).toBe(databaseVersion);
for (const table of [
'servers',
'native_instances',
'ec_hosts',
'auth_users',
'game_file_versions'
])
expect(handle.prepare('SELECT name FROM sqlite_master WHERE name=?').get(table)).toEqual({
name: table
});
handle.exec(
"INSERT INTO servers(name,last_reply_snippet) VALUES('saved','sanitized diagnostic')"
);
const schemaVersion = handle.pragma('schema_version', { simple: true });
closeDb();
const exec = vi.spyOn(Database.prototype, 'exec');
const prepare = vi.spyOn(Database.prototype, 'prepare');
const reopened = db();
expect(exec).not.toHaveBeenCalled();
expect(prepare).not.toHaveBeenCalled();
expect(reopened.pragma('schema_version', { simple: true })).toBe(schemaVersion);
expect(reopened.prepare('SELECT name,last_reply_snippet FROM servers').get()).toEqual({
name: 'saved',
last_reply_snippet: 'sanitized diagnostic'
});
expect(reopened.pragma('foreign_key_check')).toEqual([]);
});
it('adopts a fully migrated unversioned database without replaying destructive backfills', async () => {
const { db, closeDb } = await import('../../src/lib/server/db/index.js');
const { databaseVersion } = await import('../../src/lib/server/db/migrations.js');
const handle = db();
handle.exec(`INSERT INTO servers(name,desired_state,last_reply_snippet) VALUES('saved','stopped','keep');
INSERT INTO file_blobs(sha256,size,content) VALUES('hash',1,X'01');
INSERT INTO scripts(id,name,target_rel_path) VALUES(1,'saved','test.gsc');
INSERT INTO script_versions(id,script_id,version,sha256,size,content_hash) VALUES(1,1,1,'hash',1,'hash');
INSERT INTO betas(id,script_id,script_version_id,trigger_kind,state) VALUES(1,1,1,'bundle','running');
INSERT INTO beta_test_plans(beta_id,plan,outcome) VALUES(1,'{"requirements":[]}','active');`);
// All pre-versioning releases used 0; their sentinels are compatibility inputs.
handle.pragma('user_version = 0');
closeDb();
expect(db().pragma('user_version', { simple: true })).toBe(databaseVersion);
expect(db().prepare('SELECT desired_state,last_reply_snippet FROM servers').get()).toEqual({
desired_state: 'stopped',
last_reply_snippet: 'keep'
});
expect(db().prepare('SELECT state FROM betas').get()).toEqual({ state: 'running' });
expect(db().prepare('SELECT plan FROM beta_test_plans').get()).toEqual({
plan: '{"requirements":[]}'
});
expect(db().pragma('foreign_key_check')).toEqual([]);
});
it('closes a failed startup handle and resumes from the last committed version', async () => {
const { db } = await import('../../src/lib/server/db/index.js');
const { migrations, databaseVersion } = await import('../../src/lib/server/db/migrations.js');
const hosts = migrations.find(
(migration) => migration.name === 'hosts and saved command files'
)!;
const run = hosts.run;
const failure = vi.spyOn(hosts, 'run').mockImplementation((handle) => {
run(handle);
throw new Error('interrupted host upgrade');
});
expect(db).toThrow('interrupted host upgrade');
const inspect = new Database(fixture.dbPath);
try {
expect(inspect.pragma('user_version', { simple: true })).toBe(hosts.version - 1);
expect(
inspect.prepare("SELECT 1 FROM sqlite_master WHERE name='ec_hosts'").get()
).toBeUndefined();
} finally {
inspect.close();
}
failure.mockRestore();
const completed = migrations
.slice(0, hosts.version - 1)
.map((migration) => vi.spyOn(migration, 'run'));
expect(db().pragma('user_version', { simple: true })).toBe(databaseVersion);
for (const migration of completed) expect(migration).not.toHaveBeenCalled();
expect(db().pragma('foreign_key_check')).toEqual([]);
});
it('keeps the legacy snapshot safety guard and rolls back an unsafe first upgrade', async () => {
const { db, closeDb } = await import('../../src/lib/server/db/index.js');
const handle = db();
handle.exec(`INSERT INTO file_blobs(sha256,size,content) VALUES('hash',1,X'01');
INSERT INTO scripts(id,name,target_rel_path) VALUES(1,'saved','test.gsc');
INSERT INTO script_versions(id,script_id,version,sha256,size,content_hash) VALUES(1,1,1,'hash',1,'hash');
INSERT INTO betas(id,script_id,script_version_id,trigger_kind,state) VALUES(1,1,1,'bundle','running');
DELETE FROM settings WHERE key='snapshots_v1';`);
handle.pragma('user_version = 0');
closeDb();
expect(db).toThrow('close active beta 1 before upgrading');
const inspect = new Database(fixture.dbPath);
try {
expect(inspect.pragma('user_version', { simple: true })).toBe(0);
expect(inspect.prepare('SELECT state FROM betas').get()).toEqual({ state: 'running' });
// The join backfill precedes the guard, but must roll back with it.
expect(inspect.prepare('SELECT * FROM beta_scripts').all()).toEqual([]);
expect(inspect.pragma('foreign_key_check')).toEqual([]);
} finally {
inspect.close();
}
});
it('rejects a database from a newer application without changing its version or schema', async () => {
const { db, closeDb } = await import('../../src/lib/server/db/index.js');
const { databaseVersion } = await import('../../src/lib/server/db/migrations.js');
db().pragma(`user_version = ${databaseVersion + 1}`);
const schemaVersion = db().pragma('schema_version', { simple: true });
closeDb();
expect(db).toThrow('not supported by this application');
const inspect = new Database(fixture.dbPath);
try {
expect(inspect.pragma('user_version', { simple: true })).toBe(databaseVersion + 1);
expect(inspect.pragma('schema_version', { simple: true })).toBe(schemaVersion);
} finally {
inspect.close();
}
});
@@ -368,6 +368,7 @@ describe('matching a blueprint exactly', () => {
// Recreate the older schema, which predates the history preservation trigger.
db().exec('DROP TRIGGER preserve_deleted_server_history');
db().exec('ALTER TABLE server_launch_collections DROP COLUMN retained_servers');
db().pragma('user_version = 0');
closeDb();
const { previewCollection } = await import('../../src/lib/server/fleet/collections.js');
expect(previewCollection(plan.id).mode).toBe('fill');
+1
View File
@@ -163,6 +163,7 @@ it('migrates legacy command and receipt bytes without changing replay identity',
"DELETE FROM ec_host_schema_migrations WHERE name IN ('staged-operation-payloads','operation-file-blobs')"
)
.run();
db().pragma('user_version = 0');
closeDb();
expect(hosts.getHostOperation('legacy')).toMatchObject({ payload, result, state: 'running' });
expect(
+3
View File
@@ -65,6 +65,7 @@ describe('fleet settings API and persistence', () => {
const { db, closeDb } = await import('../../src/lib/server/db/index.js');
db().prepare("DELETE FROM settings WHERE key = 'fleet_settings'").run();
db().prepare("INSERT INTO settings (key, value) VALUES ('unrelated', 'true')").run();
db().pragma('user_version = 0');
closeDb();
vi.stubEnv('DEPLOY_KEEP_DAYS', '14');
vi.stubEnv('DEPLOYMENT_RESTART_CONCURRENCY', '4');
@@ -92,6 +93,7 @@ describe('fleet settings API and persistence', () => {
db()
.prepare("UPDATE settings SET value = ? WHERE key = 'fleet_settings'")
.run(JSON.stringify(previous));
db().pragma('user_version = 0');
closeDb();
vi.stubEnv('DEPLOY_KEEP_DAYS', 'bad');
vi.stubEnv('DEPLOYMENT_RESTART_CONCURRENCY', '9');
@@ -129,6 +131,7 @@ describe('fleet settings API and persistence', () => {
.prepare("UPDATE settings SET value = ? WHERE key = 'fleet_settings'")
.run(JSON.stringify(previous));
db().prepare("INSERT INTO settings(key,value) VALUES('unrelated','keep')").run();
db().pragma('user_version = 0');
closeDb();
vi.stubEnv('DEPLOY_KEEP_DAYS', 'bad');
const { GET } = await import('../../src/routes/api/settings/+server.js');
@@ -354,6 +354,7 @@ describe('host game-file transfer progress', () => {
const before = cache();
database.db().exec('ALTER TABLE host_library_caches DROP COLUMN transfer_progress');
database.db().exec('ALTER TABLE host_library_caches DROP COLUMN bytes_verified');
database.db().pragma('user_version = 0');
database.closeDb();
expect(cache()).toMatchObject({
hostId,
@@ -238,6 +238,7 @@ describe('measured instance capacity', () => {
db().exec(
'UPDATE ec_resource_allowances SET cpu_cores=cpu_cores*1.25, memory_bytes=memory_bytes*1.25'
);
db().pragma('user_version = 0');
closeDb();
const { capacityCalibration } = await import('../../src/lib/server/hosts/calibration.js');
expect(capacityCalibration(hostId)).toEqual(model);
+3 -5
View File
@@ -51,14 +51,14 @@ async function instance(hostId: string, retired = false) {
describe('host deletion', () => {
it('adds the deletion marker to existing host schemas without losing hosts', async () => {
const { ensureHostSchema } = await import('../../src/lib/server/hosts/schema.js');
const { migrateHostSchema } = await import('../../src/lib/server/hosts/schema.js');
const legacy = new Database(':memory:');
try {
legacy.exec(
"CREATE TABLE ec_hosts(id TEXT PRIMARY KEY, name TEXT NOT NULL); INSERT INTO ec_hosts VALUES('old','Existing host')"
);
ensureHostSchema(legacy);
ensureHostSchema(legacy);
migrateHostSchema(legacy);
migrateHostSchema(legacy);
expect(legacy.prepare('SELECT * FROM ec_hosts').get()).toEqual({
id: 'old',
name: 'Existing host',
@@ -99,8 +99,6 @@ describe('host deletion', () => {
const file = join(fixture.root, 'game-files-sentinel');
await writeFile(file, 'Game files remain');
const { db } = await import('../../src/lib/server/db/index.js');
const { ensureGameFileSchema } = await import('../../src/lib/server/game-files/schema.js');
ensureGameFileSchema(db());
db()
.prepare(
"INSERT INTO game_file_libraries(id,name,game,description,created_at) VALUES('library','T5','t5','',?)"
@@ -222,6 +222,7 @@ describe('routine host observation retention', () => {
api.handle.exec(
"UPDATE ec_host_operations SET observation_success=0; DELETE FROM ec_host_schema_migrations WHERE name='completed-status-batches'"
);
api.handle.pragma('user_version = 0');
api.closeDb();
expect(
api
@@ -380,6 +381,7 @@ describe('routine host observation retention', () => {
DROP TRIGGER operation_history_count_update; DROP TABLE ec_host_operation_history_counts;
DELETE FROM ec_host_schema_migrations WHERE name='history-counts-v1'`);
api.handle.exec('ALTER TABLE ec_host_operations DROP COLUMN observation_success');
api.handle.pragma('user_version = 0');
api.closeDb();
expect(api.pruneHostObservations()).toBe(2);
@@ -574,6 +574,7 @@ describe('host operation history', () => {
ALTER TABLE ec_host_operations DROP COLUMN server_name;
ALTER TABLE ec_host_operations DROP COLUMN target_path;
ALTER TABLE ec_host_operations DROP COLUMN source;`);
api.handle.pragma('user_version = 0');
closeDb();
const history = api.listHostOperationHistory(1, 'all');
expect(history.operations).toHaveLength(4);
+1
View File
@@ -638,6 +638,7 @@ it('backfills valid saved telemetry once while skipping malformed and legacy sna
insert.run('t6_zombie_town_1', 99, 4, receivedAt, '{malformed');
insert.run('t6_zombie_farm_1', 99, 4, receivedAt, null);
db().prepare("DELETE FROM settings WHERE key = 'leaderboard_v1'").run();
db().pragma('user_version = 0');
closeDb();
expect(await matches()).toEqual([
expect.objectContaining({ highest_round: 8, start_observed: 0 })
+1
View File
@@ -291,6 +291,7 @@ it('migrates legacy participation as unknown before snapshot backfill and preser
)
.run(saved.server, 8, 1, saved.receivedAt, JSON.stringify(saved));
handle.prepare("DELETE FROM settings WHERE key = 'leaderboard_v1'").run();
handle.pragma('user_version = 0');
closeDb();
expect(
db()
+159
View File
@@ -0,0 +1,159 @@
import Database from 'better-sqlite3';
import { afterEach, beforeEach, expect, it, vi } from 'vitest';
import { runMigrations, type Migration } from '../../src/lib/server/db/migration-runner.js';
let handle: Database.Database;
beforeEach(() => {
handle = new Database(':memory:');
handle.pragma('foreign_keys = ON');
});
afterEach(() => {
vi.restoreAllMocks();
handle.close();
});
it('applies ordered upgrades once and appends only pending versions', () => {
const first = vi.fn((db: Database.Database) =>
db.exec("CREATE TABLE records(value TEXT); INSERT INTO records VALUES('original')")
);
const migrations: Migration[] = [{ version: 1, name: 'records', run: first }];
runMigrations(handle, migrations);
handle.exec("UPDATE records SET value='edited'");
const next = vi.fn((db: Database.Database) => {
expect(db.pragma('user_version', { simple: true })).toBe(1);
db.exec('ALTER TABLE records ADD COLUMN added INTEGER DEFAULT 7');
});
migrations.push({ version: 2, name: 'additional field', run: next });
runMigrations(handle, migrations);
runMigrations(handle, migrations);
expect(first).toHaveBeenCalledTimes(1);
expect(next).toHaveBeenCalledTimes(1);
expect(handle.prepare('SELECT * FROM records').get()).toEqual({ value: 'edited', added: 7 });
expect(handle.pragma('user_version', { simple: true })).toBe(2);
});
it('rolls back schema, data and version on failure, then retries only the failed step', () => {
let fail = true;
const migrations: Migration[] = [
{
version: 1,
name: 'records',
run: (db) =>
db.exec("CREATE TABLE records(value TEXT); INSERT INTO records VALUES('old')")
},
{
version: 2,
name: 'backfill',
run: (db) => {
db.exec(
"ALTER TABLE records ADD COLUMN extra TEXT; UPDATE records SET value='new'"
);
if (fail) throw new Error('interrupted');
}
},
{
version: 3,
name: 'index',
run: (db) => db.exec('CREATE INDEX records_value ON records(value)')
}
];
expect(() => runMigrations(handle, migrations)).toThrow('interrupted');
expect(handle.pragma('user_version', { simple: true })).toBe(1);
expect(handle.prepare('SELECT * FROM records').get()).toEqual({ value: 'old' });
expect(handle.pragma('index_list(records)')).toEqual([]);
fail = false;
runMigrations(handle, migrations);
expect(handle.prepare('SELECT * FROM records').get()).toEqual({ value: 'new', extra: null });
expect(handle.pragma('user_version', { simple: true })).toBe(3);
});
it('does not commit a migration if recording its version fails', () => {
const pragma = handle.pragma.bind(handle);
vi.spyOn(handle, 'pragma').mockImplementation((sql, options) => {
if (sql === 'user_version = 1') throw new Error('version write failed');
return pragma(sql, options);
});
expect(() =>
runMigrations(handle, [
{
version: 1,
name: 'records',
run: (db) => db.exec('CREATE TABLE records(value TEXT)')
}
])
).toThrow('version write failed');
expect(pragma('user_version', { simple: true })).toBe(0);
expect(
handle.prepare("SELECT 1 FROM sqlite_master WHERE name='records'").get()
).toBeUndefined();
});
it('rebuilds a referenced table without cascading and restores foreign key enforcement', () => {
handle.exec(`CREATE TABLE parent(id INTEGER PRIMARY KEY);
CREATE TABLE child(parent_id INTEGER REFERENCES parent(id) ON DELETE CASCADE);
INSERT INTO parent VALUES(1); INSERT INTO child VALUES(1);`);
runMigrations(handle, [
{
version: 1,
name: 'rebuild',
mode: 'foreign-keys-off',
run: (db) =>
db.exec(`CREATE TABLE replacement(id INTEGER PRIMARY KEY, label TEXT);
INSERT INTO replacement(id) SELECT id FROM parent;
DROP TABLE parent; ALTER TABLE replacement RENAME TO parent;`)
}
]);
expect(handle.prepare('SELECT * FROM child').all()).toEqual([{ parent_id: 1 }]);
expect(handle.pragma('foreign_keys', { simple: true })).toBe(1);
expect(handle.pragma('foreign_key_check')).toEqual([]);
});
it('rolls back broken references and restores foreign keys on failure', () => {
handle.exec(`CREATE TABLE parent(id INTEGER PRIMARY KEY);
CREATE TABLE child(parent_id INTEGER REFERENCES parent(id));`);
expect(() =>
runMigrations(handle, [
{
version: 1,
name: 'broken reference',
mode: 'foreign-keys-off',
run: (db) => db.exec('INSERT INTO child VALUES(99)')
}
])
).toThrow('broken references');
expect(handle.prepare('SELECT * FROM child').all()).toEqual([]);
expect(handle.pragma('foreign_keys', { simple: true })).toBe(1);
expect(handle.pragma('user_version', { simple: true })).toBe(0);
});
it('leaves interrupted autocommit work pending for a resumable retry', () => {
let fail = true;
const migrations: Migration[] = [
{
version: 1,
name: 'vacuum',
mode: 'autocommit',
run: (db) => {
expect(db.inTransaction).toBe(false);
db.exec('CREATE TABLE IF NOT EXISTS records(value TEXT); VACUUM');
if (fail) throw new Error('interrupted after vacuum');
}
}
];
expect(() => runMigrations(handle, migrations)).toThrow('interrupted after vacuum');
expect(handle.pragma('user_version', { simple: true })).toBe(0);
fail = false;
runMigrations(handle, migrations);
expect(handle.pragma('user_version', { simple: true })).toBe(1);
});
it('rejects newer databases and invalid migration sequences before running work', () => {
const run = vi.fn();
handle.pragma('user_version = 2');
expect(() => runMigrations(handle, [{ version: 1, name: 'older app', run }])).toThrow(
'Database version 2 is not supported'
);
handle.pragma('user_version = 0');
expect(() => runMigrations(handle, [{ version: 2, name: 'gap', run }])).toThrow('consecutive');
expect(run).not.toHaveBeenCalled();
});
@@ -21,8 +21,6 @@ afterEach(async () => {
async function intent() {
const { db } = await import('../../src/lib/server/db/index.js');
const { ensureHostSchema } = await import('../../src/lib/server/hosts/schema.js');
ensureHostSchema(db());
const { beginDeployment } = await import('../../src/lib/server/provisioning/history.js');
const id = beginDeployment(server, 'create');
db().prepare("UPDATE server_deployments SET state='queued' WHERE id=?").run(id);
+1
View File
@@ -75,6 +75,7 @@ describe('plugin release versions', () => {
const { db, closeDb } = await import('../../src/lib/server/db/index.js');
const { version } = createScript(plugin);
db().exec('ALTER TABLE script_versions DROP COLUMN release_version');
db().pragma('user_version = 0');
closeDb();
expect(getVersion(version.id)).toMatchObject({
id: version.id,
@@ -122,6 +122,7 @@ it('adds the requester column to existing databases without inventing historical
await import('../../src/lib/server/provisioning/history.js');
const id = beginDeployment('legacy', 'restart');
db().exec('ALTER TABLE server_deployments DROP COLUMN requested_by');
db().pragma('user_version = 0');
closeDb();
expect(getDeployment(id)?.deployment).toMatchObject({ requested_by: null, triggered_by: 'ui' });
});
+1
View File
@@ -103,6 +103,7 @@ it('discards legacy plain text and hex snippets and unsafe error details once du
db().exec(`DELETE FROM settings WHERE key='rcon_private_diagnostics_v1';
UPDATE servers SET last_error='bad-reply:print reply not recognized: "player 192.0.2.123:28960"';
INSERT INTO servers(name,last_reply_snippet) VALUES ('legacy-hex','0000 31 39 32 2e 30 2e 32 2e 31 32 33 3a 32 38 39 36 192.0.2.123:2896');`);
db().pragma('user_version = 0');
closeDb();
expect(db().prepare('SELECT last_reply_snippet FROM servers').all()).toEqual([
{ last_reply_snippet: null },
-2
View File
@@ -89,11 +89,9 @@ it('expires completed file payloads while keeping history, replay identity, hold
it('retains rebalance recovery files until the move is reconciled, then lets history expire', async () => {
const { db } = await import('../../src/lib/server/db/index.js');
const { ensureHostSchema } = await import('../../src/lib/server/hosts/schema.js');
const { pruneHistory } = await import('../../src/lib/server/retention.js');
const { putFileBlob } = await import('../../src/lib/server/db/file-blobs.js');
const { fleetSettings, saveFleetSettings } = await import('../../src/lib/server/settings.js');
ensureHostSchema(db());
saveFleetSettings({
...fleetSettings(),
historyKeepCount: 0,
-2
View File
@@ -12,8 +12,6 @@ afterEach(async () => {
});
async function api() {
const { db } = await import('../../src/lib/server/db/index.js');
const { ensureHostSchema } = await import('../../src/lib/server/hosts/schema.js');
ensureHostSchema(db());
return {
db: db(),
...(await import('../../src/lib/server/retention.js')),
-6
View File
@@ -188,13 +188,7 @@ describe('useful synthetic fixture data', () => {
vi.resetModules();
fixture = await newFixture([]);
const { db } = await import('../../src/lib/server/db/index.js');
const { ensureHostSchema } = await import('../../src/lib/server/hosts/schema.js');
const { ensureAuthSchema } = await import('../../src/lib/server/auth/schema.js');
const { ensureGameFileSchema } = await import('../../src/lib/server/game-files/schema.js');
handle = db();
ensureHostSchema(handle);
ensureAuthSchema(handle);
ensureGameFileSchema(handle);
seedFixtureData(handle, NOW);
}, 20_000);
beforeEach(() => handle.exec('SAVEPOINT fixture_test'));
+2 -2
View File
@@ -72,9 +72,9 @@ it('recovers actual launch credentials and holds both sides of an unresolved leg
previous.exec(`DROP TABLE server_keys;
CREATE TABLE server_keys(server_id TEXT PRIMARY KEY,pluto_key TEXT NOT NULL,updated_at TEXT NOT NULL DEFAULT (datetime('now')));`);
const { migrateNativeInstances } = await import('../../src/lib/server/backends/schema.js');
const { ensureHostSchema } = await import('../../src/lib/server/hosts/schema.js');
const { migrateHostSchema } = await import('../../src/lib/server/hosts/schema.js');
migrateNativeInstances(previous);
ensureHostSchema(previous);
migrateHostSchema(previous);
previous.exec(`INSERT INTO ec_hosts(id,name,created_at) VALUES('host','Fixture host','2026-01-01');
INSERT INTO servers(name,game) VALUES('t5_zombie_theater_1','t5'),('t5_zombie_pentagon_1','t5');
INSERT INTO server_keys(server_id,pluto_key) VALUES('t5_zombie_theater_1','edited-saved-key'),
+1
View File
@@ -206,6 +206,7 @@ describe('database fleet and container readiness', () => {
const { db, getServer } = await setup();
const { closeDb } = await import('../../src/lib/server/db/index.js');
db.exec('ALTER TABLE servers DROP COLUMN rcon_failures');
db.pragma('user_version = 0');
closeDb();
expect(getServer('managed')).toMatchObject({
rcon_failures: 0,
+1
View File
@@ -416,6 +416,7 @@ it('migrates old round-only storage without fabricating player identities', asyn
db()
.prepare('INSERT INTO server_telemetry VALUES (?, ?, ?, ?)')
.run(payload.server, 99, 4, new Date().toISOString());
db().pragma('user_version = 0');
closeDb();
const { listTelemetry } = await import('../../src/lib/server/fleet/telemetry.js');
expect(listTelemetry()).toEqual([]);