Skip to main content
Glama
YUChoe

SQLite MCP Server

by YUChoe
updateData.test.ts15.9 kB
/** * UPDATE 데이터 도구 테스트 */ import { updateDataTool } from './updateData'; import { createTableTool } from './createTable'; import { insertDataTool } from './insertData'; import { selectDataTool } from './selectData'; import fs from 'fs'; import path from 'path'; describe('updateDataTool', () => { const testDbDir = 'test-dbs'; let testDbPath: string; beforeEach(() => { // 테스트 데이터베이스 디렉토리 생성 if (!fs.existsSync(testDbDir)) { fs.mkdirSync(testDbDir, { recursive: true }); } // 고유한 테스트 데이터베이스 파일 경로 생성 testDbPath = path.join(testDbDir, `test-update-${Date.now()}.db`); }); afterEach(() => { // 데이터베이스 연결 닫기 const dbManager = new (require('../database/DatabaseManager').DatabaseManager)(); dbManager.closeAllDatabases(); // 테스트 후 데이터베이스 파일 정리 if (fs.existsSync(testDbPath)) { try { fs.unlinkSync(testDbPath); } catch (error) { // Windows에서 파일이 잠겨있을 수 있으므로 무시 console.warn(`파일 삭제 실패: ${testDbPath}`, error); } } }); describe('기본 UPDATE 작업', () => { test('단일 행 업데이트가 성공해야 함', async () => { // 테이블 생성 await createTableTool.handler({ dbPath: testDbPath, tableName: 'users', columns: [ { name: 'id', type: 'INTEGER', constraints: 'PRIMARY KEY' }, { name: 'name', type: 'TEXT' }, { name: 'age', type: 'INTEGER' } ] }); // 테스트 데이터 삽입 await insertDataTool.handler({ dbPath: testDbPath, tableName: 'users', data: { id: 1, name: 'Alice', age: 25 } }); await insertDataTool.handler({ dbPath: testDbPath, tableName: 'users', data: { id: 2, name: 'Bob', age: 30 } }); // UPDATE 실행 const result = await updateDataTool.handler({ dbPath: testDbPath, query: 'UPDATE users SET age = ? WHERE id = ?', params: [26, 1] }); expect(result.structuredContent.success).toBe(true); expect(result.structuredContent.rowsAffected).toBe(1); // 업데이트 결과 확인 const selectResult = await selectDataTool.handler({ dbPath: testDbPath, query: 'SELECT * FROM users WHERE id = 1' }); expect(selectResult.structuredContent.data).toEqual([ { id: 1, name: 'Alice', age: 26 } ]); }); test('여러 행 업데이트가 성공해야 함', async () => { // 테이블 생성 await createTableTool.handler({ dbPath: testDbPath, tableName: 'products', columns: [ { name: 'id', type: 'INTEGER', constraints: 'PRIMARY KEY' }, { name: 'name', type: 'TEXT' }, { name: 'price', type: 'REAL' }, { name: 'category', type: 'TEXT' } ] }); // 테스트 데이터 삽입 const products = [ { id: 1, name: 'Product A', price: 10.0, category: 'electronics' }, { id: 2, name: 'Product B', price: 20.0, category: 'electronics' }, { id: 3, name: 'Product C', price: 15.0, category: 'books' } ]; for (const product of products) { await insertDataTool.handler({ dbPath: testDbPath, tableName: 'products', data: product }); } // 카테고리별 가격 업데이트 const result = await updateDataTool.handler({ dbPath: testDbPath, query: 'UPDATE products SET price = price * 1.1 WHERE category = ?', params: ['electronics'] }); expect(result.structuredContent.success).toBe(true); expect(result.structuredContent.rowsAffected).toBe(2); // 업데이트 결과 확인 const selectResult = await selectDataTool.handler({ dbPath: testDbPath, query: 'SELECT * FROM products WHERE category = ? ORDER BY id', params: ['electronics'] }); expect(selectResult.structuredContent.data).toEqual([ { id: 1, name: 'Product A', price: 11.0, category: 'electronics' }, { id: 2, name: 'Product B', price: 22.0, category: 'electronics' } ]); }); test('조건에 맞는 행이 없을 때 0개 행 영향을 반환해야 함', async () => { // 테이블 생성 await createTableTool.handler({ dbPath: testDbPath, tableName: 'users', columns: [ { name: 'id', type: 'INTEGER', constraints: 'PRIMARY KEY' }, { name: 'name', type: 'TEXT' } ] }); // 존재하지 않는 ID로 업데이트 시도 const result = await updateDataTool.handler({ dbPath: testDbPath, query: 'UPDATE users SET name = ? WHERE id = ?', params: ['New Name', 999] }); expect(result.structuredContent.success).toBe(true); expect(result.structuredContent.rowsAffected).toBe(0); }); }); describe('오류 처리', () => { test('잘못된 테이블명으로 업데이트 시 오류를 반환해야 함', async () => { const result = await updateDataTool.handler({ dbPath: testDbPath, query: 'UPDATE nonexistent_table SET name = ?', params: ['test'] }); expect(result.structuredContent.success).toBe(false); expect(result.structuredContent.error).toContain('no such table'); }); test('잘못된 컬럼명으로 업데이트 시 오류를 반환해야 함', async () => { // 테이블 생성 await createTableTool.handler({ dbPath: testDbPath, tableName: 'users', columns: [ { name: 'id', type: 'INTEGER', constraints: 'PRIMARY KEY' }, { name: 'name', type: 'TEXT' } ] }); const result = await updateDataTool.handler({ dbPath: testDbPath, query: 'UPDATE users SET nonexistent_column = ?', params: ['test'] }); expect(result.structuredContent.success).toBe(false); expect(result.structuredContent.error).toContain('no such column'); }); test('UPDATE가 아닌 쿼리 실행 시 오류를 반환해야 함', async () => { const result = await updateDataTool.handler({ dbPath: testDbPath, query: 'SELECT * FROM users', params: [] }); expect(result.structuredContent.success).toBe(false); expect(result.structuredContent.error).toContain('UPDATE 쿼리만 허용됩니다'); }); test('SET 절이 없는 UPDATE 쿼리 시 오류를 반환해야 함', async () => { const result = await updateDataTool.handler({ dbPath: testDbPath, query: 'UPDATE users WHERE id = 1', params: [] }); expect(result.structuredContent.success).toBe(false); expect(result.structuredContent.error).toContain('SET 절이 필요합니다'); }); test('위험한 SQL 구문 포함 시 오류를 반환해야 함', async () => { const dangerousQueries = [ 'UPDATE users SET name = "test"; DROP TABLE users;', 'UPDATE users SET name = (SELECT * FROM (DELETE FROM users))', 'UPDATE users SET name = "test"; TRUNCATE TABLE users;' ]; for (const query of dangerousQueries) { const result = await updateDataTool.handler({ dbPath: testDbPath, query, params: [] }); expect(result.structuredContent.success).toBe(false); expect(result.structuredContent.error).toContain('허용되지 않는 SQL 구문'); } }); test('잘못된 데이터베이스 경로 시 오류를 반환해야 함', async () => { const result = await updateDataTool.handler({ dbPath: '/invalid/path/database.db', query: 'UPDATE users SET name = ?', params: ['test'] }); expect(result.structuredContent.success).toBe(false); expect(result.structuredContent.error).toBeDefined(); }); }); describe('입력 검증', () => { test('빈 데이터베이스 경로 시 오류를 반환해야 함', async () => { const result = await updateDataTool.handler({ dbPath: '', query: 'UPDATE users SET name = ?', params: ['test'] }); expect(result.structuredContent.success).toBe(false); expect(result.structuredContent.error).toContain('데이터베이스 경로는 필수입니다'); }); test('빈 쿼리 시 오류를 반환해야 함', async () => { const result = await updateDataTool.handler({ dbPath: testDbPath, query: '', params: [] }); expect(result.structuredContent.success).toBe(false); expect(result.structuredContent.error).toContain('SQL 쿼리는 필수입니다'); }); }); }); /** * Property-Based Tests for UPDATE operation accuracy * **Feature: sqlite-mcp-server, Property 6: UPDATE 작업 정확성** * **Validates: Requirements 4.1, 4.2** */ import * as fc from 'fast-check'; describe('Property-Based Tests', () => { const testDbDir = 'test-dbs'; let propertyTestDbPath: string; beforeEach(() => { // 테스트 데이터베이스 디렉토리 생성 if (!fs.existsSync(testDbDir)) { fs.mkdirSync(testDbDir, { recursive: true }); } // 고유한 테스트 데이터베이스 파일 경로 생성 propertyTestDbPath = path.join(testDbDir, `test-update-property-${Date.now()}.db`); }); afterEach(() => { // 데이터베이스 연결 닫기 const dbManager = new (require('../database/DatabaseManager').DatabaseManager)(); dbManager.closeAllDatabases(); // 테스트 후 데이터베이스 파일 정리 if (fs.existsSync(propertyTestDbPath)) { try { fs.unlinkSync(propertyTestDbPath); } catch (error) { // Windows에서 파일이 잠겨있을 수 있으므로 무시 console.warn(`파일 삭제 실패: ${propertyTestDbPath}`, error); } } }); test('**Feature: sqlite-mcp-server, Property 6: UPDATE 작업 정확성**', async () => { await fc.assert( fc.asyncProperty( // 테이블 이름 생성기 fc.string({ minLength: 1, maxLength: 20 }).filter(s => /^[a-zA-Z_][a-zA-Z0-9_]*$/.test(s)), // 초기 데이터 생성기 fc.array( fc.record({ id: fc.integer({ min: 1, max: 1000 }), name: fc.string({ minLength: 1, maxLength: 50 }), value: fc.integer({ min: 0, max: 100 }) }), { minLength: 1, maxLength: 10 } ).map(arr => { // ID 중복 제거 const uniqueIds = new Set(); return arr.filter(item => { if (uniqueIds.has(item.id)) { return false; } uniqueIds.add(item.id); return true; }); }).filter(arr => arr.length > 0), // 업데이트할 새 값 생성기 fc.integer({ min: 101, max: 200 }), // 업데이트 조건 생성기 (특정 ID 선택) fc.integer({ min: 1, max: 1000 }), async (tableName, initialData, newValue, targetId) => { // 고유한 데이터베이스 경로 생성 const dbPath = path.join(testDbDir, `test-property-${Date.now()}-${Math.random()}.db`); try { // 테이블 생성 await createTableTool.handler({ dbPath, tableName, columns: [ { name: 'id', type: 'INTEGER', constraints: 'PRIMARY KEY' }, { name: 'name', type: 'TEXT' }, { name: 'value', type: 'INTEGER' } ] }); // 초기 데이터 삽입 for (const data of initialData) { await insertDataTool.handler({ dbPath, tableName, data }); } // 업데이트 전 상태 확인 const beforeResult = await selectDataTool.handler({ dbPath, query: `SELECT * FROM ${tableName} WHERE id = ?`, params: [targetId] }); const beforeCount = beforeResult.structuredContent.data?.length || 0; // UPDATE 실행 const updateResult = await updateDataTool.handler({ dbPath, query: `UPDATE ${tableName} SET value = ? WHERE id = ?`, params: [newValue, targetId] }); // 업데이트 후 상태 확인 const afterResult = await selectDataTool.handler({ dbPath, query: `SELECT * FROM ${tableName} WHERE id = ?`, params: [targetId] }); const afterCount = afterResult.structuredContent.data?.length || 0; // Property 검증: UPDATE 작업 정확성 if (updateResult.structuredContent.success) { // 1. 영향받은 행 수가 정확해야 함 expect(updateResult.structuredContent.rowsAffected).toBe(beforeCount); // 2. 대상 행이 존재했다면 값이 업데이트되어야 함 if (beforeCount > 0) { expect(afterCount).toBe(1); const updatedRow = afterResult.structuredContent.data?.[0]; expect(updatedRow?.value).toBe(newValue); expect(updatedRow?.id).toBe(targetId); } else { // 3. 대상 행이 없었다면 여전히 없어야 함 expect(afterCount).toBe(0); } // 4. 다른 행들은 영향받지 않아야 함 const allRowsAfter = await selectDataTool.handler({ dbPath, query: `SELECT * FROM ${tableName}`, params: [] }); const totalRowsAfter = allRowsAfter.structuredContent.data?.length || 0; expect(totalRowsAfter).toBe(initialData.length); // 5. 업데이트되지 않은 행들의 값은 변경되지 않아야 함 const unchangedRows = await selectDataTool.handler({ dbPath, query: `SELECT * FROM ${tableName} WHERE id != ?`, params: [targetId] }); if (unchangedRows.structuredContent.data) { for (const row of unchangedRows.structuredContent.data) { const originalRow = initialData.find(d => d.id === row.id); if (originalRow) { expect(row.value).toBe(originalRow.value); expect(row.name).toBe(originalRow.name); } } } } } finally { // 데이터베이스 연결 닫기 const dbManager = new (require('../database/DatabaseManager').DatabaseManager)(); dbManager.closeDatabase(dbPath); // 테스트 후 정리 if (fs.existsSync(dbPath)) { try { fs.unlinkSync(dbPath); } catch (error) { // Windows에서 파일이 잠겨있을 수 있으므로 무시 console.warn(`파일 삭제 실패: ${dbPath}`, error); } } } } ), { numRuns: 100 } ); }, 30000); // 30초 타임아웃 });

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/YUChoe/sqlite-mcp'

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