// tests/main/db-open-atomic-redteam-r2-1.test.ts // 계정 DB 가 손상돼 열리지 않아도 앱이 DB 없이 남지 않는다 — openForUser 는 새 DB 를 연 뒤에야 // 이전 DB 를 닫는다(원자적 교체). import { afterEach, describe, expect, it } from 'vitest' import fs from 'fs' import path from 'path' import { app } from 'electron' import { ErrorCode } from '@d3ro/core/errors' import { sql } from 'drizzle-orm' import { closeCurrent, getCurrentUserId, getDatabase, isDatabaseOpen, openForUser, openLocal, } from '../../src/main/db' const CORRUPT_USER = `corrupt-${process.pid}-${Date.now()}` const GOOD_USER = `good-${process.pid}-${Date.now()}` function userDir(userId: string): string { return path.join(app.getPath('userData'), 'users', userId) } afterEach(() => { closeCurrent() for (const id of [CORRUPT_USER, GOOD_USER]) fs.rmSync(userDir(id), { recursive: true, force: true }) }) describe('openForUser 원자적 교체', () => { it('손상된 계정 DB 를 열지 못하면 이전(로컬) DB 가 그대로 열려 있다', () => { openLocal() fs.mkdirSync(userDir(CORRUPT_USER), { recursive: true }) fs.writeFileSync(path.join(userDir(CORRUPT_USER), 'd3ro.db'), 'this is not a sqlite database'.repeat(64)) expect(() => openForUser(CORRUPT_USER)).toThrow(expect.objectContaining({ code: ErrorCode.DBOpenFailed })) expect(isDatabaseOpen()).toBe(true) expect(getCurrentUserId()).toBe('_local') expect(() => getDatabase().run(sql`SELECT 1`)).not.toThrow() }) it('정상 계정 DB 는 열고 이전 DB 와 교체한다', () => { openLocal() const result = openForUser(GOOD_USER) expect(result.dbPath).toContain(GOOD_USER) expect(getCurrentUserId()).toBe(GOOD_USER) expect(() => getDatabase().run(sql`SELECT 1`)).not.toThrow() }) })