import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; import { settingsApi } from "./settings"; 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("settingsApi", () => { describe("getEditorCommand", () => { it("sends GET to /settings/editor and returns editor settings", async () => { const expected = { editor_command: "zed" }; mockFetch.mockResolvedValueOnce(okResponse(expected)); const result = await settingsApi.getEditorCommand(); expect(mockFetch).toHaveBeenCalledWith( "/api/settings/editor", expect.objectContaining({ headers: expect.objectContaining({ "Content-Type": "application/json", }), }), ); expect(result).toEqual(expected); }); it("returns null editor_command when not configured", async () => { const expected = { editor_command: null }; mockFetch.mockResolvedValueOnce(okResponse(expected)); const result = await settingsApi.getEditorCommand(); expect(result.editor_command).toBeNull(); }); it("uses custom baseUrl when provided", async () => { mockFetch.mockResolvedValueOnce(okResponse({ editor_command: "code" })); await settingsApi.getEditorCommand("http://localhost:4000/api"); expect(mockFetch).toHaveBeenCalledWith( "http://localhost:4000/api/settings/editor", expect.anything(), ); }); }); describe("setEditorCommand", () => { it("sends PUT to /settings/editor with command body", async () => { const expected = { editor_command: "zed" }; mockFetch.mockResolvedValueOnce(okResponse(expected)); const result = await settingsApi.setEditorCommand("zed"); expect(mockFetch).toHaveBeenCalledWith( "/api/settings/editor", expect.objectContaining({ method: "PUT", body: JSON.stringify({ editor_command: "zed" }), }), ); expect(result).toEqual(expected); }); it("sends PUT with null to clear the editor command", async () => { const expected = { editor_command: null }; mockFetch.mockResolvedValueOnce(okResponse(expected)); const result = await settingsApi.setEditorCommand(null); expect(mockFetch).toHaveBeenCalledWith( "/api/settings/editor", expect.objectContaining({ method: "PUT", body: JSON.stringify({ editor_command: null }), }), ); expect(result.editor_command).toBeNull(); }); it("uses custom baseUrl when provided", async () => { mockFetch.mockResolvedValueOnce(okResponse({ editor_command: "vim" })); await settingsApi.setEditorCommand("vim", "http://localhost:4000/api"); expect(mockFetch).toHaveBeenCalledWith( "http://localhost:4000/api/settings/editor", expect.objectContaining({ method: "PUT" }), ); }); }); describe("error handling", () => { it("throws with response body text on non-ok response", async () => { mockFetch.mockResolvedValueOnce(errorResponse(400, "Bad Request")); await expect(settingsApi.getEditorCommand()).rejects.toThrow( "Bad Request", ); }); it("throws with status code message when response body is empty", async () => { mockFetch.mockResolvedValueOnce(errorResponse(500, "")); await expect(settingsApi.getEditorCommand()).rejects.toThrow( "Request failed (500)", ); }); it("throws on setEditorCommand error", async () => { mockFetch.mockResolvedValueOnce(errorResponse(403, "Forbidden")); await expect(settingsApi.setEditorCommand("code")).rejects.toThrow( "Forbidden", ); }); }); });