/ tests / helpers / setup-split-dbs.js
setup-split-dbs.js
  1  /**
  2   * setup-split-dbs.js
  3   *
  4   * Creates minimal ops.db and telemetry.db test fixtures and sets OPS_DB_PATH /
  5   * TEL_DB_PATH env vars so that createDatabaseConnection() can ATTACH them.
  6   *
  7   * Run as a --require / --import before the test suite, or call setupSplitDbs()
  8   * from a beforeEach/before hook.
  9   *
 10   * The created files are co-located with DATABASE_PATH so each isolated test DB
 11   * gets its own ops/tel pair.
 12   */
 13  
 14  import Database from 'better-sqlite3';
 15  import { mkdirSync } from 'fs';
 16  import { dirname, join } from 'path';
 17  
 18  export function setupSplitDbs(sitesDbPath) {
 19    const dir = dirname(sitesDbPath || process.env.DATABASE_PATH || '/tmp/test-sites.db');
 20    mkdirSync(dir, { recursive: true });
 21  
 22    const opsPath = join(dir, 'test-ops.db');
 23    const telPath = join(dir, 'test-telemetry.db');
 24  
 25    process.env.OPS_DB_PATH = opsPath;
 26    process.env.TEL_DB_PATH = telPath;
 27  
 28    // Create ops.db with full schema matching db/ops.db
 29    const ops = new Database(opsPath);
 30    ops.exec(`
 31      CREATE TABLE IF NOT EXISTS migrations (
 32        id INTEGER PRIMARY KEY AUTOINCREMENT,
 33        filename TEXT UNIQUE NOT NULL,
 34        applied_at DATETIME DEFAULT CURRENT_TIMESTAMP
 35      );
 36      CREATE TABLE IF NOT EXISTS settings (
 37        key TEXT PRIMARY KEY,
 38        value TEXT NOT NULL,
 39        description TEXT,
 40        updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
 41      );
 42      CREATE TABLE IF NOT EXISTS pipeline_control (
 43        id INTEGER PRIMARY KEY CHECK (id = 1),
 44        paused BOOLEAN DEFAULT 0,
 45        paused_by TEXT,
 46        paused_at DATETIME,
 47        current_stage TEXT,
 48        last_cycle_at DATETIME,
 49        created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
 50        updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
 51        current_browser_stage TEXT,
 52        last_api_cycle_at DATETIME,
 53        last_browser_cycle_at DATETIME
 54      );
 55      INSERT OR IGNORE INTO pipeline_control (id) VALUES (1);
 56      CREATE TABLE IF NOT EXISTS cron_jobs (
 57        id INTEGER PRIMARY KEY AUTOINCREMENT,
 58        name TEXT NOT NULL UNIQUE,
 59        task_key TEXT NOT NULL UNIQUE,
 60        description TEXT,
 61        handler_type TEXT NOT NULL CHECK(handler_type IN ('function', 'command')),
 62        handler_value TEXT NOT NULL,
 63        interval_value INTEGER NOT NULL,
 64        interval_unit TEXT NOT NULL CHECK(interval_unit IN ('minutes', 'hours', 'days', 'weeks')),
 65        enabled BOOLEAN DEFAULT 1,
 66        last_run_at DATETIME,
 67        created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
 68        updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
 69        pause_pipeline BOOLEAN DEFAULT 0,
 70        critical BOOLEAN DEFAULT 1,
 71        timeout_seconds INTEGER DEFAULT NULL
 72      );
 73      CREATE INDEX IF NOT EXISTS idx_cron_jobs_enabled ON cron_jobs(enabled);
 74      CREATE INDEX IF NOT EXISTS idx_cron_jobs_task_key ON cron_jobs(task_key);
 75      CREATE TABLE IF NOT EXISTS cron_job_logs (
 76        id INTEGER PRIMARY KEY AUTOINCREMENT,
 77        job_name TEXT NOT NULL,
 78        started_at DATETIME NOT NULL,
 79        finished_at DATETIME,
 80        status TEXT NOT NULL CHECK(status IN ('running', 'success', 'failed', 'timeout')),
 81        summary TEXT,
 82        full_log TEXT,
 83        items_processed INTEGER DEFAULT 0,
 84        items_failed INTEGER DEFAULT 0,
 85        error_message TEXT,
 86        created_at DATETIME DEFAULT CURRENT_TIMESTAMP
 87      );
 88      CREATE INDEX IF NOT EXISTS idx_cron_logs_job ON cron_job_logs(job_name);
 89      CREATE INDEX IF NOT EXISTS idx_cron_logs_started ON cron_job_logs(started_at);
 90      CREATE INDEX IF NOT EXISTS idx_cron_logs_status ON cron_job_logs(status);
 91      CREATE INDEX IF NOT EXISTS idx_cron_logs_job_started ON cron_job_logs(job_name, started_at);
 92      CREATE TABLE IF NOT EXISTS cron_locks (
 93        lock_key TEXT PRIMARY KEY,
 94        acquired_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
 95        updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
 96        description TEXT
 97      );
 98      CREATE INDEX IF NOT EXISTS idx_cron_locks_updated ON cron_locks(updated_at);
 99    `);
100    ops.close();
101  
102    // Create telemetry.db with full schema matching db/telemetry.db
103    const tel = new Database(telPath);
104    tel.exec(`
105      CREATE TABLE IF NOT EXISTS llm_usage (
106        id INTEGER PRIMARY KEY AUTOINCREMENT,
107        site_id INTEGER,
108        stage TEXT NOT NULL,
109        provider TEXT NOT NULL,
110        model TEXT NOT NULL,
111        prompt_tokens INTEGER NOT NULL,
112        completion_tokens INTEGER NOT NULL,
113        total_tokens INTEGER NOT NULL,
114        estimated_cost DECIMAL(10, 6),
115        request_id TEXT,
116        created_at DATETIME DEFAULT CURRENT_TIMESTAMP
117      );
118      CREATE TABLE IF NOT EXISTS agent_logs (
119        id INTEGER PRIMARY KEY AUTOINCREMENT,
120        task_id INTEGER,
121        agent_name TEXT NOT NULL,
122        log_level TEXT CHECK(log_level IN ('debug', 'info', 'warn', 'error')),
123        message TEXT NOT NULL,
124        data_json TEXT,
125        created_at DATETIME DEFAULT CURRENT_TIMESTAMP
126      );
127      CREATE INDEX IF NOT EXISTS idx_agent_logs_task ON agent_logs(task_id);
128      CREATE INDEX IF NOT EXISTS idx_agent_logs_agent ON agent_logs(agent_name, created_at);
129      CREATE TABLE IF NOT EXISTS agent_tasks (
130        id INT,
131        task_type TEXT,
132        assigned_to TEXT,
133        created_by TEXT,
134        parent_task_id INT,
135        priority INT,
136        status TEXT,
137        context_json TEXT,
138        result_json TEXT,
139        error_message TEXT,
140        retry_count INT,
141        reviewed_by TEXT,
142        approval_json TEXT,
143        created_at NUM,
144        started_at NUM,
145        completed_at NUM
146      );
147      CREATE INDEX IF NOT EXISTS idx_agent_tasks_assigned_to ON agent_tasks(assigned_to, status);
148      CREATE INDEX IF NOT EXISTS idx_agent_tasks_parent ON agent_tasks(parent_task_id);
149      CREATE INDEX IF NOT EXISTS idx_agent_tasks_priority ON agent_tasks(priority DESC, created_at ASC);
150      CREATE INDEX IF NOT EXISTS idx_agent_tasks_status ON agent_tasks(status);
151      CREATE TABLE IF NOT EXISTS agent_messages (
152        id INTEGER PRIMARY KEY AUTOINCREMENT,
153        task_id INTEGER,
154        from_agent TEXT NOT NULL,
155        to_agent TEXT NOT NULL,
156        message_type TEXT CHECK(message_type IN ('question', 'answer', 'handoff', 'notification')),
157        content TEXT NOT NULL,
158        metadata_json TEXT,
159        created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
160        read_at DATETIME
161      );
162      CREATE INDEX IF NOT EXISTS idx_agent_messages_task ON agent_messages(task_id);
163      CREATE INDEX IF NOT EXISTS idx_agent_messages_to ON agent_messages(to_agent, read_at);
164      CREATE TABLE IF NOT EXISTS agent_outcomes (
165        id INT,
166        task_id INT,
167        agent_name TEXT,
168        task_type TEXT,
169        outcome TEXT,
170        context_json TEXT,
171        result_json TEXT,
172        duration_ms INT,
173        created_at NUM
174      );
175      CREATE INDEX IF NOT EXISTS idx_agent_outcomes_agent ON agent_outcomes(agent_name, task_type);
176      CREATE INDEX IF NOT EXISTS idx_agent_outcomes_task_type ON agent_outcomes(task_type, outcome);
177      CREATE INDEX IF NOT EXISTS idx_agent_outcomes_outcome ON agent_outcomes(outcome, created_at);
178      CREATE INDEX IF NOT EXISTS idx_agent_outcomes_created ON agent_outcomes(created_at);
179      CREATE TABLE IF NOT EXISTS agent_state (
180        agent_name TEXT PRIMARY KEY,
181        last_active DATETIME DEFAULT CURRENT_TIMESTAMP,
182        current_task_id INTEGER,
183        status TEXT DEFAULT 'idle',
184        metrics_json TEXT
185      );
186      CREATE TABLE IF NOT EXISTS agent_llm_usage (
187        id INTEGER PRIMARY KEY AUTOINCREMENT,
188        agent_name TEXT NOT NULL,
189        task_id INTEGER,
190        model TEXT NOT NULL,
191        prompt_tokens INTEGER NOT NULL,
192        completion_tokens INTEGER NOT NULL,
193        cost_usd DECIMAL(10, 6) NOT NULL,
194        created_at DATETIME DEFAULT CURRENT_TIMESTAMP
195      );
196      CREATE INDEX IF NOT EXISTS idx_agent_llm_usage_agent ON agent_llm_usage(agent_name);
197      CREATE INDEX IF NOT EXISTS idx_agent_llm_usage_task ON agent_llm_usage(task_id);
198      CREATE INDEX IF NOT EXISTS idx_agent_llm_usage_created ON agent_llm_usage(created_at);
199      CREATE INDEX IF NOT EXISTS idx_agent_llm_usage_cost ON agent_llm_usage(cost_usd DESC);
200      CREATE INDEX IF NOT EXISTS idx_agent_llm_usage_agent_date ON agent_llm_usage(agent_name, DATE(created_at));
201      CREATE TABLE IF NOT EXISTS system_metrics (
202        id INTEGER PRIMARY KEY AUTOINCREMENT,
203        cpu_percent REAL NOT NULL,
204        disk_read_mb REAL NOT NULL,
205        disk_write_mb REAL NOT NULL,
206        memory_percent REAL NOT NULL,
207        recorded_at DATETIME DEFAULT CURRENT_TIMESTAMP
208      );
209      CREATE INDEX IF NOT EXISTS idx_system_metrics_recorded_at ON system_metrics(recorded_at);
210      CREATE TABLE IF NOT EXISTS system_health (
211        id INTEGER PRIMARY KEY AUTOINCREMENT,
212        check_type TEXT NOT NULL,
213        status TEXT NOT NULL CHECK (status IN ('ok', 'warning', 'critical')),
214        details TEXT,
215        action_taken TEXT,
216        created_at TEXT DEFAULT (datetime('now'))
217      );
218      CREATE INDEX IF NOT EXISTS idx_system_health_check_type ON system_health(check_type);
219      CREATE INDEX IF NOT EXISTS idx_system_health_created_at ON system_health(created_at);
220      CREATE INDEX IF NOT EXISTS idx_system_health_status ON system_health(status);
221      CREATE TABLE IF NOT EXISTS pipeline_metrics (
222        id INTEGER PRIMARY KEY AUTOINCREMENT,
223        stage_name TEXT NOT NULL,
224        sites_processed INTEGER DEFAULT 0,
225        sites_succeeded INTEGER DEFAULT 0,
226        sites_failed INTEGER DEFAULT 0,
227        duration_ms INTEGER NOT NULL,
228        started_at DATETIME NOT NULL,
229        finished_at DATETIME NOT NULL,
230        created_at DATETIME DEFAULT CURRENT_TIMESTAMP
231      );
232      CREATE INDEX IF NOT EXISTS idx_pipeline_metrics_stage ON pipeline_metrics(stage_name, started_at);
233      CREATE INDEX IF NOT EXISTS idx_pipeline_metrics_started ON pipeline_metrics(started_at);
234    `);
235    tel.close();
236  
237    return { opsPath, telPath };
238  }
239  
240  export function teardownSplitDbs() {
241    delete process.env.OPS_DB_PATH;
242    delete process.env.TEL_DB_PATH;
243  }
244  
245  // Auto-run if invoked as a module initialiser (--import flag)
246  if (process.env.DATABASE_PATH) {
247    setupSplitDbs(process.env.DATABASE_PATH);
248  }