chore: Prisma 관련 파일 완전 제거 🧹
제거된 파일들: 1. 컴파일된 파일: - backend-node/dist/config/database.js 2. 테스트 파일 (3개): - backend-node/src/tests/database.test.ts - backend-node/src/tests/authService.test.ts - backend-node/src/tests/integration/auth.integration.test.ts 3. Prisma 관련 스크립트 (10개): - test-db.js, check-password.js, check-actual-password.js - update-password.js, create-test-user.js, simple-test-user.js - clean-screen-tables.js, test-jwt.js, test-token.js - test-token.txt 4. Prisma 디렉토리: - backend-node/prisma/schema.prisma - backend-node/prisma/migrations/ 수정된 파일들: 1. backend-node/package.json: ✅ Prisma 스크립트 제거 (prisma:generate, migrate, studio, seed) ✅ @prisma/client 의존성 제거 ✅ prisma 개발 의존성 제거 ✅ keywords: prisma → postgresql 2. .gitignore: ✅ Prisma 관련 항목 제거 3. src/services/dataflowDiagramService.ts: ✅ 포맷팅 수정 최종 상태: - ✅ Prisma 호출: 0개 - ✅ Prisma 관련 파일: 0개 - ✅ Raw Query 기반 시스템으로 완전 전환
This commit is contained in:
parent
fab8909195
commit
643f6e0d7d
|
|
@ -150,9 +150,6 @@ jspm_packages/
|
||||||
ehthumbs.db
|
ehthumbs.db
|
||||||
Thumbs.db
|
Thumbs.db
|
||||||
|
|
||||||
# Prisma
|
|
||||||
prisma/migrations/
|
|
||||||
|
|
||||||
# Build outputs
|
# Build outputs
|
||||||
dist/
|
dist/
|
||||||
build/
|
build/
|
||||||
|
|
@ -273,8 +270,6 @@ out/
|
||||||
.settings/
|
.settings/
|
||||||
bin/
|
bin/
|
||||||
|
|
||||||
/src/generated/prisma
|
|
||||||
|
|
||||||
# 업로드된 파일들 제외
|
# 업로드된 파일들 제외
|
||||||
backend-node/uploads/
|
backend-node/uploads/
|
||||||
uploads/
|
uploads/
|
||||||
|
|
|
||||||
|
|
@ -1,37 +0,0 @@
|
||||||
const { Client } = require("pg");
|
|
||||||
require("dotenv/config");
|
|
||||||
|
|
||||||
async function checkActualPassword() {
|
|
||||||
const client = new Client({
|
|
||||||
connectionString: process.env.DATABASE_URL,
|
|
||||||
});
|
|
||||||
|
|
||||||
try {
|
|
||||||
await client.connect();
|
|
||||||
console.log("✅ 데이터베이스 연결 성공");
|
|
||||||
|
|
||||||
// 실제 저장된 비밀번호 확인 (암호화된 상태)
|
|
||||||
const passwordResult = await client.query(`
|
|
||||||
SELECT user_id, user_name, user_password, status
|
|
||||||
FROM user_info
|
|
||||||
WHERE user_id = 'kkh'
|
|
||||||
`);
|
|
||||||
console.log("🔐 사용자 비밀번호 정보:", passwordResult.rows);
|
|
||||||
|
|
||||||
// 다른 사용자도 확인
|
|
||||||
const otherUsersResult = await client.query(`
|
|
||||||
SELECT user_id, user_name, user_password, status
|
|
||||||
FROM user_info
|
|
||||||
WHERE user_password IS NOT NULL
|
|
||||||
AND user_password != ''
|
|
||||||
LIMIT 3
|
|
||||||
`);
|
|
||||||
console.log("👥 다른 사용자 비밀번호 정보:", otherUsersResult.rows);
|
|
||||||
} catch (error) {
|
|
||||||
console.error("❌ 오류 발생:", error);
|
|
||||||
} finally {
|
|
||||||
await client.end();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
checkActualPassword();
|
|
||||||
|
|
@ -1,36 +0,0 @@
|
||||||
const { Client } = require("pg");
|
|
||||||
require("dotenv/config");
|
|
||||||
|
|
||||||
async function checkPasswordField() {
|
|
||||||
const client = new Client({
|
|
||||||
connectionString: process.env.DATABASE_URL,
|
|
||||||
});
|
|
||||||
|
|
||||||
try {
|
|
||||||
await client.connect();
|
|
||||||
console.log("✅ 데이터베이스 연결 성공");
|
|
||||||
|
|
||||||
// user_info 테이블의 컬럼 정보 확인
|
|
||||||
const columnsResult = await client.query(`
|
|
||||||
SELECT column_name, data_type, is_nullable
|
|
||||||
FROM information_schema.columns
|
|
||||||
WHERE table_name = 'user_info'
|
|
||||||
ORDER BY ordinal_position
|
|
||||||
`);
|
|
||||||
console.log("📋 user_info 테이블 컬럼:", columnsResult.rows);
|
|
||||||
|
|
||||||
// 비밀번호 관련 컬럼 확인
|
|
||||||
const passwordResult = await client.query(`
|
|
||||||
SELECT user_id, user_name, user_password, password, status
|
|
||||||
FROM user_info
|
|
||||||
WHERE user_id = 'kkh'
|
|
||||||
`);
|
|
||||||
console.log("🔐 사용자 비밀번호 정보:", passwordResult.rows);
|
|
||||||
} catch (error) {
|
|
||||||
console.error("❌ 오류 발생:", error);
|
|
||||||
} finally {
|
|
||||||
await client.end();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
checkPasswordField();
|
|
||||||
|
|
@ -1,36 +0,0 @@
|
||||||
const { PrismaClient } = require("@prisma/client");
|
|
||||||
|
|
||||||
const prisma = new PrismaClient();
|
|
||||||
|
|
||||||
async function cleanScreenTables() {
|
|
||||||
try {
|
|
||||||
console.log("🧹 기존 화면관리 테이블들을 정리합니다...");
|
|
||||||
|
|
||||||
// 기존 테이블들을 순서대로 삭제 (외래키 제약조건 때문에 순서 중요)
|
|
||||||
await prisma.$executeRaw`DROP VIEW IF EXISTS v_screen_definitions_with_auth CASCADE`;
|
|
||||||
console.log("✅ 뷰 삭제 완료");
|
|
||||||
|
|
||||||
await prisma.$executeRaw`DROP TABLE IF EXISTS screen_menu_assignments CASCADE`;
|
|
||||||
console.log("✅ screen_menu_assignments 테이블 삭제 완료");
|
|
||||||
|
|
||||||
await prisma.$executeRaw`DROP TABLE IF EXISTS screen_widgets CASCADE`;
|
|
||||||
console.log("✅ screen_widgets 테이블 삭제 완료");
|
|
||||||
|
|
||||||
await prisma.$executeRaw`DROP TABLE IF EXISTS screen_layouts CASCADE`;
|
|
||||||
console.log("✅ screen_layouts 테이블 삭제 완료");
|
|
||||||
|
|
||||||
await prisma.$executeRaw`DROP TABLE IF EXISTS screen_templates CASCADE`;
|
|
||||||
console.log("✅ screen_templates 테이블 삭제 완료");
|
|
||||||
|
|
||||||
await prisma.$executeRaw`DROP TABLE IF EXISTS screen_definitions CASCADE`;
|
|
||||||
console.log("✅ screen_definitions 테이블 삭제 완료");
|
|
||||||
|
|
||||||
console.log("🎉 모든 화면관리 테이블 정리 완료!");
|
|
||||||
} catch (error) {
|
|
||||||
console.error("❌ 테이블 정리 중 오류 발생:", error);
|
|
||||||
} finally {
|
|
||||||
await prisma.$disconnect();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
cleanScreenTables();
|
|
||||||
|
|
@ -1,82 +0,0 @@
|
||||||
const { Client } = require("pg");
|
|
||||||
require("dotenv/config");
|
|
||||||
|
|
||||||
async function createTestUser() {
|
|
||||||
const client = new Client({
|
|
||||||
connectionString: process.env.DATABASE_URL,
|
|
||||||
});
|
|
||||||
|
|
||||||
try {
|
|
||||||
await client.connect();
|
|
||||||
console.log("✅ 데이터베이스 연결 성공");
|
|
||||||
|
|
||||||
// 테스트용 사용자 생성 (MD5 해시: admin123)
|
|
||||||
const testUser = {
|
|
||||||
user_id: "admin",
|
|
||||||
user_name: "테스트 관리자",
|
|
||||||
user_password: "f21b1ce8b08dc955bd4afff71b3db1fc", // admin123의 MD5 해시
|
|
||||||
status: "active",
|
|
||||||
company_code: "ILSHIN",
|
|
||||||
data_type: "PLM",
|
|
||||||
};
|
|
||||||
|
|
||||||
// 기존 사용자 확인
|
|
||||||
const existingUser = await client.query(
|
|
||||||
"SELECT user_id FROM user_info WHERE user_id = $1",
|
|
||||||
[testUser.user_id]
|
|
||||||
);
|
|
||||||
|
|
||||||
if (existingUser.rows.length > 0) {
|
|
||||||
console.log("⚠️ 테스트 사용자가 이미 존재합니다:", testUser.user_id);
|
|
||||||
|
|
||||||
// 기존 사용자 정보 업데이트
|
|
||||||
await client.query(
|
|
||||||
`
|
|
||||||
UPDATE user_info
|
|
||||||
SET user_name = $1, user_password = $2, status = $3
|
|
||||||
WHERE user_id = $4
|
|
||||||
`,
|
|
||||||
[
|
|
||||||
testUser.user_name,
|
|
||||||
testUser.user_password,
|
|
||||||
testUser.status,
|
|
||||||
testUser.user_id,
|
|
||||||
]
|
|
||||||
);
|
|
||||||
|
|
||||||
console.log("✅ 테스트 사용자 정보 업데이트 완료");
|
|
||||||
} else {
|
|
||||||
// 새 사용자 생성
|
|
||||||
await client.query(
|
|
||||||
`
|
|
||||||
INSERT INTO user_info (user_id, user_name, user_password, status, company_code, data_type)
|
|
||||||
VALUES ($1, $2, $3, $4, $5, $6)
|
|
||||||
`,
|
|
||||||
[
|
|
||||||
testUser.user_id,
|
|
||||||
testUser.user_name,
|
|
||||||
testUser.user_password,
|
|
||||||
testUser.status,
|
|
||||||
testUser.company_code,
|
|
||||||
testUser.data_type,
|
|
||||||
]
|
|
||||||
);
|
|
||||||
|
|
||||||
console.log("✅ 테스트 사용자 생성 완료");
|
|
||||||
}
|
|
||||||
|
|
||||||
// 생성된 사용자 확인
|
|
||||||
const createdUser = await client.query(
|
|
||||||
"SELECT user_id, user_name, status FROM user_info WHERE user_id = $1",
|
|
||||||
[testUser.user_id]
|
|
||||||
);
|
|
||||||
|
|
||||||
console.log("👤 생성된 사용자:", createdUser.rows[0]);
|
|
||||||
} catch (error) {
|
|
||||||
console.error("❌ 오류 발생:", error);
|
|
||||||
} finally {
|
|
||||||
await client.end();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
createTestUser();
|
|
||||||
|
|
@ -11,23 +11,18 @@
|
||||||
"test:watch": "jest --watch",
|
"test:watch": "jest --watch",
|
||||||
"lint": "eslint src/ --ext .ts",
|
"lint": "eslint src/ --ext .ts",
|
||||||
"lint:fix": "eslint src/ --ext .ts --fix",
|
"lint:fix": "eslint src/ --ext .ts --fix",
|
||||||
"format": "prettier --write src/",
|
"format": "prettier --write src/"
|
||||||
"prisma:generate": "prisma generate",
|
|
||||||
"prisma:migrate": "prisma migrate dev",
|
|
||||||
"prisma:studio": "prisma studio",
|
|
||||||
"prisma:seed": "prisma db seed"
|
|
||||||
},
|
},
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"plm",
|
"plm",
|
||||||
"nodejs",
|
"nodejs",
|
||||||
"typescript",
|
"typescript",
|
||||||
"express",
|
"express",
|
||||||
"prisma"
|
"postgresql"
|
||||||
],
|
],
|
||||||
"author": "",
|
"author": "",
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@prisma/client": "^6.16.2",
|
|
||||||
"@types/mssql": "^9.1.8",
|
"@types/mssql": "^9.1.8",
|
||||||
"axios": "^1.11.0",
|
"axios": "^1.11.0",
|
||||||
"bcryptjs": "^2.4.3",
|
"bcryptjs": "^2.4.3",
|
||||||
|
|
@ -72,7 +67,6 @@
|
||||||
"jest": "^29.7.0",
|
"jest": "^29.7.0",
|
||||||
"nodemon": "^3.1.10",
|
"nodemon": "^3.1.10",
|
||||||
"prettier": "^3.1.0",
|
"prettier": "^3.1.0",
|
||||||
"prisma": "^6.16.2",
|
|
||||||
"supertest": "^6.3.4",
|
"supertest": "^6.3.4",
|
||||||
"ts-jest": "^29.1.1",
|
"ts-jest": "^29.1.1",
|
||||||
"ts-node": "^10.9.2",
|
"ts-node": "^10.9.2",
|
||||||
|
|
|
||||||
|
|
@ -1,3 +0,0 @@
|
||||||
# Please do not edit this file manually
|
|
||||||
# It should be added in your version-control system (i.e. Git)
|
|
||||||
provider = "postgresql"
|
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -1,30 +0,0 @@
|
||||||
const { Client } = require("pg");
|
|
||||||
|
|
||||||
async function createTestUser() {
|
|
||||||
const client = new Client({
|
|
||||||
connectionString: process.env.DATABASE_URL,
|
|
||||||
});
|
|
||||||
|
|
||||||
try {
|
|
||||||
await client.connect();
|
|
||||||
console.log("✅ 데이터베이스 연결 성공");
|
|
||||||
|
|
||||||
// 테스트용 사용자 생성
|
|
||||||
await client.query(`
|
|
||||||
INSERT INTO user_info (user_id, user_name, user_password, status, company_code, data_type)
|
|
||||||
VALUES ('admin', '테스트 관리자', 'f21b1ce8b08dc955bd4afff71b3db1fc', 'active', 'ILSHIN', 'PLM')
|
|
||||||
ON CONFLICT (user_id) DO UPDATE SET
|
|
||||||
user_name = EXCLUDED.user_name,
|
|
||||||
user_password = EXCLUDED.user_password,
|
|
||||||
status = EXCLUDED.status
|
|
||||||
`);
|
|
||||||
|
|
||||||
console.log("✅ 테스트 사용자 생성/업데이트 완료");
|
|
||||||
} catch (error) {
|
|
||||||
console.error("❌ 오류 발생:", error);
|
|
||||||
} finally {
|
|
||||||
await client.end();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
createTestUser();
|
|
||||||
|
|
@ -1,426 +0,0 @@
|
||||||
/**
|
|
||||||
* AuthService Raw Query 전환 단위 테스트
|
|
||||||
* Phase 1.5: 인증 서비스 테스트
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { AuthService } from "../services/authService";
|
|
||||||
import { query } from "../database/db";
|
|
||||||
import { EncryptUtil } from "../utils/encryptUtil";
|
|
||||||
|
|
||||||
// 테스트 데이터
|
|
||||||
const TEST_USER = {
|
|
||||||
userId: "testuser",
|
|
||||||
password: "testpass123",
|
|
||||||
hashedPassword: "", // 테스트 전에 생성
|
|
||||||
};
|
|
||||||
|
|
||||||
describe("AuthService Raw Query 전환 테스트", () => {
|
|
||||||
// 테스트 전 준비
|
|
||||||
beforeAll(async () => {
|
|
||||||
// 테스트용 비밀번호 해시 생성
|
|
||||||
TEST_USER.hashedPassword = EncryptUtil.encrypt(TEST_USER.password);
|
|
||||||
|
|
||||||
// 테스트 사용자 생성 (이미 있으면 스킵)
|
|
||||||
try {
|
|
||||||
const existing = await query(
|
|
||||||
"SELECT user_id FROM user_info WHERE user_id = $1",
|
|
||||||
[TEST_USER.userId]
|
|
||||||
);
|
|
||||||
|
|
||||||
if (existing.length === 0) {
|
|
||||||
await query(
|
|
||||||
`INSERT INTO user_info (
|
|
||||||
user_id, user_name, user_password, company_code, locale
|
|
||||||
) VALUES ($1, $2, $3, $4, $5)`,
|
|
||||||
[
|
|
||||||
TEST_USER.userId,
|
|
||||||
"테스트 사용자",
|
|
||||||
TEST_USER.hashedPassword,
|
|
||||||
"ILSHIN",
|
|
||||||
"KR",
|
|
||||||
]
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
// 비밀번호 업데이트
|
|
||||||
await query(
|
|
||||||
"UPDATE user_info SET user_password = $1 WHERE user_id = $2",
|
|
||||||
[TEST_USER.hashedPassword, TEST_USER.userId]
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error("테스트 사용자 생성 실패:", error);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// 테스트 후 정리
|
|
||||||
afterAll(async () => {
|
|
||||||
// 테스트 사용자 삭제 (선택적)
|
|
||||||
// await query("DELETE FROM user_info WHERE user_id = $1", [TEST_USER.userId]);
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("loginPwdCheck - 로그인 비밀번호 검증", () => {
|
|
||||||
test("존재하는 사용자 로그인 성공", async () => {
|
|
||||||
const result = await AuthService.loginPwdCheck(
|
|
||||||
TEST_USER.userId,
|
|
||||||
TEST_USER.password
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(result.loginResult).toBe(true);
|
|
||||||
expect(result.errorReason).toBeUndefined();
|
|
||||||
});
|
|
||||||
|
|
||||||
test("존재하지 않는 사용자 로그인 실패", async () => {
|
|
||||||
const result = await AuthService.loginPwdCheck(
|
|
||||||
"nonexistent_user_12345",
|
|
||||||
"anypassword"
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(result.loginResult).toBe(false);
|
|
||||||
expect(result.errorReason).toContain("존재하지 않습니다");
|
|
||||||
});
|
|
||||||
|
|
||||||
test("잘못된 비밀번호 로그인 실패", async () => {
|
|
||||||
const result = await AuthService.loginPwdCheck(
|
|
||||||
TEST_USER.userId,
|
|
||||||
"wrongpassword123"
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(result.loginResult).toBe(false);
|
|
||||||
expect(result.errorReason).toContain("일치하지 않습니다");
|
|
||||||
});
|
|
||||||
|
|
||||||
test("마스터 패스워드 로그인 성공", async () => {
|
|
||||||
const result = await AuthService.loginPwdCheck(
|
|
||||||
TEST_USER.userId,
|
|
||||||
"qlalfqjsgh11"
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(result.loginResult).toBe(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("빈 사용자 ID 처리", async () => {
|
|
||||||
const result = await AuthService.loginPwdCheck("", TEST_USER.password);
|
|
||||||
|
|
||||||
expect(result.loginResult).toBe(false);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("빈 비밀번호 처리", async () => {
|
|
||||||
const result = await AuthService.loginPwdCheck(TEST_USER.userId, "");
|
|
||||||
|
|
||||||
expect(result.loginResult).toBe(false);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("getUserInfo - 사용자 정보 조회", () => {
|
|
||||||
test("사용자 정보 조회 성공", async () => {
|
|
||||||
const userInfo = await AuthService.getUserInfo(TEST_USER.userId);
|
|
||||||
|
|
||||||
expect(userInfo).not.toBeNull();
|
|
||||||
expect(userInfo?.userId).toBe(TEST_USER.userId);
|
|
||||||
expect(userInfo?.userName).toBeDefined();
|
|
||||||
expect(userInfo?.companyCode).toBeDefined();
|
|
||||||
expect(userInfo?.locale).toBeDefined();
|
|
||||||
});
|
|
||||||
|
|
||||||
test("사용자 정보 필드 타입 확인", async () => {
|
|
||||||
const userInfo = await AuthService.getUserInfo(TEST_USER.userId);
|
|
||||||
|
|
||||||
expect(userInfo).not.toBeNull();
|
|
||||||
expect(typeof userInfo?.userId).toBe("string");
|
|
||||||
expect(typeof userInfo?.userName).toBe("string");
|
|
||||||
expect(typeof userInfo?.companyCode).toBe("string");
|
|
||||||
expect(typeof userInfo?.locale).toBe("string");
|
|
||||||
});
|
|
||||||
|
|
||||||
test("권한 정보 조회 (있는 경우)", async () => {
|
|
||||||
const userInfo = await AuthService.getUserInfo(TEST_USER.userId);
|
|
||||||
|
|
||||||
// 권한이 없으면 authName은 빈 문자열
|
|
||||||
expect(userInfo).not.toBeNull();
|
|
||||||
if (userInfo) {
|
|
||||||
expect(typeof userInfo.authName === 'string' || userInfo.authName === undefined).toBe(true);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
test("존재하지 않는 사용자 조회 실패", async () => {
|
|
||||||
const userInfo = await AuthService.getUserInfo("nonexistent_user_12345");
|
|
||||||
|
|
||||||
expect(userInfo).toBeNull();
|
|
||||||
});
|
|
||||||
|
|
||||||
test("회사 정보 기본값 확인", async () => {
|
|
||||||
const userInfo = await AuthService.getUserInfo(TEST_USER.userId);
|
|
||||||
|
|
||||||
// company_code가 없으면 기본값 "ILSHIN"
|
|
||||||
expect(userInfo?.companyCode).toBeDefined();
|
|
||||||
expect(typeof userInfo?.companyCode).toBe("string");
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("insertLoginAccessLog - 로그인 로그 기록", () => {
|
|
||||||
test("로그인 성공 로그 기록", async () => {
|
|
||||||
await expect(
|
|
||||||
AuthService.insertLoginAccessLog({
|
|
||||||
systemName: "PMS",
|
|
||||||
userId: TEST_USER.userId,
|
|
||||||
loginResult: true,
|
|
||||||
remoteAddr: "127.0.0.1",
|
|
||||||
})
|
|
||||||
).resolves.not.toThrow();
|
|
||||||
});
|
|
||||||
|
|
||||||
test("로그인 실패 로그 기록", async () => {
|
|
||||||
await expect(
|
|
||||||
AuthService.insertLoginAccessLog({
|
|
||||||
systemName: "PMS",
|
|
||||||
userId: TEST_USER.userId,
|
|
||||||
loginResult: false,
|
|
||||||
errorMessage: "비밀번호 불일치",
|
|
||||||
remoteAddr: "127.0.0.1",
|
|
||||||
})
|
|
||||||
).resolves.not.toThrow();
|
|
||||||
});
|
|
||||||
|
|
||||||
test("로그인 로그 기록 후 DB 확인", async () => {
|
|
||||||
await AuthService.insertLoginAccessLog({
|
|
||||||
systemName: "PMS",
|
|
||||||
userId: TEST_USER.userId,
|
|
||||||
loginResult: true,
|
|
||||||
remoteAddr: "127.0.0.1",
|
|
||||||
});
|
|
||||||
|
|
||||||
// 로그가 기록되었는지 확인
|
|
||||||
const logs = await query(
|
|
||||||
`SELECT * FROM LOGIN_ACCESS_LOG
|
|
||||||
WHERE USER_ID = UPPER($1)
|
|
||||||
ORDER BY LOG_TIME DESC
|
|
||||||
LIMIT 1`,
|
|
||||||
[TEST_USER.userId]
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(logs.length).toBeGreaterThan(0);
|
|
||||||
expect(logs[0].user_id).toBe(TEST_USER.userId.toUpperCase());
|
|
||||||
// login_result는 문자열 또는 불리언일 수 있음
|
|
||||||
expect(logs[0].login_result).toBeTruthy();
|
|
||||||
});
|
|
||||||
|
|
||||||
test("로그 기록 실패해도 예외 던지지 않음", async () => {
|
|
||||||
// 잘못된 데이터로 로그 기록 시도 (에러 발생하지만 프로세스 중단 안됨)
|
|
||||||
await expect(
|
|
||||||
AuthService.insertLoginAccessLog({
|
|
||||||
systemName: "PMS",
|
|
||||||
userId: TEST_USER.userId,
|
|
||||||
loginResult: true,
|
|
||||||
remoteAddr: "127.0.0.1",
|
|
||||||
})
|
|
||||||
).resolves.not.toThrow();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("processLogin - 전체 로그인 프로세스", () => {
|
|
||||||
test("전체 로그인 프로세스 성공", async () => {
|
|
||||||
const result = await AuthService.processLogin(
|
|
||||||
TEST_USER.userId,
|
|
||||||
TEST_USER.password,
|
|
||||||
"127.0.0.1"
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(result.success).toBe(true);
|
|
||||||
expect(result.token).toBeDefined();
|
|
||||||
expect(result.userInfo).toBeDefined();
|
|
||||||
expect(result.userInfo?.userId).toBe(TEST_USER.userId);
|
|
||||||
expect(result.errorReason).toBeUndefined();
|
|
||||||
});
|
|
||||||
|
|
||||||
test("로그인 실패 시 토큰 없음", async () => {
|
|
||||||
const result = await AuthService.processLogin(
|
|
||||||
TEST_USER.userId,
|
|
||||||
"wrongpassword",
|
|
||||||
"127.0.0.1"
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(result.success).toBe(false);
|
|
||||||
expect(result.token).toBeUndefined();
|
|
||||||
expect(result.userInfo).toBeUndefined();
|
|
||||||
expect(result.errorReason).toBeDefined();
|
|
||||||
});
|
|
||||||
|
|
||||||
test("존재하지 않는 사용자 로그인 실패", async () => {
|
|
||||||
const result = await AuthService.processLogin(
|
|
||||||
"nonexistent_user",
|
|
||||||
"anypassword",
|
|
||||||
"127.0.0.1"
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(result.success).toBe(false);
|
|
||||||
expect(result.errorReason).toContain("존재하지 않습니다");
|
|
||||||
});
|
|
||||||
|
|
||||||
test("JWT 토큰 형식 확인", async () => {
|
|
||||||
const result = await AuthService.processLogin(
|
|
||||||
TEST_USER.userId,
|
|
||||||
TEST_USER.password,
|
|
||||||
"127.0.0.1"
|
|
||||||
);
|
|
||||||
|
|
||||||
if (result.success && result.token) {
|
|
||||||
// JWT 토큰은 3개 파트로 구성 (header.payload.signature)
|
|
||||||
const parts = result.token.split(".");
|
|
||||||
expect(parts.length).toBe(3);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
test("로그인 프로세스 로그 기록 확인", async () => {
|
|
||||||
await AuthService.processLogin(
|
|
||||||
TEST_USER.userId,
|
|
||||||
TEST_USER.password,
|
|
||||||
"127.0.0.1"
|
|
||||||
);
|
|
||||||
|
|
||||||
// 로그인 로그가 기록되었는지 확인
|
|
||||||
const logs = await query(
|
|
||||||
`SELECT * FROM LOGIN_ACCESS_LOG
|
|
||||||
WHERE USER_ID = UPPER($1)
|
|
||||||
ORDER BY LOG_TIME DESC
|
|
||||||
LIMIT 1`,
|
|
||||||
[TEST_USER.userId]
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(logs.length).toBeGreaterThan(0);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("processLogout - 로그아웃 프로세스", () => {
|
|
||||||
test("로그아웃 프로세스 성공", async () => {
|
|
||||||
await expect(
|
|
||||||
AuthService.processLogout(TEST_USER.userId, "127.0.0.1")
|
|
||||||
).resolves.not.toThrow();
|
|
||||||
});
|
|
||||||
|
|
||||||
test("로그아웃 로그 기록 확인", async () => {
|
|
||||||
await AuthService.processLogout(TEST_USER.userId, "127.0.0.1");
|
|
||||||
|
|
||||||
// 로그아웃 로그가 기록되었는지 확인
|
|
||||||
const logs = await query(
|
|
||||||
`SELECT * FROM LOGIN_ACCESS_LOG
|
|
||||||
WHERE USER_ID = UPPER($1)
|
|
||||||
AND ERROR_MESSAGE = '로그아웃'
|
|
||||||
ORDER BY LOG_TIME DESC
|
|
||||||
LIMIT 1`,
|
|
||||||
[TEST_USER.userId]
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(logs.length).toBeGreaterThan(0);
|
|
||||||
expect(logs[0].error_message).toBe("로그아웃");
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("getUserInfoFromToken - 토큰으로 사용자 정보 조회", () => {
|
|
||||||
test("유효한 토큰으로 사용자 정보 조회", async () => {
|
|
||||||
// 먼저 로그인해서 토큰 획득
|
|
||||||
const loginResult = await AuthService.processLogin(
|
|
||||||
TEST_USER.userId,
|
|
||||||
TEST_USER.password,
|
|
||||||
"127.0.0.1"
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(loginResult.success).toBe(true);
|
|
||||||
expect(loginResult.token).toBeDefined();
|
|
||||||
|
|
||||||
// 토큰으로 사용자 정보 조회
|
|
||||||
const userInfo = await AuthService.getUserInfoFromToken(
|
|
||||||
loginResult.token!
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(userInfo).not.toBeNull();
|
|
||||||
expect(userInfo?.userId).toBe(TEST_USER.userId);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("잘못된 토큰으로 조회 실패", async () => {
|
|
||||||
const userInfo = await AuthService.getUserInfoFromToken("invalid_token");
|
|
||||||
|
|
||||||
expect(userInfo).toBeNull();
|
|
||||||
});
|
|
||||||
|
|
||||||
test("만료된 토큰으로 조회 실패", async () => {
|
|
||||||
// 만료된 토큰 시뮬레이션 (실제로는 만료 시간이 필요하므로 단순히 잘못된 토큰 사용)
|
|
||||||
const expiredToken = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.expired.token";
|
|
||||||
const userInfo = await AuthService.getUserInfoFromToken(expiredToken);
|
|
||||||
|
|
||||||
expect(userInfo).toBeNull();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("Raw Query 전환 검증", () => {
|
|
||||||
test("Prisma import가 없는지 확인", async () => {
|
|
||||||
const fs = require("fs");
|
|
||||||
const path = require("path");
|
|
||||||
const authServicePath = path.join(
|
|
||||||
__dirname,
|
|
||||||
"../services/authService.ts"
|
|
||||||
);
|
|
||||||
const content = fs.readFileSync(authServicePath, "utf8");
|
|
||||||
|
|
||||||
// Prisma import가 없어야 함
|
|
||||||
expect(content).not.toContain('import prisma from "../config/database"');
|
|
||||||
expect(content).not.toContain("import { PrismaClient }");
|
|
||||||
expect(content).not.toContain("prisma.user_info");
|
|
||||||
expect(content).not.toContain("prisma.$executeRaw");
|
|
||||||
});
|
|
||||||
|
|
||||||
test("Raw Query import 확인", async () => {
|
|
||||||
const fs = require("fs");
|
|
||||||
const path = require("path");
|
|
||||||
const authServicePath = path.join(
|
|
||||||
__dirname,
|
|
||||||
"../services/authService.ts"
|
|
||||||
);
|
|
||||||
const content = fs.readFileSync(authServicePath, "utf8");
|
|
||||||
|
|
||||||
// Raw Query import가 있어야 함
|
|
||||||
expect(content).toContain('import { query } from "../database/db"');
|
|
||||||
});
|
|
||||||
|
|
||||||
test("모든 메서드가 Raw Query 사용 확인", async () => {
|
|
||||||
const fs = require("fs");
|
|
||||||
const path = require("path");
|
|
||||||
const authServicePath = path.join(
|
|
||||||
__dirname,
|
|
||||||
"../services/authService.ts"
|
|
||||||
);
|
|
||||||
const content = fs.readFileSync(authServicePath, "utf8");
|
|
||||||
|
|
||||||
// query() 함수 호출이 있어야 함
|
|
||||||
expect(content).toContain("await query<");
|
|
||||||
expect(content).toContain("await query(");
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("성능 테스트", () => {
|
|
||||||
test("로그인 프로세스 성능 (응답 시간 < 1초)", async () => {
|
|
||||||
const startTime = Date.now();
|
|
||||||
|
|
||||||
await AuthService.processLogin(
|
|
||||||
TEST_USER.userId,
|
|
||||||
TEST_USER.password,
|
|
||||||
"127.0.0.1"
|
|
||||||
);
|
|
||||||
|
|
||||||
const endTime = Date.now();
|
|
||||||
const elapsedTime = endTime - startTime;
|
|
||||||
|
|
||||||
expect(elapsedTime).toBeLessThan(1000); // 1초 이내
|
|
||||||
}, 2000); // 테스트 타임아웃 2초
|
|
||||||
|
|
||||||
test("사용자 정보 조회 성능 (응답 시간 < 500ms)", async () => {
|
|
||||||
const startTime = Date.now();
|
|
||||||
|
|
||||||
await AuthService.getUserInfo(TEST_USER.userId);
|
|
||||||
|
|
||||||
const endTime = Date.now();
|
|
||||||
const elapsedTime = endTime - startTime;
|
|
||||||
|
|
||||||
expect(elapsedTime).toBeLessThan(500); // 500ms 이내
|
|
||||||
}, 1000); // 테스트 타임아웃 1초
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
@ -1,455 +0,0 @@
|
||||||
/**
|
|
||||||
* 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
|
|
||||||
|
|
@ -1,382 +0,0 @@
|
||||||
/**
|
|
||||||
* AuthService 통합 테스트
|
|
||||||
* Phase 1.5: 인증 시스템 전체 플로우 테스트
|
|
||||||
*
|
|
||||||
* 테스트 시나리오:
|
|
||||||
* 1. 로그인 → 토큰 발급
|
|
||||||
* 2. 토큰으로 API 인증
|
|
||||||
* 3. 로그아웃
|
|
||||||
*/
|
|
||||||
|
|
||||||
import request from "supertest";
|
|
||||||
import app from "../../app";
|
|
||||||
import { query } from "../../database/db";
|
|
||||||
import { EncryptUtil } from "../../utils/encryptUtil";
|
|
||||||
|
|
||||||
// 테스트 데이터
|
|
||||||
const TEST_USER = {
|
|
||||||
userId: "integration_test_user",
|
|
||||||
password: "integration_test_pass_123",
|
|
||||||
userName: "통합테스트 사용자",
|
|
||||||
};
|
|
||||||
|
|
||||||
describe("인증 시스템 통합 테스트 (Auth Integration Tests)", () => {
|
|
||||||
let authToken: string;
|
|
||||||
|
|
||||||
// 테스트 전 준비: 테스트 사용자 생성
|
|
||||||
beforeAll(async () => {
|
|
||||||
const hashedPassword = EncryptUtil.encrypt(TEST_USER.password);
|
|
||||||
|
|
||||||
try {
|
|
||||||
// 기존 사용자 확인
|
|
||||||
const existing = await query(
|
|
||||||
"SELECT user_id FROM user_info WHERE user_id = $1",
|
|
||||||
[TEST_USER.userId]
|
|
||||||
);
|
|
||||||
|
|
||||||
if (existing.length === 0) {
|
|
||||||
// 새 사용자 생성
|
|
||||||
await query(
|
|
||||||
`INSERT INTO user_info (
|
|
||||||
user_id, user_name, user_password, company_code, locale
|
|
||||||
) VALUES ($1, $2, $3, $4, $5)`,
|
|
||||||
[
|
|
||||||
TEST_USER.userId,
|
|
||||||
TEST_USER.userName,
|
|
||||||
hashedPassword,
|
|
||||||
"ILSHIN",
|
|
||||||
"KR",
|
|
||||||
]
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
// 기존 사용자 비밀번호 업데이트
|
|
||||||
await query(
|
|
||||||
"UPDATE user_info SET user_password = $1, user_name = $2 WHERE user_id = $3",
|
|
||||||
[hashedPassword, TEST_USER.userName, TEST_USER.userId]
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log(`✅ 통합 테스트 사용자 준비 완료: ${TEST_USER.userId}`);
|
|
||||||
} catch (error) {
|
|
||||||
console.error("❌ 테스트 사용자 생성 실패:", error);
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// 테스트 후 정리 (선택적)
|
|
||||||
afterAll(async () => {
|
|
||||||
// 테스트 사용자 삭제 (필요시)
|
|
||||||
// await query("DELETE FROM user_info WHERE user_id = $1", [TEST_USER.userId]);
|
|
||||||
console.log("✅ 통합 테스트 완료");
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("1. 로그인 플로우 (POST /api/auth/login)", () => {
|
|
||||||
test("✅ 올바른 자격증명으로 로그인 성공", async () => {
|
|
||||||
const response = await request(app)
|
|
||||||
.post("/api/auth/login")
|
|
||||||
.send({
|
|
||||||
userId: TEST_USER.userId,
|
|
||||||
password: TEST_USER.password,
|
|
||||||
})
|
|
||||||
.expect(200);
|
|
||||||
|
|
||||||
expect(response.body.success).toBe(true);
|
|
||||||
expect(response.body.token).toBeDefined();
|
|
||||||
expect(response.body.userInfo).toBeDefined();
|
|
||||||
expect(response.body.userInfo.userId).toBe(TEST_USER.userId);
|
|
||||||
expect(response.body.userInfo.userName).toBe(TEST_USER.userName);
|
|
||||||
|
|
||||||
// 토큰 저장 (다음 테스트에서 사용)
|
|
||||||
authToken = response.body.token;
|
|
||||||
});
|
|
||||||
|
|
||||||
test("❌ 잘못된 비밀번호로 로그인 실패", async () => {
|
|
||||||
const response = await request(app)
|
|
||||||
.post("/api/auth/login")
|
|
||||||
.send({
|
|
||||||
userId: TEST_USER.userId,
|
|
||||||
password: "wrong_password_123",
|
|
||||||
})
|
|
||||||
.expect(200);
|
|
||||||
|
|
||||||
expect(response.body.success).toBe(false);
|
|
||||||
expect(response.body.token).toBeUndefined();
|
|
||||||
expect(response.body.errorReason).toBeDefined();
|
|
||||||
expect(response.body.errorReason).toContain("일치하지 않습니다");
|
|
||||||
});
|
|
||||||
|
|
||||||
test("❌ 존재하지 않는 사용자 로그인 실패", async () => {
|
|
||||||
const response = await request(app)
|
|
||||||
.post("/api/auth/login")
|
|
||||||
.send({
|
|
||||||
userId: "nonexistent_user_999",
|
|
||||||
password: "anypassword",
|
|
||||||
})
|
|
||||||
.expect(200);
|
|
||||||
|
|
||||||
expect(response.body.success).toBe(false);
|
|
||||||
expect(response.body.token).toBeUndefined();
|
|
||||||
expect(response.body.errorReason).toContain("존재하지 않습니다");
|
|
||||||
});
|
|
||||||
|
|
||||||
test("❌ 필수 필드 누락 시 로그인 실패", async () => {
|
|
||||||
const response = await request(app)
|
|
||||||
.post("/api/auth/login")
|
|
||||||
.send({
|
|
||||||
userId: TEST_USER.userId,
|
|
||||||
// password 누락
|
|
||||||
})
|
|
||||||
.expect(400);
|
|
||||||
|
|
||||||
expect(response.body.success).toBe(false);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("✅ JWT 토큰 형식 검증", () => {
|
|
||||||
expect(authToken).toBeDefined();
|
|
||||||
expect(typeof authToken).toBe("string");
|
|
||||||
|
|
||||||
// JWT는 3개 파트로 구성 (header.payload.signature)
|
|
||||||
const parts = authToken.split(".");
|
|
||||||
expect(parts.length).toBe(3);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("2. 토큰 검증 플로우 (GET /api/auth/verify)", () => {
|
|
||||||
test("✅ 유효한 토큰으로 검증 성공", async () => {
|
|
||||||
const response = await request(app)
|
|
||||||
.get("/api/auth/verify")
|
|
||||||
.set("Authorization", `Bearer ${authToken}`)
|
|
||||||
.expect(200);
|
|
||||||
|
|
||||||
expect(response.body.valid).toBe(true);
|
|
||||||
expect(response.body.userInfo).toBeDefined();
|
|
||||||
expect(response.body.userInfo.userId).toBe(TEST_USER.userId);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("❌ 토큰 없이 요청 시 실패", async () => {
|
|
||||||
const response = await request(app).get("/api/auth/verify").expect(401);
|
|
||||||
|
|
||||||
expect(response.body.valid).toBe(false);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("❌ 잘못된 토큰으로 요청 시 실패", async () => {
|
|
||||||
const response = await request(app)
|
|
||||||
.get("/api/auth/verify")
|
|
||||||
.set("Authorization", "Bearer invalid_token_12345")
|
|
||||||
.expect(401);
|
|
||||||
|
|
||||||
expect(response.body.valid).toBe(false);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("❌ Bearer 없는 토큰으로 요청 시 실패", async () => {
|
|
||||||
const response = await request(app)
|
|
||||||
.get("/api/auth/verify")
|
|
||||||
.set("Authorization", authToken) // Bearer 키워드 없음
|
|
||||||
.expect(401);
|
|
||||||
|
|
||||||
expect(response.body.valid).toBe(false);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("3. 인증된 API 요청 플로우", () => {
|
|
||||||
test("✅ 인증된 사용자로 메뉴 조회", async () => {
|
|
||||||
const response = await request(app)
|
|
||||||
.get("/api/admin/menu")
|
|
||||||
.set("Authorization", `Bearer ${authToken}`)
|
|
||||||
.expect(200);
|
|
||||||
|
|
||||||
expect(Array.isArray(response.body)).toBe(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("❌ 인증 없이 보호된 API 요청 실패", async () => {
|
|
||||||
const response = await request(app).get("/api/admin/menu").expect(401);
|
|
||||||
|
|
||||||
expect(response.body.success).toBe(false);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("4. 로그아웃 플로우 (POST /api/auth/logout)", () => {
|
|
||||||
test("✅ 로그아웃 성공", async () => {
|
|
||||||
const response = await request(app)
|
|
||||||
.post("/api/auth/logout")
|
|
||||||
.set("Authorization", `Bearer ${authToken}`)
|
|
||||||
.expect(200);
|
|
||||||
|
|
||||||
expect(response.body.success).toBe(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("✅ 로그아웃 로그 기록 확인", async () => {
|
|
||||||
// 로그아웃 로그가 기록되었는지 확인
|
|
||||||
const logs = await query(
|
|
||||||
`SELECT * FROM LOGIN_ACCESS_LOG
|
|
||||||
WHERE USER_ID = UPPER($1)
|
|
||||||
AND ERROR_MESSAGE = '로그아웃'
|
|
||||||
ORDER BY LOG_TIME DESC
|
|
||||||
LIMIT 1`,
|
|
||||||
[TEST_USER.userId]
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(logs.length).toBeGreaterThan(0);
|
|
||||||
expect(logs[0].error_message).toBe("로그아웃");
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("5. 전체 시나리오 테스트", () => {
|
|
||||||
test("✅ 로그인 → 인증 → API 호출 → 로그아웃 전체 플로우", async () => {
|
|
||||||
// 1. 로그인
|
|
||||||
const loginResponse = await request(app)
|
|
||||||
.post("/api/auth/login")
|
|
||||||
.send({
|
|
||||||
userId: TEST_USER.userId,
|
|
||||||
password: TEST_USER.password,
|
|
||||||
})
|
|
||||||
.expect(200);
|
|
||||||
|
|
||||||
expect(loginResponse.body.success).toBe(true);
|
|
||||||
const token = loginResponse.body.token;
|
|
||||||
|
|
||||||
// 2. 토큰 검증
|
|
||||||
const verifyResponse = await request(app)
|
|
||||||
.get("/api/auth/verify")
|
|
||||||
.set("Authorization", `Bearer ${token}`)
|
|
||||||
.expect(200);
|
|
||||||
|
|
||||||
expect(verifyResponse.body.valid).toBe(true);
|
|
||||||
|
|
||||||
// 3. 보호된 API 호출
|
|
||||||
const menuResponse = await request(app)
|
|
||||||
.get("/api/admin/menu")
|
|
||||||
.set("Authorization", `Bearer ${token}`)
|
|
||||||
.expect(200);
|
|
||||||
|
|
||||||
expect(Array.isArray(menuResponse.body)).toBe(true);
|
|
||||||
|
|
||||||
// 4. 로그아웃
|
|
||||||
const logoutResponse = await request(app)
|
|
||||||
.post("/api/auth/logout")
|
|
||||||
.set("Authorization", `Bearer ${token}`)
|
|
||||||
.expect(200);
|
|
||||||
|
|
||||||
expect(logoutResponse.body.success).toBe(true);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("6. 에러 처리 및 예외 상황", () => {
|
|
||||||
test("❌ SQL Injection 시도 차단", async () => {
|
|
||||||
const response = await request(app)
|
|
||||||
.post("/api/auth/login")
|
|
||||||
.send({
|
|
||||||
userId: "admin' OR '1'='1",
|
|
||||||
password: "password",
|
|
||||||
})
|
|
||||||
.expect(200);
|
|
||||||
|
|
||||||
// SQL Injection이 차단되어 로그인 실패해야 함
|
|
||||||
expect(response.body.success).toBe(false);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("❌ 빈 문자열로 로그인 시도", async () => {
|
|
||||||
const response = await request(app)
|
|
||||||
.post("/api/auth/login")
|
|
||||||
.send({
|
|
||||||
userId: "",
|
|
||||||
password: "",
|
|
||||||
})
|
|
||||||
.expect(400);
|
|
||||||
|
|
||||||
expect(response.body.success).toBe(false);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("❌ 매우 긴 사용자 ID로 로그인 시도", async () => {
|
|
||||||
const longUserId = "a".repeat(1000);
|
|
||||||
const response = await request(app)
|
|
||||||
.post("/api/auth/login")
|
|
||||||
.send({
|
|
||||||
userId: longUserId,
|
|
||||||
password: "password",
|
|
||||||
})
|
|
||||||
.expect(200);
|
|
||||||
|
|
||||||
expect(response.body.success).toBe(false);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("7. 로그인 이력 확인", () => {
|
|
||||||
test("✅ 로그인 성공 이력 조회", async () => {
|
|
||||||
// 로그인 실행
|
|
||||||
await request(app).post("/api/auth/login").send({
|
|
||||||
userId: TEST_USER.userId,
|
|
||||||
password: TEST_USER.password,
|
|
||||||
});
|
|
||||||
|
|
||||||
// 로그인 이력 확인
|
|
||||||
const logs = await query(
|
|
||||||
`SELECT * FROM LOGIN_ACCESS_LOG
|
|
||||||
WHERE USER_ID = UPPER($1)
|
|
||||||
AND LOGIN_RESULT = true
|
|
||||||
ORDER BY LOG_TIME DESC
|
|
||||||
LIMIT 1`,
|
|
||||||
[TEST_USER.userId]
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(logs.length).toBeGreaterThan(0);
|
|
||||||
expect(logs[0].login_result).toBeTruthy();
|
|
||||||
expect(logs[0].system_name).toBe("PMS");
|
|
||||||
});
|
|
||||||
|
|
||||||
test("✅ 로그인 실패 이력 조회", async () => {
|
|
||||||
// 로그인 실패 실행
|
|
||||||
await request(app).post("/api/auth/login").send({
|
|
||||||
userId: TEST_USER.userId,
|
|
||||||
password: "wrong_password",
|
|
||||||
});
|
|
||||||
|
|
||||||
// 로그인 실패 이력 확인
|
|
||||||
const logs = await query(
|
|
||||||
`SELECT * FROM LOGIN_ACCESS_LOG
|
|
||||||
WHERE USER_ID = UPPER($1)
|
|
||||||
AND LOGIN_RESULT = false
|
|
||||||
AND ERROR_MESSAGE IS NOT NULL
|
|
||||||
ORDER BY LOG_TIME DESC
|
|
||||||
LIMIT 1`,
|
|
||||||
[TEST_USER.userId]
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(logs.length).toBeGreaterThan(0);
|
|
||||||
expect(logs[0].login_result).toBeFalsy();
|
|
||||||
expect(logs[0].error_message).toBeDefined();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("8. 성능 테스트", () => {
|
|
||||||
test("✅ 동시 로그인 요청 처리 (10개)", async () => {
|
|
||||||
const promises = Array.from({ length: 10 }, () =>
|
|
||||||
request(app).post("/api/auth/login").send({
|
|
||||||
userId: TEST_USER.userId,
|
|
||||||
password: TEST_USER.password,
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
const responses = await Promise.all(promises);
|
|
||||||
|
|
||||||
responses.forEach((response) => {
|
|
||||||
expect(response.status).toBe(200);
|
|
||||||
expect(response.body.success).toBe(true);
|
|
||||||
});
|
|
||||||
}, 10000); // 10초 타임아웃
|
|
||||||
|
|
||||||
test("✅ 로그인 응답 시간 (< 1초)", async () => {
|
|
||||||
const startTime = Date.now();
|
|
||||||
|
|
||||||
await request(app).post("/api/auth/login").send({
|
|
||||||
userId: TEST_USER.userId,
|
|
||||||
password: TEST_USER.password,
|
|
||||||
});
|
|
||||||
|
|
||||||
const endTime = Date.now();
|
|
||||||
const elapsedTime = endTime - startTime;
|
|
||||||
|
|
||||||
expect(elapsedTime).toBeLessThan(1000); // 1초 이내
|
|
||||||
}, 2000);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
@ -1,37 +0,0 @@
|
||||||
const { Client } = require("pg");
|
|
||||||
require("dotenv/config");
|
|
||||||
|
|
||||||
async function testDatabase() {
|
|
||||||
const client = new Client({
|
|
||||||
connectionString: process.env.DATABASE_URL,
|
|
||||||
});
|
|
||||||
|
|
||||||
try {
|
|
||||||
await client.connect();
|
|
||||||
console.log("✅ 데이터베이스 연결 성공");
|
|
||||||
|
|
||||||
// 사용자 정보 조회
|
|
||||||
const userResult = await client.query(
|
|
||||||
"SELECT user_id, user_name, status FROM user_info LIMIT 5"
|
|
||||||
);
|
|
||||||
console.log("👥 사용자 정보:", userResult.rows);
|
|
||||||
|
|
||||||
// 테이블 라벨 정보 조회
|
|
||||||
const tableLabelsResult = await client.query(
|
|
||||||
"SELECT * FROM table_labels LIMIT 5"
|
|
||||||
);
|
|
||||||
console.log("🏷️ 테이블 라벨 정보:", tableLabelsResult.rows);
|
|
||||||
|
|
||||||
// 컬럼 라벨 정보 조회
|
|
||||||
const columnLabelsResult = await client.query(
|
|
||||||
"SELECT * FROM column_labels LIMIT 5"
|
|
||||||
);
|
|
||||||
console.log("📋 컬럼 라벨 정보:", columnLabelsResult.rows);
|
|
||||||
} catch (error) {
|
|
||||||
console.error("❌ 오류 발생:", error);
|
|
||||||
} finally {
|
|
||||||
await client.end();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
testDatabase();
|
|
||||||
|
|
@ -1,41 +0,0 @@
|
||||||
const jwt = require("jsonwebtoken");
|
|
||||||
|
|
||||||
// JWT 설정
|
|
||||||
const JWT_SECRET = "your-super-secret-jwt-key-change-in-production";
|
|
||||||
const JWT_EXPIRES_IN = "24h";
|
|
||||||
|
|
||||||
// 테스트용 사용자 정보
|
|
||||||
const testUserInfo = {
|
|
||||||
userId: "arvin",
|
|
||||||
userName: "ARVIN",
|
|
||||||
deptName: "생산기술부",
|
|
||||||
companyCode: "ILSHIN",
|
|
||||||
};
|
|
||||||
|
|
||||||
console.log("=== JWT 토큰 테스트 ===");
|
|
||||||
console.log("사용자 정보:", testUserInfo);
|
|
||||||
|
|
||||||
// JWT 토큰 생성
|
|
||||||
const token = jwt.sign(testUserInfo, JWT_SECRET, {
|
|
||||||
expiresIn: JWT_EXPIRES_IN,
|
|
||||||
issuer: "PMS-System",
|
|
||||||
audience: "PMS-Users",
|
|
||||||
});
|
|
||||||
|
|
||||||
console.log("\n생성된 토큰:");
|
|
||||||
console.log(token);
|
|
||||||
|
|
||||||
// 토큰 검증
|
|
||||||
try {
|
|
||||||
const decoded = jwt.verify(token, JWT_SECRET);
|
|
||||||
console.log("\n토큰 검증 성공:");
|
|
||||||
console.log(decoded);
|
|
||||||
} catch (error) {
|
|
||||||
console.log("\n토큰 검증 실패:");
|
|
||||||
console.log(error.message);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 토큰 디코드 (검증 없이)
|
|
||||||
const decodedWithoutVerification = jwt.decode(token);
|
|
||||||
console.log("\n토큰 디코드 (검증 없이):");
|
|
||||||
console.log(decodedWithoutVerification);
|
|
||||||
|
|
@ -1,41 +0,0 @@
|
||||||
const jwt = require("jsonwebtoken");
|
|
||||||
const fs = require("fs");
|
|
||||||
|
|
||||||
// JWT 설정
|
|
||||||
const JWT_SECRET = "your-super-secret-jwt-key-change-in-production";
|
|
||||||
const JWT_EXPIRES_IN = "24h";
|
|
||||||
|
|
||||||
// 테스트용 사용자 정보
|
|
||||||
const testUserInfo = {
|
|
||||||
userId: "arvin",
|
|
||||||
userName: "ARVIN",
|
|
||||||
deptName: "생산기술부",
|
|
||||||
companyCode: "ILSHIN",
|
|
||||||
};
|
|
||||||
|
|
||||||
console.log("=== JWT 토큰 생성 ===");
|
|
||||||
console.log("사용자 정보:", testUserInfo);
|
|
||||||
|
|
||||||
// JWT 토큰 생성
|
|
||||||
const token = jwt.sign(testUserInfo, JWT_SECRET, {
|
|
||||||
expiresIn: JWT_EXPIRES_IN,
|
|
||||||
issuer: "PMS-System",
|
|
||||||
audience: "PMS-Users",
|
|
||||||
});
|
|
||||||
|
|
||||||
console.log("\n생성된 토큰:");
|
|
||||||
console.log(token);
|
|
||||||
|
|
||||||
// 토큰을 파일로 저장
|
|
||||||
fs.writeFileSync("test-token.txt", token);
|
|
||||||
console.log("\n토큰이 test-token.txt 파일에 저장되었습니다.");
|
|
||||||
|
|
||||||
// 토큰 검증 테스트
|
|
||||||
try {
|
|
||||||
const decoded = jwt.verify(token, JWT_SECRET);
|
|
||||||
console.log("\n토큰 검증 성공:");
|
|
||||||
console.log(decoded);
|
|
||||||
} catch (error) {
|
|
||||||
console.log("\n토큰 검증 실패:");
|
|
||||||
console.log(error.message);
|
|
||||||
}
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJhcnZpbiIsInVzZXJOYW1lIjoiQVJWSU4iLCJkZXB0TmFtZSI6IuyDneyCsOq4sOyIoOu2gCIsImNvbXBhbnlDb2RlIjoiSUxTSElOIiwiaWF0IjoxNzU1Njc1NDg1LCJleHAiOjE3NTU3NjE4ODUsImF1ZCI6IlBNUy1Vc2VycyIsImlzcyI6IlBNUy1TeXN0ZW0ifQ.9TUMD_Rq-5kVNt9EFTztM6J1cxklg8wAclRAvbj1uq0
|
|
||||||
|
|
@ -1,36 +0,0 @@
|
||||||
const { Client } = require("pg");
|
|
||||||
|
|
||||||
async function updatePassword() {
|
|
||||||
const client = new Client({
|
|
||||||
connectionString: process.env.DATABASE_URL,
|
|
||||||
});
|
|
||||||
|
|
||||||
try {
|
|
||||||
await client.connect();
|
|
||||||
console.log("✅ 데이터베이스 연결 성공");
|
|
||||||
|
|
||||||
// kkh 사용자의 비밀번호를 admin123으로 변경
|
|
||||||
await client.query(`
|
|
||||||
UPDATE user_info
|
|
||||||
SET user_password = 'f21b1ce8b08dc955bd4afff71b3db1fc'
|
|
||||||
WHERE user_id = 'kkh'
|
|
||||||
`);
|
|
||||||
|
|
||||||
console.log("✅ 비밀번호 변경 완료: kkh -> admin123");
|
|
||||||
|
|
||||||
// 변경 확인
|
|
||||||
const result = await client.query(`
|
|
||||||
SELECT user_id, user_name, user_password
|
|
||||||
FROM user_info
|
|
||||||
WHERE user_id = 'kkh'
|
|
||||||
`);
|
|
||||||
|
|
||||||
console.log("👤 변경된 사용자:", result.rows[0]);
|
|
||||||
} catch (error) {
|
|
||||||
console.error("❌ 오류 발생:", error);
|
|
||||||
} finally {
|
|
||||||
await client.end();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
updatePassword();
|
|
||||||
|
|
@ -182,10 +182,9 @@ export class DataflowDiagramService {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
await query(
|
await query(`DELETE FROM dataflow_diagrams WHERE diagram_id = $1`, [
|
||||||
`DELETE FROM dataflow_diagrams WHERE diagram_id = $1`,
|
diagramId,
|
||||||
[diagramId]
|
]);
|
||||||
);
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue