1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
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" });
});
});
|