@aaronshaf/plane 0.1.3 → 0.1.5

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.
@@ -1,291 +1,363 @@
1
- import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, it } from "bun:test"
2
- import { Effect } from "effect"
3
- import { http, HttpResponse } from "msw"
4
- import { setupServer } from "msw/node"
5
- import { _clearProjectCache } from "@/resolve"
1
+ import {
2
+ afterAll,
3
+ afterEach,
4
+ beforeAll,
5
+ beforeEach,
6
+ describe,
7
+ expect,
8
+ it,
9
+ } from "bun:test";
10
+ import { Effect } from "effect";
11
+ import { http, HttpResponse } from "msw";
12
+ import { setupServer } from "msw/node";
13
+ import { _clearProjectCache } from "@/resolve";
6
14
 
7
- const BASE = "http://cw-test.local"
8
- const WS = "testws"
15
+ const BASE = "http://cw-test.local";
16
+ const WS = "testws";
9
17
 
10
- const PROJECTS = [{ id: "proj-acme", identifier: "ACME", name: "Acme Project" }]
11
- const ISSUES = [{ id: "i1", sequence_id: 29, name: "Migrate Button", priority: "high", state: "s1" }]
18
+ const PROJECTS = [
19
+ { id: "proj-acme", identifier: "ACME", name: "Acme Project" },
20
+ ];
21
+ const ISSUES = [
22
+ {
23
+ id: "i1",
24
+ sequence_id: 29,
25
+ name: "Migrate Button",
26
+ priority: "high",
27
+ state: "s1",
28
+ },
29
+ ];
12
30
  const COMMENTS = [
13
- {
14
- id: "c1",
15
- comment_html: "<p>Fixed in v2</p>",
16
- actor_detail: { display_name: "Aaron" },
17
- created_at: "2025-01-15T10:30:00Z",
18
- },
19
- {
20
- id: "c2",
21
- comment_html: "<p>LGTM</p>",
22
- actor_detail: { display_name: "Bea" },
23
- created_at: "2025-01-16T09:00:00Z",
24
- },
25
- ]
31
+ {
32
+ id: "c1",
33
+ comment_html: "<p>Fixed in v2</p>",
34
+ actor_detail: { display_name: "Aaron" },
35
+ created_at: "2025-01-15T10:30:00Z",
36
+ },
37
+ {
38
+ id: "c2",
39
+ comment_html: "<p>LGTM</p>",
40
+ actor_detail: { display_name: "Bea" },
41
+ created_at: "2025-01-16T09:00:00Z",
42
+ },
43
+ ];
26
44
  const WORKLOGS = [
27
- {
28
- id: "w1",
29
- description: "Code review",
30
- duration: 90,
31
- logged_by_detail: { display_name: "Aaron" },
32
- created_at: "2025-01-15T10:00:00Z",
33
- },
34
- ]
45
+ {
46
+ id: "w1",
47
+ description: "Code review",
48
+ duration: 90,
49
+ logged_by_detail: { display_name: "Aaron" },
50
+ created_at: "2025-01-15T10:00:00Z",
51
+ },
52
+ ];
35
53
 
36
54
  const server = setupServer(
37
- http.get(`${BASE}/api/v1/workspaces/${WS}/projects/`, () =>
38
- HttpResponse.json({ results: PROJECTS }),
39
- ),
40
- http.get(`${BASE}/api/v1/workspaces/${WS}/projects/proj-acme/issues/`, () =>
41
- HttpResponse.json({ results: ISSUES }),
42
- ),
43
- http.get(`${BASE}/api/v1/workspaces/${WS}/projects/proj-acme/issues/i1/comments/`, () =>
44
- HttpResponse.json({ results: COMMENTS }),
45
- ),
46
- http.get(`${BASE}/api/v1/workspaces/${WS}/projects/proj-acme/issues/i1/worklogs/`, () =>
47
- HttpResponse.json({ results: WORKLOGS }),
48
- ),
49
- )
55
+ http.get(`${BASE}/api/v1/workspaces/${WS}/projects/`, () =>
56
+ HttpResponse.json({ results: PROJECTS }),
57
+ ),
58
+ http.get(`${BASE}/api/v1/workspaces/${WS}/projects/proj-acme/issues/`, () =>
59
+ HttpResponse.json({ results: ISSUES }),
60
+ ),
61
+ http.get(
62
+ `${BASE}/api/v1/workspaces/${WS}/projects/proj-acme/issues/i1/comments/`,
63
+ () => HttpResponse.json({ results: COMMENTS }),
64
+ ),
65
+ http.get(
66
+ `${BASE}/api/v1/workspaces/${WS}/projects/proj-acme/issues/i1/worklogs/`,
67
+ () => HttpResponse.json({ results: WORKLOGS }),
68
+ ),
69
+ );
50
70
 
51
- beforeAll(() => server.listen({ onUnhandledRequest: "error" }))
52
- afterAll(() => server.close())
71
+ beforeAll(() => server.listen({ onUnhandledRequest: "error" }));
72
+ afterAll(() => server.close());
53
73
 
54
74
  beforeEach(() => {
55
- _clearProjectCache()
56
- process.env["PLANE_HOST"] = BASE
57
- process.env["PLANE_WORKSPACE"] = WS
58
- process.env["PLANE_API_TOKEN"] = "test-token"
59
- })
75
+ _clearProjectCache();
76
+ process.env["PLANE_HOST"] = BASE;
77
+ process.env["PLANE_WORKSPACE"] = WS;
78
+ process.env["PLANE_API_TOKEN"] = "test-token";
79
+ });
60
80
 
61
81
  afterEach(() => {
62
- server.resetHandlers()
63
- delete process.env["PLANE_HOST"]
64
- delete process.env["PLANE_WORKSPACE"]
65
- delete process.env["PLANE_API_TOKEN"]
66
- })
82
+ server.resetHandlers();
83
+ delete process.env["PLANE_HOST"];
84
+ delete process.env["PLANE_WORKSPACE"];
85
+ delete process.env["PLANE_API_TOKEN"];
86
+ });
67
87
 
68
88
  describe("issueCommentsList", () => {
69
- it("lists comments with author and stripped HTML", async () => {
70
- const { issueCommentsList } = await import("@/commands/issue")
71
- const logs: string[] = []
72
- const orig = console.log
73
- console.log = (...args: unknown[]) => logs.push(args.join(" "))
74
- try {
75
- await Effect.runPromise((issueCommentsList as any).handler({ ref: "ACME-29" }))
76
- } finally {
77
- console.log = orig
78
- }
79
- const output = logs.join("\n")
80
- expect(output).toContain("c1")
81
- expect(output).toContain("Aaron")
82
- expect(output).toContain("Fixed in v2")
83
- expect(output).not.toContain("<p>")
84
- })
89
+ it("lists comments with author and stripped HTML", async () => {
90
+ const { issueCommentsList } = await import("@/commands/issue");
91
+ const logs: string[] = [];
92
+ const orig = console.log;
93
+ console.log = (...args: unknown[]) => logs.push(args.join(" "));
94
+ try {
95
+ await Effect.runPromise(
96
+ (issueCommentsList as any).handler({ ref: "ACME-29" }),
97
+ );
98
+ } finally {
99
+ console.log = orig;
100
+ }
101
+ const output = logs.join("\n");
102
+ expect(output).toContain("c1");
103
+ expect(output).toContain("Aaron");
104
+ expect(output).toContain("Fixed in v2");
105
+ expect(output).not.toContain("<p>");
106
+ });
85
107
 
86
- it("shows 'No comments' when empty", async () => {
87
- server.use(
88
- http.get(`${BASE}/api/v1/workspaces/${WS}/projects/proj-acme/issues/i1/comments/`, () =>
89
- HttpResponse.json({ results: [] }),
90
- ),
91
- )
92
- const { issueCommentsList } = await import("@/commands/issue")
93
- const logs: string[] = []
94
- const orig = console.log
95
- console.log = (...args: unknown[]) => logs.push(args.join(" "))
96
- try {
97
- await Effect.runPromise((issueCommentsList as any).handler({ ref: "ACME-29" }))
98
- } finally {
99
- console.log = orig
100
- }
101
- expect(logs.join("\n")).toBe("No comments")
102
- })
108
+ it("shows 'No comments' when empty", async () => {
109
+ server.use(
110
+ http.get(
111
+ `${BASE}/api/v1/workspaces/${WS}/projects/proj-acme/issues/i1/comments/`,
112
+ () => HttpResponse.json({ results: [] }),
113
+ ),
114
+ );
115
+ const { issueCommentsList } = await import("@/commands/issue");
116
+ const logs: string[] = [];
117
+ const orig = console.log;
118
+ console.log = (...args: unknown[]) => logs.push(args.join(" "));
119
+ try {
120
+ await Effect.runPromise(
121
+ (issueCommentsList as any).handler({ ref: "ACME-29" }),
122
+ );
123
+ } finally {
124
+ console.log = orig;
125
+ }
126
+ expect(logs.join("\n")).toBe("No comments");
127
+ });
103
128
 
104
- it("shows '?' for missing actor", async () => {
105
- server.use(
106
- http.get(`${BASE}/api/v1/workspaces/${WS}/projects/proj-acme/issues/i1/comments/`, () =>
107
- HttpResponse.json({ results: [{ id: "c3", comment_html: "<p>hi</p>", created_at: "2025-01-17T10:00:00Z" }] }),
108
- ),
109
- )
110
- const { issueCommentsList } = await import("@/commands/issue")
111
- const logs: string[] = []
112
- const orig = console.log
113
- console.log = (...args: unknown[]) => logs.push(args.join(" "))
114
- try {
115
- await Effect.runPromise((issueCommentsList as any).handler({ ref: "ACME-29" }))
116
- } finally {
117
- console.log = orig
118
- }
119
- expect(logs.join("\n")).toContain("?")
120
- })
121
- })
129
+ it("shows '?' for missing actor", async () => {
130
+ server.use(
131
+ http.get(
132
+ `${BASE}/api/v1/workspaces/${WS}/projects/proj-acme/issues/i1/comments/`,
133
+ () =>
134
+ HttpResponse.json({
135
+ results: [
136
+ {
137
+ id: "c3",
138
+ comment_html: "<p>hi</p>",
139
+ created_at: "2025-01-17T10:00:00Z",
140
+ },
141
+ ],
142
+ }),
143
+ ),
144
+ );
145
+ const { issueCommentsList } = await import("@/commands/issue");
146
+ const logs: string[] = [];
147
+ const orig = console.log;
148
+ console.log = (...args: unknown[]) => logs.push(args.join(" "));
149
+ try {
150
+ await Effect.runPromise(
151
+ (issueCommentsList as any).handler({ ref: "ACME-29" }),
152
+ );
153
+ } finally {
154
+ console.log = orig;
155
+ }
156
+ expect(logs.join("\n")).toContain("?");
157
+ });
158
+ });
122
159
 
123
160
  describe("issueCommentUpdate", () => {
124
- it("updates a comment", async () => {
125
- let patchedBody: unknown
126
- server.use(
127
- http.patch(
128
- `${BASE}/api/v1/workspaces/${WS}/projects/proj-acme/issues/i1/comments/c1/`,
129
- async ({ request }) => {
130
- patchedBody = await request.json()
131
- return HttpResponse.json({ id: "c1", comment_html: "<p>Updated</p>", created_at: "2025-01-15T10:30:00Z" })
132
- },
133
- ),
134
- )
135
- const { issueCommentUpdate } = await import("@/commands/issue")
136
- const logs: string[] = []
137
- const orig = console.log
138
- console.log = (...args: unknown[]) => logs.push(args.join(" "))
139
- try {
140
- await Effect.runPromise(
141
- (issueCommentUpdate as any).handler({ ref: "ACME-29", commentId: "c1", text: "Updated text" }),
142
- )
143
- } finally {
144
- console.log = orig
145
- }
146
- expect((patchedBody as any).comment_html).toContain("Updated text")
147
- expect(logs.join("\n")).toContain("c1")
148
- expect(logs.join("\n")).toContain("updated")
149
- })
150
- })
161
+ it("updates a comment", async () => {
162
+ let patchedBody: unknown;
163
+ server.use(
164
+ http.patch(
165
+ `${BASE}/api/v1/workspaces/${WS}/projects/proj-acme/issues/i1/comments/c1/`,
166
+ async ({ request }) => {
167
+ patchedBody = await request.json();
168
+ return HttpResponse.json({
169
+ id: "c1",
170
+ comment_html: "<p>Updated</p>",
171
+ created_at: "2025-01-15T10:30:00Z",
172
+ });
173
+ },
174
+ ),
175
+ );
176
+ const { issueCommentUpdate } = await import("@/commands/issue");
177
+ const logs: string[] = [];
178
+ const orig = console.log;
179
+ console.log = (...args: unknown[]) => logs.push(args.join(" "));
180
+ try {
181
+ await Effect.runPromise(
182
+ (issueCommentUpdate as any).handler({
183
+ ref: "ACME-29",
184
+ commentId: "c1",
185
+ text: "Updated text",
186
+ }),
187
+ );
188
+ } finally {
189
+ console.log = orig;
190
+ }
191
+ expect((patchedBody as any).comment_html).toContain("Updated text");
192
+ expect(logs.join("\n")).toContain("c1");
193
+ expect(logs.join("\n")).toContain("updated");
194
+ });
195
+ });
151
196
 
152
197
  describe("issueCommentDelete", () => {
153
- it("deletes a comment", async () => {
154
- let deleted = false
155
- server.use(
156
- http.delete(
157
- `${BASE}/api/v1/workspaces/${WS}/projects/proj-acme/issues/i1/comments/c1/`,
158
- () => {
159
- deleted = true
160
- return new HttpResponse(null, { status: 204 })
161
- },
162
- ),
163
- )
164
- const { issueCommentDelete } = await import("@/commands/issue")
165
- const logs: string[] = []
166
- const orig = console.log
167
- console.log = (...args: unknown[]) => logs.push(args.join(" "))
168
- try {
169
- await Effect.runPromise(
170
- (issueCommentDelete as any).handler({ ref: "ACME-29", commentId: "c1" }),
171
- )
172
- } finally {
173
- console.log = orig
174
- }
175
- expect(deleted).toBe(true)
176
- expect(logs.join("\n")).toContain("c1")
177
- })
178
- })
198
+ it("deletes a comment", async () => {
199
+ let deleted = false;
200
+ server.use(
201
+ http.delete(
202
+ `${BASE}/api/v1/workspaces/${WS}/projects/proj-acme/issues/i1/comments/c1/`,
203
+ () => {
204
+ deleted = true;
205
+ return new HttpResponse(null, { status: 204 });
206
+ },
207
+ ),
208
+ );
209
+ const { issueCommentDelete } = await import("@/commands/issue");
210
+ const logs: string[] = [];
211
+ const orig = console.log;
212
+ console.log = (...args: unknown[]) => logs.push(args.join(" "));
213
+ try {
214
+ await Effect.runPromise(
215
+ (issueCommentDelete as any).handler({
216
+ ref: "ACME-29",
217
+ commentId: "c1",
218
+ }),
219
+ );
220
+ } finally {
221
+ console.log = orig;
222
+ }
223
+ expect(deleted).toBe(true);
224
+ expect(logs.join("\n")).toContain("c1");
225
+ });
226
+ });
179
227
 
180
228
  describe("issueWorklogsList", () => {
181
- it("lists worklogs with hours", async () => {
182
- const { issueWorklogsList } = await import("@/commands/issue")
183
- const logs: string[] = []
184
- const orig = console.log
185
- console.log = (...args: unknown[]) => logs.push(args.join(" "))
186
- try {
187
- await Effect.runPromise((issueWorklogsList as any).handler({ ref: "ACME-29" }))
188
- } finally {
189
- console.log = orig
190
- }
191
- const output = logs.join("\n")
192
- expect(output).toContain("w1")
193
- expect(output).toContain("1.5h")
194
- expect(output).toContain("Aaron")
195
- expect(output).toContain("Code review")
196
- })
229
+ it("lists worklogs with hours", async () => {
230
+ const { issueWorklogsList } = await import("@/commands/issue");
231
+ const logs: string[] = [];
232
+ const orig = console.log;
233
+ console.log = (...args: unknown[]) => logs.push(args.join(" "));
234
+ try {
235
+ await Effect.runPromise(
236
+ (issueWorklogsList as any).handler({ ref: "ACME-29" }),
237
+ );
238
+ } finally {
239
+ console.log = orig;
240
+ }
241
+ const output = logs.join("\n");
242
+ expect(output).toContain("w1");
243
+ expect(output).toContain("1.5h");
244
+ expect(output).toContain("Aaron");
245
+ expect(output).toContain("Code review");
246
+ });
197
247
 
198
- it("shows 'No worklogs' when empty", async () => {
199
- server.use(
200
- http.get(`${BASE}/api/v1/workspaces/${WS}/projects/proj-acme/issues/i1/worklogs/`, () =>
201
- HttpResponse.json({ results: [] }),
202
- ),
203
- )
204
- const { issueWorklogsList } = await import("@/commands/issue")
205
- const logs: string[] = []
206
- const orig = console.log
207
- console.log = (...args: unknown[]) => logs.push(args.join(" "))
208
- try {
209
- await Effect.runPromise((issueWorklogsList as any).handler({ ref: "ACME-29" }))
210
- } finally {
211
- console.log = orig
212
- }
213
- expect(logs.join("\n")).toBe("No worklogs")
214
- })
215
- })
248
+ it("shows 'No worklogs' when empty", async () => {
249
+ server.use(
250
+ http.get(
251
+ `${BASE}/api/v1/workspaces/${WS}/projects/proj-acme/issues/i1/worklogs/`,
252
+ () => HttpResponse.json({ results: [] }),
253
+ ),
254
+ );
255
+ const { issueWorklogsList } = await import("@/commands/issue");
256
+ const logs: string[] = [];
257
+ const orig = console.log;
258
+ console.log = (...args: unknown[]) => logs.push(args.join(" "));
259
+ try {
260
+ await Effect.runPromise(
261
+ (issueWorklogsList as any).handler({ ref: "ACME-29" }),
262
+ );
263
+ } finally {
264
+ console.log = orig;
265
+ }
266
+ expect(logs.join("\n")).toBe("No worklogs");
267
+ });
268
+ });
216
269
 
217
270
  describe("issueWorklogsAdd", () => {
218
- it("logs time without description", async () => {
219
- server.use(
220
- http.post(
221
- `${BASE}/api/v1/workspaces/${WS}/projects/proj-acme/issues/i1/worklogs/`,
222
- async ({ request }) => {
223
- const body = (await request.json()) as any
224
- return HttpResponse.json({ id: "w-new", duration: body.duration, created_at: "2025-01-15T10:00:00Z" })
225
- },
226
- ),
227
- )
228
- const { issueWorklogsAdd } = await import("@/commands/issue")
229
- const logs: string[] = []
230
- const orig = console.log
231
- console.log = (...args: unknown[]) => logs.push(args.join(" "))
232
- try {
233
- await Effect.runPromise(
234
- (issueWorklogsAdd as any).handler({ ref: "ACME-29", duration: 60, description: { _tag: "None" } }),
235
- )
236
- } finally {
237
- console.log = orig
238
- }
239
- expect(logs.join("\n")).toContain("1.0h")
240
- expect(logs.join("\n")).toContain("w-new")
241
- })
271
+ it("logs time without description", async () => {
272
+ server.use(
273
+ http.post(
274
+ `${BASE}/api/v1/workspaces/${WS}/projects/proj-acme/issues/i1/worklogs/`,
275
+ async ({ request }) => {
276
+ const body = (await request.json()) as any;
277
+ return HttpResponse.json({
278
+ id: "w-new",
279
+ duration: body.duration,
280
+ created_at: "2025-01-15T10:00:00Z",
281
+ });
282
+ },
283
+ ),
284
+ );
285
+ const { issueWorklogsAdd } = await import("@/commands/issue");
286
+ const logs: string[] = [];
287
+ const orig = console.log;
288
+ console.log = (...args: unknown[]) => logs.push(args.join(" "));
289
+ try {
290
+ await Effect.runPromise(
291
+ (issueWorklogsAdd as any).handler({
292
+ ref: "ACME-29",
293
+ duration: 60,
294
+ description: { _tag: "None" },
295
+ }),
296
+ );
297
+ } finally {
298
+ console.log = orig;
299
+ }
300
+ expect(logs.join("\n")).toContain("1.0h");
301
+ expect(logs.join("\n")).toContain("w-new");
302
+ });
242
303
 
243
- it("logs time with description", async () => {
244
- server.use(
245
- http.post(
246
- `${BASE}/api/v1/workspaces/${WS}/projects/proj-acme/issues/i1/worklogs/`,
247
- async ({ request }) => {
248
- const body = (await request.json()) as any
249
- return HttpResponse.json({ id: "w-new2", duration: body.duration, description: body.description, created_at: "2025-01-15T10:00:00Z" })
250
- },
251
- ),
252
- )
253
- const { issueWorklogsAdd } = await import("@/commands/issue")
254
- const logs: string[] = []
255
- const orig = console.log
256
- console.log = (...args: unknown[]) => logs.push(args.join(" "))
257
- try {
258
- await Effect.runPromise(
259
- (issueWorklogsAdd as any).handler({
260
- ref: "ACME-29",
261
- duration: 30,
262
- description: { _tag: "Some", value: "standup" },
263
- }),
264
- )
265
- } finally {
266
- console.log = orig
267
- }
268
- expect(logs.join("\n")).toContain("0.5h")
269
- })
304
+ it("logs time with description", async () => {
305
+ server.use(
306
+ http.post(
307
+ `${BASE}/api/v1/workspaces/${WS}/projects/proj-acme/issues/i1/worklogs/`,
308
+ async ({ request }) => {
309
+ const body = (await request.json()) as any;
310
+ return HttpResponse.json({
311
+ id: "w-new2",
312
+ duration: body.duration,
313
+ description: body.description,
314
+ created_at: "2025-01-15T10:00:00Z",
315
+ });
316
+ },
317
+ ),
318
+ );
319
+ const { issueWorklogsAdd } = await import("@/commands/issue");
320
+ const logs: string[] = [];
321
+ const orig = console.log;
322
+ console.log = (...args: unknown[]) => logs.push(args.join(" "));
323
+ try {
324
+ await Effect.runPromise(
325
+ (issueWorklogsAdd as any).handler({
326
+ ref: "ACME-29",
327
+ duration: 30,
328
+ description: { _tag: "Some", value: "standup" },
329
+ }),
330
+ );
331
+ } finally {
332
+ console.log = orig;
333
+ }
334
+ expect(logs.join("\n")).toContain("0.5h");
335
+ });
270
336
 
271
- it("handles missing logged_by_detail in worklogs list", async () => {
272
- server.use(
273
- http.get(`${BASE}/api/v1/workspaces/${WS}/projects/proj-acme/issues/i1/worklogs/`, () =>
274
- HttpResponse.json({
275
- results: [{ id: "w2", duration: 45, created_at: "2025-01-15T10:00:00Z" }],
276
- }),
277
- ),
278
- )
279
- const { issueWorklogsList } = await import("@/commands/issue")
280
- const logs: string[] = []
281
- const orig = console.log
282
- console.log = (...args: unknown[]) => logs.push(args.join(" "))
283
- try {
284
- await Effect.runPromise((issueWorklogsList as any).handler({ ref: "ACME-29" }))
285
- } finally {
286
- console.log = orig
287
- }
288
- expect(logs.join("\n")).toContain("?")
289
- expect(logs.join("\n")).toContain("0.8h")
290
- })
291
- })
337
+ it("handles missing logged_by_detail in worklogs list", async () => {
338
+ server.use(
339
+ http.get(
340
+ `${BASE}/api/v1/workspaces/${WS}/projects/proj-acme/issues/i1/worklogs/`,
341
+ () =>
342
+ HttpResponse.json({
343
+ results: [
344
+ { id: "w2", duration: 45, created_at: "2025-01-15T10:00:00Z" },
345
+ ],
346
+ }),
347
+ ),
348
+ );
349
+ const { issueWorklogsList } = await import("@/commands/issue");
350
+ const logs: string[] = [];
351
+ const orig = console.log;
352
+ console.log = (...args: unknown[]) => logs.push(args.join(" "));
353
+ try {
354
+ await Effect.runPromise(
355
+ (issueWorklogsList as any).handler({ ref: "ACME-29" }),
356
+ );
357
+ } finally {
358
+ console.log = orig;
359
+ }
360
+ expect(logs.join("\n")).toContain("?");
361
+ expect(logs.join("\n")).toContain("0.8h");
362
+ });
363
+ });