import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; import { workflowApi } from "./workflow"; const mockFetch = vi.fn(); beforeEach(() => { vi.stubGlobal("fetch", mockFetch); }); afterEach(() => { vi.restoreAllMocks(); }); function okResponse(body: unknown) { return new Response(JSON.stringify(body), { status: 200, headers: { "Content-Type": "application/json" }, }); } function errorResponse(status: number, text: string) { return new Response(text, { status }); } describe("workflowApi", () => { describe("recordTests", () => { it("sends POST to /workflow/tests/record", async () => { mockFetch.mockResolvedValueOnce(okResponse(true)); const payload = { story_id: "story-29", unit: [{ name: "t1", status: "pass" as const }], integration: [], }; await workflowApi.recordTests(payload); expect(mockFetch).toHaveBeenCalledWith( "/api/workflow/tests/record", expect.objectContaining({ method: "POST" }), ); }); }); describe("getAcceptance", () => { it("sends POST and returns acceptance response", async () => { const response = { can_accept: true, reasons: [], warning: null, summary: { total: 2, passed: 2, failed: 0 }, missing_categories: [], }; mockFetch.mockResolvedValueOnce(okResponse(response)); const result = await workflowApi.getAcceptance({ story_id: "story-29", }); expect(result.can_accept).toBe(true); expect(result.summary.total).toBe(2); }); }); describe("getReviewQueueAll", () => { it("sends GET to /workflow/review/all", async () => { mockFetch.mockResolvedValueOnce(okResponse({ stories: [] })); const result = await workflowApi.getReviewQueueAll(); expect(mockFetch).toHaveBeenCalledWith( "/api/workflow/review/all", expect.objectContaining({}), ); expect(result.stories).toEqual([]); }); }); describe("ensureAcceptance", () => { it("returns true when acceptance passes", async () => { mockFetch.mockResolvedValueOnce(okResponse(true)); const result = await workflowApi.ensureAcceptance({ story_id: "story-29", }); expect(result).toBe(true); }); it("throws on error response", async () => { mockFetch.mockResolvedValueOnce( errorResponse(400, "Acceptance is blocked"), ); await expect( workflowApi.ensureAcceptance({ story_id: "story-29" }), ).rejects.toThrow("Acceptance is blocked"); }); }); describe("getUpcomingStories", () => { it("sends GET to /workflow/upcoming", async () => { const response = { stories: [ { story_id: "31_view_upcoming", name: "View Upcoming" }, { story_id: "32_worktree", name: null }, ], }; mockFetch.mockResolvedValueOnce(okResponse(response)); const result = await workflowApi.getUpcomingStories(); expect(mockFetch).toHaveBeenCalledWith( "/api/workflow/upcoming", expect.objectContaining({}), ); expect(result.stories).toHaveLength(2); expect(result.stories[0].name).toBe("View Upcoming"); expect(result.stories[1].name).toBeNull(); }); }); describe("getReviewQueue", () => { it("sends GET to /workflow/review", async () => { mockFetch.mockResolvedValueOnce( okResponse({ stories: [{ story_id: "s1", can_accept: true }] }), ); const result = await workflowApi.getReviewQueue(); expect(result.stories).toHaveLength(1); expect(result.stories[0].story_id).toBe("s1"); }); }); });