/**
* SELECT 쿼리 도구 테스트
* **Feature: sqlite-mcp-server, Property 5: SELECT 쿼리 결과 형식**
* **Validates: Requirements 3.1, 3.2**
*/
import { describe, test, expect, beforeEach, afterEach } from '@jest/globals';
import * as fc from 'fast-check';
import * as fs from 'fs';
import * as path from 'path';
import { selectDataTool } from './selectData';
import { createTableTool } from './createTable';
import { insertDataTool } from './insertData';
import { DatabaseManager } from '../database/DatabaseManager';
describe('SELECT Data Tool Tests', () => {
let testDbPath: string;
let dbManager: DatabaseManager;
beforeEach(() => {
// 테스트용 임시 데이터베이스 파일 경로 생성
testDbPath = path.join(__dirname, '../../test-dbs', `test-select-${Date.now()}.db`);
// 테스트 디렉토리 생성
const testDir = path.dirname(testDbPath);
if (!fs.existsSync(testDir)) {
fs.mkdirSync(testDir, { recursive: true });
}
dbManager = new DatabaseManager();
});
afterEach(() => {
// 테스트 후 정리
try {
dbManager.closeDatabase(testDbPath);
if (fs.existsSync(testDbPath)) {
fs.unlinkSync(testDbPath);
}
} catch (error) {
// 정리 중 오류는 무시
}
});
test('**Feature: sqlite-mcp-server, Property 5: SELECT 쿼리 결과 형식**', 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 }
),
// WHERE 조건 생성기 (선택적)
fc.option(
fc.record({
column: fc.constantFrom('id', 'name', 'value'),
operator: fc.constantFrom('=', '>', '<', '>=', '<='),
value: fc.oneof(
fc.integer({ min: 1, max: 1000 }),
fc.string({ minLength: 1, maxLength: 50 })
)
}),
{ freq: 7 } // 70% 확률로 WHERE 조건 포함
),
async (tableName, testData, whereCondition) => {
try {
// 1. 테이블 생성
const createResult = await (createTableTool.handler as any)({
dbPath: testDbPath,
tableName,
columns: [
{ name: 'id', type: 'INTEGER', constraints: 'PRIMARY KEY' },
{ name: 'name', type: 'TEXT', constraints: 'NOT NULL' },
{ name: 'value', type: 'INTEGER' }
]
});
// structuredContent를 사용하여 결과 확인
const createResponse = createResult.structuredContent || { success: false };
if (!createResponse.success) {
console.log('Table creation failed:', createResponse.error);
console.log('Table name:', tableName);
return; // 테이블 생성 실패 시 테스트 스킵
}
expect(createResponse.success).toBe(true);
// 2. 테스트 데이터 삽입
for (const data of testData) {
const insertResult = await (insertDataTool.handler as any)({
dbPath: testDbPath,
tableName,
data
});
const insertResponse = insertResult.structuredContent || { success: false };
if (!insertResponse.success) {
console.log('Data insertion failed:', insertResponse.error);
console.log('Data:', data);
return; // 데이터 삽입 실패 시 테스트 스킵
}
expect(insertResponse.success).toBe(true);
}
// 3. SELECT 쿼리 구성
let query = `SELECT * FROM ${tableName}`;
let params: any[] = [];
if (whereCondition) {
query += ` WHERE ${whereCondition.column} ${whereCondition.operator} ?`;
params = [whereCondition.value];
}
// 4. SELECT 쿼리 실행
const selectResult = await (selectDataTool.handler as any)({
dbPath: testDbPath,
query,
params
});
const response = selectResult.structuredContent || JSON.parse(selectResult.content[0].text);
// Property 5: SELECT 쿼리 결과 형식 검증
// - 결과는 JSON 형태로 반환되어야 함
expect(response).toHaveProperty('success');
expect(typeof response.success).toBe('boolean');
if (response.success) {
// 성공한 경우 data 속성이 배열이어야 함
expect(response).toHaveProperty('data');
expect(Array.isArray(response.data)).toBe(true);
// WHERE 절이 있는 경우 필터링 검증
if (whereCondition && response.data.length > 0) {
for (const row of response.data) {
const columnValue = row[whereCondition.column];
switch (whereCondition.operator) {
case '=':
expect(columnValue).toBe(whereCondition.value);
break;
case '>':
if (typeof columnValue === 'number' && typeof whereCondition.value === 'number') {
expect(columnValue).toBeGreaterThan(whereCondition.value);
}
break;
case '<':
if (typeof columnValue === 'number' && typeof whereCondition.value === 'number') {
expect(columnValue).toBeLessThan(whereCondition.value);
}
break;
case '>=':
if (typeof columnValue === 'number' && typeof whereCondition.value === 'number') {
expect(columnValue).toBeGreaterThanOrEqual(whereCondition.value);
}
break;
case '<=':
if (typeof columnValue === 'number' && typeof whereCondition.value === 'number') {
expect(columnValue).toBeLessThanOrEqual(whereCondition.value);
}
break;
}
}
}
// 반환된 데이터의 구조 검증
if (response.data.length > 0) {
const firstRow = response.data[0];
expect(firstRow).toHaveProperty('id');
expect(firstRow).toHaveProperty('name');
expect(firstRow).toHaveProperty('value');
}
} else {
// 실패한 경우 error 속성이 있어야 함
expect(response).toHaveProperty('error');
expect(typeof response.error).toBe('string');
}
} catch (error) {
// 예상치 못한 오류가 발생한 경우 실패
throw new Error(`Property test failed: ${error instanceof Error ? error.message : 'Unknown error'}`);
}
}
),
{ numRuns: 100 }
);
});
test('SELECT 쿼리가 아닌 경우 오류 반환', async () => {
const result = await (selectDataTool.handler as any)({
dbPath: testDbPath,
query: 'INSERT INTO test (name) VALUES (?)',
params: ['test']
});
const response = result.structuredContent || JSON.parse(result.content[0].text);
expect(response.success).toBe(false);
expect(response.error).toContain('SELECT 쿼리만 허용됩니다');
});
test('존재하지 않는 테이블 조회 시 오류 반환', async () => {
const result = await (selectDataTool.handler as any)({
dbPath: testDbPath,
query: 'SELECT * FROM nonexistent_table'
});
const response = result.structuredContent || JSON.parse(result.content[0].text);
expect(response.success).toBe(false);
expect(response.error).toBeDefined();
});
test('빈 결과 집합 반환', async () => {
// 테이블 생성
await (createTableTool.handler as any)({
dbPath: testDbPath,
tableName: 'empty_test',
columns: [
{ name: 'id', type: 'INTEGER', constraints: 'PRIMARY KEY' },
{ name: 'name', type: 'TEXT' }
]
});
// 빈 테이블에서 조회
const result = await (selectDataTool.handler as any)({
dbPath: testDbPath,
query: 'SELECT * FROM empty_test'
});
const response = result.structuredContent || JSON.parse(result.content[0].text);
expect(response.success).toBe(true);
expect(response.data).toEqual([]);
});
});