/** * Database Manager 테스트 * * Phase 1 기반 구조 검증 */ import { query, queryOne, transaction, getPoolStatus } from "../database/db"; import { QueryBuilder } from "../utils/queryBuilder"; import { DatabaseValidator } from "../utils/databaseValidator"; describe("Database Manager Tests", () => { describe("QueryBuilder", () => { test("SELECT 쿼리 생성 - 기본", () => { const { query: sql, params } = QueryBuilder.select("users", { where: { user_id: "test_user" }, }); expect(sql).toContain("SELECT * FROM users"); expect(sql).toContain("WHERE user_id = $1"); expect(params).toEqual(["test_user"]); }); test("SELECT 쿼리 생성 - 복잡한 조건", () => { const { query: sql, params } = QueryBuilder.select("users", { columns: ["user_id", "username", "email"], where: { status: "active", role: "admin" }, orderBy: "created_at DESC", limit: 10, offset: 20, }); expect(sql).toContain("SELECT user_id, username, email FROM users"); expect(sql).toContain("WHERE status = $1 AND role = $2"); expect(sql).toContain("ORDER BY created_at DESC"); expect(sql).toContain("LIMIT $3"); expect(sql).toContain("OFFSET $4"); expect(params).toEqual(["active", "admin", 10, 20]); }); test("SELECT 쿼리 생성 - JOIN", () => { const { query: sql, params } = QueryBuilder.select("users", { columns: ["users.user_id", "users.username", "departments.dept_name"], joins: [ { type: "LEFT", table: "departments", on: "users.dept_id = departments.dept_id", }, ], where: { "users.status": "active" }, }); expect(sql).toContain("LEFT JOIN departments"); expect(sql).toContain("ON users.dept_id = departments.dept_id"); expect(sql).toContain("WHERE users.status = $1"); expect(params).toEqual(["active"]); }); test("INSERT 쿼리 생성 - RETURNING", () => { const { query: sql, params } = QueryBuilder.insert( "users", { user_id: "new_user", username: "John Doe", email: "john@example.com", }, { returning: ["id", "user_id"], } ); expect(sql).toContain("INSERT INTO users"); expect(sql).toContain("(user_id, username, email)"); expect(sql).toContain("VALUES ($1, $2, $3)"); expect(sql).toContain("RETURNING id, user_id"); expect(params).toEqual(["new_user", "John Doe", "john@example.com"]); }); test("INSERT 쿼리 생성 - UPSERT", () => { const { query: sql, params } = QueryBuilder.insert( "users", { user_id: "user123", username: "Jane", email: "jane@example.com", }, { onConflict: { columns: ["user_id"], action: "DO UPDATE", updateSet: ["username", "email"], }, returning: ["*"], } ); expect(sql).toContain("ON CONFLICT (user_id) DO UPDATE"); expect(sql).toContain( "SET username = EXCLUDED.username, email = EXCLUDED.email" ); expect(sql).toContain("RETURNING *"); }); test("UPDATE 쿼리 생성", () => { const { query: sql, params } = QueryBuilder.update( "users", { username: "Updated Name", email: "updated@example.com" }, { user_id: "user123" }, { returning: ["*"] } ); expect(sql).toContain("UPDATE users"); expect(sql).toContain("SET username = $1, email = $2"); expect(sql).toContain("WHERE user_id = $3"); expect(sql).toContain("RETURNING *"); expect(params).toEqual([ "Updated Name", "updated@example.com", "user123", ]); }); test("DELETE 쿼리 생성", () => { const { query: sql, params } = QueryBuilder.delete("users", { user_id: "user_to_delete", }); expect(sql).toContain("DELETE FROM users"); expect(sql).toContain("WHERE user_id = $1"); expect(params).toEqual(["user_to_delete"]); }); test("COUNT 쿼리 생성", () => { const { query: sql, params } = QueryBuilder.count("users", { status: "active", }); expect(sql).toContain("SELECT COUNT(*) as count FROM users"); expect(sql).toContain("WHERE status = $1"); expect(params).toEqual(["active"]); }); }); describe("DatabaseValidator", () => { test("테이블명 검증 - 유효한 이름", () => { expect(DatabaseValidator.validateTableName("users")).toBe(true); expect(DatabaseValidator.validateTableName("user_info")).toBe(true); expect(DatabaseValidator.validateTableName("_internal_table")).toBe(true); expect(DatabaseValidator.validateTableName("table123")).toBe(true); }); test("테이블명 검증 - 유효하지 않은 이름", () => { expect(DatabaseValidator.validateTableName("")).toBe(false); expect(DatabaseValidator.validateTableName("123table")).toBe(false); expect(DatabaseValidator.validateTableName("user-table")).toBe(false); expect(DatabaseValidator.validateTableName("user table")).toBe(false); expect(DatabaseValidator.validateTableName("SELECT")).toBe(false); // 예약어 expect(DatabaseValidator.validateTableName("a".repeat(64))).toBe(false); // 너무 긺 }); test("컬럼명 검증 - 유효한 이름", () => { expect(DatabaseValidator.validateColumnName("user_id")).toBe(true); expect(DatabaseValidator.validateColumnName("created_at")).toBe(true); expect(DatabaseValidator.validateColumnName("is_active")).toBe(true); }); test("컬럼명 검증 - 유효하지 않은 이름", () => { expect(DatabaseValidator.validateColumnName("user-id")).toBe(false); expect(DatabaseValidator.validateColumnName("user id")).toBe(false); expect(DatabaseValidator.validateColumnName("WHERE")).toBe(false); // 예약어 }); test("데이터 타입 검증", () => { expect(DatabaseValidator.validateDataType("VARCHAR")).toBe(true); expect(DatabaseValidator.validateDataType("VARCHAR(255)")).toBe(true); expect(DatabaseValidator.validateDataType("INTEGER")).toBe(true); expect(DatabaseValidator.validateDataType("TIMESTAMP")).toBe(true); expect(DatabaseValidator.validateDataType("JSONB")).toBe(true); expect(DatabaseValidator.validateDataType("INTEGER[]")).toBe(true); expect(DatabaseValidator.validateDataType("DECIMAL(10,2)")).toBe(true); }); test("WHERE 조건 검증", () => { expect( DatabaseValidator.validateWhereClause({ user_id: "test", status: "active", }) ).toBe(true); expect( DatabaseValidator.validateWhereClause({ "config->>type": "form", // JSON 쿼리 }) ).toBe(true); expect( DatabaseValidator.validateWhereClause({ "invalid-column": "value", }) ).toBe(false); }); test("페이지네이션 검증", () => { expect(DatabaseValidator.validatePagination(1, 10)).toBe(true); expect(DatabaseValidator.validatePagination(5, 100)).toBe(true); expect(DatabaseValidator.validatePagination(0, 10)).toBe(false); // page < 1 expect(DatabaseValidator.validatePagination(1, 0)).toBe(false); // pageSize < 1 expect(DatabaseValidator.validatePagination(1, 2000)).toBe(false); // pageSize > 1000 }); test("ORDER BY 검증", () => { expect(DatabaseValidator.validateOrderBy("created_at")).toBe(true); expect(DatabaseValidator.validateOrderBy("created_at ASC")).toBe(true); expect(DatabaseValidator.validateOrderBy("created_at DESC")).toBe(true); expect(DatabaseValidator.validateOrderBy("created_at INVALID")).toBe( false ); expect(DatabaseValidator.validateOrderBy("invalid-column ASC")).toBe( false ); }); test("UUID 검증", () => { expect( DatabaseValidator.validateUUID("550e8400-e29b-41d4-a716-446655440000") ).toBe(true); expect(DatabaseValidator.validateUUID("invalid-uuid")).toBe(false); }); test("이메일 검증", () => { expect(DatabaseValidator.validateEmail("test@example.com")).toBe(true); expect(DatabaseValidator.validateEmail("user.name@domain.co.kr")).toBe( true ); expect(DatabaseValidator.validateEmail("invalid-email")).toBe(false); expect(DatabaseValidator.validateEmail("test@")).toBe(false); }); }); describe("Integration Tests (실제 DB 연결 필요)", () => { // 실제 데이터베이스 연결이 필요한 테스트들 // DB 연결 실패 시 스킵되도록 설정 beforeAll(async () => { // DB 연결 테스트 try { await query("SELECT 1 as test"); console.log("✅ 데이터베이스 연결 성공 - Integration Tests 실행"); } catch (error) { console.warn("⚠️ 데이터베이스 연결 실패 - Integration Tests 스킵"); console.warn("DB 연결 오류:", error); } }); test("실제 쿼리 실행 테스트", async () => { try { const result = await query( "SELECT NOW() as current_time, version() as pg_version" ); expect(result).toHaveLength(1); expect(result[0]).toHaveProperty("current_time"); expect(result[0]).toHaveProperty("pg_version"); expect(result[0].pg_version).toContain("PostgreSQL"); console.log("🕐 현재 시간:", result[0].current_time); console.log("📊 PostgreSQL 버전:", result[0].pg_version); } catch (error) { console.error("❌ 쿼리 실행 테스트 실패:", error); throw error; } }); test("파라미터화된 쿼리 테스트", async () => { try { const testValue = "test_value_" + Date.now(); const result = await query( "SELECT $1 as input_value, $2 as number_value, $3 as boolean_value", [testValue, 42, true] ); expect(result).toHaveLength(1); expect(result[0].input_value).toBe(testValue); expect(parseInt(result[0].number_value)).toBe(42); // PostgreSQL은 숫자를 문자열로 반환 expect( result[0].boolean_value === true || result[0].boolean_value === "true" ).toBe(true); // PostgreSQL boolean 처리 console.log("📝 파라미터 테스트 결과:", result[0]); } catch (error) { console.error("❌ 파라미터 쿼리 테스트 실패:", error); throw error; } }); test("단일 행 조회 테스트", async () => { try { // 존재하는 데이터 조회 const result = await queryOne("SELECT 1 as value, 'exists' as status"); expect(result).not.toBeNull(); expect(result?.value).toBe(1); expect(result?.status).toBe("exists"); // 존재하지 않는 데이터 조회 const emptyResult = await queryOne( "SELECT * FROM (SELECT 1 as id) t WHERE id = 999" ); expect(emptyResult).toBeNull(); console.log("🔍 단일 행 조회 결과:", result); } catch (error) { console.error("❌ 단일 행 조회 테스트 실패:", error); throw error; } }); test("트랜잭션 테스트", async () => { try { const result = await transaction(async (client) => { const res1 = await client.query( "SELECT 1 as value, 'first' as label" ); const res2 = await client.query( "SELECT 2 as value, 'second' as label" ); const res3 = await client.query("SELECT $1 as computed_value", [ res1.rows[0].value + res2.rows[0].value, ]); return { res1: res1.rows, res2: res2.rows, res3: res3.rows, transaction_id: Math.random().toString(36).substr(2, 9), }; }); expect(result.res1[0].value).toBe(1); expect(result.res1[0].label).toBe("first"); expect(result.res2[0].value).toBe(2); expect(result.res2[0].label).toBe("second"); expect(parseInt(result.res3[0].computed_value)).toBe(3); // PostgreSQL은 숫자를 문자열로 반환 expect(result.transaction_id).toBeDefined(); console.log("🔄 트랜잭션 테스트 결과:", { first_value: result.res1[0].value, second_value: result.res2[0].value, computed_value: result.res3[0].computed_value, transaction_id: result.transaction_id, }); } catch (error) { console.error("❌ 트랜잭션 테스트 실패:", error); throw error; } }); test("트랜잭션 롤백 테스트", async () => { try { await expect( transaction(async (client) => { await client.query("SELECT 1 as value"); // 의도적으로 오류 발생 throw new Error("의도적인 롤백 테스트"); }) ).rejects.toThrow("의도적인 롤백 테스트"); console.log("🔄 트랜잭션 롤백 테스트 성공"); } catch (error) { console.error("❌ 트랜잭션 롤백 테스트 실패:", error); throw error; } }); test("연결 풀 상태 확인", () => { try { const status = getPoolStatus(); expect(status).toHaveProperty("totalCount"); expect(status).toHaveProperty("idleCount"); expect(status).toHaveProperty("waitingCount"); expect(typeof status.totalCount).toBe("number"); expect(typeof status.idleCount).toBe("number"); expect(typeof status.waitingCount).toBe("number"); console.log("🏊‍♂️ 연결 풀 상태:", { 총_연결수: status.totalCount, 유휴_연결수: status.idleCount, 대기_연결수: status.waitingCount, }); } catch (error) { console.error("❌ 연결 풀 상태 확인 실패:", error); throw error; } }); test("데이터베이스 메타데이터 조회", async () => { try { // 현재 데이터베이스 정보 조회 const dbInfo = await query(` SELECT current_database() as database_name, current_user as current_user, inet_server_addr() as server_address, inet_server_port() as server_port `); expect(dbInfo).toHaveLength(1); expect(dbInfo[0].database_name).toBeDefined(); expect(dbInfo[0].current_user).toBeDefined(); console.log("🗄️ 데이터베이스 정보:", { 데이터베이스명: dbInfo[0].database_name, 현재사용자: dbInfo[0].current_user, 서버주소: dbInfo[0].server_address, 서버포트: dbInfo[0].server_port, }); } catch (error) { console.error("❌ 데이터베이스 메타데이터 조회 실패:", error); throw error; } }); test("테이블 존재 여부 확인", async () => { try { // 시스템 테이블 조회로 안전하게 테스트 const tables = await query(` SELECT table_name, table_type FROM information_schema.tables WHERE table_schema = 'public' AND table_type = 'BASE TABLE' LIMIT 5 `); expect(Array.isArray(tables)).toBe(true); console.log(`📋 발견된 테이블 수: ${tables.length}`); if (tables.length > 0) { console.log( "📋 테이블 목록 (최대 5개):", tables.map((t) => t.table_name).join(", ") ); } } catch (error) { console.error("❌ 테이블 존재 여부 확인 실패:", error); throw error; } }); }); }); // 테스트 실행 방법: // npm test -- database.test.ts