ERP-node/backend-node/src/tests/database.test.ts

456 lines
16 KiB
TypeScript

/**
* 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