@cliangdev/flux-plugin 0.3.1-dev.bdbaeae → 0.3.1
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/commands/dashboard.md +1 -1
- package/package.json +1 -3
- package/src/server/adapters/factory.ts +28 -6
- package/src/server/adapters/types.ts +1 -1
- package/src/server/index.ts +0 -2
- package/src/server/tools/__tests__/mcp-interface.test.ts +0 -6
- package/src/server/tools/index.ts +1 -2
- package/src/server/tools/init-project.ts +12 -26
- package/src/server/adapters/github/__tests__/criteria-deps.test.ts +0 -579
- package/src/server/adapters/github/__tests__/documents-stats.test.ts +0 -789
- package/src/server/adapters/github/__tests__/epic-task-crud.test.ts +0 -1072
- package/src/server/adapters/github/__tests__/foundation.test.ts +0 -537
- package/src/server/adapters/github/__tests__/index-store.test.ts +0 -319
- package/src/server/adapters/github/__tests__/prd-crud.test.ts +0 -836
- package/src/server/adapters/github/adapter.ts +0 -1552
- package/src/server/adapters/github/client.ts +0 -33
- package/src/server/adapters/github/config.ts +0 -59
- package/src/server/adapters/github/helpers/criteria.ts +0 -157
- package/src/server/adapters/github/helpers/index-store.ts +0 -75
- package/src/server/adapters/github/helpers/meta.ts +0 -26
- package/src/server/adapters/github/index.ts +0 -5
- package/src/server/adapters/github/mappers/epic.ts +0 -21
- package/src/server/adapters/github/mappers/index.ts +0 -15
- package/src/server/adapters/github/mappers/prd.ts +0 -50
- package/src/server/adapters/github/mappers/task.ts +0 -37
- package/src/server/adapters/github/types.ts +0 -27
- package/src/server/tools/__tests__/z-configure-github.test.ts +0 -509
- package/src/server/tools/__tests__/z-init-project.test.ts +0 -168
- package/src/server/tools/configure-github.ts +0 -411
|
@@ -1,1072 +0,0 @@
|
|
|
1
|
-
import { afterEach, beforeEach, describe, expect, mock, test } from "bun:test";
|
|
2
|
-
import { realpathSync } from "node:fs";
|
|
3
|
-
import { tmpdir } from "node:os";
|
|
4
|
-
|
|
5
|
-
const TEST_DIR = `${realpathSync(tmpdir())}/flux-epic-task-crud-test-${Date.now()}-${Math.random().toString(36).slice(2)}`;
|
|
6
|
-
process.env.FLUX_PROJECT_ROOT = TEST_DIR;
|
|
7
|
-
|
|
8
|
-
let mockIssuesCreate: any = null;
|
|
9
|
-
let mockIssuesUpdate: any = null;
|
|
10
|
-
let mockIssuesGet: Record<number, any> = {};
|
|
11
|
-
let mockIssuesList: any[] = [];
|
|
12
|
-
let mockSubIssues: Record<number, any[]> = {};
|
|
13
|
-
let mockGetContent: any = null;
|
|
14
|
-
let mockGraphqlFn: (
|
|
15
|
-
query: string,
|
|
16
|
-
variables?: Record<string, unknown>,
|
|
17
|
-
) => Promise<unknown> = async () => ({});
|
|
18
|
-
let _lastCreateParams: any = null;
|
|
19
|
-
let _lastUpdateParams: any = null;
|
|
20
|
-
let lastUpdateByNumberParams: Record<number, any> = {};
|
|
21
|
-
let lastPutParams: any = null;
|
|
22
|
-
let lastAddSubIssueParams: any[] = [];
|
|
23
|
-
let issueNodeIdCounter = 0;
|
|
24
|
-
|
|
25
|
-
mock.module("@octokit/rest", () => ({
|
|
26
|
-
Octokit: class MockOctokit {
|
|
27
|
-
repos = {
|
|
28
|
-
getContent: async (_params: any) => {
|
|
29
|
-
if (mockGetContent === null) {
|
|
30
|
-
const err: any = new Error("Not Found");
|
|
31
|
-
err.status = 404;
|
|
32
|
-
throw err;
|
|
33
|
-
}
|
|
34
|
-
return mockGetContent;
|
|
35
|
-
},
|
|
36
|
-
createOrUpdateFileContents: async (params: any) => {
|
|
37
|
-
lastPutParams = params;
|
|
38
|
-
return { data: { content: { sha: "new-sha" } } };
|
|
39
|
-
},
|
|
40
|
-
};
|
|
41
|
-
|
|
42
|
-
issues = {
|
|
43
|
-
create: async (params: any) => {
|
|
44
|
-
_lastCreateParams = params;
|
|
45
|
-
return { data: mockIssuesCreate };
|
|
46
|
-
},
|
|
47
|
-
update: async (params: any) => {
|
|
48
|
-
const num = params.issue_number;
|
|
49
|
-
lastUpdateByNumberParams[num] = params;
|
|
50
|
-
_lastUpdateParams = params;
|
|
51
|
-
return { data: mockIssuesUpdate ?? mockIssuesCreate };
|
|
52
|
-
},
|
|
53
|
-
get: async (params: any) => {
|
|
54
|
-
const num = params.issue_number;
|
|
55
|
-
if (mockIssuesGet[num]) return { data: mockIssuesGet[num] };
|
|
56
|
-
const err: any = new Error("Not Found");
|
|
57
|
-
err.status = 404;
|
|
58
|
-
throw err;
|
|
59
|
-
},
|
|
60
|
-
listForRepo: async (_params: any) => {
|
|
61
|
-
return { data: mockIssuesList };
|
|
62
|
-
},
|
|
63
|
-
};
|
|
64
|
-
},
|
|
65
|
-
}));
|
|
66
|
-
|
|
67
|
-
mock.module("@octokit/graphql", () => ({
|
|
68
|
-
graphql: {
|
|
69
|
-
defaults: (_opts: any) => {
|
|
70
|
-
return async (query: string, variables?: Record<string, unknown>) => {
|
|
71
|
-
return mockGraphqlFn(query, variables);
|
|
72
|
-
};
|
|
73
|
-
},
|
|
74
|
-
},
|
|
75
|
-
}));
|
|
76
|
-
|
|
77
|
-
function makeIssue(
|
|
78
|
-
overrides: Partial<{
|
|
79
|
-
number: number;
|
|
80
|
-
id: number;
|
|
81
|
-
title: string;
|
|
82
|
-
body: string;
|
|
83
|
-
labels: string[];
|
|
84
|
-
state: "open" | "closed";
|
|
85
|
-
created_at: string;
|
|
86
|
-
updated_at: string;
|
|
87
|
-
node_id: string;
|
|
88
|
-
}> = {},
|
|
89
|
-
) {
|
|
90
|
-
issueNodeIdCounter++;
|
|
91
|
-
const number = overrides.number ?? issueNodeIdCounter;
|
|
92
|
-
return {
|
|
93
|
-
number,
|
|
94
|
-
id: overrides.id ?? number * 1000,
|
|
95
|
-
title: overrides.title ?? "Test Issue",
|
|
96
|
-
body: overrides.body ?? "",
|
|
97
|
-
labels: (overrides.labels ?? []).map((name) => ({ name })),
|
|
98
|
-
state: overrides.state ?? "open",
|
|
99
|
-
created_at: overrides.created_at ?? "2026-01-01T00:00:00Z",
|
|
100
|
-
updated_at: overrides.updated_at ?? "2026-01-01T00:00:00Z",
|
|
101
|
-
node_id: overrides.node_id ?? `MDU6SXNzdWU${number}`,
|
|
102
|
-
};
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
function makeAdapter() {
|
|
106
|
-
const { GitHubAdapter } = require("../adapter.js");
|
|
107
|
-
const adapter = new GitHubAdapter({
|
|
108
|
-
token: "ghp_test",
|
|
109
|
-
owner: "test-owner",
|
|
110
|
-
repo: "test-repo",
|
|
111
|
-
projectId: "PVT_kwDO123",
|
|
112
|
-
refPrefix: "FP",
|
|
113
|
-
});
|
|
114
|
-
|
|
115
|
-
(adapter as any).client.rest.issues = {
|
|
116
|
-
create: async (params: any) => {
|
|
117
|
-
_lastCreateParams = params;
|
|
118
|
-
return { data: mockIssuesCreate };
|
|
119
|
-
},
|
|
120
|
-
update: async (params: any) => {
|
|
121
|
-
const num = params.issue_number;
|
|
122
|
-
lastUpdateByNumberParams[num] = params;
|
|
123
|
-
_lastUpdateParams = params;
|
|
124
|
-
if (mockIssuesUpdate !== null) {
|
|
125
|
-
return { data: mockIssuesUpdate };
|
|
126
|
-
}
|
|
127
|
-
return { data: mockIssuesCreate };
|
|
128
|
-
},
|
|
129
|
-
get: async (params: any) => {
|
|
130
|
-
const num = params.issue_number;
|
|
131
|
-
if (mockIssuesGet[num]) return { data: mockIssuesGet[num] };
|
|
132
|
-
const err: any = new Error("Not Found");
|
|
133
|
-
err.status = 404;
|
|
134
|
-
throw err;
|
|
135
|
-
},
|
|
136
|
-
listForRepo: async (_params: any) => {
|
|
137
|
-
return { data: mockIssuesList };
|
|
138
|
-
},
|
|
139
|
-
listSubIssues: async (params: any) => {
|
|
140
|
-
const issueNum = params.issue_number;
|
|
141
|
-
return { data: mockSubIssues[issueNum] ?? [] };
|
|
142
|
-
},
|
|
143
|
-
addSubIssue: async (params: any) => {
|
|
144
|
-
lastAddSubIssueParams.push(params);
|
|
145
|
-
return { data: {} };
|
|
146
|
-
},
|
|
147
|
-
};
|
|
148
|
-
|
|
149
|
-
(adapter as any).client.rest.repos = {
|
|
150
|
-
getContent: async (_params: any) => {
|
|
151
|
-
if (mockGetContent === null) {
|
|
152
|
-
const err: any = new Error("Not Found");
|
|
153
|
-
err.status = 404;
|
|
154
|
-
throw err;
|
|
155
|
-
}
|
|
156
|
-
return mockGetContent;
|
|
157
|
-
},
|
|
158
|
-
createOrUpdateFileContents: async (params: any) => {
|
|
159
|
-
lastPutParams = params;
|
|
160
|
-
return { data: { content: { sha: "new-sha" } } };
|
|
161
|
-
},
|
|
162
|
-
};
|
|
163
|
-
|
|
164
|
-
(adapter as any).client.rest.request = async (url: string, params: any) => {
|
|
165
|
-
if (url.includes("sub_issues") && url.startsWith("GET")) {
|
|
166
|
-
return { data: mockSubIssues[params.issue_number] ?? [] };
|
|
167
|
-
}
|
|
168
|
-
if (url.includes("sub_issues") && url.startsWith("POST")) {
|
|
169
|
-
lastAddSubIssueParams.push(params);
|
|
170
|
-
return { data: {} };
|
|
171
|
-
}
|
|
172
|
-
throw new Error(`Unmocked request: ${url}`);
|
|
173
|
-
};
|
|
174
|
-
|
|
175
|
-
return adapter;
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
describe("epic status mappers", () => {
|
|
179
|
-
test("epicStatusToLabel returns null for PENDING", async () => {
|
|
180
|
-
const { epicStatusToLabel } = await import("../mappers/epic.js");
|
|
181
|
-
expect(epicStatusToLabel("PENDING")).toBeNull();
|
|
182
|
-
});
|
|
183
|
-
|
|
184
|
-
test("epicStatusToLabel returns status:in-progress for IN_PROGRESS", async () => {
|
|
185
|
-
const { epicStatusToLabel } = await import("../mappers/epic.js");
|
|
186
|
-
expect(epicStatusToLabel("IN_PROGRESS")).toBe("status:in-progress");
|
|
187
|
-
});
|
|
188
|
-
|
|
189
|
-
test("epicStatusToLabel returns null for COMPLETED", async () => {
|
|
190
|
-
const { epicStatusToLabel } = await import("../mappers/epic.js");
|
|
191
|
-
expect(epicStatusToLabel("COMPLETED")).toBeNull();
|
|
192
|
-
});
|
|
193
|
-
|
|
194
|
-
test("labelToEpicStatus returns COMPLETED for closed issue", async () => {
|
|
195
|
-
const { labelToEpicStatus } = await import("../mappers/epic.js");
|
|
196
|
-
expect(labelToEpicStatus(["flux:epic"], true)).toBe("COMPLETED");
|
|
197
|
-
expect(labelToEpicStatus(["flux:epic", "status:in-progress"], true)).toBe(
|
|
198
|
-
"COMPLETED",
|
|
199
|
-
);
|
|
200
|
-
});
|
|
201
|
-
|
|
202
|
-
test("labelToEpicStatus returns IN_PROGRESS for open issue with status:in-progress", async () => {
|
|
203
|
-
const { labelToEpicStatus } = await import("../mappers/epic.js");
|
|
204
|
-
expect(labelToEpicStatus(["flux:epic", "status:in-progress"], false)).toBe(
|
|
205
|
-
"IN_PROGRESS",
|
|
206
|
-
);
|
|
207
|
-
});
|
|
208
|
-
|
|
209
|
-
test("labelToEpicStatus returns PENDING for open issue with no status label", async () => {
|
|
210
|
-
const { labelToEpicStatus } = await import("../mappers/epic.js");
|
|
211
|
-
expect(labelToEpicStatus(["flux:epic"], false)).toBe("PENDING");
|
|
212
|
-
});
|
|
213
|
-
|
|
214
|
-
test("all EpicStatus values round-trip through label encoding", async () => {
|
|
215
|
-
const { labelToEpicStatus } = await import("../mappers/epic.js");
|
|
216
|
-
expect(labelToEpicStatus([], false)).toBe("PENDING");
|
|
217
|
-
expect(labelToEpicStatus(["status:in-progress"], false)).toBe(
|
|
218
|
-
"IN_PROGRESS",
|
|
219
|
-
);
|
|
220
|
-
expect(labelToEpicStatus([], true)).toBe("COMPLETED");
|
|
221
|
-
});
|
|
222
|
-
});
|
|
223
|
-
|
|
224
|
-
describe("task status and priority mappers", () => {
|
|
225
|
-
test("taskStatusToLabel returns null for PENDING", async () => {
|
|
226
|
-
const { taskStatusToLabel } = await import("../mappers/task.js");
|
|
227
|
-
expect(taskStatusToLabel("PENDING")).toBeNull();
|
|
228
|
-
});
|
|
229
|
-
|
|
230
|
-
test("taskStatusToLabel returns status:in-progress for IN_PROGRESS", async () => {
|
|
231
|
-
const { taskStatusToLabel } = await import("../mappers/task.js");
|
|
232
|
-
expect(taskStatusToLabel("IN_PROGRESS")).toBe("status:in-progress");
|
|
233
|
-
});
|
|
234
|
-
|
|
235
|
-
test("taskStatusToLabel returns null for COMPLETED", async () => {
|
|
236
|
-
const { taskStatusToLabel } = await import("../mappers/task.js");
|
|
237
|
-
expect(taskStatusToLabel("COMPLETED")).toBeNull();
|
|
238
|
-
});
|
|
239
|
-
|
|
240
|
-
test("labelToTaskStatus returns COMPLETED for closed issue", async () => {
|
|
241
|
-
const { labelToTaskStatus } = await import("../mappers/task.js");
|
|
242
|
-
expect(labelToTaskStatus(["flux:task"], true)).toBe("COMPLETED");
|
|
243
|
-
});
|
|
244
|
-
|
|
245
|
-
test("labelToTaskStatus returns IN_PROGRESS for open issue with status:in-progress", async () => {
|
|
246
|
-
const { labelToTaskStatus } = await import("../mappers/task.js");
|
|
247
|
-
expect(labelToTaskStatus(["flux:task", "status:in-progress"], false)).toBe(
|
|
248
|
-
"IN_PROGRESS",
|
|
249
|
-
);
|
|
250
|
-
});
|
|
251
|
-
|
|
252
|
-
test("labelToTaskStatus returns PENDING for open issue with no status label", async () => {
|
|
253
|
-
const { labelToTaskStatus } = await import("../mappers/task.js");
|
|
254
|
-
expect(labelToTaskStatus(["flux:task"], false)).toBe("PENDING");
|
|
255
|
-
});
|
|
256
|
-
|
|
257
|
-
test("all TaskStatus values round-trip", async () => {
|
|
258
|
-
const { labelToTaskStatus } = await import("../mappers/task.js");
|
|
259
|
-
expect(labelToTaskStatus([], false)).toBe("PENDING");
|
|
260
|
-
expect(labelToTaskStatus(["status:in-progress"], false)).toBe(
|
|
261
|
-
"IN_PROGRESS",
|
|
262
|
-
);
|
|
263
|
-
expect(labelToTaskStatus([], true)).toBe("COMPLETED");
|
|
264
|
-
});
|
|
265
|
-
|
|
266
|
-
test("priorityToLabel returns correct labels", async () => {
|
|
267
|
-
const { priorityToLabel } = await import("../mappers/task.js");
|
|
268
|
-
expect(priorityToLabel("LOW")).toBe("priority:low");
|
|
269
|
-
expect(priorityToLabel("MEDIUM")).toBe("priority:medium");
|
|
270
|
-
expect(priorityToLabel("HIGH")).toBe("priority:high");
|
|
271
|
-
});
|
|
272
|
-
|
|
273
|
-
test("labelToPriority returns HIGH when priority:high present", async () => {
|
|
274
|
-
const { labelToPriority } = await import("../mappers/task.js");
|
|
275
|
-
expect(labelToPriority(["flux:task", "priority:high"])).toBe("HIGH");
|
|
276
|
-
});
|
|
277
|
-
|
|
278
|
-
test("labelToPriority returns LOW when priority:low present", async () => {
|
|
279
|
-
const { labelToPriority } = await import("../mappers/task.js");
|
|
280
|
-
expect(labelToPriority(["flux:task", "priority:low"])).toBe("LOW");
|
|
281
|
-
});
|
|
282
|
-
|
|
283
|
-
test("labelToPriority defaults to MEDIUM", async () => {
|
|
284
|
-
const { labelToPriority } = await import("../mappers/task.js");
|
|
285
|
-
expect(labelToPriority(["flux:task"])).toBe("MEDIUM");
|
|
286
|
-
expect(labelToPriority(["flux:task", "priority:medium"])).toBe("MEDIUM");
|
|
287
|
-
});
|
|
288
|
-
|
|
289
|
-
test("all Priority values round-trip through label encoding", async () => {
|
|
290
|
-
const { priorityToLabel, labelToPriority } = await import(
|
|
291
|
-
"../mappers/task.js"
|
|
292
|
-
);
|
|
293
|
-
for (const priority of ["LOW", "MEDIUM", "HIGH"] as const) {
|
|
294
|
-
const label = priorityToLabel(priority);
|
|
295
|
-
expect(labelToPriority([label])).toBe(priority);
|
|
296
|
-
}
|
|
297
|
-
});
|
|
298
|
-
});
|
|
299
|
-
|
|
300
|
-
describe("GitHubAdapter Epic CRUD", () => {
|
|
301
|
-
beforeEach(() => {
|
|
302
|
-
mockIssuesCreate = null;
|
|
303
|
-
mockIssuesUpdate = null;
|
|
304
|
-
mockIssuesGet = {};
|
|
305
|
-
mockIssuesList = [];
|
|
306
|
-
mockSubIssues = {};
|
|
307
|
-
mockGetContent = null;
|
|
308
|
-
_lastCreateParams = null;
|
|
309
|
-
_lastUpdateParams = null;
|
|
310
|
-
lastUpdateByNumberParams = {};
|
|
311
|
-
lastPutParams = null;
|
|
312
|
-
lastAddSubIssueParams = [];
|
|
313
|
-
issueNodeIdCounter = 0;
|
|
314
|
-
mockGraphqlFn = async () => ({
|
|
315
|
-
addProjectV2ItemById: { item: { id: "PVTI_123" } },
|
|
316
|
-
});
|
|
317
|
-
});
|
|
318
|
-
|
|
319
|
-
afterEach(() => {});
|
|
320
|
-
|
|
321
|
-
describe("createEpic", () => {
|
|
322
|
-
test("returns Epic with correct prdId", async () => {
|
|
323
|
-
const prdIssue = makeIssue({
|
|
324
|
-
number: 1,
|
|
325
|
-
labels: ["flux:prd", "status:draft"],
|
|
326
|
-
body: '<!-- flux-meta\n{"ref":"FP-P1"}\n-->',
|
|
327
|
-
});
|
|
328
|
-
|
|
329
|
-
const epicIssue = makeIssue({
|
|
330
|
-
number: 10,
|
|
331
|
-
title: "My Epic",
|
|
332
|
-
labels: ["flux:epic"],
|
|
333
|
-
body: '<!-- flux-meta\n{"ref":"FP-E10","prd_ref":"FP-P1","dependencies":[]}\n-->',
|
|
334
|
-
node_id: "NODE_EPIC_10",
|
|
335
|
-
});
|
|
336
|
-
|
|
337
|
-
mockGetContent = {
|
|
338
|
-
data: {
|
|
339
|
-
sha: "sha1",
|
|
340
|
-
content: Buffer.from(JSON.stringify({ "FP-P1": 1 })).toString(
|
|
341
|
-
"base64",
|
|
342
|
-
),
|
|
343
|
-
encoding: "base64",
|
|
344
|
-
},
|
|
345
|
-
};
|
|
346
|
-
|
|
347
|
-
mockIssuesCreate = epicIssue;
|
|
348
|
-
mockIssuesUpdate = epicIssue;
|
|
349
|
-
mockIssuesGet[1] = prdIssue;
|
|
350
|
-
mockIssuesGet[10] = epicIssue;
|
|
351
|
-
|
|
352
|
-
const adapter = makeAdapter();
|
|
353
|
-
const epic = await adapter.createEpic({
|
|
354
|
-
prdRef: "FP-P1",
|
|
355
|
-
title: "My Epic",
|
|
356
|
-
});
|
|
357
|
-
|
|
358
|
-
expect(epic.ref).toBe("FP-E10");
|
|
359
|
-
expect(epic.prdId).toBe("FP-P1");
|
|
360
|
-
expect(epic.title).toBe("My Epic");
|
|
361
|
-
expect(epic.status).toBe("PENDING");
|
|
362
|
-
});
|
|
363
|
-
|
|
364
|
-
test("created epic issue is a sub-issue of the PRD issue", async () => {
|
|
365
|
-
const prdIssue = makeIssue({
|
|
366
|
-
number: 2,
|
|
367
|
-
labels: ["flux:prd", "status:draft"],
|
|
368
|
-
body: '<!-- flux-meta\n{"ref":"FP-P2"}\n-->',
|
|
369
|
-
});
|
|
370
|
-
|
|
371
|
-
const epicIssue = makeIssue({
|
|
372
|
-
number: 20,
|
|
373
|
-
labels: ["flux:epic"],
|
|
374
|
-
body: '<!-- flux-meta\n{"ref":"FP-E20","prd_ref":"FP-P2","dependencies":[]}\n-->',
|
|
375
|
-
node_id: "NODE_20",
|
|
376
|
-
});
|
|
377
|
-
|
|
378
|
-
mockGetContent = {
|
|
379
|
-
data: {
|
|
380
|
-
sha: "sha1",
|
|
381
|
-
content: Buffer.from(JSON.stringify({ "FP-P2": 2 })).toString(
|
|
382
|
-
"base64",
|
|
383
|
-
),
|
|
384
|
-
encoding: "base64",
|
|
385
|
-
},
|
|
386
|
-
};
|
|
387
|
-
|
|
388
|
-
mockIssuesCreate = epicIssue;
|
|
389
|
-
mockIssuesUpdate = epicIssue;
|
|
390
|
-
mockIssuesGet[2] = prdIssue;
|
|
391
|
-
mockIssuesGet[20] = epicIssue;
|
|
392
|
-
|
|
393
|
-
const adapter = makeAdapter();
|
|
394
|
-
await adapter.createEpic({ prdRef: "FP-P2", title: "Epic for PRD 2" });
|
|
395
|
-
|
|
396
|
-
expect(lastAddSubIssueParams.length).toBeGreaterThan(0);
|
|
397
|
-
const subIssueCall = lastAddSubIssueParams[0];
|
|
398
|
-
expect(subIssueCall.issue_number).toBe(2);
|
|
399
|
-
expect(subIssueCall.sub_issue_id).toBe(20000);
|
|
400
|
-
});
|
|
401
|
-
|
|
402
|
-
test("flux-meta contains ref and prd_ref", async () => {
|
|
403
|
-
const prdIssue = makeIssue({
|
|
404
|
-
number: 3,
|
|
405
|
-
labels: ["flux:prd"],
|
|
406
|
-
body: '<!-- flux-meta\n{"ref":"FP-P3"}\n-->',
|
|
407
|
-
});
|
|
408
|
-
|
|
409
|
-
const epicIssue = makeIssue({
|
|
410
|
-
number: 30,
|
|
411
|
-
labels: ["flux:epic"],
|
|
412
|
-
body: '<!-- flux-meta\n{"ref":"FP-E30","prd_ref":"FP-P3","dependencies":[]}\n-->',
|
|
413
|
-
node_id: "NODE_30",
|
|
414
|
-
});
|
|
415
|
-
|
|
416
|
-
mockGetContent = {
|
|
417
|
-
data: {
|
|
418
|
-
sha: "sha1",
|
|
419
|
-
content: Buffer.from(JSON.stringify({ "FP-P3": 3 })).toString(
|
|
420
|
-
"base64",
|
|
421
|
-
),
|
|
422
|
-
encoding: "base64",
|
|
423
|
-
},
|
|
424
|
-
};
|
|
425
|
-
|
|
426
|
-
mockIssuesCreate = epicIssue;
|
|
427
|
-
mockIssuesUpdate = epicIssue;
|
|
428
|
-
mockIssuesGet[3] = prdIssue;
|
|
429
|
-
mockIssuesGet[30] = epicIssue;
|
|
430
|
-
|
|
431
|
-
const adapter = makeAdapter();
|
|
432
|
-
await adapter.createEpic({ prdRef: "FP-P3", title: "Epic Meta Check" });
|
|
433
|
-
|
|
434
|
-
const updateCall = lastUpdateByNumberParams[30];
|
|
435
|
-
expect(updateCall).toBeDefined();
|
|
436
|
-
expect(updateCall.body).toContain('"ref":"FP-E30"');
|
|
437
|
-
expect(updateCall.body).toContain('"prd_ref":"FP-P3"');
|
|
438
|
-
});
|
|
439
|
-
});
|
|
440
|
-
|
|
441
|
-
describe("updateEpic", () => {
|
|
442
|
-
test("updateEpic status COMPLETED closes the issue", async () => {
|
|
443
|
-
const epicIssue = makeIssue({
|
|
444
|
-
number: 50,
|
|
445
|
-
labels: ["flux:epic"],
|
|
446
|
-
body: '<!-- flux-meta\n{"ref":"FP-E50","prd_ref":"FP-P5"}\n-->',
|
|
447
|
-
});
|
|
448
|
-
|
|
449
|
-
const closedEpicIssue = {
|
|
450
|
-
...epicIssue,
|
|
451
|
-
state: "closed" as const,
|
|
452
|
-
};
|
|
453
|
-
|
|
454
|
-
mockGetContent = {
|
|
455
|
-
data: {
|
|
456
|
-
sha: "sha1",
|
|
457
|
-
content: Buffer.from(JSON.stringify({ "FP-E50": 50 })).toString(
|
|
458
|
-
"base64",
|
|
459
|
-
),
|
|
460
|
-
encoding: "base64",
|
|
461
|
-
},
|
|
462
|
-
};
|
|
463
|
-
|
|
464
|
-
mockIssuesGet[50] = epicIssue;
|
|
465
|
-
mockIssuesUpdate = closedEpicIssue;
|
|
466
|
-
|
|
467
|
-
const adapter = makeAdapter();
|
|
468
|
-
const epic = await adapter.updateEpic("FP-E50", { status: "COMPLETED" });
|
|
469
|
-
|
|
470
|
-
expect(lastUpdateByNumberParams[50]?.state).toBe("closed");
|
|
471
|
-
expect(epic.status).toBe("COMPLETED");
|
|
472
|
-
});
|
|
473
|
-
|
|
474
|
-
test("updateEpic on unknown ref throws descriptive error", async () => {
|
|
475
|
-
mockGetContent = null;
|
|
476
|
-
mockIssuesList = [];
|
|
477
|
-
|
|
478
|
-
const adapter = makeAdapter();
|
|
479
|
-
|
|
480
|
-
await expect(
|
|
481
|
-
adapter.updateEpic("FP-E999", { title: "New Title" }),
|
|
482
|
-
).rejects.toThrow("FP-E999");
|
|
483
|
-
});
|
|
484
|
-
|
|
485
|
-
test("updateEpic IN_PROGRESS adds status:in-progress label", async () => {
|
|
486
|
-
const epicIssue = makeIssue({
|
|
487
|
-
number: 55,
|
|
488
|
-
labels: ["flux:epic"],
|
|
489
|
-
body: '<!-- flux-meta\n{"ref":"FP-E55","prd_ref":"FP-P5"}\n-->',
|
|
490
|
-
});
|
|
491
|
-
|
|
492
|
-
const updatedEpicIssue = makeIssue({
|
|
493
|
-
number: 55,
|
|
494
|
-
labels: ["flux:epic", "status:in-progress"],
|
|
495
|
-
body: '<!-- flux-meta\n{"ref":"FP-E55","prd_ref":"FP-P5"}\n-->',
|
|
496
|
-
});
|
|
497
|
-
|
|
498
|
-
mockGetContent = {
|
|
499
|
-
data: {
|
|
500
|
-
sha: "sha1",
|
|
501
|
-
content: Buffer.from(JSON.stringify({ "FP-E55": 55 })).toString(
|
|
502
|
-
"base64",
|
|
503
|
-
),
|
|
504
|
-
encoding: "base64",
|
|
505
|
-
},
|
|
506
|
-
};
|
|
507
|
-
|
|
508
|
-
mockIssuesGet[55] = epicIssue;
|
|
509
|
-
mockIssuesUpdate = updatedEpicIssue;
|
|
510
|
-
|
|
511
|
-
const adapter = makeAdapter();
|
|
512
|
-
await adapter.updateEpic("FP-E55", { status: "IN_PROGRESS" });
|
|
513
|
-
|
|
514
|
-
const updatedLabels: string[] =
|
|
515
|
-
lastUpdateByNumberParams[55]?.labels ?? [];
|
|
516
|
-
expect(updatedLabels).toContain("status:in-progress");
|
|
517
|
-
});
|
|
518
|
-
});
|
|
519
|
-
|
|
520
|
-
describe("getEpic", () => {
|
|
521
|
-
test("returns null for unknown ref", async () => {
|
|
522
|
-
mockGetContent = null;
|
|
523
|
-
mockIssuesList = [];
|
|
524
|
-
|
|
525
|
-
const adapter = makeAdapter();
|
|
526
|
-
const result = await adapter.getEpic("FP-E999");
|
|
527
|
-
|
|
528
|
-
expect(result).toBeNull();
|
|
529
|
-
});
|
|
530
|
-
|
|
531
|
-
test("returns correct Epic with status parsed from labels", async () => {
|
|
532
|
-
const epicIssue = makeIssue({
|
|
533
|
-
number: 60,
|
|
534
|
-
title: "My Epic",
|
|
535
|
-
labels: ["flux:epic", "status:in-progress"],
|
|
536
|
-
body: '<!-- flux-meta\n{"ref":"FP-E60","prd_ref":"FP-P6"}\n-->',
|
|
537
|
-
});
|
|
538
|
-
|
|
539
|
-
mockGetContent = {
|
|
540
|
-
data: {
|
|
541
|
-
sha: "sha1",
|
|
542
|
-
content: Buffer.from(JSON.stringify({ "FP-E60": 60 })).toString(
|
|
543
|
-
"base64",
|
|
544
|
-
),
|
|
545
|
-
encoding: "base64",
|
|
546
|
-
},
|
|
547
|
-
};
|
|
548
|
-
mockIssuesGet[60] = epicIssue;
|
|
549
|
-
|
|
550
|
-
const adapter = makeAdapter();
|
|
551
|
-
const epic = await adapter.getEpic("FP-E60");
|
|
552
|
-
|
|
553
|
-
expect(epic).not.toBeNull();
|
|
554
|
-
expect(epic?.ref).toBe("FP-E60");
|
|
555
|
-
expect(epic?.status).toBe("IN_PROGRESS");
|
|
556
|
-
expect(epic?.prdId).toBe("FP-P6");
|
|
557
|
-
expect(epic?.title).toBe("My Epic");
|
|
558
|
-
});
|
|
559
|
-
});
|
|
560
|
-
|
|
561
|
-
describe("listEpics", () => {
|
|
562
|
-
test("listEpics filtered by prd_ref returns only epics belonging to that PRD", async () => {
|
|
563
|
-
makeIssue({
|
|
564
|
-
number: 70,
|
|
565
|
-
labels: ["flux:prd"],
|
|
566
|
-
body: '<!-- flux-meta\n{"ref":"FP-P7"}\n-->',
|
|
567
|
-
});
|
|
568
|
-
|
|
569
|
-
const epicIssue1 = makeIssue({
|
|
570
|
-
number: 71,
|
|
571
|
-
labels: ["flux:epic"],
|
|
572
|
-
body: '<!-- flux-meta\n{"ref":"FP-E71","prd_ref":"FP-P7"}\n-->',
|
|
573
|
-
});
|
|
574
|
-
|
|
575
|
-
const epicIssue2 = makeIssue({
|
|
576
|
-
number: 72,
|
|
577
|
-
labels: ["flux:epic"],
|
|
578
|
-
body: '<!-- flux-meta\n{"ref":"FP-E72","prd_ref":"FP-P7"}\n-->',
|
|
579
|
-
});
|
|
580
|
-
|
|
581
|
-
mockGetContent = {
|
|
582
|
-
data: {
|
|
583
|
-
sha: "sha1",
|
|
584
|
-
content: Buffer.from(JSON.stringify({ "FP-P7": 70 })).toString(
|
|
585
|
-
"base64",
|
|
586
|
-
),
|
|
587
|
-
encoding: "base64",
|
|
588
|
-
},
|
|
589
|
-
};
|
|
590
|
-
|
|
591
|
-
mockIssuesList = [epicIssue1, epicIssue2];
|
|
592
|
-
|
|
593
|
-
const adapter = makeAdapter();
|
|
594
|
-
const result = await adapter.listEpics({ prdRef: "FP-P7" });
|
|
595
|
-
|
|
596
|
-
expect(result.items.length).toBe(2);
|
|
597
|
-
expect(result.items[0].ref).toBe("FP-E71");
|
|
598
|
-
expect(result.items[1].ref).toBe("FP-E72");
|
|
599
|
-
});
|
|
600
|
-
|
|
601
|
-
test("listEpics without filter queries by flux:epic label", async () => {
|
|
602
|
-
const epicIssue = makeIssue({
|
|
603
|
-
number: 80,
|
|
604
|
-
labels: ["flux:epic"],
|
|
605
|
-
body: '<!-- flux-meta\n{"ref":"FP-E80","prd_ref":"FP-P8"}\n-->',
|
|
606
|
-
});
|
|
607
|
-
mockIssuesList = [epicIssue];
|
|
608
|
-
|
|
609
|
-
const adapter = makeAdapter();
|
|
610
|
-
const result = await adapter.listEpics();
|
|
611
|
-
|
|
612
|
-
expect(result.items.length).toBe(1);
|
|
613
|
-
expect(result.items[0].ref).toBe("FP-E80");
|
|
614
|
-
});
|
|
615
|
-
});
|
|
616
|
-
|
|
617
|
-
describe("deleteEpic", () => {
|
|
618
|
-
test("deleteEpic closes epic and all child task issues, returns correct cascade counts", async () => {
|
|
619
|
-
const epicIssue = makeIssue({
|
|
620
|
-
number: 90,
|
|
621
|
-
labels: ["flux:epic"],
|
|
622
|
-
body: '<!-- flux-meta\n{"ref":"FP-E90","prd_ref":"FP-P9"}\n-->',
|
|
623
|
-
});
|
|
624
|
-
|
|
625
|
-
const taskIssue1 = makeIssue({
|
|
626
|
-
number: 91,
|
|
627
|
-
labels: ["flux:task"],
|
|
628
|
-
body: '<!-- flux-meta\n{"ref":"FP-T91","epic_ref":"FP-E90"}\n-->',
|
|
629
|
-
});
|
|
630
|
-
|
|
631
|
-
const taskIssue2 = makeIssue({
|
|
632
|
-
number: 92,
|
|
633
|
-
labels: ["flux:task"],
|
|
634
|
-
body: '<!-- flux-meta\n{"ref":"FP-T92","epic_ref":"FP-E90"}\n-->',
|
|
635
|
-
});
|
|
636
|
-
|
|
637
|
-
mockGetContent = {
|
|
638
|
-
data: {
|
|
639
|
-
sha: "sha1",
|
|
640
|
-
content: Buffer.from(
|
|
641
|
-
JSON.stringify({ "FP-E90": 90, "FP-T91": 91, "FP-T92": 92 }),
|
|
642
|
-
).toString("base64"),
|
|
643
|
-
encoding: "base64",
|
|
644
|
-
},
|
|
645
|
-
};
|
|
646
|
-
|
|
647
|
-
mockIssuesList = [taskIssue1, taskIssue2];
|
|
648
|
-
mockIssuesGet[90] = epicIssue;
|
|
649
|
-
mockIssuesGet[91] = taskIssue1;
|
|
650
|
-
mockIssuesGet[92] = taskIssue2;
|
|
651
|
-
|
|
652
|
-
const closedIssues: number[] = [];
|
|
653
|
-
const adapter = makeAdapter();
|
|
654
|
-
(adapter as any).client.rest.issues.update = async (params: any) => {
|
|
655
|
-
closedIssues.push(params.issue_number);
|
|
656
|
-
lastUpdateByNumberParams[params.issue_number] = params;
|
|
657
|
-
return { data: { ...epicIssue, state: "closed" } };
|
|
658
|
-
};
|
|
659
|
-
|
|
660
|
-
const result = await adapter.deleteEpic("FP-E90");
|
|
661
|
-
|
|
662
|
-
expect(result.deleted).toBe("FP-E90");
|
|
663
|
-
expect(result.cascade.tasks).toBe(2);
|
|
664
|
-
expect(result.cascade.epics).toBe(0);
|
|
665
|
-
expect(closedIssues).toContain(90);
|
|
666
|
-
expect(closedIssues).toContain(91);
|
|
667
|
-
expect(closedIssues).toContain(92);
|
|
668
|
-
});
|
|
669
|
-
|
|
670
|
-
test("all deleted refs removed from index", async () => {
|
|
671
|
-
const epicIssue = makeIssue({
|
|
672
|
-
number: 95,
|
|
673
|
-
labels: ["flux:epic"],
|
|
674
|
-
body: '<!-- flux-meta\n{"ref":"FP-E95","prd_ref":"FP-P9"}\n-->',
|
|
675
|
-
});
|
|
676
|
-
|
|
677
|
-
const taskIssue = makeIssue({
|
|
678
|
-
number: 96,
|
|
679
|
-
labels: ["flux:task"],
|
|
680
|
-
body: '<!-- flux-meta\n{"ref":"FP-T96","epic_ref":"FP-E95"}\n-->',
|
|
681
|
-
});
|
|
682
|
-
|
|
683
|
-
const indexData = { "FP-E95": 95, "FP-T96": 96 };
|
|
684
|
-
mockGetContent = {
|
|
685
|
-
data: {
|
|
686
|
-
sha: "sha1",
|
|
687
|
-
content: Buffer.from(JSON.stringify(indexData)).toString("base64"),
|
|
688
|
-
encoding: "base64",
|
|
689
|
-
},
|
|
690
|
-
};
|
|
691
|
-
|
|
692
|
-
mockSubIssues[95] = [taskIssue];
|
|
693
|
-
mockIssuesGet[95] = epicIssue;
|
|
694
|
-
mockIssuesGet[96] = taskIssue;
|
|
695
|
-
|
|
696
|
-
const adapter = makeAdapter();
|
|
697
|
-
(adapter as any).client.rest.issues.update = async (_params: any) => ({
|
|
698
|
-
data: { ...epicIssue, state: "closed" },
|
|
699
|
-
});
|
|
700
|
-
|
|
701
|
-
await adapter.deleteEpic("FP-E95");
|
|
702
|
-
|
|
703
|
-
const written = JSON.parse(
|
|
704
|
-
Buffer.from(lastPutParams.content, "base64").toString("utf-8"),
|
|
705
|
-
);
|
|
706
|
-
expect(written["FP-E95"]).toBeUndefined();
|
|
707
|
-
});
|
|
708
|
-
});
|
|
709
|
-
});
|
|
710
|
-
|
|
711
|
-
describe("GitHubAdapter Task CRUD", () => {
|
|
712
|
-
beforeEach(() => {
|
|
713
|
-
mockIssuesCreate = null;
|
|
714
|
-
mockIssuesUpdate = null;
|
|
715
|
-
mockIssuesGet = {};
|
|
716
|
-
mockIssuesList = [];
|
|
717
|
-
mockSubIssues = {};
|
|
718
|
-
mockGetContent = null;
|
|
719
|
-
_lastCreateParams = null;
|
|
720
|
-
_lastUpdateParams = null;
|
|
721
|
-
lastUpdateByNumberParams = {};
|
|
722
|
-
lastPutParams = null;
|
|
723
|
-
lastAddSubIssueParams = [];
|
|
724
|
-
issueNodeIdCounter = 0;
|
|
725
|
-
mockGraphqlFn = async () => ({
|
|
726
|
-
addProjectV2ItemById: { item: { id: "PVTI_123" } },
|
|
727
|
-
});
|
|
728
|
-
});
|
|
729
|
-
|
|
730
|
-
afterEach(() => {});
|
|
731
|
-
|
|
732
|
-
describe("createTask", () => {
|
|
733
|
-
test("returns Task with correct epicId and priority", async () => {
|
|
734
|
-
const epicIssue = makeIssue({
|
|
735
|
-
number: 100,
|
|
736
|
-
labels: ["flux:epic"],
|
|
737
|
-
body: '<!-- flux-meta\n{"ref":"FP-E100","prd_ref":"FP-P10"}\n-->',
|
|
738
|
-
});
|
|
739
|
-
|
|
740
|
-
const taskIssue = makeIssue({
|
|
741
|
-
number: 101,
|
|
742
|
-
title: "My Task",
|
|
743
|
-
labels: ["flux:task", "priority:high"],
|
|
744
|
-
body: '<!-- flux-meta\n{"ref":"FP-T101","epic_ref":"FP-E100","prd_ref":"FP-P10","priority":"HIGH","dependencies":[]}\n-->',
|
|
745
|
-
node_id: "NODE_TASK_101",
|
|
746
|
-
});
|
|
747
|
-
|
|
748
|
-
mockGetContent = {
|
|
749
|
-
data: {
|
|
750
|
-
sha: "sha1",
|
|
751
|
-
content: Buffer.from(JSON.stringify({ "FP-E100": 100 })).toString(
|
|
752
|
-
"base64",
|
|
753
|
-
),
|
|
754
|
-
encoding: "base64",
|
|
755
|
-
},
|
|
756
|
-
};
|
|
757
|
-
|
|
758
|
-
mockIssuesCreate = taskIssue;
|
|
759
|
-
mockIssuesUpdate = taskIssue;
|
|
760
|
-
mockIssuesGet[100] = epicIssue;
|
|
761
|
-
mockIssuesGet[101] = taskIssue;
|
|
762
|
-
|
|
763
|
-
const adapter = makeAdapter();
|
|
764
|
-
const task = await adapter.createTask({
|
|
765
|
-
epicRef: "FP-E100",
|
|
766
|
-
title: "My Task",
|
|
767
|
-
priority: "HIGH",
|
|
768
|
-
});
|
|
769
|
-
|
|
770
|
-
expect(task.ref).toBe("FP-T101");
|
|
771
|
-
expect(task.epicId).toBe("FP-E100");
|
|
772
|
-
expect(task.priority).toBe("HIGH");
|
|
773
|
-
expect(task.title).toBe("My Task");
|
|
774
|
-
});
|
|
775
|
-
|
|
776
|
-
test("created task issue is a sub-issue of the epic issue", async () => {
|
|
777
|
-
const epicIssue = makeIssue({
|
|
778
|
-
number: 110,
|
|
779
|
-
labels: ["flux:epic"],
|
|
780
|
-
body: '<!-- flux-meta\n{"ref":"FP-E110","prd_ref":"FP-P11"}\n-->',
|
|
781
|
-
});
|
|
782
|
-
|
|
783
|
-
const taskIssue = makeIssue({
|
|
784
|
-
number: 111,
|
|
785
|
-
labels: ["flux:task", "priority:medium"],
|
|
786
|
-
body: '<!-- flux-meta\n{"ref":"FP-T111","epic_ref":"FP-E110","prd_ref":"FP-P11","priority":"MEDIUM","dependencies":[]}\n-->',
|
|
787
|
-
node_id: "NODE_111",
|
|
788
|
-
});
|
|
789
|
-
|
|
790
|
-
mockGetContent = {
|
|
791
|
-
data: {
|
|
792
|
-
sha: "sha1",
|
|
793
|
-
content: Buffer.from(JSON.stringify({ "FP-E110": 110 })).toString(
|
|
794
|
-
"base64",
|
|
795
|
-
),
|
|
796
|
-
encoding: "base64",
|
|
797
|
-
},
|
|
798
|
-
};
|
|
799
|
-
|
|
800
|
-
mockIssuesCreate = taskIssue;
|
|
801
|
-
mockIssuesUpdate = taskIssue;
|
|
802
|
-
mockIssuesGet[110] = epicIssue;
|
|
803
|
-
mockIssuesGet[111] = taskIssue;
|
|
804
|
-
|
|
805
|
-
const adapter = makeAdapter();
|
|
806
|
-
await adapter.createTask({ epicRef: "FP-E110", title: "Sub-task" });
|
|
807
|
-
|
|
808
|
-
expect(lastAddSubIssueParams.length).toBeGreaterThan(0);
|
|
809
|
-
const subIssueCall = lastAddSubIssueParams[0];
|
|
810
|
-
expect(subIssueCall.issue_number).toBe(110);
|
|
811
|
-
expect(subIssueCall.sub_issue_id).toBe(111000);
|
|
812
|
-
});
|
|
813
|
-
|
|
814
|
-
test("flux-meta contains ref, epic_ref, prd_ref, priority", async () => {
|
|
815
|
-
const epicIssue = makeIssue({
|
|
816
|
-
number: 120,
|
|
817
|
-
labels: ["flux:epic"],
|
|
818
|
-
body: '<!-- flux-meta\n{"ref":"FP-E120","prd_ref":"FP-P12"}\n-->',
|
|
819
|
-
});
|
|
820
|
-
|
|
821
|
-
const taskIssue = makeIssue({
|
|
822
|
-
number: 121,
|
|
823
|
-
labels: ["flux:task", "priority:low"],
|
|
824
|
-
body: '<!-- flux-meta\n{"ref":"FP-T121","epic_ref":"FP-E120","prd_ref":"FP-P12","priority":"LOW","dependencies":[]}\n-->',
|
|
825
|
-
node_id: "NODE_121",
|
|
826
|
-
});
|
|
827
|
-
|
|
828
|
-
mockGetContent = {
|
|
829
|
-
data: {
|
|
830
|
-
sha: "sha1",
|
|
831
|
-
content: Buffer.from(JSON.stringify({ "FP-E120": 120 })).toString(
|
|
832
|
-
"base64",
|
|
833
|
-
),
|
|
834
|
-
encoding: "base64",
|
|
835
|
-
},
|
|
836
|
-
};
|
|
837
|
-
|
|
838
|
-
mockIssuesCreate = taskIssue;
|
|
839
|
-
mockIssuesUpdate = taskIssue;
|
|
840
|
-
mockIssuesGet[120] = epicIssue;
|
|
841
|
-
mockIssuesGet[121] = taskIssue;
|
|
842
|
-
|
|
843
|
-
const adapter = makeAdapter();
|
|
844
|
-
await adapter.createTask({
|
|
845
|
-
epicRef: "FP-E120",
|
|
846
|
-
title: "Meta Check Task",
|
|
847
|
-
priority: "LOW",
|
|
848
|
-
});
|
|
849
|
-
|
|
850
|
-
const updateCall = lastUpdateByNumberParams[121];
|
|
851
|
-
expect(updateCall).toBeDefined();
|
|
852
|
-
expect(updateCall.body).toContain('"ref":"FP-T121"');
|
|
853
|
-
expect(updateCall.body).toContain('"epic_ref":"FP-E120"');
|
|
854
|
-
expect(updateCall.body).toContain('"prd_ref":"FP-P12"');
|
|
855
|
-
expect(updateCall.body).toContain('"priority":"LOW"');
|
|
856
|
-
});
|
|
857
|
-
});
|
|
858
|
-
|
|
859
|
-
describe("updateTask", () => {
|
|
860
|
-
test("updateTask status COMPLETED closes the issue", async () => {
|
|
861
|
-
const taskIssue = makeIssue({
|
|
862
|
-
number: 130,
|
|
863
|
-
labels: ["flux:task", "priority:medium"],
|
|
864
|
-
body: '<!-- flux-meta\n{"ref":"FP-T130","epic_ref":"FP-E13","prd_ref":"FP-P13","priority":"MEDIUM"}\n-->',
|
|
865
|
-
});
|
|
866
|
-
|
|
867
|
-
const closedTaskIssue = {
|
|
868
|
-
...taskIssue,
|
|
869
|
-
state: "closed" as const,
|
|
870
|
-
};
|
|
871
|
-
|
|
872
|
-
mockGetContent = {
|
|
873
|
-
data: {
|
|
874
|
-
sha: "sha1",
|
|
875
|
-
content: Buffer.from(JSON.stringify({ "FP-T130": 130 })).toString(
|
|
876
|
-
"base64",
|
|
877
|
-
),
|
|
878
|
-
encoding: "base64",
|
|
879
|
-
},
|
|
880
|
-
};
|
|
881
|
-
|
|
882
|
-
mockIssuesGet[130] = taskIssue;
|
|
883
|
-
mockIssuesUpdate = closedTaskIssue;
|
|
884
|
-
|
|
885
|
-
const adapter = makeAdapter();
|
|
886
|
-
const task = await adapter.updateTask("FP-T130", { status: "COMPLETED" });
|
|
887
|
-
|
|
888
|
-
expect(lastUpdateByNumberParams[130]?.state).toBe("closed");
|
|
889
|
-
expect(task.status).toBe("COMPLETED");
|
|
890
|
-
});
|
|
891
|
-
|
|
892
|
-
test("updateTask priority change swaps old priority label for new one", async () => {
|
|
893
|
-
const taskIssue = makeIssue({
|
|
894
|
-
number: 135,
|
|
895
|
-
labels: ["flux:task", "priority:low"],
|
|
896
|
-
body: '<!-- flux-meta\n{"ref":"FP-T135","epic_ref":"FP-E13","prd_ref":"FP-P13","priority":"LOW"}\n-->',
|
|
897
|
-
});
|
|
898
|
-
|
|
899
|
-
const updatedTaskIssue = makeIssue({
|
|
900
|
-
number: 135,
|
|
901
|
-
labels: ["flux:task", "priority:high"],
|
|
902
|
-
body: '<!-- flux-meta\n{"ref":"FP-T135","epic_ref":"FP-E13","prd_ref":"FP-P13","priority":"HIGH"}\n-->',
|
|
903
|
-
});
|
|
904
|
-
|
|
905
|
-
mockGetContent = {
|
|
906
|
-
data: {
|
|
907
|
-
sha: "sha1",
|
|
908
|
-
content: Buffer.from(JSON.stringify({ "FP-T135": 135 })).toString(
|
|
909
|
-
"base64",
|
|
910
|
-
),
|
|
911
|
-
encoding: "base64",
|
|
912
|
-
},
|
|
913
|
-
};
|
|
914
|
-
|
|
915
|
-
mockIssuesGet[135] = taskIssue;
|
|
916
|
-
mockIssuesUpdate = updatedTaskIssue;
|
|
917
|
-
|
|
918
|
-
const adapter = makeAdapter();
|
|
919
|
-
await adapter.updateTask("FP-T135", { priority: "HIGH" });
|
|
920
|
-
|
|
921
|
-
const updatedLabels: string[] =
|
|
922
|
-
lastUpdateByNumberParams[135]?.labels ?? [];
|
|
923
|
-
expect(updatedLabels).toContain("priority:high");
|
|
924
|
-
expect(updatedLabels).not.toContain("priority:low");
|
|
925
|
-
});
|
|
926
|
-
});
|
|
927
|
-
|
|
928
|
-
describe("getTask", () => {
|
|
929
|
-
test("returns null for unknown ref", async () => {
|
|
930
|
-
mockGetContent = null;
|
|
931
|
-
mockIssuesList = [];
|
|
932
|
-
|
|
933
|
-
const adapter = makeAdapter();
|
|
934
|
-
const result = await adapter.getTask("FP-T999");
|
|
935
|
-
|
|
936
|
-
expect(result).toBeNull();
|
|
937
|
-
});
|
|
938
|
-
|
|
939
|
-
test("returns correct Task with status and priority parsed from labels", async () => {
|
|
940
|
-
const taskIssue = makeIssue({
|
|
941
|
-
number: 140,
|
|
942
|
-
title: "My Task",
|
|
943
|
-
labels: ["flux:task", "status:in-progress", "priority:high"],
|
|
944
|
-
body: '<!-- flux-meta\n{"ref":"FP-T140","epic_ref":"FP-E14","prd_ref":"FP-P14","priority":"HIGH"}\n-->',
|
|
945
|
-
});
|
|
946
|
-
|
|
947
|
-
mockGetContent = {
|
|
948
|
-
data: {
|
|
949
|
-
sha: "sha1",
|
|
950
|
-
content: Buffer.from(JSON.stringify({ "FP-T140": 140 })).toString(
|
|
951
|
-
"base64",
|
|
952
|
-
),
|
|
953
|
-
encoding: "base64",
|
|
954
|
-
},
|
|
955
|
-
};
|
|
956
|
-
mockIssuesGet[140] = taskIssue;
|
|
957
|
-
|
|
958
|
-
const adapter = makeAdapter();
|
|
959
|
-
const task = await adapter.getTask("FP-T140");
|
|
960
|
-
|
|
961
|
-
expect(task).not.toBeNull();
|
|
962
|
-
expect(task?.ref).toBe("FP-T140");
|
|
963
|
-
expect(task?.status).toBe("IN_PROGRESS");
|
|
964
|
-
expect(task?.priority).toBe("HIGH");
|
|
965
|
-
expect(task?.epicId).toBe("FP-E14");
|
|
966
|
-
});
|
|
967
|
-
});
|
|
968
|
-
|
|
969
|
-
describe("listTasks", () => {
|
|
970
|
-
test("listTasks filtered by epic_ref returns only tasks belonging to that epic", async () => {
|
|
971
|
-
makeIssue({
|
|
972
|
-
number: 150,
|
|
973
|
-
labels: ["flux:epic"],
|
|
974
|
-
body: '<!-- flux-meta\n{"ref":"FP-E150","prd_ref":"FP-P15"}\n-->',
|
|
975
|
-
});
|
|
976
|
-
|
|
977
|
-
const taskIssue1 = makeIssue({
|
|
978
|
-
number: 151,
|
|
979
|
-
labels: ["flux:task", "priority:medium"],
|
|
980
|
-
body: '<!-- flux-meta\n{"ref":"FP-T151","epic_ref":"FP-E150"}\n-->',
|
|
981
|
-
});
|
|
982
|
-
|
|
983
|
-
const taskIssue2 = makeIssue({
|
|
984
|
-
number: 152,
|
|
985
|
-
labels: ["flux:task", "priority:low"],
|
|
986
|
-
body: '<!-- flux-meta\n{"ref":"FP-T152","epic_ref":"FP-E150"}\n-->',
|
|
987
|
-
});
|
|
988
|
-
|
|
989
|
-
mockGetContent = {
|
|
990
|
-
data: {
|
|
991
|
-
sha: "sha1",
|
|
992
|
-
content: Buffer.from(JSON.stringify({ "FP-E150": 150 })).toString(
|
|
993
|
-
"base64",
|
|
994
|
-
),
|
|
995
|
-
encoding: "base64",
|
|
996
|
-
},
|
|
997
|
-
};
|
|
998
|
-
|
|
999
|
-
mockIssuesList = [taskIssue1, taskIssue2];
|
|
1000
|
-
|
|
1001
|
-
const adapter = makeAdapter();
|
|
1002
|
-
const result = await adapter.listTasks({ epicRef: "FP-E150" });
|
|
1003
|
-
|
|
1004
|
-
expect(result.items.length).toBe(2);
|
|
1005
|
-
expect(result.items[0].ref).toBe("FP-T151");
|
|
1006
|
-
expect(result.items[1].ref).toBe("FP-T152");
|
|
1007
|
-
});
|
|
1008
|
-
|
|
1009
|
-
test("listTasks filtered by status returns correct subset", async () => {
|
|
1010
|
-
const pendingTask = makeIssue({
|
|
1011
|
-
number: 160,
|
|
1012
|
-
labels: ["flux:task", "priority:medium"],
|
|
1013
|
-
body: '<!-- flux-meta\n{"ref":"FP-T160","epic_ref":"FP-E16"}\n-->',
|
|
1014
|
-
state: "open",
|
|
1015
|
-
});
|
|
1016
|
-
|
|
1017
|
-
const inProgressTask = makeIssue({
|
|
1018
|
-
number: 161,
|
|
1019
|
-
labels: ["flux:task", "status:in-progress", "priority:high"],
|
|
1020
|
-
body: '<!-- flux-meta\n{"ref":"FP-T161","epic_ref":"FP-E16"}\n-->',
|
|
1021
|
-
state: "open",
|
|
1022
|
-
});
|
|
1023
|
-
|
|
1024
|
-
mockIssuesList = [pendingTask, inProgressTask];
|
|
1025
|
-
|
|
1026
|
-
const adapter = makeAdapter();
|
|
1027
|
-
const result = await adapter.listTasks({ status: "IN_PROGRESS" });
|
|
1028
|
-
|
|
1029
|
-
expect(result.items.length).toBe(1);
|
|
1030
|
-
expect(result.items[0].status).toBe("IN_PROGRESS");
|
|
1031
|
-
});
|
|
1032
|
-
});
|
|
1033
|
-
|
|
1034
|
-
describe("deleteTask", () => {
|
|
1035
|
-
test("deleteTask closes issue and removes ref from index", async () => {
|
|
1036
|
-
const taskIssue = makeIssue({
|
|
1037
|
-
number: 170,
|
|
1038
|
-
labels: ["flux:task", "priority:medium"],
|
|
1039
|
-
body: '<!-- flux-meta\n{"ref":"FP-T170","epic_ref":"FP-E17"}\n-->',
|
|
1040
|
-
});
|
|
1041
|
-
|
|
1042
|
-
const indexData = { "FP-T170": 170 };
|
|
1043
|
-
mockGetContent = {
|
|
1044
|
-
data: {
|
|
1045
|
-
sha: "sha1",
|
|
1046
|
-
content: Buffer.from(JSON.stringify(indexData)).toString("base64"),
|
|
1047
|
-
encoding: "base64",
|
|
1048
|
-
},
|
|
1049
|
-
};
|
|
1050
|
-
|
|
1051
|
-
mockIssuesGet[170] = taskIssue;
|
|
1052
|
-
|
|
1053
|
-
const closedIssues: number[] = [];
|
|
1054
|
-
const adapter = makeAdapter();
|
|
1055
|
-
(adapter as any).client.rest.issues.update = async (params: any) => {
|
|
1056
|
-
closedIssues.push(params.issue_number);
|
|
1057
|
-
return { data: { ...taskIssue, state: "closed" } };
|
|
1058
|
-
};
|
|
1059
|
-
|
|
1060
|
-
const result = await adapter.deleteTask("FP-T170");
|
|
1061
|
-
|
|
1062
|
-
expect(result.deleted).toBe("FP-T170");
|
|
1063
|
-
expect(result.cascade.tasks).toBe(0);
|
|
1064
|
-
expect(closedIssues).toContain(170);
|
|
1065
|
-
|
|
1066
|
-
const written = JSON.parse(
|
|
1067
|
-
Buffer.from(lastPutParams.content, "base64").toString("utf-8"),
|
|
1068
|
-
);
|
|
1069
|
-
expect(written["FP-T170"]).toBeUndefined();
|
|
1070
|
-
});
|
|
1071
|
-
});
|
|
1072
|
-
});
|