summaryrefslogtreecommitdiffhomepage
path: root/src/features/workspaces/adapter/http.test.ts
diff options
context:
space:
mode:
Diffstat (limited to 'src/features/workspaces/adapter/http.test.ts')
-rw-r--r--src/features/workspaces/adapter/http.test.ts189
1 files changed, 189 insertions, 0 deletions
diff --git a/src/features/workspaces/adapter/http.test.ts b/src/features/workspaces/adapter/http.test.ts
new file mode 100644
index 0000000..18d8939
--- /dev/null
+++ b/src/features/workspaces/adapter/http.test.ts
@@ -0,0 +1,189 @@
+import { describe, expect, it, vi } from "vitest";
+import { createWorkspaceHttp } from "./http";
+
+/** Build a fake `fetch` returning a canned Response. */
+function fakeFetch(responses: Array<{ status?: number; body?: unknown } | Error>): typeof fetch {
+ let i = 0;
+ return vi.fn(async () => {
+ const next = responses[i++];
+ if (next === undefined) throw new Error("fakeFetch: no more canned responses");
+ if (next instanceof Error) throw next;
+ const status = next.status ?? 200;
+ const body = next.body;
+ return {
+ ok: status >= 200 && status < 300,
+ status,
+ async json() {
+ return body;
+ },
+ } as Response;
+ }) as unknown as typeof fetch;
+}
+
+const BASE = "http://x";
+
+describe("createWorkspaceHttp", () => {
+ it("list returns the workspaces", async () => {
+ const fetchImpl = fakeFetch([
+ {
+ body: {
+ workspaces: [
+ {
+ id: "a",
+ title: "A",
+ defaultCwd: null,
+ createdAt: 1,
+ lastActivityAt: 2,
+ conversationCount: 3,
+ },
+ ],
+ },
+ },
+ ]);
+ const http = createWorkspaceHttp(BASE, fetchImpl);
+ const list = await http.list();
+ expect(list).toHaveLength(1);
+ expect(list[0]?.id).toBe("a");
+ expect(list[0]?.conversationCount).toBe(3);
+ expect(fetchImpl).toHaveBeenCalledWith(`${BASE}/workspaces`);
+ });
+
+ it("list returns [] on a failed response (non-fatal)", async () => {
+ const http = createWorkspaceHttp(BASE, fakeFetch([{ status: 500 }]));
+ expect(await http.list()).toEqual([]);
+ });
+
+ it("list returns [] on a network error", async () => {
+ const http = createWorkspaceHttp(BASE, fakeFetch([new Error("network")]));
+ expect(await http.list()).toEqual([]);
+ });
+
+ it("ensure PUTs the id + returns the workspace", async () => {
+ const ws = { id: "my-ws", title: "my-ws", defaultCwd: null, createdAt: 10, lastActivityAt: 10 };
+ const fetchImpl = fakeFetch([{ body: ws }]);
+ const http = createWorkspaceHttp(BASE, fetchImpl);
+ const result = await http.ensure("my-ws");
+ expect(result).toEqual({ ok: true, value: ws });
+ expect(fetchImpl).toHaveBeenCalledWith(
+ `${BASE}/workspaces/my-ws`,
+ expect.objectContaining({ method: "PUT" }),
+ );
+ });
+
+ it("ensure surfaces the backend error on a 400 (invalid slug)", async () => {
+ const http = createWorkspaceHttp(
+ BASE,
+ fakeFetch([{ status: 400, body: { error: "invalid slug" } }]),
+ );
+ const result = await http.ensure("UPPER");
+ expect(result).toEqual({ ok: false, error: "invalid slug" });
+ });
+
+ it("get returns null on 404", async () => {
+ const http = createWorkspaceHttp(BASE, fakeFetch([{ status: 404 }]));
+ expect(await http.get("nope")).toBeNull();
+ });
+
+ it("get returns the workspace on 200", async () => {
+ const ws = { id: "x", title: "X", defaultCwd: "/home", createdAt: 1, lastActivityAt: 2 };
+ const http = createWorkspaceHttp(BASE, fakeFetch([{ body: ws }]));
+ expect(await http.get("x")).toEqual(ws);
+ });
+
+ it("setTitle PUTs the title", async () => {
+ const ws = { id: "a", title: "Renamed", defaultCwd: null, createdAt: 1, lastActivityAt: 2 };
+ const fetchImpl = fakeFetch([{ body: ws }]);
+ const http = createWorkspaceHttp(BASE, fetchImpl);
+ const result = await http.setTitle("a", "Renamed");
+ expect(result).toEqual({ ok: true, value: ws });
+ const call = (fetchImpl as unknown as ReturnType<typeof vi.fn>).mock.calls[0];
+ expect(call?.[0]).toBe(`${BASE}/workspaces/a/title`);
+ expect(JSON.parse(call?.[1]?.body)).toEqual({ title: "Renamed" });
+ });
+
+ it("setDefaultCwd PUTs null to clear", async () => {
+ const ws = { id: "a", title: "A", defaultCwd: null, createdAt: 1, lastActivityAt: 2 };
+ const fetchImpl = fakeFetch([{ body: ws }]);
+ const http = createWorkspaceHttp(BASE, fetchImpl);
+ await http.setDefaultCwd("a", null);
+ const call = (fetchImpl as unknown as ReturnType<typeof vi.fn>).mock.calls[0];
+ expect(call?.[0]).toBe(`${BASE}/workspaces/a/default-cwd`);
+ expect(JSON.parse(call?.[1]?.body)).toEqual({ defaultCwd: null });
+ });
+
+ it("delete returns closedCount", async () => {
+ const fetchImpl = fakeFetch([{ body: { workspaceId: "a", closedCount: 4 } }]);
+ const http = createWorkspaceHttp(BASE, fetchImpl);
+ const result = await http.delete("a");
+ expect(result).toEqual({ ok: true, value: { closedCount: 4 } });
+ expect(fetchImpl).toHaveBeenCalledWith(
+ `${BASE}/workspaces/a`,
+ expect.objectContaining({ method: "DELETE" }),
+ );
+ });
+
+ it("delete surfaces 409 for 'default'", async () => {
+ const http = createWorkspaceHttp(
+ BASE,
+ fakeFetch([{ status: 409, body: { error: "cannot delete default" } }]),
+ );
+ const result = await http.delete("default");
+ expect(result).toEqual({ ok: false, error: "cannot delete default" });
+ });
+
+ it("star PUTs /star with no body and returns the updated workspace", async () => {
+ const ws = {
+ id: "a",
+ title: "A",
+ defaultCwd: null,
+ defaultComputerId: null,
+ starred: true,
+ createdAt: 1,
+ lastActivityAt: 2,
+ };
+ const fetchImpl = fakeFetch([{ body: ws }]);
+ const http = createWorkspaceHttp(BASE, fetchImpl);
+ const result = await http.star("a");
+ expect(result).toEqual({ ok: true, value: ws });
+ const call = (fetchImpl as unknown as ReturnType<typeof vi.fn>).mock.calls[0];
+ expect(call?.[0]).toBe(`${BASE}/workspaces/a/star`);
+ expect(call?.[1]).toEqual({ method: "PUT" });
+ });
+
+ it("unstar DELETEs /star with no body and returns the updated workspace", async () => {
+ const ws = {
+ id: "a",
+ title: "A",
+ defaultCwd: null,
+ defaultComputerId: null,
+ starred: false,
+ createdAt: 1,
+ lastActivityAt: 2,
+ };
+ const fetchImpl = fakeFetch([{ body: ws }]);
+ const http = createWorkspaceHttp(BASE, fetchImpl);
+ const result = await http.unstar("a");
+ expect(result).toEqual({ ok: true, value: ws });
+ const call = (fetchImpl as unknown as ReturnType<typeof vi.fn>).mock.calls[0];
+ expect(call?.[0]).toBe(`${BASE}/workspaces/a/star`);
+ expect(call?.[1]).toEqual({ method: "DELETE" });
+ });
+
+ it("star surfaces a 400 for an invalid slug", async () => {
+ const http = createWorkspaceHttp(
+ BASE,
+ fakeFetch([{ status: 400, body: { error: "invalid slug" } }]),
+ );
+ const result = await http.star("UPPER");
+ expect(result).toEqual({ ok: false, error: "invalid slug" });
+ });
+
+ it("unstar surfaces the backend error on failure", async () => {
+ const http = createWorkspaceHttp(
+ BASE,
+ fakeFetch([{ status: 500, body: { error: "Failed to unstar workspace" } }]),
+ );
+ const result = await http.unstar("a");
+ expect(result).toEqual({ ok: false, error: "Failed to unstar workspace" });
+ });
+});