426 lines
13 KiB
TypeScript
426 lines
13 KiB
TypeScript
|
|
/**
|
||
|
|
* 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초
|
||
|
|
});
|
||
|
|
});
|