import Database from 'better-sqlite3'; import { readFile } from 'node:fs/promises'; 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 () => { await fixture.cleanup(); }); it('retires only obsolete backend columns while preserving instances, history, keys and foreign keys', async () => { const schema = await readFile(new URL('../../src/lib/server/db/schema.sql', import.meta.url), 'utf8'); const previous = new Database(fixture.dbPath); // Model the former required resource identity and an unrelated extension. previous.exec(schema); 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')));`); previous.exec(`ALTER TABLE servers ADD COLUMN komodo_type TEXT NOT NULL DEFAULT 'deployment'; ALTER TABLE servers ADD COLUMN komodo_name TEXT NOT NULL; ALTER TABLE servers ADD COLUMN komodo_server TEXT; ALTER TABLE servers ADD COLUMN operator_note TEXT;`); previous.prepare(`INSERT INTO servers(name,komodo_name,komodo_server,game,game_port,operator_note) VALUES('native-server','','old-host','t5',28960,'keep this')`).run(); previous.prepare("INSERT INTO server_keys(server_id,pluto_key) VALUES('native-server','saved-key')").run(); previous.prepare(`INSERT INTO server_deployments(id,server,action,state,stage,started_at,triggered_by) VALUES('creation','native-server','create','done','Ready','2026-01-01','fixture')`).run(); previous.prepare(`INSERT INTO server_deployment_events(deployment_id,created_at,message) VALUES('creation','2026-01-01','Original receipt')`).run(); const { migrateNativeInstances } = await import('../../src/lib/server/backends/schema.js'); migrateNativeInstances(previous); previous.prepare(`INSERT INTO native_instances(id,server,host_id,library_id,image,port,created_at) VALUES('instance','native-server','host','library','image',28960,'2026-01-01')`).run(); previous.close(); const { db, closeDb } = await import('../../src/lib/server/db/index.js'); for (let pass = 0; pass < 2; pass++) { const handle = db(); const columns = (handle.prepare('PRAGMA table_info(servers)').all() as { name: string }[]).map(column => column.name); expect(columns).not.toEqual(expect.arrayContaining(['komodo_type'])); expect(columns).not.toContain('komodo_name'); expect(columns).not.toContain('komodo_server'); expect(columns).toContain('operator_note'); expect(handle.prepare('SELECT name,game_port,operator_note FROM servers WHERE name=?').get('native-server')) .toEqual({ name: 'native-server', game_port: 28960, operator_note: 'keep this' }); expect(handle.prepare('SELECT id,generation FROM native_instances').all()).toEqual([{ id: 'instance', generation: 1 }]); expect(handle.prepare('SELECT pluto_key FROM server_keys').get()).toEqual({ pluto_key: 'saved-key' }); expect(handle.prepare('SELECT message FROM server_deployment_events').get()).toEqual({ message: 'Original receipt' }); expect(handle.pragma('foreign_key_check')).toEqual([]); expect(handle.pragma('foreign_keys', { simple: true })).toBe(1); closeDb(); } // Native inserts must work without supplying the retired required field. expect(() => db().prepare("INSERT INTO servers(name) VALUES('new-native-server')").run()).not.toThrow(); });