@qelos/aidev 0.5.1 → 0.5.3
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/.aidev/assets/86c8yjxrr/9ea11c36-311c-4022-889c-1bb0915122dc.jpg-40e6939e3b68e864260f7ae7e85bd623_exif.jpg +0 -0
- package/.env.aidev.example +92 -84
- package/CONTRIBUTING.md +78 -78
- package/LICENSE +21 -21
- package/README.md +735 -622
- package/dist/autoCompress.d.ts +54 -0
- package/dist/autoCompress.d.ts.map +1 -0
- package/dist/autoCompress.js +300 -0
- package/dist/autoCompress.js.map +1 -0
- package/dist/cli.js +13 -0
- package/dist/cli.js.map +1 -1
- package/dist/commands/accepted.d.ts +7 -2
- package/dist/commands/accepted.d.ts.map +1 -1
- package/dist/commands/accepted.js +17 -2
- package/dist/commands/accepted.js.map +1 -1
- package/dist/commands/help.d.ts.map +1 -1
- package/dist/commands/help.js +80 -67
- package/dist/commands/help.js.map +1 -1
- package/dist/commands/init.js +105 -105
- package/dist/commands/run.d.ts +9 -1
- package/dist/commands/run.d.ts.map +1 -1
- package/dist/commands/run.js +307 -140
- package/dist/commands/run.js.map +1 -1
- package/dist/commands/tasks.d.ts +1 -0
- package/dist/commands/tasks.d.ts.map +1 -1
- package/dist/commands/tasks.js +29 -0
- package/dist/commands/tasks.js.map +1 -1
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +6 -0
- package/dist/config.js.map +1 -1
- package/dist/github.js +27 -27
- package/dist/hooks.d.ts +1 -1
- package/dist/hooks.d.ts.map +1 -1
- package/dist/providers/linear.d.ts +4 -1
- package/dist/providers/linear.d.ts.map +1 -1
- package/dist/providers/linear.js +142 -78
- package/dist/providers/linear.js.map +1 -1
- package/dist/providers/monday.js +39 -39
- package/dist/sessions.d.ts +20 -0
- package/dist/sessions.d.ts.map +1 -0
- package/dist/sessions.js +171 -0
- package/dist/sessions.js.map +1 -0
- package/dist/types.d.ts +2 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +51 -51
- package/scripts/run-tests.cjs +18 -18
- package/dist/__tests__/ai-runners.test.d.ts +0 -2
- package/dist/__tests__/ai-runners.test.d.ts.map +0 -1
- package/dist/__tests__/ai-runners.test.js +0 -367
- package/dist/__tests__/ai-runners.test.js.map +0 -1
- package/dist/__tests__/clickup-format.test.d.ts +0 -2
- package/dist/__tests__/clickup-format.test.d.ts.map +0 -1
- package/dist/__tests__/clickup-format.test.js +0 -256
- package/dist/__tests__/clickup-format.test.js.map +0 -1
- package/dist/__tests__/config.test.d.ts +0 -2
- package/dist/__tests__/config.test.d.ts.map +0 -1
- package/dist/__tests__/config.test.js +0 -418
- package/dist/__tests__/config.test.js.map +0 -1
- package/dist/__tests__/git.test.d.ts +0 -2
- package/dist/__tests__/git.test.d.ts.map +0 -1
- package/dist/__tests__/git.test.js +0 -697
- package/dist/__tests__/git.test.js.map +0 -1
- package/dist/__tests__/github.test.d.ts +0 -2
- package/dist/__tests__/github.test.d.ts.map +0 -1
- package/dist/__tests__/github.test.js +0 -273
- package/dist/__tests__/github.test.js.map +0 -1
- package/dist/__tests__/help.test.d.ts +0 -2
- package/dist/__tests__/help.test.d.ts.map +0 -1
- package/dist/__tests__/help.test.js +0 -34
- package/dist/__tests__/help.test.js.map +0 -1
- package/dist/__tests__/hooks.test.d.ts +0 -2
- package/dist/__tests__/hooks.test.d.ts.map +0 -1
- package/dist/__tests__/hooks.test.js +0 -381
- package/dist/__tests__/hooks.test.js.map +0 -1
- package/dist/__tests__/init.test.d.ts +0 -2
- package/dist/__tests__/init.test.d.ts.map +0 -1
- package/dist/__tests__/init.test.js +0 -596
- package/dist/__tests__/init.test.js.map +0 -1
- package/dist/__tests__/local-provider.test.d.ts +0 -2
- package/dist/__tests__/local-provider.test.d.ts.map +0 -1
- package/dist/__tests__/local-provider.test.js +0 -631
- package/dist/__tests__/local-provider.test.js.map +0 -1
- package/dist/__tests__/lockfile.test.d.ts +0 -2
- package/dist/__tests__/lockfile.test.d.ts.map +0 -1
- package/dist/__tests__/lockfile.test.js +0 -144
- package/dist/__tests__/lockfile.test.js.map +0 -1
- package/dist/__tests__/permissions.test.d.ts +0 -2
- package/dist/__tests__/permissions.test.d.ts.map +0 -1
- package/dist/__tests__/permissions.test.js +0 -151
- package/dist/__tests__/permissions.test.js.map +0 -1
- package/dist/__tests__/platform.test.d.ts +0 -2
- package/dist/__tests__/platform.test.d.ts.map +0 -1
- package/dist/__tests__/platform.test.js +0 -329
- package/dist/__tests__/platform.test.js.map +0 -1
- package/dist/__tests__/providers.test.d.ts +0 -2
- package/dist/__tests__/providers.test.d.ts.map +0 -1
- package/dist/__tests__/providers.test.js +0 -925
- package/dist/__tests__/providers.test.js.map +0 -1
- package/dist/__tests__/run.test.d.ts +0 -2
- package/dist/__tests__/run.test.d.ts.map +0 -1
- package/dist/__tests__/run.test.js +0 -767
- package/dist/__tests__/run.test.js.map +0 -1
- package/dist/__tests__/schedule.test.d.ts +0 -2
- package/dist/__tests__/schedule.test.d.ts.map +0 -1
- package/dist/__tests__/schedule.test.js +0 -258
- package/dist/__tests__/schedule.test.js.map +0 -1
|
@@ -1,925 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
-
if (k2 === undefined) k2 = k;
|
|
4
|
-
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
-
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
-
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
-
}
|
|
8
|
-
Object.defineProperty(o, k2, desc);
|
|
9
|
-
}) : (function(o, m, k, k2) {
|
|
10
|
-
if (k2 === undefined) k2 = k;
|
|
11
|
-
o[k2] = m[k];
|
|
12
|
-
}));
|
|
13
|
-
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
-
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
-
}) : function(o, v) {
|
|
16
|
-
o["default"] = v;
|
|
17
|
-
});
|
|
18
|
-
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
-
var ownKeys = function(o) {
|
|
20
|
-
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
-
var ar = [];
|
|
22
|
-
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
-
return ar;
|
|
24
|
-
};
|
|
25
|
-
return ownKeys(o);
|
|
26
|
-
};
|
|
27
|
-
return function (mod) {
|
|
28
|
-
if (mod && mod.__esModule) return mod;
|
|
29
|
-
var result = {};
|
|
30
|
-
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
-
__setModuleDefault(result, mod);
|
|
32
|
-
return result;
|
|
33
|
-
};
|
|
34
|
-
})();
|
|
35
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
36
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
|
-
};
|
|
38
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
-
const fs = __importStar(require("node:fs"));
|
|
40
|
-
const os = __importStar(require("node:os"));
|
|
41
|
-
const path = __importStar(require("node:path"));
|
|
42
|
-
const node_test_1 = require("node:test");
|
|
43
|
-
const strict_1 = __importDefault(require("node:assert/strict"));
|
|
44
|
-
const node_test_2 = require("node:test");
|
|
45
|
-
const clickup_1 = require("../providers/clickup");
|
|
46
|
-
const jira_1 = require("../providers/jira");
|
|
47
|
-
const linear_1 = require("../providers/linear");
|
|
48
|
-
const monday_1 = require("../providers/monday");
|
|
49
|
-
const notion_1 = require("../providers/notion");
|
|
50
|
-
const trello_1 = require("../providers/trello");
|
|
51
|
-
const baseClickUpConfig = {
|
|
52
|
-
clickupApiKey: 'test-key',
|
|
53
|
-
clickupTeamId: 'team1',
|
|
54
|
-
clickupTag: 'aidev',
|
|
55
|
-
assigneeTag: '',
|
|
56
|
-
};
|
|
57
|
-
const baseJiraConfig = {
|
|
58
|
-
jiraBaseUrl: 'https://example.atlassian.net',
|
|
59
|
-
jiraEmail: 'test@example.com',
|
|
60
|
-
jiraApiToken: 'token',
|
|
61
|
-
jiraProject: 'PROJ',
|
|
62
|
-
jiraLabel: 'aidev',
|
|
63
|
-
assigneeTag: '',
|
|
64
|
-
};
|
|
65
|
-
const baseLinearConfig = {
|
|
66
|
-
linearApiKey: 'lin_api_test',
|
|
67
|
-
linearTeamId: 'team-uuid-123',
|
|
68
|
-
linearLabel: 'aidev',
|
|
69
|
-
linearPendingStatus: 'Backlog',
|
|
70
|
-
linearInReviewStatus: 'In Review',
|
|
71
|
-
assigneeTag: '',
|
|
72
|
-
};
|
|
73
|
-
const baseMondayConfig = {
|
|
74
|
-
mondayApiToken: 'test-token',
|
|
75
|
-
mondayBoardId: '12345',
|
|
76
|
-
mondayStatusColumnId: 'status',
|
|
77
|
-
mondayGroupId: 'topics',
|
|
78
|
-
clickupPendingStatus: 'Working on it',
|
|
79
|
-
clickupInReviewStatus: 'Done',
|
|
80
|
-
};
|
|
81
|
-
const baseNotionConfig = {
|
|
82
|
-
notionApiKey: 'test-notion-key',
|
|
83
|
-
notionDatabaseId: 'a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6',
|
|
84
|
-
notionStatusProperty: 'Status',
|
|
85
|
-
notionPendingStatus: 'pending',
|
|
86
|
-
notionInReviewStatus: 'review',
|
|
87
|
-
};
|
|
88
|
-
const baseTrelloConfig = {
|
|
89
|
-
trelloApiKey: 'trello-key',
|
|
90
|
-
trelloToken: 'trello-token',
|
|
91
|
-
trelloBoardId: 'board1',
|
|
92
|
-
trelloLabel: 'aidev',
|
|
93
|
-
trelloOpenList: 'To Do',
|
|
94
|
-
trelloPendingList: 'Blocked',
|
|
95
|
-
trelloInProgressList: 'Doing',
|
|
96
|
-
trelloInReviewList: 'In Review',
|
|
97
|
-
trelloOpenStatus: 'open',
|
|
98
|
-
trelloPendingStatus: 'pending',
|
|
99
|
-
trelloInReviewStatus: 'review',
|
|
100
|
-
};
|
|
101
|
-
function jsonResponse(body) {
|
|
102
|
-
return {
|
|
103
|
-
ok: true,
|
|
104
|
-
status: 200,
|
|
105
|
-
statusText: 'OK',
|
|
106
|
-
text: async () => JSON.stringify(body),
|
|
107
|
-
json: async () => body,
|
|
108
|
-
arrayBuffer: async () => Buffer.from(JSON.stringify(body)),
|
|
109
|
-
};
|
|
110
|
-
}
|
|
111
|
-
function binaryResponse(text) {
|
|
112
|
-
return {
|
|
113
|
-
ok: true,
|
|
114
|
-
status: 200,
|
|
115
|
-
statusText: 'OK',
|
|
116
|
-
text: async () => text,
|
|
117
|
-
json: async () => ({ text }),
|
|
118
|
-
arrayBuffer: async () => Buffer.from(text),
|
|
119
|
-
};
|
|
120
|
-
}
|
|
121
|
-
function mockFetch(body) {
|
|
122
|
-
node_test_2.mock.method(globalThis, 'fetch', async () => jsonResponse(body));
|
|
123
|
-
}
|
|
124
|
-
function withTempCwd(fn) {
|
|
125
|
-
const previous = process.cwd();
|
|
126
|
-
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'aidev-provider-test-'));
|
|
127
|
-
process.chdir(tmpDir);
|
|
128
|
-
return fn(tmpDir).finally(() => {
|
|
129
|
-
process.chdir(previous);
|
|
130
|
-
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
131
|
-
});
|
|
132
|
-
}
|
|
133
|
-
function mockJiraCommentsFetch(comments, attachments = []) {
|
|
134
|
-
node_test_2.mock.method(globalThis, 'fetch', async (input) => {
|
|
135
|
-
const url = String(input);
|
|
136
|
-
if (url.includes('?fields=attachment')) {
|
|
137
|
-
return jsonResponse({ fields: { attachment: attachments } });
|
|
138
|
-
}
|
|
139
|
-
return jsonResponse({ comments });
|
|
140
|
-
});
|
|
141
|
-
}
|
|
142
|
-
// ─── ClickUpProvider.getComments ─────────────────────────────────────────────
|
|
143
|
-
(0, node_test_1.describe)('ClickUpProvider.fetchTasks', () => {
|
|
144
|
-
(0, node_test_1.afterEach)(() => node_test_2.mock.restoreAll());
|
|
145
|
-
(0, node_test_1.it)('downloads task attachments and appends local asset paths to the description', async () => {
|
|
146
|
-
await withTempCwd(async (cwd) => {
|
|
147
|
-
node_test_2.mock.method(globalThis, 'fetch', async (input, init) => {
|
|
148
|
-
const url = String(input);
|
|
149
|
-
if (url.includes('/team/team1/task')) {
|
|
150
|
-
return jsonResponse({
|
|
151
|
-
tasks: [
|
|
152
|
-
{
|
|
153
|
-
id: 'task1',
|
|
154
|
-
name: 'Task with file',
|
|
155
|
-
description: 'Handle the attached screenshot.',
|
|
156
|
-
status: { status: 'open' },
|
|
157
|
-
priority: { id: '1' },
|
|
158
|
-
url: 'https://app.clickup.com/t/task1',
|
|
159
|
-
tags: [{ name: 'aidev' }],
|
|
160
|
-
},
|
|
161
|
-
],
|
|
162
|
-
});
|
|
163
|
-
}
|
|
164
|
-
if (url.endsWith('/task/task1')) {
|
|
165
|
-
return jsonResponse({
|
|
166
|
-
attachments: [
|
|
167
|
-
{
|
|
168
|
-
id: 'att1',
|
|
169
|
-
title: 'Screenshot 1.png',
|
|
170
|
-
url: 'https://files.example/screenshot.png',
|
|
171
|
-
},
|
|
172
|
-
],
|
|
173
|
-
});
|
|
174
|
-
}
|
|
175
|
-
strict_1.default.equal(init?.headers instanceof Object ? init.headers.Authorization : undefined, 'test-key');
|
|
176
|
-
return binaryResponse('image-bytes');
|
|
177
|
-
});
|
|
178
|
-
const provider = new clickup_1.ClickUpProvider(baseClickUpConfig);
|
|
179
|
-
const tasks = await provider.fetchTasks();
|
|
180
|
-
strict_1.default.equal(tasks.length, 1);
|
|
181
|
-
strict_1.default.match(tasks[0].description, /Local asset files/);
|
|
182
|
-
strict_1.default.match(tasks[0].description, /\.aidev\/assets\/task1\/att1-Screenshot-1\.png/);
|
|
183
|
-
strict_1.default.ok(fs.existsSync(path.join(cwd, '.aidev', 'assets', 'task1', 'att1-Screenshot-1.png')));
|
|
184
|
-
});
|
|
185
|
-
});
|
|
186
|
-
});
|
|
187
|
-
(0, node_test_1.describe)('ClickUpProvider.fetchTasks — wildcard tag', () => {
|
|
188
|
-
(0, node_test_1.afterEach)(() => node_test_2.mock.restoreAll());
|
|
189
|
-
(0, node_test_1.it)('omits tag filter from API call when tag is "*"', async () => {
|
|
190
|
-
await withTempCwd(async () => {
|
|
191
|
-
let capturedUrl = '';
|
|
192
|
-
node_test_2.mock.method(globalThis, 'fetch', async (input) => {
|
|
193
|
-
const url = String(input);
|
|
194
|
-
if (url.includes('/team/team1/task')) {
|
|
195
|
-
capturedUrl = url;
|
|
196
|
-
return jsonResponse({
|
|
197
|
-
tasks: [
|
|
198
|
-
{
|
|
199
|
-
id: 'task1',
|
|
200
|
-
name: 'Any-tag task',
|
|
201
|
-
description: 'No specific tag required.',
|
|
202
|
-
status: { status: 'open' },
|
|
203
|
-
priority: { id: '1' },
|
|
204
|
-
url: 'https://app.clickup.com/t/task1',
|
|
205
|
-
tags: [{ name: 'random' }],
|
|
206
|
-
},
|
|
207
|
-
],
|
|
208
|
-
});
|
|
209
|
-
}
|
|
210
|
-
if (url.endsWith('/task/task1')) {
|
|
211
|
-
return jsonResponse({ attachments: [] });
|
|
212
|
-
}
|
|
213
|
-
return jsonResponse({});
|
|
214
|
-
});
|
|
215
|
-
const provider = new clickup_1.ClickUpProvider({
|
|
216
|
-
...baseClickUpConfig,
|
|
217
|
-
clickupTag: '*',
|
|
218
|
-
});
|
|
219
|
-
const tasks = await provider.fetchTasks();
|
|
220
|
-
strict_1.default.equal(tasks.length, 1);
|
|
221
|
-
strict_1.default.equal(tasks[0].name, 'Any-tag task');
|
|
222
|
-
// The URL should NOT contain a tags[] parameter
|
|
223
|
-
strict_1.default.ok(!capturedUrl.includes('tags%5B%5D'), 'should omit tags[] filter when tag is "*"');
|
|
224
|
-
strict_1.default.ok(!capturedUrl.includes('tags[]='), 'should omit tags[] filter when tag is "*"');
|
|
225
|
-
});
|
|
226
|
-
});
|
|
227
|
-
});
|
|
228
|
-
(0, node_test_1.describe)('ClickUpProvider.getComments', () => {
|
|
229
|
-
(0, node_test_1.afterEach)(() => node_test_2.mock.restoreAll());
|
|
230
|
-
(0, node_test_1.it)('sorts newest-first API response into oldest-first (ascending by date)', async () => {
|
|
231
|
-
mockFetch({
|
|
232
|
-
comments: [
|
|
233
|
-
// ClickUp returns newest first
|
|
234
|
-
{ id: '3', comment_text: 'aidev-continue', comment: [], user: { username: 'user', id: 1 }, date: '3000' },
|
|
235
|
-
{ id: '2', comment_text: 'Some answer', comment: [], user: { username: 'user', id: 1 }, date: '2000' },
|
|
236
|
-
{ id: '1', comment_text: '[aidev] Clarification question', comment: [], user: { username: 'bot', id: 2 }, date: '1000' },
|
|
237
|
-
],
|
|
238
|
-
});
|
|
239
|
-
const provider = new clickup_1.ClickUpProvider(baseClickUpConfig);
|
|
240
|
-
const comments = await provider.getComments('task1');
|
|
241
|
-
strict_1.default.equal(comments[0].text, '[aidev] Clarification question'); // oldest first
|
|
242
|
-
strict_1.default.equal(comments[2].text, 'aidev-continue'); // newest last
|
|
243
|
-
});
|
|
244
|
-
(0, node_test_1.it)('uses comment_text when it is a plain string', async () => {
|
|
245
|
-
mockFetch({
|
|
246
|
-
comments: [
|
|
247
|
-
{ id: '1', comment_text: 'hello world', comment: [], user: { username: 'user', id: 1 }, date: '1000' },
|
|
248
|
-
],
|
|
249
|
-
});
|
|
250
|
-
const provider = new clickup_1.ClickUpProvider(baseClickUpConfig);
|
|
251
|
-
const comments = await provider.getComments('task1');
|
|
252
|
-
strict_1.default.equal(comments[0].text, 'hello world');
|
|
253
|
-
});
|
|
254
|
-
(0, node_test_1.it)('falls back to comment array when comment_text is empty (rich-text editor)', async () => {
|
|
255
|
-
mockFetch({
|
|
256
|
-
comments: [
|
|
257
|
-
{
|
|
258
|
-
id: '1',
|
|
259
|
-
comment_text: '',
|
|
260
|
-
comment: [
|
|
261
|
-
{ text: 'aidev-continue', attributes: {} },
|
|
262
|
-
{ text: '\n', attributes: { 'block-id': 'abc' } },
|
|
263
|
-
],
|
|
264
|
-
user: { username: 'user', id: 1 },
|
|
265
|
-
date: '1000',
|
|
266
|
-
},
|
|
267
|
-
],
|
|
268
|
-
});
|
|
269
|
-
const provider = new clickup_1.ClickUpProvider(baseClickUpConfig);
|
|
270
|
-
const comments = await provider.getComments('task1');
|
|
271
|
-
strict_1.default.ok(comments[0].text.includes('aidev-continue'));
|
|
272
|
-
});
|
|
273
|
-
(0, node_test_1.it)('returns empty text when both comment_text and comment array are empty', async () => {
|
|
274
|
-
mockFetch({
|
|
275
|
-
comments: [
|
|
276
|
-
{ id: '1', comment_text: '', comment: [], user: { username: 'user', id: 1 }, date: '1000' },
|
|
277
|
-
],
|
|
278
|
-
});
|
|
279
|
-
const provider = new clickup_1.ClickUpProvider(baseClickUpConfig);
|
|
280
|
-
const comments = await provider.getComments('task1');
|
|
281
|
-
strict_1.default.equal(comments[0].text, '');
|
|
282
|
-
});
|
|
283
|
-
(0, node_test_1.it)('trigger word is detected when newest comment contains it (real-world ordering)', async () => {
|
|
284
|
-
// Simulate ClickUp newest-first response: aidev-continue is newest (highest date)
|
|
285
|
-
// but sits at index 0 in the API response. After sorting it must be last.
|
|
286
|
-
mockFetch({
|
|
287
|
-
comments: [
|
|
288
|
-
{ id: '5', comment_text: 'aidev-continue', comment: [], user: { username: 'david', id: 1 }, date: '5000' },
|
|
289
|
-
{ id: '4', comment_text: 'User answer here', comment: [], user: { username: 'david', id: 1 }, date: '4000' },
|
|
290
|
-
{ id: '3', comment_text: '[aidev] All AI runners failed.', comment: [], user: { username: 'bot', id: 2 }, date: '3000' },
|
|
291
|
-
{ id: '2', comment_text: '[aidev] Starting implementation', comment: [], user: { username: 'bot', id: 2 }, date: '2000' },
|
|
292
|
-
{ id: '1', comment_text: '[aidev] Clarification question', comment: [], user: { username: 'bot', id: 2 }, date: '1000' },
|
|
293
|
-
],
|
|
294
|
-
});
|
|
295
|
-
const provider = new clickup_1.ClickUpProvider(baseClickUpConfig);
|
|
296
|
-
const comments = await provider.getComments('task1');
|
|
297
|
-
const last = comments[comments.length - 1];
|
|
298
|
-
strict_1.default.ok(last.text.toLowerCase().includes('aidev-continue'));
|
|
299
|
-
});
|
|
300
|
-
});
|
|
301
|
-
// ─── JiraProvider.getComments ─────────────────────────────────────────────────
|
|
302
|
-
(0, node_test_1.describe)('JiraProvider.getComments', () => {
|
|
303
|
-
(0, node_test_1.afterEach)(() => node_test_2.mock.restoreAll());
|
|
304
|
-
function adfComment(text) {
|
|
305
|
-
return {
|
|
306
|
-
type: 'doc',
|
|
307
|
-
version: 1,
|
|
308
|
-
content: [{ type: 'paragraph', content: [{ type: 'text', text }] }],
|
|
309
|
-
};
|
|
310
|
-
}
|
|
311
|
-
(0, node_test_1.it)('extracts plain text from ADF body', async () => {
|
|
312
|
-
mockJiraCommentsFetch([
|
|
313
|
-
{ id: '1', body: adfComment('hello world'), author: { displayName: 'Alice', accountId: 'a1' }, created: '2024-01-01T10:00:00.000Z' },
|
|
314
|
-
]);
|
|
315
|
-
const provider = new jira_1.JiraProvider(baseJiraConfig);
|
|
316
|
-
const comments = await provider.getComments('PROJ-1');
|
|
317
|
-
strict_1.default.equal(comments[0].text, 'hello world');
|
|
318
|
-
});
|
|
319
|
-
(0, node_test_1.it)('sorts out-of-order API response into ascending date order', async () => {
|
|
320
|
-
mockJiraCommentsFetch([
|
|
321
|
-
{ id: '3', body: adfComment('aidev-continue'), author: { displayName: 'Alice', accountId: 'a1' }, created: '2024-01-01T12:00:00.000Z' },
|
|
322
|
-
{ id: '1', body: adfComment('[aidev] Starting'), author: { displayName: 'bot', accountId: 'b1' }, created: '2024-01-01T10:00:00.000Z' },
|
|
323
|
-
{ id: '2', body: adfComment('User reply'), author: { displayName: 'Alice', accountId: 'a1' }, created: '2024-01-01T11:00:00.000Z' },
|
|
324
|
-
]);
|
|
325
|
-
const provider = new jira_1.JiraProvider(baseJiraConfig);
|
|
326
|
-
const comments = await provider.getComments('PROJ-1');
|
|
327
|
-
strict_1.default.equal(comments[0].text, '[aidev] Starting'); // oldest first
|
|
328
|
-
strict_1.default.equal(comments[2].text, 'aidev-continue'); // newest last
|
|
329
|
-
});
|
|
330
|
-
(0, node_test_1.it)('trigger word is detected when newest comment contains it', async () => {
|
|
331
|
-
mockJiraCommentsFetch([
|
|
332
|
-
{ id: '1', body: adfComment('[aidev] Clarification needed'), author: { displayName: 'bot', accountId: 'b1' }, created: '2024-01-01T10:00:00.000Z' },
|
|
333
|
-
{ id: '2', body: adfComment('aidev-continue'), author: { displayName: 'Alice', accountId: 'a1' }, created: '2024-01-01T11:00:00.000Z' },
|
|
334
|
-
]);
|
|
335
|
-
const provider = new jira_1.JiraProvider(baseJiraConfig);
|
|
336
|
-
const comments = await provider.getComments('PROJ-1');
|
|
337
|
-
const last = comments[comments.length - 1];
|
|
338
|
-
strict_1.default.ok(last.text.toLowerCase().includes('aidev-continue'));
|
|
339
|
-
});
|
|
340
|
-
(0, node_test_1.it)('appends local asset paths when a comment links a native Jira attachment', async () => {
|
|
341
|
-
await withTempCwd(async (cwd) => {
|
|
342
|
-
node_test_2.mock.method(globalThis, 'fetch', async (input) => {
|
|
343
|
-
const url = String(input);
|
|
344
|
-
if (url.includes('?fields=attachment')) {
|
|
345
|
-
return jsonResponse({
|
|
346
|
-
fields: {
|
|
347
|
-
attachment: [
|
|
348
|
-
{
|
|
349
|
-
id: '101',
|
|
350
|
-
filename: 'trace.json',
|
|
351
|
-
content: 'https://example.atlassian.net/rest/api/3/attachment/content/101',
|
|
352
|
-
},
|
|
353
|
-
],
|
|
354
|
-
},
|
|
355
|
-
});
|
|
356
|
-
}
|
|
357
|
-
if (url.endsWith('/comment')) {
|
|
358
|
-
return jsonResponse({
|
|
359
|
-
comments: [
|
|
360
|
-
{
|
|
361
|
-
id: '1',
|
|
362
|
-
body: {
|
|
363
|
-
type: 'doc',
|
|
364
|
-
version: 1,
|
|
365
|
-
content: [
|
|
366
|
-
{
|
|
367
|
-
type: 'paragraph',
|
|
368
|
-
content: [
|
|
369
|
-
{ type: 'text', text: 'Please inspect ' },
|
|
370
|
-
{
|
|
371
|
-
type: 'text',
|
|
372
|
-
text: 'the trace',
|
|
373
|
-
marks: [
|
|
374
|
-
{
|
|
375
|
-
type: 'link',
|
|
376
|
-
attrs: {
|
|
377
|
-
href: 'https://example.atlassian.net/rest/api/3/attachment/content/101',
|
|
378
|
-
},
|
|
379
|
-
},
|
|
380
|
-
],
|
|
381
|
-
},
|
|
382
|
-
],
|
|
383
|
-
},
|
|
384
|
-
],
|
|
385
|
-
},
|
|
386
|
-
author: { displayName: 'Alice', accountId: 'a1' },
|
|
387
|
-
created: '2024-01-01T10:00:00.000Z',
|
|
388
|
-
},
|
|
389
|
-
],
|
|
390
|
-
});
|
|
391
|
-
}
|
|
392
|
-
return binaryResponse('{"trace":true}');
|
|
393
|
-
});
|
|
394
|
-
const provider = new jira_1.JiraProvider(baseJiraConfig);
|
|
395
|
-
const comments = await provider.getComments('PROJ-1');
|
|
396
|
-
strict_1.default.match(comments[0].text, /Local asset files referenced by this comment/);
|
|
397
|
-
strict_1.default.match(comments[0].text, /\.aidev\/assets\/PROJ-1\/101-trace\.json/);
|
|
398
|
-
strict_1.default.ok(fs.existsSync(path.join(cwd, '.aidev', 'assets', 'PROJ-1', '101-trace.json')));
|
|
399
|
-
});
|
|
400
|
-
});
|
|
401
|
-
});
|
|
402
|
-
(0, node_test_1.describe)('JiraProvider.fetchTasks — wildcard label', () => {
|
|
403
|
-
(0, node_test_1.afterEach)(() => node_test_2.mock.restoreAll());
|
|
404
|
-
(0, node_test_1.it)('omits label clause from JQL when label is "*"', async () => {
|
|
405
|
-
await withTempCwd(async () => {
|
|
406
|
-
let capturedUrl = '';
|
|
407
|
-
node_test_2.mock.method(globalThis, 'fetch', async (input) => {
|
|
408
|
-
const url = String(input);
|
|
409
|
-
if (url.includes('/search/jql')) {
|
|
410
|
-
capturedUrl = url;
|
|
411
|
-
return jsonResponse({
|
|
412
|
-
issues: [
|
|
413
|
-
{
|
|
414
|
-
id: '2001',
|
|
415
|
-
key: 'PROJ-5',
|
|
416
|
-
fields: {
|
|
417
|
-
summary: 'Unlabelled task',
|
|
418
|
-
description: null,
|
|
419
|
-
status: { name: 'Open' },
|
|
420
|
-
priority: { id: '3' },
|
|
421
|
-
labels: [],
|
|
422
|
-
self: 'https://example.atlassian.net/rest/api/3/issue/2001',
|
|
423
|
-
attachment: [],
|
|
424
|
-
},
|
|
425
|
-
},
|
|
426
|
-
],
|
|
427
|
-
});
|
|
428
|
-
}
|
|
429
|
-
return jsonResponse({});
|
|
430
|
-
});
|
|
431
|
-
const provider = new jira_1.JiraProvider({
|
|
432
|
-
...baseJiraConfig,
|
|
433
|
-
jiraLabel: '*',
|
|
434
|
-
});
|
|
435
|
-
const tasks = await provider.fetchTasks();
|
|
436
|
-
strict_1.default.equal(tasks.length, 1);
|
|
437
|
-
strict_1.default.equal(tasks[0].id, 'PROJ-5');
|
|
438
|
-
// JQL should NOT contain "AND labels =" clause
|
|
439
|
-
const decodedUrl = decodeURIComponent(capturedUrl);
|
|
440
|
-
strict_1.default.ok(!decodedUrl.includes('AND labels ='), 'should omit labels clause from JQL when label is "*"');
|
|
441
|
-
});
|
|
442
|
-
});
|
|
443
|
-
});
|
|
444
|
-
(0, node_test_1.describe)('JiraProvider.fetchTasks', () => {
|
|
445
|
-
(0, node_test_1.afterEach)(() => node_test_2.mock.restoreAll());
|
|
446
|
-
(0, node_test_1.it)('downloads issue attachments and appends local asset paths to the description', async () => {
|
|
447
|
-
await withTempCwd(async (cwd) => {
|
|
448
|
-
node_test_2.mock.method(globalThis, 'fetch', async (input) => {
|
|
449
|
-
const url = String(input);
|
|
450
|
-
if (url.includes('/search/jql')) {
|
|
451
|
-
return jsonResponse({
|
|
452
|
-
issues: [
|
|
453
|
-
{
|
|
454
|
-
id: '1001',
|
|
455
|
-
key: 'PROJ-1',
|
|
456
|
-
fields: {
|
|
457
|
-
summary: 'Handle uploaded trace',
|
|
458
|
-
description: {
|
|
459
|
-
type: 'doc',
|
|
460
|
-
version: 1,
|
|
461
|
-
content: [{ type: 'paragraph', content: [{ type: 'text', text: 'Review the trace file.' }] }],
|
|
462
|
-
},
|
|
463
|
-
status: { name: 'Open' },
|
|
464
|
-
priority: { id: '2' },
|
|
465
|
-
labels: ['aidev'],
|
|
466
|
-
self: 'https://example.atlassian.net/rest/api/3/issue/1001',
|
|
467
|
-
attachment: [
|
|
468
|
-
{
|
|
469
|
-
id: '101',
|
|
470
|
-
filename: 'trace.json',
|
|
471
|
-
content: 'https://example.atlassian.net/rest/api/3/attachment/content/101',
|
|
472
|
-
},
|
|
473
|
-
],
|
|
474
|
-
},
|
|
475
|
-
},
|
|
476
|
-
],
|
|
477
|
-
});
|
|
478
|
-
}
|
|
479
|
-
return binaryResponse('{"trace":true}');
|
|
480
|
-
});
|
|
481
|
-
const provider = new jira_1.JiraProvider(baseJiraConfig);
|
|
482
|
-
const tasks = await provider.fetchTasks();
|
|
483
|
-
strict_1.default.equal(tasks.length, 1);
|
|
484
|
-
strict_1.default.match(tasks[0].description, /Local asset files/);
|
|
485
|
-
strict_1.default.match(tasks[0].description, /\.aidev\/assets\/PROJ-1\/101-trace\.json/);
|
|
486
|
-
strict_1.default.ok(fs.existsSync(path.join(cwd, '.aidev', 'assets', 'PROJ-1', '101-trace.json')));
|
|
487
|
-
});
|
|
488
|
-
});
|
|
489
|
-
});
|
|
490
|
-
// ─── LinearProvider ─────────────────────────────────────────────────────────
|
|
491
|
-
(0, node_test_1.describe)('LinearProvider.fetchTasks — wildcard label', () => {
|
|
492
|
-
(0, node_test_1.afterEach)(() => node_test_2.mock.restoreAll());
|
|
493
|
-
(0, node_test_1.it)('omits label filter from GraphQL query when label is "*"', async () => {
|
|
494
|
-
let capturedFilter = {};
|
|
495
|
-
node_test_2.mock.method(globalThis, 'fetch', async (_url, init) => {
|
|
496
|
-
const body = init?.body ? JSON.parse(init.body) : {};
|
|
497
|
-
if (body.query?.includes('workflowStates')) {
|
|
498
|
-
return jsonResponse({
|
|
499
|
-
data: { workflowStates: { nodes: [{ id: 's1', name: 'Backlog', type: 'unstarted' }] } },
|
|
500
|
-
});
|
|
501
|
-
}
|
|
502
|
-
if (body.query?.includes('issues')) {
|
|
503
|
-
capturedFilter = body.variables?.filter || {};
|
|
504
|
-
return jsonResponse({
|
|
505
|
-
data: {
|
|
506
|
-
issues: {
|
|
507
|
-
nodes: [
|
|
508
|
-
{
|
|
509
|
-
id: 'issue-uuid-2',
|
|
510
|
-
identifier: 'ENG-99',
|
|
511
|
-
title: 'No label task',
|
|
512
|
-
description: 'Should appear without label filter.',
|
|
513
|
-
url: 'https://linear.app/org/issue/ENG-99',
|
|
514
|
-
state: { id: 's1', name: 'Backlog', type: 'unstarted' },
|
|
515
|
-
priority: 1,
|
|
516
|
-
labels: { nodes: [] },
|
|
517
|
-
},
|
|
518
|
-
],
|
|
519
|
-
},
|
|
520
|
-
},
|
|
521
|
-
});
|
|
522
|
-
}
|
|
523
|
-
return jsonResponse({ data: {} });
|
|
524
|
-
});
|
|
525
|
-
const provider = new linear_1.LinearProvider({
|
|
526
|
-
...baseLinearConfig,
|
|
527
|
-
linearLabel: '*',
|
|
528
|
-
});
|
|
529
|
-
const tasks = await provider.fetchTasks();
|
|
530
|
-
strict_1.default.equal(tasks.length, 1);
|
|
531
|
-
strict_1.default.equal(tasks[0].id, 'ENG-99');
|
|
532
|
-
// Filter should NOT contain a labels key
|
|
533
|
-
strict_1.default.equal(capturedFilter.labels, undefined, 'should not include labels filter when label is "*"');
|
|
534
|
-
});
|
|
535
|
-
});
|
|
536
|
-
(0, node_test_1.describe)('LinearProvider.fetchTasks', () => {
|
|
537
|
-
(0, node_test_1.afterEach)(() => node_test_2.mock.restoreAll());
|
|
538
|
-
(0, node_test_1.it)('returns issues with identifier as id and state name as status', async () => {
|
|
539
|
-
node_test_2.mock.method(globalThis, 'fetch', async (_url, init) => {
|
|
540
|
-
const body = init?.body ? JSON.parse(init.body) : {};
|
|
541
|
-
if (body.query?.includes('workflowStates')) {
|
|
542
|
-
return jsonResponse({
|
|
543
|
-
data: { workflowStates: { nodes: [{ id: 's1', name: 'Backlog', type: 'unstarted' }, { id: 's2', name: 'In Review', type: 'started' }] } },
|
|
544
|
-
});
|
|
545
|
-
}
|
|
546
|
-
if (body.query?.includes('issues')) {
|
|
547
|
-
return jsonResponse({
|
|
548
|
-
data: {
|
|
549
|
-
issues: {
|
|
550
|
-
nodes: [
|
|
551
|
-
{
|
|
552
|
-
id: 'issue-uuid-1',
|
|
553
|
-
identifier: 'ENG-42',
|
|
554
|
-
title: 'Fix login',
|
|
555
|
-
description: 'Fix the login flow.',
|
|
556
|
-
url: 'https://linear.app/org/issue/ENG-42',
|
|
557
|
-
state: { id: 's1', name: 'Backlog', type: 'unstarted' },
|
|
558
|
-
priority: 2,
|
|
559
|
-
labels: { nodes: [{ name: 'aidev' }] },
|
|
560
|
-
},
|
|
561
|
-
],
|
|
562
|
-
},
|
|
563
|
-
},
|
|
564
|
-
});
|
|
565
|
-
}
|
|
566
|
-
return jsonResponse({ data: {} });
|
|
567
|
-
});
|
|
568
|
-
const provider = new linear_1.LinearProvider(baseLinearConfig);
|
|
569
|
-
const tasks = await provider.fetchTasks();
|
|
570
|
-
strict_1.default.equal(tasks.length, 1);
|
|
571
|
-
strict_1.default.equal(tasks[0].id, 'ENG-42');
|
|
572
|
-
strict_1.default.equal(tasks[0].name, 'Fix login');
|
|
573
|
-
strict_1.default.equal(tasks[0].description, 'Fix the login flow.');
|
|
574
|
-
strict_1.default.equal(tasks[0].status, 'Backlog');
|
|
575
|
-
strict_1.default.equal(tasks[0].url, 'https://linear.app/org/issue/ENG-42');
|
|
576
|
-
strict_1.default.deepEqual(tasks[0].tags, ['aidev']);
|
|
577
|
-
strict_1.default.equal(tasks[0].priority, 2);
|
|
578
|
-
});
|
|
579
|
-
});
|
|
580
|
-
(0, node_test_1.describe)('LinearProvider.getComments', () => {
|
|
581
|
-
(0, node_test_1.afterEach)(() => node_test_2.mock.restoreAll());
|
|
582
|
-
(0, node_test_1.it)('returns comments sorted ascending by date', async () => {
|
|
583
|
-
node_test_2.mock.method(globalThis, 'fetch', async (_url, init) => {
|
|
584
|
-
const body = init?.body ? JSON.parse(init.body) : {};
|
|
585
|
-
if (body.query?.includes('issues') && body.variables?.filter?.identifier) {
|
|
586
|
-
return jsonResponse({ data: { issues: { nodes: [{ id: 'issue-uuid-1' }] } } });
|
|
587
|
-
}
|
|
588
|
-
if (body.query?.includes('issue(id:')) {
|
|
589
|
-
return jsonResponse({
|
|
590
|
-
data: {
|
|
591
|
-
issue: {
|
|
592
|
-
comments: {
|
|
593
|
-
nodes: [
|
|
594
|
-
{ id: 'c1', body: '[aidev] Starting', user: { name: 'Bot', id: 'u1' }, createdAt: '2024-01-01T10:00:00.000Z' },
|
|
595
|
-
{ id: 'c2', body: 'aidev-continue', user: { name: 'Alice', id: 'u2' }, createdAt: '2024-01-01T11:00:00.000Z' },
|
|
596
|
-
],
|
|
597
|
-
},
|
|
598
|
-
},
|
|
599
|
-
},
|
|
600
|
-
});
|
|
601
|
-
}
|
|
602
|
-
return jsonResponse({ data: {} });
|
|
603
|
-
});
|
|
604
|
-
const provider = new linear_1.LinearProvider(baseLinearConfig);
|
|
605
|
-
const comments = await provider.getComments('ENG-42');
|
|
606
|
-
strict_1.default.equal(comments.length, 2);
|
|
607
|
-
strict_1.default.equal(comments[0].text, '[aidev] Starting');
|
|
608
|
-
strict_1.default.equal(comments[1].text, 'aidev-continue');
|
|
609
|
-
strict_1.default.equal(comments[1].author, 'Alice');
|
|
610
|
-
});
|
|
611
|
-
});
|
|
612
|
-
// ─── MondayProvider ───────────────────────────────────────────────────────────
|
|
613
|
-
(0, node_test_1.describe)('MondayProvider.fetchTasks', () => {
|
|
614
|
-
(0, node_test_1.afterEach)(() => node_test_2.mock.restoreAll());
|
|
615
|
-
(0, node_test_1.it)('returns items whose status matches pending or in-review', async () => {
|
|
616
|
-
node_test_2.mock.method(globalThis, 'fetch', async (_input, init) => {
|
|
617
|
-
const body = typeof (init?.body) === 'string' ? JSON.parse(init.body) : {};
|
|
618
|
-
const query = body.query;
|
|
619
|
-
if (query.includes('boards') && query.includes('items_page')) {
|
|
620
|
-
return jsonResponse({
|
|
621
|
-
data: {
|
|
622
|
-
boards: [
|
|
623
|
-
{
|
|
624
|
-
items_page: {
|
|
625
|
-
cursor: null,
|
|
626
|
-
items: [
|
|
627
|
-
{
|
|
628
|
-
id: '1001',
|
|
629
|
-
name: 'Task one',
|
|
630
|
-
url: 'https://example.monday.com/boards/12345/pulses/1001',
|
|
631
|
-
description: { description: 'Do something' },
|
|
632
|
-
column_values: [
|
|
633
|
-
{ id: 'status', value: '{"label":"Working on it"}', text: 'Working on it' },
|
|
634
|
-
],
|
|
635
|
-
},
|
|
636
|
-
{
|
|
637
|
-
id: '1002',
|
|
638
|
-
name: 'Task two',
|
|
639
|
-
url: 'https://example.monday.com/boards/12345/pulses/1002',
|
|
640
|
-
description: { description: '' },
|
|
641
|
-
column_values: [
|
|
642
|
-
{ id: 'status', value: '{"label":"Done"}', text: 'Done' },
|
|
643
|
-
],
|
|
644
|
-
},
|
|
645
|
-
{
|
|
646
|
-
id: '1003',
|
|
647
|
-
name: 'Task three',
|
|
648
|
-
url: '',
|
|
649
|
-
description: {},
|
|
650
|
-
column_values: [
|
|
651
|
-
{ id: 'status', value: '{}', text: 'Stuck' },
|
|
652
|
-
],
|
|
653
|
-
},
|
|
654
|
-
],
|
|
655
|
-
},
|
|
656
|
-
},
|
|
657
|
-
],
|
|
658
|
-
},
|
|
659
|
-
});
|
|
660
|
-
}
|
|
661
|
-
return jsonResponse({});
|
|
662
|
-
});
|
|
663
|
-
const provider = new monday_1.MondayProvider(baseMondayConfig);
|
|
664
|
-
const tasks = await provider.fetchTasks();
|
|
665
|
-
strict_1.default.equal(tasks.length, 2);
|
|
666
|
-
strict_1.default.equal(tasks[0].id, '1001');
|
|
667
|
-
strict_1.default.equal(tasks[0].name, 'Task one');
|
|
668
|
-
strict_1.default.equal(tasks[0].status, 'Working on it');
|
|
669
|
-
strict_1.default.equal(tasks[1].id, '1002');
|
|
670
|
-
strict_1.default.equal(tasks[1].status, 'Done');
|
|
671
|
-
});
|
|
672
|
-
});
|
|
673
|
-
(0, node_test_1.describe)('MondayProvider.getComments', () => {
|
|
674
|
-
(0, node_test_1.afterEach)(() => node_test_2.mock.restoreAll());
|
|
675
|
-
(0, node_test_1.it)('returns updates sorted ascending by date', async () => {
|
|
676
|
-
node_test_2.mock.method(globalThis, 'fetch', async () => jsonResponse({
|
|
677
|
-
data: {
|
|
678
|
-
items: [
|
|
679
|
-
{
|
|
680
|
-
updates: [
|
|
681
|
-
{ id: 'u2', text_body: 'Newest', body: '<p>Newest</p>', created_at: '2024-01-02T12:00:00Z', creator: { id: 'u1', name: 'Alice' } },
|
|
682
|
-
{ id: 'u1', text_body: 'Oldest', body: '<p>Oldest</p>', created_at: '2024-01-01T10:00:00Z', creator: { id: 'u1', name: 'Alice' } },
|
|
683
|
-
],
|
|
684
|
-
},
|
|
685
|
-
],
|
|
686
|
-
},
|
|
687
|
-
}));
|
|
688
|
-
const provider = new monday_1.MondayProvider(baseMondayConfig);
|
|
689
|
-
const comments = await provider.getComments('1001');
|
|
690
|
-
strict_1.default.equal(comments.length, 2);
|
|
691
|
-
strict_1.default.equal(comments[0].text, 'Oldest');
|
|
692
|
-
strict_1.default.equal(comments[1].text, 'Newest');
|
|
693
|
-
});
|
|
694
|
-
});
|
|
695
|
-
// ─── NotionProvider ─────────────────────────────────────────────────────────
|
|
696
|
-
(0, node_test_1.describe)('NotionProvider.fetchTasks', () => {
|
|
697
|
-
(0, node_test_1.afterEach)(() => node_test_2.mock.restoreAll());
|
|
698
|
-
(0, node_test_1.it)('returns pages with pending or review status from database query', async () => {
|
|
699
|
-
node_test_2.mock.method(globalThis, 'fetch', async (input, init) => {
|
|
700
|
-
const url = String(input);
|
|
701
|
-
if (url.includes('/databases/') && url.includes('/query') === false && (init?.method ?? 'GET') === 'GET') {
|
|
702
|
-
return jsonResponse({
|
|
703
|
-
id: 'a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6',
|
|
704
|
-
properties: {
|
|
705
|
-
Name: { type: 'title' },
|
|
706
|
-
Status: { type: 'status' },
|
|
707
|
-
},
|
|
708
|
-
});
|
|
709
|
-
}
|
|
710
|
-
if (url.includes('/query')) {
|
|
711
|
-
return jsonResponse({
|
|
712
|
-
results: [
|
|
713
|
-
{
|
|
714
|
-
id: 'a1b2c3d4-e5f6-7890-1234-5678abcdef01',
|
|
715
|
-
url: 'https://notion.so/a1b2c3d4e5f6789012345678abcdef01',
|
|
716
|
-
properties: {
|
|
717
|
-
Name: { title: [{ plain_text: 'Task one' }] },
|
|
718
|
-
Status: { status: { name: 'pending' } },
|
|
719
|
-
Description: { rich_text: [{ plain_text: 'Do something' }] },
|
|
720
|
-
},
|
|
721
|
-
},
|
|
722
|
-
{
|
|
723
|
-
id: 'a1b2c3d4-e5f6-7890-1234-5678abcdef02',
|
|
724
|
-
url: 'https://notion.so/page-two',
|
|
725
|
-
properties: {
|
|
726
|
-
Name: { title: [{ plain_text: 'Task two' }] },
|
|
727
|
-
Status: { status: { name: 'review' } },
|
|
728
|
-
},
|
|
729
|
-
},
|
|
730
|
-
],
|
|
731
|
-
next_cursor: null,
|
|
732
|
-
has_more: false,
|
|
733
|
-
});
|
|
734
|
-
}
|
|
735
|
-
return jsonResponse({});
|
|
736
|
-
});
|
|
737
|
-
const provider = new notion_1.NotionProvider(baseNotionConfig);
|
|
738
|
-
const tasks = await provider.fetchTasks();
|
|
739
|
-
strict_1.default.equal(tasks.length, 2);
|
|
740
|
-
strict_1.default.equal(tasks[0].id, 'a1b2c3d4e5f6789012345678abcdef01');
|
|
741
|
-
strict_1.default.equal(tasks[0].name, 'Task one');
|
|
742
|
-
strict_1.default.equal(tasks[0].description, 'Do something');
|
|
743
|
-
strict_1.default.equal(tasks[0].status, 'pending');
|
|
744
|
-
strict_1.default.equal(tasks[0].url, 'https://notion.so/a1b2c3d4e5f6789012345678abcdef01');
|
|
745
|
-
strict_1.default.equal(tasks[1].name, 'Task two');
|
|
746
|
-
strict_1.default.equal(tasks[1].status, 'review');
|
|
747
|
-
});
|
|
748
|
-
});
|
|
749
|
-
// ─── TrelloProvider ─────────────────────────────────────────────────────────
|
|
750
|
-
(0, node_test_1.describe)('TrelloProvider.fetchTasks', () => {
|
|
751
|
-
(0, node_test_1.afterEach)(() => node_test_2.mock.restoreAll());
|
|
752
|
-
(0, node_test_1.it)('returns cards assigned to token user with label in open or pending lists', async () => {
|
|
753
|
-
node_test_2.mock.method(globalThis, 'fetch', async (input) => {
|
|
754
|
-
const url = String(input);
|
|
755
|
-
if (url.includes('/members/me')) {
|
|
756
|
-
return jsonResponse({ id: 'member-me' });
|
|
757
|
-
}
|
|
758
|
-
if (url.includes('/boards/board1/lists')) {
|
|
759
|
-
return jsonResponse([
|
|
760
|
-
{ id: 'list-open', name: 'To Do' },
|
|
761
|
-
{ id: 'list-pending', name: 'Blocked' },
|
|
762
|
-
]);
|
|
763
|
-
}
|
|
764
|
-
if (url.includes('/boards/board1/cards')) {
|
|
765
|
-
return jsonResponse([
|
|
766
|
-
{
|
|
767
|
-
id: 'card1',
|
|
768
|
-
name: 'Do work',
|
|
769
|
-
desc: 'Details',
|
|
770
|
-
url: 'https://trello.com/c/abc/card1',
|
|
771
|
-
idList: 'list-open',
|
|
772
|
-
idMembers: ['member-me'],
|
|
773
|
-
labels: [{ id: 'l1', name: 'aidev' }],
|
|
774
|
-
},
|
|
775
|
-
{
|
|
776
|
-
id: 'card2',
|
|
777
|
-
name: 'Wrong assignee',
|
|
778
|
-
desc: '',
|
|
779
|
-
url: 'https://trello.com/c/def/card2',
|
|
780
|
-
idList: 'list-open',
|
|
781
|
-
idMembers: ['other'],
|
|
782
|
-
labels: [{ id: 'l1', name: 'aidev' }],
|
|
783
|
-
},
|
|
784
|
-
{
|
|
785
|
-
id: 'card3',
|
|
786
|
-
name: 'In Doing column',
|
|
787
|
-
desc: '',
|
|
788
|
-
url: 'https://trello.com/c/ghi/card3',
|
|
789
|
-
idList: 'list-doing',
|
|
790
|
-
idMembers: ['member-me'],
|
|
791
|
-
labels: [{ id: 'l1', name: 'aidev' }],
|
|
792
|
-
},
|
|
793
|
-
]);
|
|
794
|
-
}
|
|
795
|
-
if (url.includes('/cards/card1/attachments')) {
|
|
796
|
-
return jsonResponse([]);
|
|
797
|
-
}
|
|
798
|
-
return jsonResponse({});
|
|
799
|
-
});
|
|
800
|
-
const provider = new trello_1.TrelloProvider(baseTrelloConfig);
|
|
801
|
-
const tasks = await provider.fetchTasks();
|
|
802
|
-
strict_1.default.equal(tasks.length, 1);
|
|
803
|
-
strict_1.default.equal(tasks[0].id, 'card1');
|
|
804
|
-
strict_1.default.equal(tasks[0].status, 'open');
|
|
805
|
-
strict_1.default.deepEqual(tasks[0].tags, ['aidev']);
|
|
806
|
-
});
|
|
807
|
-
(0, node_test_1.it)('omits label filter when TRELLO_LABEL is *', async () => {
|
|
808
|
-
node_test_2.mock.method(globalThis, 'fetch', async (input) => {
|
|
809
|
-
const url = String(input);
|
|
810
|
-
if (url.includes('/members/me')) {
|
|
811
|
-
return jsonResponse({ id: 'member-me' });
|
|
812
|
-
}
|
|
813
|
-
if (url.includes('/boards/board1/lists')) {
|
|
814
|
-
return jsonResponse([
|
|
815
|
-
{ id: 'list-open', name: 'To Do' },
|
|
816
|
-
{ id: 'list-pending', name: 'Blocked' },
|
|
817
|
-
]);
|
|
818
|
-
}
|
|
819
|
-
if (url.includes('/boards/board1/cards')) {
|
|
820
|
-
return jsonResponse([
|
|
821
|
-
{
|
|
822
|
-
id: 'card1',
|
|
823
|
-
name: 'No label',
|
|
824
|
-
desc: '',
|
|
825
|
-
url: 'https://trello.com/c/abc/card1',
|
|
826
|
-
idList: 'list-open',
|
|
827
|
-
idMembers: ['member-me'],
|
|
828
|
-
labels: [],
|
|
829
|
-
},
|
|
830
|
-
]);
|
|
831
|
-
}
|
|
832
|
-
if (url.includes('/cards/card1/attachments')) {
|
|
833
|
-
return jsonResponse([]);
|
|
834
|
-
}
|
|
835
|
-
return jsonResponse({});
|
|
836
|
-
});
|
|
837
|
-
const provider = new trello_1.TrelloProvider({
|
|
838
|
-
...baseTrelloConfig,
|
|
839
|
-
trelloLabel: '*',
|
|
840
|
-
});
|
|
841
|
-
const tasks = await provider.fetchTasks();
|
|
842
|
-
strict_1.default.equal(tasks.length, 1);
|
|
843
|
-
strict_1.default.equal(tasks[0].name, 'No label');
|
|
844
|
-
});
|
|
845
|
-
});
|
|
846
|
-
(0, node_test_1.describe)('TrelloProvider.getComments', () => {
|
|
847
|
-
(0, node_test_1.afterEach)(() => node_test_2.mock.restoreAll());
|
|
848
|
-
(0, node_test_1.it)('returns comment actions sorted ascending by date', async () => {
|
|
849
|
-
node_test_2.mock.method(globalThis, 'fetch', async (input) => {
|
|
850
|
-
const url = String(input);
|
|
851
|
-
if (url.includes('/cards/card1/actions')) {
|
|
852
|
-
return jsonResponse([
|
|
853
|
-
{
|
|
854
|
-
id: 'a2',
|
|
855
|
-
type: 'commentCard',
|
|
856
|
-
date: '2024-01-02T12:00:00.000Z',
|
|
857
|
-
data: { text: 'Newest' },
|
|
858
|
-
idMemberCreator: 'u1',
|
|
859
|
-
memberCreator: { id: 'u1', fullName: 'Alice', username: 'alice' },
|
|
860
|
-
},
|
|
861
|
-
{
|
|
862
|
-
id: 'a1',
|
|
863
|
-
type: 'commentCard',
|
|
864
|
-
date: '2024-01-01T10:00:00.000Z',
|
|
865
|
-
data: { text: 'Oldest' },
|
|
866
|
-
idMemberCreator: 'u1',
|
|
867
|
-
memberCreator: { id: 'u1', fullName: 'Alice', username: 'alice' },
|
|
868
|
-
},
|
|
869
|
-
]);
|
|
870
|
-
}
|
|
871
|
-
return jsonResponse({});
|
|
872
|
-
});
|
|
873
|
-
const provider = new trello_1.TrelloProvider(baseTrelloConfig);
|
|
874
|
-
const comments = await provider.getComments('card1');
|
|
875
|
-
strict_1.default.equal(comments.length, 2);
|
|
876
|
-
strict_1.default.equal(comments[0].text, 'Oldest');
|
|
877
|
-
strict_1.default.equal(comments[1].text, 'Newest');
|
|
878
|
-
});
|
|
879
|
-
});
|
|
880
|
-
(0, node_test_1.describe)('TrelloProvider.updateStatus', () => {
|
|
881
|
-
(0, node_test_1.afterEach)(() => node_test_2.mock.restoreAll());
|
|
882
|
-
(0, node_test_1.it)('PUTs card with idList for in progress', async () => {
|
|
883
|
-
node_test_2.mock.method(globalThis, 'fetch', async (input, init) => {
|
|
884
|
-
const url = String(input);
|
|
885
|
-
if (url.includes('/boards/board1/lists')) {
|
|
886
|
-
return jsonResponse([
|
|
887
|
-
{ id: 'list-todo', name: 'To Do' },
|
|
888
|
-
{ id: 'list-doing', name: 'Doing' },
|
|
889
|
-
]);
|
|
890
|
-
}
|
|
891
|
-
if (url.includes('/cards/card1') && init?.method === 'PUT') {
|
|
892
|
-
strict_1.default.ok(url.includes('idList=list-doing'));
|
|
893
|
-
return jsonResponse({ id: 'card1', idList: 'list-doing' });
|
|
894
|
-
}
|
|
895
|
-
return jsonResponse({});
|
|
896
|
-
});
|
|
897
|
-
const provider = new trello_1.TrelloProvider(baseTrelloConfig);
|
|
898
|
-
await provider.updateStatus('card1', 'in progress');
|
|
899
|
-
});
|
|
900
|
-
});
|
|
901
|
-
(0, node_test_1.describe)('NotionProvider.getComments', () => {
|
|
902
|
-
(0, node_test_1.afterEach)(() => node_test_2.mock.restoreAll());
|
|
903
|
-
(0, node_test_1.it)('returns comments sorted ascending by date', async () => {
|
|
904
|
-
node_test_2.mock.method(globalThis, 'fetch', async (input) => {
|
|
905
|
-
const url = String(input);
|
|
906
|
-
if (url.includes('/comments')) {
|
|
907
|
-
return jsonResponse({
|
|
908
|
-
results: [
|
|
909
|
-
{ id: 'c2', created_time: '2024-01-02T12:00:00.000Z', created_by: { id: 'u1' }, rich_text: [{ plain_text: 'Newest' }] },
|
|
910
|
-
{ id: 'c1', created_time: '2024-01-01T10:00:00.000Z', created_by: { id: 'u1' }, rich_text: [{ plain_text: 'Oldest' }] },
|
|
911
|
-
],
|
|
912
|
-
next_cursor: null,
|
|
913
|
-
has_more: false,
|
|
914
|
-
});
|
|
915
|
-
}
|
|
916
|
-
return jsonResponse({});
|
|
917
|
-
});
|
|
918
|
-
const provider = new notion_1.NotionProvider(baseNotionConfig);
|
|
919
|
-
const comments = await provider.getComments('a1b2c3d4e5f6789012345678abcdef01');
|
|
920
|
-
strict_1.default.equal(comments.length, 2);
|
|
921
|
-
strict_1.default.equal(comments[0].text, 'Oldest');
|
|
922
|
-
strict_1.default.equal(comments[1].text, 'Newest');
|
|
923
|
-
});
|
|
924
|
-
});
|
|
925
|
-
//# sourceMappingURL=providers.test.js.map
|