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