import { mkdtempSync, rmSync } from 'node:fs' import { tmpdir } from 'node:os' import { join } from 'node:path' import { openDatabase, closeDatabase, type Db } from '../src/main/db/database' import type { Keyring } from '../src/main/security/keyring' import type { SafeStorageStatus } from '../src/shared/ipc-schemas' export interface TempDb { db: Db path: string cleanup: () => void } /** * A real file-backed database in a temp directory, not `:memory:`. * * Tests that assert a secret never reaches disk need an actual file to inspect, * and WAL mode does not apply to in-memory databases — so the in-memory * shortcut would quietly skip the behaviour most worth checking. */ export function createTempDb(): TempDb { const dir = mkdtempSync(join(tmpdir(), 'nexusterm-test-')) const path = join(dir, 'test.db') const db = openDatabase(path) return { db, path, cleanup: () => { closeDatabase(db) rmSync(dir, { recursive: true, force: true }) }, } } /** * A stand-in keyring. * * `wrap` is a reversible transform rather than real encryption, and that is * deliberate: it is not trying to be secure, it is trying to be *observable*. * The point is that the bytes handed to SQLite are recognisably different from * the plaintext, so tests can prove the vault actually called through to the * keyring instead of passing a secret straight to storage. */ export function fakeKeyring( status: SafeStorageStatus = { available: true, backend: 'test-keyring', secure: true, reason: 'ok', }, ): Keyring { const PREFIX = 'WRAPPED:' return { status: () => status, wrap: (plaintext) => Buffer.from(PREFIX + Buffer.from(plaintext, 'utf8').toString('hex')), unwrap: (sealed) => { const text = sealed.toString('utf8') if (!text.startsWith(PREFIX)) { throw new Error('blob was not wrapped by this keyring') } return Buffer.from(text.slice(PREFIX.length), 'hex').toString('utf8') }, } } /** * A keyring that reports the Linux `basic_text` condition: the API claims * encryption is available, but the backend uses a hardcoded key. Both `wrap` * and `unwrap` throw, so any code path that fails to check `secure` first will * fail loudly rather than storing something weak. */ export function insecureKeyring(): Keyring { const refuse = (): never => { throw new Error('insecure keyring must not be used') } return { status: () => ({ available: true, backend: 'basic_text', secure: false, reason: 'basic-text', }), wrap: refuse, unwrap: refuse, } }