skyboard-cli 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +148 -0
- package/bin/sb.js +2 -0
- package/package.json +32 -0
- package/src/__tests__/auth-state.test.ts +111 -0
- package/src/__tests__/card-ref.test.ts +42 -0
- package/src/__tests__/column-match.test.ts +57 -0
- package/src/__tests__/commands.test.ts +326 -0
- package/src/__tests__/helpers.ts +154 -0
- package/src/__tests__/materialize.test.ts +257 -0
- package/src/__tests__/pds.test.ts +213 -0
- package/src/commands/add.ts +47 -0
- package/src/commands/boards.ts +64 -0
- package/src/commands/cards.ts +65 -0
- package/src/commands/cols.ts +51 -0
- package/src/commands/comment.ts +64 -0
- package/src/commands/edit.ts +89 -0
- package/src/commands/login.ts +19 -0
- package/src/commands/logout.ts +14 -0
- package/src/commands/mv.ts +84 -0
- package/src/commands/new.ts +80 -0
- package/src/commands/rm.ts +79 -0
- package/src/commands/show.ts +100 -0
- package/src/commands/status.ts +78 -0
- package/src/commands/use.ts +72 -0
- package/src/commands/whoami.ts +22 -0
- package/src/index.ts +143 -0
- package/src/lib/auth.ts +244 -0
- package/src/lib/card-ref.ts +32 -0
- package/src/lib/column-match.ts +45 -0
- package/src/lib/config.ts +182 -0
- package/src/lib/display.ts +112 -0
- package/src/lib/materialize.ts +138 -0
- package/src/lib/pds.ts +440 -0
- package/src/lib/permissions.ts +28 -0
- package/src/lib/schemas.ts +97 -0
- package/src/lib/tid.ts +22 -0
- package/src/lib/types.ts +144 -0
- package/tsconfig.json +15 -0
- package/vitest.config.ts +7 -0
|
@@ -0,0 +1,326 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach } from "vitest";
|
|
2
|
+
import type { BoardData } from "../lib/pds.js";
|
|
3
|
+
import {
|
|
4
|
+
OWNER_DID,
|
|
5
|
+
USER_DID,
|
|
6
|
+
BOARD_RKEY,
|
|
7
|
+
BOARD_URI,
|
|
8
|
+
TASK_RKEY_1,
|
|
9
|
+
TASK_URI_1,
|
|
10
|
+
COLUMNS,
|
|
11
|
+
makeBoard,
|
|
12
|
+
makeMaterializedTask,
|
|
13
|
+
makeComment,
|
|
14
|
+
} from "./helpers.js";
|
|
15
|
+
|
|
16
|
+
// Mock modules before importing commands
|
|
17
|
+
vi.mock("../lib/auth.js", () => ({
|
|
18
|
+
requireAgent: vi.fn(),
|
|
19
|
+
}));
|
|
20
|
+
|
|
21
|
+
vi.mock("../lib/config.js", () => ({
|
|
22
|
+
getDefaultBoard: vi.fn(),
|
|
23
|
+
loadConfig: vi.fn(() => ({ knownBoards: [] })),
|
|
24
|
+
saveConfig: vi.fn(),
|
|
25
|
+
}));
|
|
26
|
+
|
|
27
|
+
vi.mock("../lib/pds.js", () => ({
|
|
28
|
+
fetchBoardData: vi.fn(),
|
|
29
|
+
fetchBoard: vi.fn(),
|
|
30
|
+
}));
|
|
31
|
+
|
|
32
|
+
// Mock generateTID to return a predictable value
|
|
33
|
+
vi.mock("../lib/tid.js", async (importOriginal) => {
|
|
34
|
+
const orig = await importOriginal<typeof import("../lib/tid.js")>();
|
|
35
|
+
return {
|
|
36
|
+
...orig,
|
|
37
|
+
generateTID: vi.fn(() => "3juitestid0000000"),
|
|
38
|
+
};
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
import { requireAgent } from "../lib/auth.js";
|
|
42
|
+
import { getDefaultBoard } from "../lib/config.js";
|
|
43
|
+
import { fetchBoardData } from "../lib/pds.js";
|
|
44
|
+
import { newCommand } from "../commands/new.js";
|
|
45
|
+
import { mvCommand } from "../commands/mv.js";
|
|
46
|
+
import { editCommand } from "../commands/edit.js";
|
|
47
|
+
import { rmCommand } from "../commands/rm.js";
|
|
48
|
+
import { showCommand } from "../commands/show.js";
|
|
49
|
+
import { cardsCommand } from "../commands/cards.js";
|
|
50
|
+
|
|
51
|
+
const mockPutRecord = vi.fn(async (_input: any) => ({ uri: "at://fake", cid: "fakecid" }));
|
|
52
|
+
const mockDeleteRecord = vi.fn(async (_input: any) => ({}));
|
|
53
|
+
const mockAgent = {
|
|
54
|
+
com: {
|
|
55
|
+
atproto: {
|
|
56
|
+
repo: {
|
|
57
|
+
putRecord: mockPutRecord,
|
|
58
|
+
deleteRecord: mockDeleteRecord,
|
|
59
|
+
listRecords: vi.fn(async () => ({ data: { records: [] } })),
|
|
60
|
+
},
|
|
61
|
+
},
|
|
62
|
+
},
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
function setupMocks(taskOverrides?: Parameters<typeof makeMaterializedTask>[0]) {
|
|
66
|
+
vi.mocked(requireAgent).mockResolvedValue({
|
|
67
|
+
agent: mockAgent as any,
|
|
68
|
+
did: OWNER_DID,
|
|
69
|
+
handle: "owner.test",
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
vi.mocked(getDefaultBoard).mockReturnValue({
|
|
73
|
+
did: OWNER_DID,
|
|
74
|
+
rkey: BOARD_RKEY,
|
|
75
|
+
name: "Test Board",
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
const task = makeMaterializedTask(
|
|
79
|
+
{ rkey: TASK_RKEY_1, did: OWNER_DID, title: "Test Task", ...taskOverrides },
|
|
80
|
+
{ effectiveTitle: taskOverrides?.title ?? "Test Task" },
|
|
81
|
+
);
|
|
82
|
+
|
|
83
|
+
const boardData: BoardData = {
|
|
84
|
+
board: makeBoard(),
|
|
85
|
+
tasks: [task],
|
|
86
|
+
trusts: [],
|
|
87
|
+
comments: [],
|
|
88
|
+
allParticipants: [OWNER_DID],
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
vi.mocked(fetchBoardData).mockResolvedValue(boardData);
|
|
92
|
+
|
|
93
|
+
return { task, boardData };
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
beforeEach(() => {
|
|
97
|
+
vi.clearAllMocks();
|
|
98
|
+
// Suppress console output during tests
|
|
99
|
+
vi.spyOn(console, "log").mockImplementation(() => {});
|
|
100
|
+
vi.spyOn(console, "error").mockImplementation(() => {});
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
describe("newCommand", () => {
|
|
104
|
+
it("creates a task record via putRecord", async () => {
|
|
105
|
+
setupMocks();
|
|
106
|
+
|
|
107
|
+
await newCommand("My New Task", { json: true });
|
|
108
|
+
|
|
109
|
+
expect(mockPutRecord).toHaveBeenCalledOnce();
|
|
110
|
+
const call = mockPutRecord.mock.calls[0][0];
|
|
111
|
+
expect(call.repo).toBe(OWNER_DID);
|
|
112
|
+
expect(call.collection).toBe("dev.skyboard.task");
|
|
113
|
+
expect(call.record.$type).toBe("dev.skyboard.task");
|
|
114
|
+
expect(call.record.title).toBe("My New Task");
|
|
115
|
+
expect(call.record.columnId).toBe("col-todo"); // first column
|
|
116
|
+
expect(call.record.position).toBeDefined();
|
|
117
|
+
expect(call.record.boardUri).toContain(BOARD_RKEY);
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
it("creates task in specified column", async () => {
|
|
121
|
+
setupMocks();
|
|
122
|
+
|
|
123
|
+
await newCommand("Task in Done", { column: "Done", json: true });
|
|
124
|
+
|
|
125
|
+
const call = mockPutRecord.mock.calls[0][0];
|
|
126
|
+
expect(call.record.columnId).toBe("col-done");
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
it("includes description when provided", async () => {
|
|
130
|
+
setupMocks();
|
|
131
|
+
|
|
132
|
+
await newCommand("With Desc", { description: "A description", json: true });
|
|
133
|
+
|
|
134
|
+
const call = mockPutRecord.mock.calls[0][0];
|
|
135
|
+
expect(call.record.description).toBe("A description");
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
it("outputs JSON when --json flag is set", async () => {
|
|
139
|
+
setupMocks();
|
|
140
|
+
|
|
141
|
+
await newCommand("JSON Task", { json: true });
|
|
142
|
+
|
|
143
|
+
expect(console.log).toHaveBeenCalled();
|
|
144
|
+
const output = vi.mocked(console.log).mock.calls[0][0];
|
|
145
|
+
const parsed = JSON.parse(output);
|
|
146
|
+
expect(parsed.title).toBe("JSON Task");
|
|
147
|
+
expect(parsed.column).toBe("To Do");
|
|
148
|
+
});
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
describe("mvCommand", () => {
|
|
152
|
+
it("creates an op record with columnId and position", async () => {
|
|
153
|
+
setupMocks();
|
|
154
|
+
|
|
155
|
+
await mvCommand(TASK_RKEY_1, "Done", { json: true });
|
|
156
|
+
|
|
157
|
+
expect(mockPutRecord).toHaveBeenCalledOnce();
|
|
158
|
+
const call = mockPutRecord.mock.calls[0][0];
|
|
159
|
+
expect(call.repo).toBe(OWNER_DID);
|
|
160
|
+
expect(call.collection).toBe("dev.skyboard.op");
|
|
161
|
+
expect(call.record.$type).toBe("dev.skyboard.op");
|
|
162
|
+
expect(call.record.fields.columnId).toBe("col-done");
|
|
163
|
+
expect(call.record.fields.position).toBeDefined();
|
|
164
|
+
expect(call.record.targetTaskUri).toContain(TASK_RKEY_1);
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
it("outputs JSON with rkey and column name", async () => {
|
|
168
|
+
setupMocks();
|
|
169
|
+
|
|
170
|
+
await mvCommand(TASK_RKEY_1, "In Progress", { json: true });
|
|
171
|
+
|
|
172
|
+
const output = vi.mocked(console.log).mock.calls[0][0];
|
|
173
|
+
const parsed = JSON.parse(output);
|
|
174
|
+
expect(parsed.rkey).toBe(TASK_RKEY_1);
|
|
175
|
+
expect(parsed.column).toBe("In Progress");
|
|
176
|
+
});
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
describe("editCommand", () => {
|
|
180
|
+
it("creates an op with title field", async () => {
|
|
181
|
+
setupMocks();
|
|
182
|
+
|
|
183
|
+
await editCommand(TASK_RKEY_1, { title: "New Title", json: true });
|
|
184
|
+
|
|
185
|
+
expect(mockPutRecord).toHaveBeenCalledOnce();
|
|
186
|
+
const call = mockPutRecord.mock.calls[0][0];
|
|
187
|
+
expect(call.collection).toBe("dev.skyboard.op");
|
|
188
|
+
expect(call.record.fields.title).toBe("New Title");
|
|
189
|
+
expect(call.record.targetTaskUri).toContain(TASK_RKEY_1);
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
it("creates an op with description field", async () => {
|
|
193
|
+
setupMocks();
|
|
194
|
+
|
|
195
|
+
await editCommand(TASK_RKEY_1, { description: "New Desc", json: true });
|
|
196
|
+
|
|
197
|
+
const call = mockPutRecord.mock.calls[0][0];
|
|
198
|
+
expect(call.record.fields.description).toBe("New Desc");
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
it("resolves label names to IDs", async () => {
|
|
202
|
+
setupMocks();
|
|
203
|
+
|
|
204
|
+
await editCommand(TASK_RKEY_1, { label: ["bug"], json: true });
|
|
205
|
+
|
|
206
|
+
const call = mockPutRecord.mock.calls[0][0];
|
|
207
|
+
expect(call.record.fields.labelIds).toEqual(["lbl-bug"]);
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
it("exits when no fields provided", async () => {
|
|
211
|
+
setupMocks();
|
|
212
|
+
|
|
213
|
+
const exitSpy = vi.spyOn(process, "exit").mockImplementation((() => {
|
|
214
|
+
throw new Error("process.exit");
|
|
215
|
+
}) as any);
|
|
216
|
+
|
|
217
|
+
await expect(editCommand(TASK_RKEY_1, {})).rejects.toThrow("process.exit");
|
|
218
|
+
expect(mockPutRecord).not.toHaveBeenCalled();
|
|
219
|
+
|
|
220
|
+
exitSpy.mockRestore();
|
|
221
|
+
});
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
describe("rmCommand", () => {
|
|
225
|
+
it("deletes own task via deleteRecord with --force", async () => {
|
|
226
|
+
setupMocks();
|
|
227
|
+
|
|
228
|
+
await rmCommand(TASK_RKEY_1, { force: true, json: true });
|
|
229
|
+
|
|
230
|
+
expect(mockDeleteRecord).toHaveBeenCalledOnce();
|
|
231
|
+
const call = mockDeleteRecord.mock.calls[0][0];
|
|
232
|
+
expect(call.repo).toBe(OWNER_DID);
|
|
233
|
+
expect(call.collection).toBe("dev.skyboard.task");
|
|
234
|
+
expect(call.rkey).toBe(TASK_RKEY_1);
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
it("rejects deletion of task owned by another user", async () => {
|
|
238
|
+
// Task owned by USER_DID, but we're logged in as OWNER_DID
|
|
239
|
+
setupMocks({ did: USER_DID });
|
|
240
|
+
|
|
241
|
+
const exitSpy = vi.spyOn(process, "exit").mockImplementation((() => {
|
|
242
|
+
throw new Error("process.exit");
|
|
243
|
+
}) as any);
|
|
244
|
+
|
|
245
|
+
await expect(
|
|
246
|
+
rmCommand(TASK_RKEY_1, { force: true, json: true }),
|
|
247
|
+
).rejects.toThrow("process.exit");
|
|
248
|
+
|
|
249
|
+
expect(mockDeleteRecord).not.toHaveBeenCalled();
|
|
250
|
+
expect(console.error).toHaveBeenCalled();
|
|
251
|
+
|
|
252
|
+
exitSpy.mockRestore();
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
it("outputs JSON with deleted rkey", async () => {
|
|
256
|
+
setupMocks();
|
|
257
|
+
|
|
258
|
+
await rmCommand(TASK_RKEY_1, { force: true, json: true });
|
|
259
|
+
|
|
260
|
+
const output = vi.mocked(console.log).mock.calls[0][0];
|
|
261
|
+
const parsed = JSON.parse(output);
|
|
262
|
+
expect(parsed.deleted).toBe(TASK_RKEY_1);
|
|
263
|
+
});
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
describe("showCommand", () => {
|
|
267
|
+
it("displays card details in JSON mode", async () => {
|
|
268
|
+
const { boardData } = setupMocks();
|
|
269
|
+
|
|
270
|
+
await showCommand(TASK_RKEY_1, { json: true });
|
|
271
|
+
|
|
272
|
+
expect(console.log).toHaveBeenCalled();
|
|
273
|
+
const output = vi.mocked(console.log).mock.calls[0][0];
|
|
274
|
+
const parsed = JSON.parse(output);
|
|
275
|
+
expect(parsed.rkey).toBe(TASK_RKEY_1);
|
|
276
|
+
expect(parsed.title).toBe("Test Task");
|
|
277
|
+
expect(parsed.column).toBe("To Do");
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
it("includes comments in JSON output", async () => {
|
|
281
|
+
const { boardData } = setupMocks();
|
|
282
|
+
const comment = makeComment({
|
|
283
|
+
targetTaskUri: `at://${OWNER_DID}/dev.skyboard.task/${TASK_RKEY_1}`,
|
|
284
|
+
text: "Hello!",
|
|
285
|
+
});
|
|
286
|
+
boardData.comments = [comment];
|
|
287
|
+
vi.mocked(fetchBoardData).mockResolvedValue(boardData);
|
|
288
|
+
|
|
289
|
+
await showCommand(TASK_RKEY_1, { json: true });
|
|
290
|
+
|
|
291
|
+
const output = vi.mocked(console.log).mock.calls[0][0];
|
|
292
|
+
const parsed = JSON.parse(output);
|
|
293
|
+
expect(parsed.comments).toHaveLength(1);
|
|
294
|
+
expect(parsed.comments[0].text).toBe("Hello!");
|
|
295
|
+
});
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
describe("cardsCommand", () => {
|
|
299
|
+
it("outputs JSON with columns and cards", async () => {
|
|
300
|
+
setupMocks();
|
|
301
|
+
|
|
302
|
+
await cardsCommand({ json: true });
|
|
303
|
+
|
|
304
|
+
expect(console.log).toHaveBeenCalled();
|
|
305
|
+
const output = vi.mocked(console.log).mock.calls[0][0];
|
|
306
|
+
const parsed = JSON.parse(output);
|
|
307
|
+
expect(parsed).toBeInstanceOf(Array);
|
|
308
|
+
expect(parsed).toHaveLength(3); // 3 columns
|
|
309
|
+
expect(parsed[0].column).toBe("To Do");
|
|
310
|
+
expect(parsed[0].cards).toHaveLength(1);
|
|
311
|
+
expect(parsed[0].cards[0].title).toBe("Test Task");
|
|
312
|
+
});
|
|
313
|
+
|
|
314
|
+
it("includes all columns even if empty", async () => {
|
|
315
|
+
setupMocks();
|
|
316
|
+
|
|
317
|
+
await cardsCommand({ json: true });
|
|
318
|
+
|
|
319
|
+
const output = vi.mocked(console.log).mock.calls[0][0];
|
|
320
|
+
const parsed = JSON.parse(output);
|
|
321
|
+
expect(parsed[1].column).toBe("In Progress");
|
|
322
|
+
expect(parsed[1].cards).toHaveLength(0);
|
|
323
|
+
expect(parsed[2].column).toBe("Done");
|
|
324
|
+
expect(parsed[2].cards).toHaveLength(0);
|
|
325
|
+
});
|
|
326
|
+
});
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import type { Board, Task, Op, Trust, Comment, Column, MaterializedTask } from "../lib/types.js";
|
|
2
|
+
|
|
3
|
+
// Fake DIDs
|
|
4
|
+
export const OWNER_DID = "did:plc:owner111111111111";
|
|
5
|
+
export const TRUSTED_DID = "did:plc:trusted22222222222";
|
|
6
|
+
export const USER_DID = "did:plc:user3333333333333";
|
|
7
|
+
export const UNTRUSTED_DID = "did:plc:untrusted44444444";
|
|
8
|
+
|
|
9
|
+
// Fake rkeys (TID-like strings)
|
|
10
|
+
export const BOARD_RKEY = "3jui7a2zbbb11";
|
|
11
|
+
export const TASK_RKEY_1 = "3jui7a2zbbb22";
|
|
12
|
+
export const TASK_RKEY_2 = "3jui7a2zbbb33";
|
|
13
|
+
export const TASK_RKEY_3 = "3jui7a2zbcc44";
|
|
14
|
+
export const OP_RKEY_1 = "3jui7a2zbdd55";
|
|
15
|
+
export const OP_RKEY_2 = "3jui7a2zbdd66";
|
|
16
|
+
|
|
17
|
+
export const BOARD_URI = `at://${OWNER_DID}/dev.skyboard.board/${BOARD_RKEY}`;
|
|
18
|
+
export const TASK_URI_1 = `at://${OWNER_DID}/dev.skyboard.task/${TASK_RKEY_1}`;
|
|
19
|
+
export const TASK_URI_2 = `at://${OWNER_DID}/dev.skyboard.task/${TASK_RKEY_2}`;
|
|
20
|
+
export const TASK_URI_3 = `at://${USER_DID}/dev.skyboard.task/${TASK_RKEY_3}`;
|
|
21
|
+
|
|
22
|
+
export const PDS_ENDPOINT = "https://pds.example.com";
|
|
23
|
+
|
|
24
|
+
export const COLUMNS: Column[] = [
|
|
25
|
+
{ id: "col-todo", name: "To Do", order: 0 },
|
|
26
|
+
{ id: "col-doing", name: "In Progress", order: 1 },
|
|
27
|
+
{ id: "col-done", name: "Done", order: 2 },
|
|
28
|
+
];
|
|
29
|
+
|
|
30
|
+
export function makeBoard(overrides?: Partial<Board>): Board {
|
|
31
|
+
return {
|
|
32
|
+
rkey: BOARD_RKEY,
|
|
33
|
+
did: OWNER_DID,
|
|
34
|
+
name: "Test Board",
|
|
35
|
+
description: "A board for testing",
|
|
36
|
+
columns: COLUMNS,
|
|
37
|
+
labels: [
|
|
38
|
+
{ id: "lbl-bug", name: "bug", color: "#ff0000" },
|
|
39
|
+
{ id: "lbl-feat", name: "feature", color: "#00ff00" },
|
|
40
|
+
],
|
|
41
|
+
open: false,
|
|
42
|
+
createdAt: "2025-01-01T00:00:00.000Z",
|
|
43
|
+
...overrides,
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function makeTask(overrides?: Partial<Task>): Task {
|
|
48
|
+
return {
|
|
49
|
+
rkey: TASK_RKEY_1,
|
|
50
|
+
did: OWNER_DID,
|
|
51
|
+
title: "Test Task",
|
|
52
|
+
description: "A task for testing",
|
|
53
|
+
columnId: "col-todo",
|
|
54
|
+
boardUri: BOARD_URI,
|
|
55
|
+
position: "a0",
|
|
56
|
+
createdAt: "2025-01-01T12:00:00.000Z",
|
|
57
|
+
...overrides,
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export function makeOp(overrides?: Partial<Op>): Op {
|
|
62
|
+
return {
|
|
63
|
+
rkey: OP_RKEY_1,
|
|
64
|
+
did: OWNER_DID,
|
|
65
|
+
targetTaskUri: TASK_URI_1,
|
|
66
|
+
boardUri: BOARD_URI,
|
|
67
|
+
fields: {},
|
|
68
|
+
createdAt: "2025-01-02T00:00:00.000Z",
|
|
69
|
+
...overrides,
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export function makeTrust(overrides?: Partial<Trust>): Trust {
|
|
74
|
+
return {
|
|
75
|
+
rkey: "3jui7trust1111111",
|
|
76
|
+
did: OWNER_DID,
|
|
77
|
+
trustedDid: TRUSTED_DID,
|
|
78
|
+
boardUri: BOARD_URI,
|
|
79
|
+
createdAt: "2025-01-01T00:00:00.000Z",
|
|
80
|
+
...overrides,
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export function makeComment(overrides?: Partial<Comment>): Comment {
|
|
85
|
+
return {
|
|
86
|
+
rkey: "3jui7comment11111",
|
|
87
|
+
did: USER_DID,
|
|
88
|
+
targetTaskUri: TASK_URI_1,
|
|
89
|
+
boardUri: BOARD_URI,
|
|
90
|
+
text: "A test comment",
|
|
91
|
+
createdAt: "2025-01-03T00:00:00.000Z",
|
|
92
|
+
...overrides,
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Wraps a task into a MaterializedTask with no ops applied.
|
|
98
|
+
*/
|
|
99
|
+
export function makeMaterializedTask(
|
|
100
|
+
taskOverrides?: Partial<Task>,
|
|
101
|
+
matOverrides?: Partial<MaterializedTask>,
|
|
102
|
+
): MaterializedTask {
|
|
103
|
+
const task = makeTask(taskOverrides);
|
|
104
|
+
return {
|
|
105
|
+
rkey: task.rkey,
|
|
106
|
+
did: task.did,
|
|
107
|
+
title: task.title,
|
|
108
|
+
description: task.description,
|
|
109
|
+
columnId: task.columnId,
|
|
110
|
+
boardUri: task.boardUri,
|
|
111
|
+
position: task.position,
|
|
112
|
+
order: task.order,
|
|
113
|
+
createdAt: task.createdAt,
|
|
114
|
+
updatedAt: task.updatedAt,
|
|
115
|
+
sourceTask: task,
|
|
116
|
+
appliedOps: [],
|
|
117
|
+
pendingOps: [],
|
|
118
|
+
effectiveTitle: task.title,
|
|
119
|
+
effectiveDescription: task.description,
|
|
120
|
+
effectiveColumnId: task.columnId,
|
|
121
|
+
effectivePosition: task.position ?? "a0",
|
|
122
|
+
effectiveLabelIds: task.labelIds ?? [],
|
|
123
|
+
ownerDid: task.did,
|
|
124
|
+
lastModifiedBy: task.did,
|
|
125
|
+
lastModifiedAt: task.createdAt,
|
|
126
|
+
...matOverrides,
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Build a fake DID document response for resolvePDS.
|
|
132
|
+
*/
|
|
133
|
+
export function makePLCResponse(did: string, pdsEndpoint: string) {
|
|
134
|
+
return {
|
|
135
|
+
id: did,
|
|
136
|
+
service: [
|
|
137
|
+
{
|
|
138
|
+
id: "#atproto_pds",
|
|
139
|
+
type: "AtprotoPersonalDataServer",
|
|
140
|
+
serviceEndpoint: pdsEndpoint,
|
|
141
|
+
},
|
|
142
|
+
],
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Build a listRecords response.
|
|
148
|
+
*/
|
|
149
|
+
export function makeListRecordsResponse(
|
|
150
|
+
records: Array<{ uri: string; value: Record<string, unknown> }>,
|
|
151
|
+
cursor?: string,
|
|
152
|
+
) {
|
|
153
|
+
return { records, cursor };
|
|
154
|
+
}
|