diff --git a/handlers/learning-status.js b/handlers/learning-status.js index 083382a..f0f7f32 100644 --- a/handlers/learning-status.js +++ b/handlers/learning-status.js @@ -112,7 +112,10 @@ export async function postLearningStatus( } // 2. 이번 기수에서 사용자가 제출한 풀이 목록 조회 - const solvedProblems = await fetchCohortUserSolutions(repoOwner, repoName, username, appToken); + const cohortSolutions = await fetchCohortUserSolutions(repoOwner, repoName, username, appToken); + const solvedProblems = cohortSolutions.filter((problemName) => + Object.hasOwn(categories, problemName) + ); console.log( `[learningStatus] PR #${prNumber}: ${username} has ${solvedProblems.length} solutions in current cohort` ); diff --git a/handlers/learning-status.test.js b/handlers/learning-status.test.js new file mode 100644 index 0000000..55ff679 --- /dev/null +++ b/handlers/learning-status.test.js @@ -0,0 +1,94 @@ +import { describe, it, expect, vi, beforeEach, afterEach } from "vitest"; + +vi.mock("../utils/learningData.js", () => ({ + fetchProblemCategories: vi.fn(), + fetchCohortUserSolutions: vi.fn(), + fetchPRSubmissions: vi.fn(), +})); + +vi.mock("../utils/openai.js", () => ({ + generateApproachAnalysis: vi.fn(), +})); + +import { postLearningStatus } from "./learning-status.js"; +import { + fetchProblemCategories, + fetchCohortUserSolutions, + fetchPRSubmissions, +} from "../utils/learningData.js"; +import { generateApproachAnalysis } from "../utils/openai.js"; + +describe("postLearningStatus — 누적 풀이 집계", () => { + let originalFetch; + + beforeEach(() => { + vi.clearAllMocks(); + originalFetch = globalThis.fetch; + fetchProblemCategories.mockResolvedValue({ + "two-sum": { + difficulty: "Easy", + blindCategories: ["Array"], + intended_approach: "Hash Table", + }, + }); + fetchPRSubmissions.mockResolvedValue([ + { problemName: "two-sum", rawUrl: "https://example.com/solution.py" }, + ]); + generateApproachAnalysis.mockResolvedValue({ + matches: true, + explanation: "Uses a hash table", + }); + globalThis.fetch = vi + .fn() + .mockResolvedValueOnce({ + ok: true, + text: () => Promise.resolve("class Solution: pass"), + }) + .mockResolvedValueOnce({ + ok: true, + json: () => Promise.resolve([]), + }) + .mockResolvedValueOnce({ ok: true }); + }); + + afterEach(() => { + globalThis.fetch = originalFetch; + }); + + it.each([ + { + name: "등록된 문제의 풀이 수를 유지한다", + solved: ["two-sum"], + expected: 1, + }, + { + name: "미등록 문제가 섞여 있으면 등록된 문제만 집계한다", + solved: ["to-lower-case", "two-sum"], + expected: 1, + }, + { + name: "미등록 문제만 풀었으면 풀이 수는 0이다", + solved: ["to-lower-case"], + expected: 0, + }, + ])("$name", async ({ solved, expected }) => { + fetchCohortUserSolutions.mockResolvedValue(solved); + + await postLearningStatus( + "DaleStudy", + "leetcode-study", + 42, + "learner", + "token", + {} + ); + + const post = globalThis.fetch.mock.calls.find( + ([, options]) => options?.method === "POST" + ); + expect(post).toBeDefined(); + + const body = JSON.parse(post[1].body).body; + expect(body).toContain(`풀이한 문제: ${expected} / 1개`); + }); +});