import { beforeEach, describe, expect, it } from "vitest"; import type { DecodedIdToken } from "firebase-admin/auth"; import { firestore } from "../../src/firebase.js"; import { createMe, getMe } from "../../src/services/userService.js"; import { HttpError } from "../../src/middleware/errors.js"; const uid = "user-1"; function fakeToken(overrides: Partial = {}): DecodedIdToken { return { uid, email: "tester@example.com", picture: "https://cdn.example.com/p.png", firebase: { identities: {}, sign_in_provider: "google.com", }, aud: "test", auth_time: 0, exp: 0, iat: 0, iss: "test", sub: uid, ...overrides, } as DecodedIdToken; } const validBody = { displayName: "유저1", favoriteTeamCode: "LG", knowledgeLevel: "casual", }; describe("userService", () => { beforeEach(async () => { await firestore.recursiveDelete(firestore.collection("users")); }); describe("getMe", () => { it("미존재 유저는 404 + USER_NOT_FOUND", async () => { await expect(getMe(uid)).rejects.toMatchObject({ status: 404, code: "USER_NOT_FOUND", }); }); it("존재하는 유저를 반환한다", async () => { await createMe(fakeToken(), validBody); const u = await getMe(uid); expect(u.displayName).toBe("유저1"); expect(u.favoriteTeamCode).toBe("LG"); expect(u.knowledgeLevel).toBe("casual"); }); }); describe("createMe", () => { it("정상 생성 시 토큰의 email/photo/provider를 사용한다", async () => { const u = await createMe(fakeToken(), validBody); expect(u.email).toBe("tester@example.com"); expect(u.photoUrl).toBe("https://cdn.example.com/p.png"); expect(u.provider).toBe("google"); expect(u.favoriteTeamCode).toBe("LG"); expect(u.knowledgeLevel).toBe("casual"); }); it("apple.com 프로바이더 매핑", async () => { const t = fakeToken({ firebase: { identities: {}, sign_in_provider: "apple.com" }, }); const u = await createMe(t, validBody); expect(u.provider).toBe("apple"); }); it("중복 생성 시 409 + USER_ALREADY_EXISTS", async () => { await createMe(fakeToken(), validBody); await expect(createMe(fakeToken(), validBody)).rejects.toMatchObject({ status: 409, code: "USER_ALREADY_EXISTS", }); }); it.each([ ["빈 displayName", { ...validBody, displayName: "" }], ["11자 displayName", { ...validBody, displayName: "1234567890X" }], ["숫자 displayName", { ...validBody, displayName: 123 }], ["잘못된 팀코드", { ...validBody, favoriteTeamCode: "XX" }], ["잘못된 레벨", { ...validBody, knowledgeLevel: "master" }], ])("%s → 400 INVALID_INPUT", async (_label, body) => { await expect(createMe(fakeToken(), body as never)).rejects.toMatchObject({ status: 400, code: "INVALID_INPUT", }); }); it("지원하지 않는 provider → 400", async () => { const t = fakeToken({ firebase: { identities: {}, sign_in_provider: "password" }, }); await expect(createMe(t, validBody)).rejects.toBeInstanceOf(HttpError); }); }); });