import assert from 'node:assert/strict' import { mkdtempSync, rmSync } from 'node:fs' import { tmpdir } from 'node:os' import path from 'node:path' import test, { after } from 'node:test' const testDataDir = mkdtempSync(path.join(tmpdir(), 'music-together-room-migration-')) process.env.DATABASE_URL = `file:${path.join(testDataDir, 'test.db')}` process.env.LOG_LEVEL = 'silent' const { db } = await import('../src/repositories/database.js') const legacyState = { name: '秋的房间', passwordEncrypted: null, creatorId: 'wing', adminUserIds: ['admin-1'], hidden: true, audioQuality: 'netease_jyeffect', queue: [], currentTrack: null, playState: { isPlaying: false, currentTime: 80.862, serverTimestamp: 1_785_305_576_966, }, playMode: 'shuffle', futureField: { nested: ['must', 'survive'] }, } const otherLegacyQualities = [ 'netease_hires', 'netease_dolby', 'netease_spatial', 'netease_master', 'tencent_flac', 'tencent_master', 'kugou_hires', 'kugou_master', ] as const const explicitSqState = { ...legacyState, name: 'Explicit SQ', creatorId: 'sq-user', audioQuality: 999, } const insertRoom = db.prepare( 'INSERT INTO permanent_rooms (id, state_json, chat_history_json, updated_at) VALUES (?, ?, ?, ?)', ) insertRoom.run('CDIBR9', JSON.stringify(legacyState), '[{"content":"preserve chat"}]', 100) for (const [index, audioQuality] of otherLegacyQualities.entries()) { insertRoom.run( `LEGACY${index}`, JSON.stringify({ ...legacyState, name: `Legacy ${audioQuality}`, audioQuality }), '[]', 200 + index, ) } insertRoom.run('SQROOM', JSON.stringify(explicitSqState), '[]', 300) insertRoom.run('BROKEN', '{invalid json', '[]', 400) const { InMemoryRoomRepository, roomRepo } = await import('../src/repositories/roomRepository.js') after(() => { db.close() rmSync(testDataDir, { recursive: true, force: true }) }) test('migrates legacy SQ-or-higher provider qualities in memory and in SQLite exactly once', () => { assert.equal(roomRepo.get('CDIBR9')?.audioQuality, 'highest') for (const index of otherLegacyQualities.keys()) { assert.equal(roomRepo.get(`LEGACY${index}`)?.audioQuality, 'highest') } const firstRow = db .prepare< [], { state_json: string; chat_history_json: string; updated_at: number } >("SELECT state_json, chat_history_json, updated_at FROM permanent_rooms WHERE id = 'CDIBR9'") .get() assert.ok(firstRow) assert.deepEqual(JSON.parse(firstRow.state_json), { ...legacyState, audioQuality: 'highest' }) assert.equal(firstRow.chat_history_json, '[{"content":"preserve chat"}]') assert.ok(firstRow.updated_at > 100) const otherFirstRows = db .prepare< [], { id: string; state_json: string; updated_at: number } >("SELECT id, state_json, updated_at FROM permanent_rooms WHERE id LIKE 'LEGACY%' ORDER BY id") .all() assert.equal(otherFirstRows.length, otherLegacyQualities.length) for (const [index, row] of otherFirstRows.entries()) { assert.equal(JSON.parse(row.state_json).audioQuality, 'highest') assert.ok(row.updated_at > 200 + index) } const reloadedRepo = new InMemoryRoomRepository() assert.equal(reloadedRepo.get('CDIBR9')?.audioQuality, 'highest') for (const index of otherLegacyQualities.keys()) { assert.equal(reloadedRepo.get(`LEGACY${index}`)?.audioQuality, 'highest') } const secondRow = db .prepare< [], { state_json: string; updated_at: number } >("SELECT state_json, updated_at FROM permanent_rooms WHERE id = 'CDIBR9'") .get() assert.ok(secondRow) assert.equal(secondRow.state_json, firstRow.state_json) assert.equal(secondRow.updated_at, firstRow.updated_at) const otherSecondRows = db .prepare< [], { id: string; state_json: string; updated_at: number } >("SELECT id, state_json, updated_at FROM permanent_rooms WHERE id LIKE 'LEGACY%' ORDER BY id") .all() assert.deepEqual(otherSecondRows, otherFirstRows) }) test('preserves explicit numeric SQ and startup after one malformed row', () => { assert.equal(roomRepo.get('SQROOM')?.audioQuality, 999) assert.equal(roomRepo.get('BROKEN'), undefined) const sqRow = db .prepare< [], { state_json: string; updated_at: number } >("SELECT state_json, updated_at FROM permanent_rooms WHERE id = 'SQROOM'") .get() assert.deepEqual(JSON.parse(sqRow!.state_json), explicitSqState) assert.equal(sqRow!.updated_at, 300) })