ERP-node/backend-node/src/tests/integration/auth.integration.test.ts

382 lines
11 KiB
TypeScript

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