import { mkdirSync, mkdtempSync, readdirSync, readFileSync, rmSync, statSync, writeFileSync } from 'node:fs' import { tmpdir } from 'node:os' import { join } from 'node:path' import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' import { defaultStorePath, readStore, writeStore } from '../../../src/config/store.js' import type { ConnectionConfig } from '../../../src/config/types.js' const connection: ConnectionConfig = { name: 'lab', type: 'postgres', host: 'localhost', user: 'postgres', readonly: false } describe('store', () => { let dir: string let path: string beforeEach(() => { dir = mkdtempSync(join(tmpdir(), 'dbmole-store-')) path = join(dir, 'nested', 'connections.json') vi.spyOn(console, 'error').mockImplementation(() => {}) }) afterEach(() => { rmSync(dir, { recursive: true, force: true }) vi.restoreAllMocks() }) it('reads missing file as empty list', () => { expect(readStore(path)).toEqual([]) }) it('round-trips connections and creates parent dirs', () => { writeStore(path, [connection]) expect(readStore(path)).toEqual([connection]) }) it('writes file with 0600 permissions and leaves no tmp files', () => { writeStore(path, [connection]) const mode = statSync(path).mode & 0o777 expect(mode).toBe(0o600) const leftovers = readdirSync(join(dir, 'nested')).filter((f) => f.includes('.tmp')) expect(leftovers).toEqual([]) }) it('treats invalid JSON as empty with a warning', () => { writeStore(path, [connection]) writeFileSync(path, '{ broken') expect(readStore(path)).toEqual([]) expect(console.error).toHaveBeenCalled() }) it('treats non-array JSON as empty with a warning', () => { writeStore(path, [connection]) writeFileSync(path, '{"connections": []}') expect(readStore(path)).toEqual([]) expect(console.error).toHaveBeenCalled() }) it('skips invalid entries but keeps valid ones', () => { mkdirSync(join(dir, 'nested'), { recursive: true }) writeFileSync(path, JSON.stringify([connection, { name: 'broken' }])) expect(readStore(path)).toEqual([connection]) expect(console.error).toHaveBeenCalled() }) it('stores pretty JSON for hand editing', () => { writeStore(path, [connection]) expect(readFileSync(path, 'utf8')).toContain('\n "name": "lab"') }) }) describe('defaultStorePath', () => { it('honors DBMOLE_STORE override', () => { expect(defaultStorePath({ DBMOLE_STORE: '/tmp/custom.json' })).toBe('/tmp/custom.json') }) it('falls back to ~/.config/dbmole/connections.json', () => { expect(defaultStorePath({})).toMatch(/\.config\/dbmole\/connections\.json$/) }) })