사용자 로그인, 가입 처리
CI / test (push) Successful in 3m40s
CI / pr_and_merge (push) Successful in 1m8s

This commit is contained in:
2025-11-30 23:08:38 +09:00
parent 25162e4c12
commit 64e4a59244
82 changed files with 2817 additions and 28 deletions
+133
View File
@@ -0,0 +1,133 @@
import { describe, it, expect, beforeEach } from "vitest";
// 앞으로 구현할 인증/보안 유틸리티 모듈을 대상으로 TDD를 진행한다.
// - 비밀번호 해시/검증 (bcrypt 기반)
// - JWT 액세스 토큰 발급/검증
// - 민감정보 JSON 암호화/복호화
import {
hashPassword,
verifyPassword,
signAccessToken,
verifyAccessToken,
encryptJson,
decryptJson,
} from "@/features/auth/authCrypto";
interface TestUser {
id: string;
email: string;
tokenVersion: number;
}
describe("authCrypto 유틸리티", () => {
beforeEach(() => {
// 테스트 환경에서 사용할 JWT/암호화 키를 설정한다.
process.env.AUTH_JWT_SECRET = "test-jwt-secret-key";
// 32바이트 키를 hex 로 표현 (예시)
process.env.AUTH_ENCRYPTION_KEY = "0123456789abcdef0123456789abcdef";
});
describe("비밀번호 해시/검증", () => {
it("8자 이상 비밀번호를 bcrypt 해시로 안전하게 저장하고, 검증에 성공해야 한다", async () => {
const password = "securePass1";
const hash = await hashPassword(password);
expect(hash).toBeTypeOf("string");
expect(hash).not.toBe(password);
const ok = await verifyPassword(password, hash);
const fail = await verifyPassword("wrongPass", hash);
expect(ok).toBe(true);
expect(fail).toBe(false);
});
it("8자 미만 비밀번호는 해시 시도 시 오류를 발생시켜야 한다", async () => {
const shortPassword = "short"; // 5자
await expect(hashPassword(shortPassword)).rejects.toThrow();
});
});
describe("JWT 액세스 토큰", () => {
it("유저 정보를 기반으로 JWT 액세스 토큰을 발급하고, 검증 시 동일한 정보가 나와야 한다", async () => {
const user: TestUser = {
id: "user-1",
email: "user@example.com",
tokenVersion: 1,
};
const token = await signAccessToken(user);
expect(token).toBeTypeOf("string");
expect(token.length).toBeGreaterThan(10);
const payload = await verifyAccessToken(token);
expect(payload).not.toBeNull();
expect(payload?.sub).toBe(user.id);
expect(payload?.email).toBe(user.email);
expect(payload?.tokenVersion).toBe(user.tokenVersion);
const anyPayload = payload as any;
const iat = anyPayload.iat;
const exp = anyPayload.exp;
expect(typeof iat).toBe("number");
expect(typeof exp).toBe("number");
expect(exp > iat).toBe(true);
});
it("잘못된 토큰이나 시크릿으로 검증할 경우 null 을 반환해야 한다", async () => {
const user: TestUser = {
id: "user-2",
email: "user2@example.com",
tokenVersion: 3,
};
const token = await signAccessToken(user);
// 시크릿을 바꿔서 검증 실패 상황을 만든다.
process.env.AUTH_JWT_SECRET = "other-secret";
const payload = await verifyAccessToken(token);
expect(payload).toBeNull();
});
});
describe("민감정보 JSON 암호화/복호화", () => {
it("JSON 객체를 암호화한 뒤 복호화하면 원래 객체와 동일해야 한다", async () => {
const original = {
cardToken: "tok_123",
customerId: "cus_456",
meta: { plan: "pro", country: "KR" },
};
const ciphertext = await encryptJson(original);
expect(ciphertext).toBeTypeOf("string");
expect(ciphertext).not.toContain("tok_123");
expect(ciphertext).not.toContain("cus_456");
const decrypted = await decryptJson<typeof original>(ciphertext);
expect(decrypted).toEqual(original);
});
it("암호화 문자열이 손상되었거나 키가 잘못된 경우 복호화 시 오류를 발생시켜야 한다", async () => {
const original = { secret: "value" };
const ciphertext = await encryptJson(original);
// 중간 일부를 잘라 손상시킨다.
const broken = ciphertext.slice(0, Math.floor(ciphertext.length / 2));
await expect(decryptJson(broken as string)).rejects.toThrow();
// 키를 바꿔서 복호화 시도
process.env.AUTH_ENCRYPTION_KEY = "ffffffffffffffffffffffffffffffff";
await expect(decryptJson(ciphertext)).rejects.toThrow();
});
});
});