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(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(); }); }); });