Skip to main content
Glama
SQLiteTestStorage.js7.57 kB
/** * SQLite Test Storage - TestManager 패턴을 따르는 Test 저장소 * async/await SQLite API 사용 */ import sqlite3 from 'sqlite3'; import { open } from 'sqlite'; import path from 'path'; import { fileURLToPath } from 'url'; const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); export class SQLiteTestStorage { constructor() { // dashboard/src/lib/server/ -> project root (4 levels up) this.dbPath = path.resolve(__dirname, '../../../../data/workflow.db'); this.db = null; } async getDatabase() { if (!this.db) { this.db = await open({ filename: this.dbPath, driver: sqlite3.Database }); await this.db.exec('PRAGMA foreign_keys = ON'); } return this.db; } async initialize() { await this.getDatabase(); console.log(`✅ SQLiteTestStorage initialized: ${this.dbPath}`); } /** * Test case 저장 (INSERT OR REPLACE) */ async saveTestCase(testCase) { console.log('🔄 SQLiteTestStorage.saveTestCase called:', testCase.id, testCase.title); const db = await this.getDatabase(); const stmt = await db.prepare(` INSERT OR REPLACE INTO test_cases ( id, title, description, type, status, priority, task_id, design_id, prd_id, preconditions, test_steps, expected_result, test_data, estimated_duration, complexity, automation_status, tags, created_at, updated_at, created_by, version ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) `); const result = await stmt.run( testCase.id, testCase.title, testCase.description, testCase.type, testCase.status, testCase.priority, testCase.task_id, testCase.design_id, testCase.prd_id, testCase.preconditions, JSON.stringify(testCase.test_steps || []), testCase.expected_result, JSON.stringify(testCase.test_data || {}), testCase.estimated_duration || 0, testCase.complexity || 'Medium', testCase.automation_status || 'manual', JSON.stringify(testCase.tags || []), testCase.created_at, testCase.updated_at, testCase.created_by || 'system', testCase.version || 1 ); await stmt.finalize(); return result; } /** * Test case 조회 */ async getTestCase(id) { const db = await this.getDatabase(); const row = await db.get(` SELECT tc.*, COUNT(te.id) AS total_executions, COUNT(CASE WHEN te.status = 'pass' THEN 1 END) AS passed_executions, COUNT(CASE WHEN te.status = 'fail' THEN 1 END) AS failed_executions, MAX(te.execution_date) AS last_execution_date, (SELECT status FROM test_executions WHERE test_case_id = tc.id ORDER BY execution_date DESC LIMIT 1) AS last_execution_status FROM test_cases tc LEFT JOIN test_executions te ON tc.id = te.test_case_id WHERE tc.id = ? GROUP BY tc.id `, [id]); if (!row) return null; return this.formatTestCaseRow(row); } /** * 모든 Test case 목록 조회 */ async listAllTestCases() { const db = await this.getDatabase(); const rows = await db.all(` SELECT tc.*, COUNT(te.id) AS total_executions, COUNT(CASE WHEN te.status = 'pass' THEN 1 END) AS passed_executions, MAX(te.execution_date) AS last_execution_date, (SELECT status FROM test_executions WHERE test_case_id = tc.id ORDER BY execution_date DESC LIMIT 1) AS last_execution_status FROM test_cases tc LEFT JOIN test_executions te ON tc.id = te.test_case_id GROUP BY tc.id ORDER BY tc.updated_at DESC `); const testCases = []; for (const row of rows) { testCases.push(this.formatTestCaseRow(row)); } return testCases; } /** * Test case 삭제 */ async deleteTestCase(id) { const db = await this.getDatabase(); try { await db.run('BEGIN TRANSACTION'); // Test executions 먼저 삭제 (외래키 제약조건) await db.run('DELETE FROM test_executions WHERE test_case_id = ?', [id]); // Test case 삭제 const result = await db.run('DELETE FROM test_cases WHERE id = ?', [id]); await db.run('COMMIT'); return result.changes > 0; } catch (error) { await db.run('ROLLBACK'); throw error; } } /** * Test execution 저장 */ async saveTestExecution(execution) { const db = await this.getDatabase(); const result = await db.run(` INSERT INTO test_executions ( id, test_case_id, execution_date, executed_by, environment, status, actual_result, notes, defects_found, actual_duration, started_at, completed_at, attachments, created_at, updated_at ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) `, [ execution.id, execution.test_case_id, execution.execution_date, execution.executed_by, execution.environment, execution.status, execution.actual_result, execution.notes, JSON.stringify(execution.defects_found || []), execution.actual_duration || 0, execution.started_at, execution.completed_at, JSON.stringify(execution.attachments || []), execution.created_at, execution.updated_at ]); return result; } /** * Test execution 목록 조회 */ async getTestExecutions(testCaseId) { const db = await this.getDatabase(); const rows = await db.all(` SELECT te.*, tc.title AS test_case_title FROM test_executions te LEFT JOIN test_cases tc ON te.test_case_id = tc.id WHERE te.test_case_id = ? ORDER BY te.execution_date DESC `, [testCaseId]); return rows.map(row => ({ ...row, defects_found: this.safeJsonParse(row.defects_found, []), attachments: this.safeJsonParse(row.attachments, []) })); } /** * 데이터베이스 행을 Test case 객체로 변환 */ formatTestCaseRow(row) { return { id: row.id, title: row.title, description: row.description, type: row.type, status: row.status, priority: row.priority, task_id: row.task_id, design_id: row.design_id, prd_id: row.prd_id, preconditions: row.preconditions, test_steps: this.safeJsonParse(row.test_steps, []), expected_result: row.expected_result, test_data: this.safeJsonParse(row.test_data, {}), estimated_duration: row.estimated_duration, complexity: row.complexity, automation_status: row.automation_status, tags: this.safeJsonParse(row.tags, []), created_at: row.created_at, updated_at: row.updated_at, created_by: row.created_by, version: row.version, // Analytics data total_executions: row.total_executions || 0, passed_executions: row.passed_executions || 0, failed_executions: row.failed_executions || 0, last_execution_date: row.last_execution_date, last_execution_status: row.last_execution_status }; } /** * 안전한 JSON 파싱 */ safeJsonParse(jsonString, defaultValue = null) { try { return jsonString ? JSON.parse(jsonString) : defaultValue; } catch (error) { console.warn('JSON 파싱 실패:', jsonString); return defaultValue; } } async cleanup() { if (this.db) { await this.db.close(); this.db = null; } } }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/foswmine/workflow-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server