import { test } from 'node:test'; import assert from 'node:assert/strict'; import Database from 'better-sqlite3'; import { mkdtempSync, rmSync } from 'node:fs'; import { tmpdir } from 'node:os'; import { join } from 'node:path'; import { openDb } from '../src/store/db.js'; import { Store } from '../src/store/index.js'; /** * 回归:旧 DB 的 projects 表早于 checks、auto_approve_x、budget、agent_rules 等列创建, * schema.sql 的 CREATE TABLE IF NOT EXISTS 对已存在的表是 no-op → 这些列必须靠 ensureColumn 迁移补上。 * 漏迁移会导致 createProject 的 INSERT 引用不存在的列而报错(线上实测复现)。 */ test('迁移:旧 projects 表缺新列 → openDb 补齐 + createProject 不报错', () => { const dir = mkdtempSync(join(tmpdir(), 'maestro-mig-')); const file = join(dir, 'old.db'); try { // 造一个"旧版" projects 表(只有最早期那批列,缺 checks/auto_approve_*/budget/agent_rules…) const raw = new Database(file); raw.exec(`CREATE TABLE projects ( id TEXT PRIMARY KEY, name TEXT NOT NULL, repo_path TEXT NOT NULL UNIQUE, default_branch TEXT NOT NULL DEFAULT 'main', verify_cmd TEXT, autonomy TEXT NOT NULL DEFAULT 'manual', model TEXT, concurrency INTEGER NOT NULL DEFAULT 1, status TEXT NOT NULL DEFAULT 'active', created_at TEXT NOT NULL )`); raw.close(); // openDb 跑迁移:应补齐所有后加列 const db = openDb(file); const cols = (db.prepare(`PRAGMA table_info(projects)`).all() as Array<{ name: string }>).map((c) => c.name); for (const c of [ 'checks', 'auto_approve_plan', 'auto_approve_exec', 'agent_rules', 'models', 'budget_usd', 'budget_period', 'max_retries', 'timeout_ms', 'last_sync_at', 'logo', 'sort_order', ]) { assert.ok(cols.includes(c), `应补列 ${c}`); } // tasks.scope_files 也走 ensureColumn 迁移 const taskCols = (db.prepare(`PRAGMA table_info(tasks)`).all() as Array<{ name: string }>).map((c) => c.name); assert.ok(taskCols.includes('scope_files'), '应补 tasks.scope_files 列'); db.close(); // Store 在该旧库上建项目(INSERT 引用 checks/auto_approve_* 列)应成功 const s = new Store(file); const p = s.createProject({ name: 'mig', repoPath: '/tmp/mig-' + Math.random() }); assert.equal(p.autoApprovePlan, false); assert.equal(p.autoApproveExec, false); assert.equal(p.checks, null); s.close(); } finally { rmSync(dir, { recursive: true, force: true }); } });