/** * 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); }); });