mirror of
https://github.com/hellodigua/ChatLab.git
synced 2026-10-26 02:04:37 +08:00
fix(storage): avoid rewriting satisfied data directory compatibility markers
This commit is contained in:
1 parent
8a77a759db
commit
af0e844f8a
3 files changed
+121
No files matched your search
@@ -291,3 +291,63 @@ test('raising minRuntimeVersion records prerelease runtime by stable core versio
|
||||
version: '0.26.4',
|
||||
})
|
||||
})
|
||||
|
||||
test('satisfied compatibility requirements preserve the original metadata across runtime upgrades', () => {
|
||||
for (const minRuntimeVersion of ['0.25.1', '0.26.0']) {
|
||||
const userDataDir = makeTempDir()
|
||||
const provider = makePathProvider(userDataDir)
|
||||
const existing = raiseDataDirMinRuntimeVersion(provider, {
|
||||
minRuntimeVersion,
|
||||
dataCompatibilityVersion: 2,
|
||||
reason: 'segment-schema',
|
||||
runtime: { version: '0.37.3', kind: 'cli' },
|
||||
module: 'chat-db-migration',
|
||||
now: () => 1000,
|
||||
})
|
||||
const metaPath = path.join(userDataDir, '.chatlab-meta.json')
|
||||
const original = fs.readFileSync(metaPath, 'utf-8')
|
||||
|
||||
const result = raiseDataDirMinRuntimeVersion(provider, {
|
||||
minRuntimeVersion: '0.25.1',
|
||||
dataCompatibilityVersion: 1,
|
||||
reason: 'segment-schema',
|
||||
runtime: { version: '0.37.4', kind: 'desktop' },
|
||||
module: 'chat-db-migration',
|
||||
now: () => 2000,
|
||||
})
|
||||
|
||||
assert.deepEqual(result, existing)
|
||||
assert.equal(fs.readFileSync(metaPath, 'utf-8'), original)
|
||||
}
|
||||
})
|
||||
|
||||
test('required compatibility updates propagate replacement failures and preserve the previous metadata', (t) => {
|
||||
const userDataDir = makeTempDir()
|
||||
const provider = makePathProvider(userDataDir)
|
||||
const input = {
|
||||
minRuntimeVersion: '0.25.1',
|
||||
dataCompatibilityVersion: 1,
|
||||
reason: 'segment-schema',
|
||||
runtime: { version: '0.37.4', kind: 'desktop' as const },
|
||||
module: 'chat-db-migration',
|
||||
}
|
||||
const existing = raiseDataDirMinRuntimeVersion(provider, input)
|
||||
const metaPath = path.join(userDataDir, '.chatlab-meta.json')
|
||||
const original = fs.readFileSync(metaPath, 'utf-8')
|
||||
const denied = Object.assign(new Error('Metadata replacement denied'), { code: 'EPERM' })
|
||||
const renameSync = fs.renameSync
|
||||
t.mock.method(fs, 'renameSync', (source: fs.PathLike, target: fs.PathLike) => {
|
||||
if (target === metaPath) throw denied
|
||||
renameSync(source, target)
|
||||
})
|
||||
|
||||
for (const update of [{ minRuntimeVersion: '0.26.0' }, { dataCompatibilityVersion: 2 }, { reason: 'new-schema' }]) {
|
||||
assert.throws(
|
||||
() => raiseDataDirMinRuntimeVersion(provider, { ...input, ...update }),
|
||||
(error) => error === denied
|
||||
)
|
||||
assert.equal(fs.readFileSync(metaPath, 'utf-8'), original)
|
||||
assert.deepEqual(readDataDirCompatibilityMeta(userDataDir), existing)
|
||||
assert.deepEqual(fs.readdirSync(userDataDir), ['.chatlab-meta.json'])
|
||||
}
|
||||
})
|
||||
@@ -157,6 +157,17 @@ export function raiseDataDirMinRuntimeVersion(
|
||||
}
|
||||
|
||||
const existing = readDataDirCompatibilityMeta(userDataDir)
|
||||
// This is a migration marker, not a last-open timestamp. Replacing an already sufficient file
|
||||
// can fail on Windows even when the metadata and chat databases are readable.
|
||||
if (
|
||||
existing &&
|
||||
compareStableSemver(existing.minRuntimeVersion, input.minRuntimeVersion) >= 0 &&
|
||||
existing.dataCompatibilityVersion >= input.dataCompatibilityVersion &&
|
||||
existing.reasons.includes(input.reason)
|
||||
) {
|
||||
return existing
|
||||
}
|
||||
|
||||
const minRuntimeVersion =
|
||||
existing && compareStableSemver(existing.minRuntimeVersion, input.minRuntimeVersion) > 0
|
||||
? existing.minRuntimeVersion
|
||||
|
||||
@@ -999,6 +999,56 @@ test('open repairs the data directory gate for existing current-schema databases
|
||||
assert.deepEqual(meta?.reasons, ['segment-schema'])
|
||||
})
|
||||
|
||||
test('existing sessions remain readable after upgrades when compatible metadata cannot be replaced', (t) => {
|
||||
const root = makeTempDir()
|
||||
const provider = createPathProvider(root)
|
||||
fs.mkdirSync(provider.getDatabaseDir(), { recursive: true })
|
||||
for (const id of ['first', 'second']) {
|
||||
const db = new Database(path.join(provider.getDatabaseDir(), `${id}.db`), { nativeBinding })
|
||||
try {
|
||||
db.exec(CHAT_DB_SCHEMA)
|
||||
db.prepare('INSERT INTO meta (name, platform, type, imported_at) VALUES (?, ?, ?, ?)').run(
|
||||
id,
|
||||
'qq',
|
||||
'group',
|
||||
1000
|
||||
)
|
||||
db.exec(`
|
||||
INSERT INTO member (platform_id, account_name) VALUES ('u1', 'Test Member');
|
||||
INSERT INTO message (sender_id, ts, type, content) VALUES (1, 1000, 0, 'Saved message');
|
||||
`)
|
||||
} finally {
|
||||
db.close()
|
||||
}
|
||||
}
|
||||
|
||||
const previousManager = new DatabaseManager(provider, {
|
||||
nativeBinding,
|
||||
runtime: { version: '0.37.3', kind: 'desktop' },
|
||||
})
|
||||
previousManager.raiseCurrentChatDbCompatibilityGate()
|
||||
const metaPath = path.join(provider.getUserDataDir(), '.chatlab-meta.json')
|
||||
const original = fs.readFileSync(metaPath, 'utf-8')
|
||||
const renameSync = fs.renameSync
|
||||
t.mock.method(fs, 'renameSync', (source: fs.PathLike, target: fs.PathLike) => {
|
||||
if (target === metaPath) throw Object.assign(new Error('Metadata replacement denied'), { code: 'EPERM' })
|
||||
renameSync(source, target)
|
||||
})
|
||||
|
||||
for (const kind of ['desktop', 'cli'] as const) {
|
||||
const manager = new DatabaseManager(provider, { nativeBinding, runtime: { version: '0.37.4', kind } })
|
||||
t.after(() => manager.closeAll())
|
||||
assert.deepEqual(manager.listSessionIds().sort(), ['first', 'second'])
|
||||
for (const id of manager.listSessionIds()) {
|
||||
const db = manager.open(id)
|
||||
assert.ok(db)
|
||||
assert.equal(getSessionInfo(db)?.name, id)
|
||||
assert.deepEqual(db.prepare('SELECT content FROM message').all(), [{ content: 'Saved message' }])
|
||||
}
|
||||
assert.equal(fs.readFileSync(metaPath, 'utf-8'), original)
|
||||
}
|
||||
})
|
||||
|
||||
test('open keeps a higher existing data directory runtime requirement after migration', () => {
|
||||
const root = makeTempDir()
|
||||
const dbDir = path.join(root, 'data', 'databases')
|
||||
|
||||
Reference in new issue
Block a user