206 lines
5.7 KiB
TypeScript
206 lines
5.7 KiB
TypeScript
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
import type { ProjectSettings } from "./settings";
|
|
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 });
|
|
}
|
|
|
|
const defaultProjectSettings: ProjectSettings = {
|
|
default_qa: "server",
|
|
default_coder_model: null,
|
|
max_coders: null,
|
|
max_retries: 2,
|
|
base_branch: null,
|
|
rate_limit_notifications: true,
|
|
timezone: null,
|
|
rendezvous: null,
|
|
watcher_sweep_interval_secs: 60,
|
|
watcher_done_retention_secs: 14400,
|
|
};
|
|
|
|
describe("settingsApi", () => {
|
|
describe("getProjectSettings", () => {
|
|
it("sends GET to /settings and returns project settings", async () => {
|
|
mockFetch.mockResolvedValueOnce(okResponse(defaultProjectSettings));
|
|
|
|
const result = await settingsApi.getProjectSettings();
|
|
|
|
expect(mockFetch).toHaveBeenCalledWith(
|
|
"/api/settings",
|
|
expect.objectContaining({
|
|
headers: expect.objectContaining({
|
|
"Content-Type": "application/json",
|
|
}),
|
|
}),
|
|
);
|
|
expect(result).toEqual(defaultProjectSettings);
|
|
});
|
|
|
|
it("uses custom baseUrl when provided", async () => {
|
|
mockFetch.mockResolvedValueOnce(okResponse(defaultProjectSettings));
|
|
await settingsApi.getProjectSettings("http://localhost:4000/api");
|
|
expect(mockFetch).toHaveBeenCalledWith(
|
|
"http://localhost:4000/api/settings",
|
|
expect.anything(),
|
|
);
|
|
});
|
|
});
|
|
|
|
describe("putProjectSettings", () => {
|
|
it("sends PUT to /settings with settings body", async () => {
|
|
const updated = { ...defaultProjectSettings, default_qa: "agent" };
|
|
mockFetch.mockResolvedValueOnce(okResponse(updated));
|
|
|
|
const result = await settingsApi.putProjectSettings(updated);
|
|
|
|
expect(mockFetch).toHaveBeenCalledWith(
|
|
"/api/settings",
|
|
expect.objectContaining({
|
|
method: "PUT",
|
|
body: JSON.stringify(updated),
|
|
}),
|
|
);
|
|
expect(result.default_qa).toBe("agent");
|
|
});
|
|
|
|
it("throws on validation error", async () => {
|
|
mockFetch.mockResolvedValueOnce(
|
|
errorResponse(400, "Invalid default_qa value"),
|
|
);
|
|
await expect(
|
|
settingsApi.putProjectSettings({
|
|
...defaultProjectSettings,
|
|
default_qa: "invalid",
|
|
}),
|
|
).rejects.toThrow("Invalid default_qa value");
|
|
});
|
|
});
|
|
|
|
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",
|
|
);
|
|
});
|
|
});
|
|
});
|