@jira-deploy/core 1.0.3 → 1.0.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.
- package/constants/defaults.js +1 -1
- package/constants/field-ids.js +4 -4
- package/package.json +1 -1
- package/poller.js +16 -3
- package/tools/cd.js +6 -4
- package/tools/grayrelease.js +553 -302
- package/tools/index.js +173 -108
- package/tools/release.js +48 -21
- package/tools/workflows.js +20 -0
- package/tools.test.js +444 -97
package/tools.test.js
CHANGED
|
@@ -6,6 +6,7 @@ import { test, describe } from 'node:test';
|
|
|
6
6
|
import assert from 'node:assert/strict';
|
|
7
7
|
import http from 'node:http';
|
|
8
8
|
import { executeTool, getToolDefinitions } from './tools/index.js';
|
|
9
|
+
import { Poller } from './poller.js';
|
|
9
10
|
|
|
10
11
|
process.env.JIRA_BASE_URL = 'https://jira.test';
|
|
11
12
|
|
|
@@ -110,9 +111,9 @@ function makeMockJira({
|
|
|
110
111
|
},
|
|
111
112
|
getIssue: async () => ({ fields: { status: { name: 'TO DO' }, summary: 'test' } }),
|
|
112
113
|
getTransitions: async () => [],
|
|
113
|
-
transitionById: async () => {},
|
|
114
|
+
transitionById: async () => { },
|
|
114
115
|
transitionByName: async () => ({ transitioned: 'Test', toStatus: 'Done' }),
|
|
115
|
-
addComment: async () => {},
|
|
116
|
+
addComment: async () => { },
|
|
116
117
|
getSubTasks: async () => [],
|
|
117
118
|
};
|
|
118
119
|
}
|
|
@@ -141,6 +142,19 @@ describe('tool schemas — agent contract', () => {
|
|
|
141
142
|
}
|
|
142
143
|
});
|
|
143
144
|
|
|
145
|
+
test('create_cd_ticket schema requires explicit CD intent and CI ticket context', () => {
|
|
146
|
+
const tool = getToolDefinition('create_cd_ticket');
|
|
147
|
+
|
|
148
|
+
assert.match(tool.description, /明確要求或確認/);
|
|
149
|
+
assert.match(tool.description, /deploy_grayrelease/);
|
|
150
|
+
assert.match(tool.description, /不可直接開 CD 單/);
|
|
151
|
+
assert.match(tool.inputSchema.properties.environment.description, /應先詢問/);
|
|
152
|
+
assert.match(tool.inputSchema.properties.environment.description, /不可.*自行補 stg/);
|
|
153
|
+
assert.match(tool.inputSchema.properties.ciTicket.description, /必須是 CI 單/);
|
|
154
|
+
assert.match(tool.inputSchema.properties.ciTicket.description, /不可使用 GrayRelease 單/);
|
|
155
|
+
assert.match(tool.inputSchema.properties.linkedCiKey.description, /不可使用 GrayRelease 單/);
|
|
156
|
+
});
|
|
157
|
+
|
|
144
158
|
test('create_library_ticket schema treats module as defaultable', () => {
|
|
145
159
|
const tool = getToolDefinition('create_library_ticket');
|
|
146
160
|
|
|
@@ -157,22 +171,108 @@ describe('tool schemas — agent contract', () => {
|
|
|
157
171
|
assert.ok(getToolDefinition('wait_to_dev'));
|
|
158
172
|
});
|
|
159
173
|
|
|
174
|
+
test('deploy_grayrelease is exposed as the GrayRelease deployment tool', () => {
|
|
175
|
+
const deployTool = getToolDefinition('deploy_grayrelease');
|
|
176
|
+
const buildTool = getToolDefinition('build_ticket');
|
|
177
|
+
|
|
178
|
+
assert.ok(deployTool);
|
|
179
|
+
assert.match(deployTool.description, /與 CD 部署完全分離/);
|
|
180
|
+
assert.match(deployTool.description, /只說 deploy\/部署/);
|
|
181
|
+
assert.match(deployTool.description, /先確認是否部署該 GrayRelease 單/);
|
|
182
|
+
assert.match(deployTool.description, /不會建立 CD 單/);
|
|
183
|
+
assert.match(deployTool.description, /不接受 CI 單/);
|
|
184
|
+
assert.match(deployTool.description, /不會重新 build/);
|
|
185
|
+
assert.match(buildTool.description, /deploy_grayrelease/);
|
|
186
|
+
assert.match(buildTool.description, /不可用此 tool 觸發 rebuild/);
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
test('build_ticket schema is limited to explicit GrayRelease build or rebuild', () => {
|
|
190
|
+
const tool = getToolDefinition('build_ticket');
|
|
191
|
+
|
|
192
|
+
assert.match(tool.description, /GrayRelease/);
|
|
193
|
+
assert.match(tool.description, /build\/rebuild/);
|
|
194
|
+
assert.doesNotMatch(tool.description, /build\/deploy/);
|
|
195
|
+
assert.match(tool.inputSchema.properties.issueKey.description, /GrayRelease/);
|
|
196
|
+
assert.equal(tool.inputSchema.properties.rebuild.type, 'boolean');
|
|
197
|
+
assert.match(tool.inputSchema.properties.rebuild.description, /rebuild=true/);
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
test('build_ticket refuses GrayRelease VERIFY rebuild without rebuild flag', async () => {
|
|
201
|
+
let status = 'VERIFY';
|
|
202
|
+
const transitions = [];
|
|
203
|
+
const jira = {
|
|
204
|
+
...makeMockJira(),
|
|
205
|
+
getIssue: async () => ({ fields: { status: { name: status }, summary: 'GrayRelease' } }),
|
|
206
|
+
getTransitions: async () => {
|
|
207
|
+
if (status === 'VERIFY') return [{ id: '10', name: 'Verify fail', to: { name: 'PLANNING' } }];
|
|
208
|
+
return [];
|
|
209
|
+
},
|
|
210
|
+
transitionById: async (_issueKey, transitionId) => {
|
|
211
|
+
transitions.push(transitionId);
|
|
212
|
+
},
|
|
213
|
+
};
|
|
214
|
+
|
|
215
|
+
const result = await executeTool(
|
|
216
|
+
'build_ticket',
|
|
217
|
+
{ issueKey: 'CID-822' },
|
|
218
|
+
{ jira, notifier: mockNotifier },
|
|
219
|
+
);
|
|
220
|
+
|
|
221
|
+
assert.equal(result.isError, true);
|
|
222
|
+
assert.match(result.content[0].text, /rebuild=true/);
|
|
223
|
+
assert.deepEqual(transitions, []);
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
test('build_ticket resets GrayRelease VERIFY flow before GrayRelease Build when rebuild is explicit', async () => {
|
|
227
|
+
let status = 'VERIFY';
|
|
228
|
+
const transitions = [];
|
|
229
|
+
const jira = {
|
|
230
|
+
...makeMockJira(),
|
|
231
|
+
getIssue: async () => ({ fields: { status: { name: status }, summary: 'GrayRelease' } }),
|
|
232
|
+
getTransitions: async () => {
|
|
233
|
+
if (status === 'VERIFY') return [{ id: '10', name: 'Verify fail', to: { name: 'PLANNING' } }];
|
|
234
|
+
if (status === 'PLANNING') return [{ id: '11', name: 'Accept', to: { name: 'WAIT FOR BUILD' } }];
|
|
235
|
+
if (status === 'WAIT FOR BUILD') {
|
|
236
|
+
return [{ id: '12', name: 'GrayRelease Build', to: { name: 'WAIT FOR BUILD' } }];
|
|
237
|
+
}
|
|
238
|
+
return [];
|
|
239
|
+
},
|
|
240
|
+
transitionById: async (_issueKey, transitionId) => {
|
|
241
|
+
transitions.push(transitionId);
|
|
242
|
+
if (transitionId === '10') status = 'PLANNING';
|
|
243
|
+
if (transitionId === '11') status = 'WAIT FOR BUILD';
|
|
244
|
+
},
|
|
245
|
+
};
|
|
246
|
+
|
|
247
|
+
const result = await executeTool(
|
|
248
|
+
'build_ticket',
|
|
249
|
+
{ issueKey: 'CID-822', rebuild: true },
|
|
250
|
+
{ jira, notifier: mockNotifier },
|
|
251
|
+
);
|
|
252
|
+
|
|
253
|
+
assert.ok(!result.isError, result.content[0].text);
|
|
254
|
+
const output = JSON.parse(result.content[0].text);
|
|
255
|
+
assert.equal(output.status, 'WAIT FOR BUILD');
|
|
256
|
+
assert.deepEqual(transitions, ['10', '11', '12']);
|
|
257
|
+
assert.match(output.steps.join('\n'), /GrayRelease Build/);
|
|
258
|
+
});
|
|
259
|
+
|
|
160
260
|
test('run_stg_full_release dispatches shared workflow steps', async () => {
|
|
161
261
|
const calls = [];
|
|
162
|
-
const result = await executeTool('run_stg_full_release', {systemCode: 'IBK'}, {
|
|
262
|
+
const result = await executeTool('run_stg_full_release', { systemCode: 'IBK' }, {
|
|
163
263
|
jira: makeMockJira(),
|
|
164
264
|
notifier: mockNotifier,
|
|
165
265
|
executeToolImpl: async (name, args) => {
|
|
166
|
-
calls.push({name, args});
|
|
266
|
+
calls.push({ name, args });
|
|
167
267
|
const outputs = {
|
|
168
|
-
create_ci_ticket: {issueKey: 'CID-100'},
|
|
169
|
-
build_ticket: {issueKey: args.issueKey, status: 'Compliance Scan'},
|
|
170
|
-
wait_to_stg: {issueKey: args.issueKey, status: 'Wait To STG'},
|
|
171
|
-
create_cd_ticket: {issueKey: 'CID-200'},
|
|
172
|
-
prepare_cd_deployment: {issueKey: args.issueKey, status: 'Deployment Created'},
|
|
173
|
-
trigger_deployment: {cdIssueKey: args.cdIssueKey, status: 'Done'},
|
|
268
|
+
create_ci_ticket: { issueKey: 'CID-100' },
|
|
269
|
+
build_ticket: { issueKey: args.issueKey, status: 'Compliance Scan' },
|
|
270
|
+
wait_to_stg: { issueKey: args.issueKey, status: 'Wait To STG' },
|
|
271
|
+
create_cd_ticket: { issueKey: 'CID-200' },
|
|
272
|
+
prepare_cd_deployment: { issueKey: args.issueKey, status: 'Deployment Created' },
|
|
273
|
+
trigger_deployment: { cdIssueKey: args.cdIssueKey, status: 'Done' },
|
|
174
274
|
};
|
|
175
|
-
return {content: [{type: 'text', text: JSON.stringify(outputs[name])}]};
|
|
275
|
+
return { content: [{ type: 'text', text: JSON.stringify(outputs[name]) }] };
|
|
176
276
|
},
|
|
177
277
|
});
|
|
178
278
|
|
|
@@ -192,6 +292,38 @@ describe('tool schemas — agent contract', () => {
|
|
|
192
292
|
assert.equal(calls[5].args.applyForClose, true);
|
|
193
293
|
});
|
|
194
294
|
|
|
295
|
+
test('run_stg_full_release emits route progress for each workflow step', async () => {
|
|
296
|
+
const events = [];
|
|
297
|
+
const result = await executeTool('run_stg_full_release', { systemCode: 'IBK' }, {
|
|
298
|
+
jira: makeMockJira(),
|
|
299
|
+
notifier: mockNotifier,
|
|
300
|
+
progress: (event) => events.push(event),
|
|
301
|
+
executeToolImpl: async (name, args) => {
|
|
302
|
+
const outputs = {
|
|
303
|
+
create_ci_ticket: { issueKey: 'CID-100' },
|
|
304
|
+
build_ticket: { issueKey: args.issueKey, status: 'Compliance Scan' },
|
|
305
|
+
wait_to_stg: { issueKey: args.issueKey, status: 'Wait To STG' },
|
|
306
|
+
create_cd_ticket: { issueKey: 'CID-200' },
|
|
307
|
+
prepare_cd_deployment: { issueKey: args.issueKey, status: 'Deployment Created' },
|
|
308
|
+
trigger_deployment: { cdIssueKey: args.cdIssueKey, status: 'Done' },
|
|
309
|
+
};
|
|
310
|
+
return { content: [{ type: 'text', text: JSON.stringify(outputs[name]) }] };
|
|
311
|
+
},
|
|
312
|
+
});
|
|
313
|
+
|
|
314
|
+
assert.ok(!result.isError, result.content[0].text);
|
|
315
|
+
assert.deepEqual(events.map((event) => event.toolName), [
|
|
316
|
+
'create_ci_ticket',
|
|
317
|
+
'build_ticket',
|
|
318
|
+
'wait_to_stg',
|
|
319
|
+
'create_cd_ticket',
|
|
320
|
+
'prepare_cd_deployment',
|
|
321
|
+
'trigger_deployment',
|
|
322
|
+
]);
|
|
323
|
+
assert.ok(events.every((event) => event.phase === 'action'));
|
|
324
|
+
assert.equal(events[0].title, '執行 workflow step: create_ci_ticket');
|
|
325
|
+
});
|
|
326
|
+
|
|
195
327
|
test('run_lib_to_stg_release waits for the Library ticket to reach Released', async () => {
|
|
196
328
|
const calls = [];
|
|
197
329
|
const result = await executeTool('run_lib_to_stg_release', {
|
|
@@ -200,17 +332,17 @@ describe('tool schemas — agent contract', () => {
|
|
|
200
332
|
}, {
|
|
201
333
|
jira: {
|
|
202
334
|
...makeMockJira(),
|
|
203
|
-
getIssue: async () => ({fields: {status: {name: 'WAIT FOR LIB BUILD'}}}),
|
|
335
|
+
getIssue: async () => ({ fields: { status: { name: 'WAIT FOR LIB BUILD' } } }),
|
|
204
336
|
},
|
|
205
337
|
notifier: mockNotifier,
|
|
206
|
-
workflowWaitOptions: {timeoutMs: 1, intervalMs: 1},
|
|
338
|
+
workflowWaitOptions: { timeoutMs: 1, intervalMs: 1 },
|
|
207
339
|
executeToolImpl: async (name, args) => {
|
|
208
|
-
calls.push({name, args});
|
|
340
|
+
calls.push({ name, args });
|
|
209
341
|
const outputs = {
|
|
210
|
-
create_library_ticket: {issueKey: 'LIB-100'},
|
|
211
|
-
build_ticket: {issueKey: args.issueKey, status: 'WAIT FOR LIB BUILD'},
|
|
342
|
+
create_library_ticket: { issueKey: 'LIB-100' },
|
|
343
|
+
build_ticket: { issueKey: args.issueKey, status: 'WAIT FOR LIB BUILD' },
|
|
212
344
|
};
|
|
213
|
-
return {content: [{type: 'text', text: JSON.stringify(outputs[name])}]};
|
|
345
|
+
return { content: [{ type: 'text', text: JSON.stringify(outputs[name]) }] };
|
|
214
346
|
},
|
|
215
347
|
});
|
|
216
348
|
|
|
@@ -223,11 +355,11 @@ describe('tool schemas — agent contract', () => {
|
|
|
223
355
|
});
|
|
224
356
|
|
|
225
357
|
test('workflow tool failures are marked as MCP errors', async () => {
|
|
226
|
-
const result = await executeTool('run_stg_full_release', {systemCode: 'IBK'}, {
|
|
358
|
+
const result = await executeTool('run_stg_full_release', { systemCode: 'IBK' }, {
|
|
227
359
|
jira: makeMockJira(),
|
|
228
360
|
notifier: mockNotifier,
|
|
229
361
|
executeToolImpl: async () => ({
|
|
230
|
-
content: [{type: 'text', text: '❌ 錯誤: nested tool failed'}],
|
|
362
|
+
content: [{ type: 'text', text: '❌ 錯誤: nested tool failed' }],
|
|
231
363
|
isError: true,
|
|
232
364
|
}),
|
|
233
365
|
});
|
|
@@ -240,8 +372,8 @@ describe('tool schemas — agent contract', () => {
|
|
|
240
372
|
test('core dispatcher tool failures are marked as MCP errors', async () => {
|
|
241
373
|
const result = await executeTool(
|
|
242
374
|
'prepare_cd_deployment',
|
|
243
|
-
{issueKey: 'CID-1', environment: 'qa'},
|
|
244
|
-
{jira: makeMockJira(), notifier: mockNotifier},
|
|
375
|
+
{ issueKey: 'CID-1', environment: 'qa' },
|
|
376
|
+
{ jira: makeMockJira(), notifier: mockNotifier },
|
|
245
377
|
);
|
|
246
378
|
|
|
247
379
|
assert.equal(result.isError, true);
|
|
@@ -265,6 +397,75 @@ describe('tool schemas — agent contract', () => {
|
|
|
265
397
|
assert.notEqual(getToolDefinition('run_stg_full_release').annotations?.readOnlyHint, true);
|
|
266
398
|
assert.notEqual(getToolDefinition('run_lib_to_stg_release').annotations?.readOnlyHint, true);
|
|
267
399
|
});
|
|
400
|
+
|
|
401
|
+
test('Poller reports each polling attempt with current status and timing', async () => {
|
|
402
|
+
const events = [];
|
|
403
|
+
let calls = 0;
|
|
404
|
+
const jira = {
|
|
405
|
+
getIssue: async () => {
|
|
406
|
+
calls += 1;
|
|
407
|
+
return {
|
|
408
|
+
fields: {
|
|
409
|
+
status: {
|
|
410
|
+
name: calls === 1 ? 'WAIT APPROVAL' : 'WAIT DEPLOY',
|
|
411
|
+
},
|
|
412
|
+
},
|
|
413
|
+
};
|
|
414
|
+
},
|
|
415
|
+
};
|
|
416
|
+
|
|
417
|
+
const result = await new Poller(jira).waitForStatus('CID-822', 'WAIT DEPLOY', {
|
|
418
|
+
intervalMs: 1,
|
|
419
|
+
timeoutMs: 100,
|
|
420
|
+
onProgress: (event) => events.push(event),
|
|
421
|
+
});
|
|
422
|
+
|
|
423
|
+
assert.equal(result.attempts, 2);
|
|
424
|
+
assert.deepEqual(events.map((event) => event.attempts), [1, 2]);
|
|
425
|
+
assert.equal(events[0].phase, 'polling');
|
|
426
|
+
assert.equal(events[0].issueKey, 'CID-822');
|
|
427
|
+
assert.equal(events[0].currentStatus, 'WAIT APPROVAL');
|
|
428
|
+
assert.equal(events[0].targetStatus, 'WAIT DEPLOY');
|
|
429
|
+
assert.equal(events[0].nextPollMs, 1);
|
|
430
|
+
assert.equal(events[1].currentStatus, 'WAIT DEPLOY');
|
|
431
|
+
});
|
|
432
|
+
|
|
433
|
+
test('wait_for_comment reports each polling attempt before the comment is found', async () => {
|
|
434
|
+
const events = [];
|
|
435
|
+
let calls = 0;
|
|
436
|
+
const jira = {
|
|
437
|
+
getComments: async () => {
|
|
438
|
+
calls += 1;
|
|
439
|
+
if (calls === 1) return [];
|
|
440
|
+
return [
|
|
441
|
+
{
|
|
442
|
+
body: 'Approved',
|
|
443
|
+
author: { name: 'BK00178', displayName: 'James Yu' },
|
|
444
|
+
},
|
|
445
|
+
];
|
|
446
|
+
},
|
|
447
|
+
};
|
|
448
|
+
|
|
449
|
+
const result = await executeTool('wait_for_comment', {
|
|
450
|
+
issueKey: 'CID-822',
|
|
451
|
+
keyword: 'approved',
|
|
452
|
+
authorAccountId: 'BK00178',
|
|
453
|
+
intervalMs: 1,
|
|
454
|
+
timeoutMs: 100,
|
|
455
|
+
}, {
|
|
456
|
+
jira,
|
|
457
|
+
notifier: mockNotifier,
|
|
458
|
+
progress: (event) => events.push(event),
|
|
459
|
+
});
|
|
460
|
+
|
|
461
|
+
const data = JSON.parse(result.content[0].text);
|
|
462
|
+
assert.equal(data.found, true);
|
|
463
|
+
assert.deepEqual(events.map((event) => event.attempts), [1, 2]);
|
|
464
|
+
assert.equal(events[0].phase, 'polling');
|
|
465
|
+
assert.equal(events[0].title, '等待 Jira comment');
|
|
466
|
+
assert.equal(events[0].detail, 'keyword="approved", author=BK00178');
|
|
467
|
+
assert.equal(events[0].issueKey, 'CID-822');
|
|
468
|
+
});
|
|
268
469
|
});
|
|
269
470
|
|
|
270
471
|
/** 執行 tool,回傳 createIssue 收到的 fields(合併 updateIssue 的欄位,或 throw 若 ❌) */
|
|
@@ -377,10 +578,10 @@ describe('create_library_ticket — fields', () => {
|
|
|
377
578
|
const notifications = [];
|
|
378
579
|
const result = await executeTool(
|
|
379
580
|
'create_library_ticket',
|
|
380
|
-
{systemCode: 'CWA', gitBranch: 'release/v1.0.0'},
|
|
581
|
+
{ systemCode: 'CWA', gitBranch: 'release/v1.0.0' },
|
|
381
582
|
{
|
|
382
583
|
jira: makeMockJira(),
|
|
383
|
-
notifier: {notify: async (key, message) => notifications.push({key, message})},
|
|
584
|
+
notifier: { notify: async (key, message) => notifications.push({ key, message }) },
|
|
384
585
|
},
|
|
385
586
|
);
|
|
386
587
|
|
|
@@ -617,10 +818,10 @@ describe('create_cd_ticket — fields', () => {
|
|
|
617
818
|
const notifications = [];
|
|
618
819
|
const result = await executeTool(
|
|
619
820
|
'create_cd_ticket',
|
|
620
|
-
{systemCode: 'CWA', environment: 'dev'},
|
|
821
|
+
{ systemCode: 'CWA', environment: 'dev' },
|
|
621
822
|
{
|
|
622
823
|
jira: makeMockJira(),
|
|
623
|
-
notifier: {notify: async (key, message) => notifications.push({key, message})},
|
|
824
|
+
notifier: { notify: async (key, message) => notifications.push({ key, message }) },
|
|
624
825
|
},
|
|
625
826
|
);
|
|
626
827
|
|
|
@@ -1409,34 +1610,34 @@ describe('auto_grayrelease — approval payload handling', () => {
|
|
|
1409
1610
|
}
|
|
1410
1611
|
}
|
|
1411
1612
|
|
|
1412
|
-
function makeApprovalJira(environment, {comments = []} = {}) {
|
|
1613
|
+
function makeApprovalJira(environment, { comments = [] } = {}) {
|
|
1413
1614
|
let status = 'WAIT APPROVAL';
|
|
1414
|
-
const calls = {updateAssignee: [], transitionByName: []};
|
|
1615
|
+
const calls = { updateAssignee: [], transitionByName: [] };
|
|
1415
1616
|
|
|
1416
1617
|
return {
|
|
1417
1618
|
calls,
|
|
1418
1619
|
getIssueFields: async () => ({
|
|
1419
|
-
customfield_13436: {value: environment},
|
|
1420
|
-
customfield_13443: {value: 'CWA'},
|
|
1421
|
-
|
|
1422
|
-
|
|
1620
|
+
customfield_13436: { value: environment },
|
|
1621
|
+
customfield_13443: { value: 'CWA' },
|
|
1622
|
+
customfield_13432: 'pass',
|
|
1623
|
+
customfield_13433: 'pass',
|
|
1423
1624
|
}),
|
|
1424
|
-
getIssue: async () => ({fields: {status: {name: status}, summary: 'GrayRelease'}}),
|
|
1625
|
+
getIssue: async () => ({ fields: { status: { name: status }, summary: 'GrayRelease' } }),
|
|
1425
1626
|
getTransitions: async () => [],
|
|
1426
1627
|
updateAssignee: async (issueKey, accountId) => {
|
|
1427
|
-
calls.updateAssignee.push({issueKey, accountId});
|
|
1628
|
+
calls.updateAssignee.push({ issueKey, accountId });
|
|
1428
1629
|
if (environment === 'stg' || calls.updateAssignee.length >= 2) {
|
|
1429
1630
|
status = 'WAIT DEPLOY';
|
|
1430
1631
|
}
|
|
1431
1632
|
},
|
|
1432
1633
|
getComments: async () => comments,
|
|
1433
|
-
searchIssues: async () => [{fields: {customfield_13436: {value: 'stg'}}}],
|
|
1634
|
+
searchIssues: async () => [{ fields: { customfield_13436: { value: 'stg' } } }],
|
|
1434
1635
|
transitionByName: async (issueKey, transitionName) => {
|
|
1435
|
-
calls.transitionByName.push({issueKey, transitionName});
|
|
1636
|
+
calls.transitionByName.push({ issueKey, transitionName });
|
|
1436
1637
|
if (transitionName === 'To Verify') {
|
|
1437
1638
|
status = 'VERIFY';
|
|
1438
1639
|
}
|
|
1439
|
-
return {transitioned: transitionName, toStatus: status};
|
|
1640
|
+
return { transitioned: transitionName, toStatus: status };
|
|
1440
1641
|
},
|
|
1441
1642
|
};
|
|
1442
1643
|
}
|
|
@@ -1444,7 +1645,7 @@ describe('auto_grayrelease — approval payload handling', () => {
|
|
|
1444
1645
|
test('STG approval parses get_release_manager MCP content payload', async () => {
|
|
1445
1646
|
const server = http.createServer((_req, res) => {
|
|
1446
1647
|
res.setHeader('content-type', 'application/json');
|
|
1447
|
-
res.end(JSON.stringify({events: [{what: 'Sign off staff', who: 'Alvin Wang'}]}));
|
|
1648
|
+
res.end(JSON.stringify({ events: [{ what: 'Sign off staff', who: 'Alvin Wang' }] }));
|
|
1448
1649
|
});
|
|
1449
1650
|
await new Promise((resolve) => server.listen(0, '127.0.0.1', resolve));
|
|
1450
1651
|
|
|
@@ -1458,16 +1659,19 @@ describe('auto_grayrelease — approval payload handling', () => {
|
|
|
1458
1659
|
const jira = makeApprovalJira('stg');
|
|
1459
1660
|
const result = await executeTool(
|
|
1460
1661
|
'auto_grayrelease',
|
|
1461
|
-
{issueKey: 'CID-100', autoVerify: false},
|
|
1462
|
-
{jira, notifier: mockNotifier},
|
|
1662
|
+
{ issueKey: 'CID-100', autoVerify: false },
|
|
1663
|
+
{ jira, notifier: mockNotifier },
|
|
1463
1664
|
);
|
|
1464
1665
|
|
|
1465
1666
|
assert.ok(!result.content[0].text.startsWith('❌'), 'STG approval should continue');
|
|
1466
1667
|
const data = JSON.parse(result.content[0].text);
|
|
1467
1668
|
assert.equal(data.finalStatus, 'VERIFY');
|
|
1468
|
-
assert.deepEqual(jira.calls.updateAssignee, [{issueKey: 'CID-100', accountId: 'BK00619'}]);
|
|
1669
|
+
assert.deepEqual(jira.calls.updateAssignee, [{ issueKey: 'CID-100', accountId: 'BK00619' }]);
|
|
1469
1670
|
} finally {
|
|
1470
|
-
await new Promise((resolve) =>
|
|
1671
|
+
await new Promise((resolve, reject) => {
|
|
1672
|
+
server.close((err) => (err ? reject(err) : resolve()));
|
|
1673
|
+
server.closeAllConnections?.();
|
|
1674
|
+
});
|
|
1471
1675
|
restoreEnv();
|
|
1472
1676
|
}
|
|
1473
1677
|
});
|
|
@@ -1479,33 +1683,64 @@ describe('auto_grayrelease — approval payload handling', () => {
|
|
|
1479
1683
|
process.env.POLL_TIMEOUT_MS = '10';
|
|
1480
1684
|
|
|
1481
1685
|
const jira = makeApprovalJira('uat', {
|
|
1482
|
-
comments: [{body: 'Approved, please proceed', author: {name: 'BK00619', displayName: 'James Yu'}}],
|
|
1686
|
+
comments: [{ body: 'Approved, please proceed', author: { name: 'BK00619', displayName: 'James Yu' } }],
|
|
1483
1687
|
});
|
|
1484
1688
|
const result = await executeTool(
|
|
1485
1689
|
'auto_grayrelease',
|
|
1486
|
-
{issueKey: 'CID-101', autoVerify: false},
|
|
1487
|
-
{jira, notifier: mockNotifier},
|
|
1690
|
+
{ issueKey: 'CID-101', autoVerify: false },
|
|
1691
|
+
{ jira, notifier: mockNotifier },
|
|
1488
1692
|
);
|
|
1489
1693
|
|
|
1490
1694
|
assert.ok(!result.content[0].text.startsWith('❌'), 'UAT approval should continue');
|
|
1491
1695
|
const data = JSON.parse(result.content[0].text);
|
|
1492
1696
|
assert.equal(data.finalStatus, 'VERIFY');
|
|
1493
1697
|
assert.deepEqual(jira.calls.updateAssignee, [
|
|
1494
|
-
{issueKey: 'CID-101', accountId: 'BK00619'},
|
|
1495
|
-
{issueKey: 'CID-101', accountId: 'BK00619'},
|
|
1698
|
+
{ issueKey: 'CID-101', accountId: 'BK00619' },
|
|
1699
|
+
{ issueKey: 'CID-101', accountId: 'BK00619' },
|
|
1496
1700
|
]);
|
|
1497
1701
|
} finally {
|
|
1498
1702
|
restoreEnv();
|
|
1499
1703
|
}
|
|
1500
1704
|
});
|
|
1501
1705
|
|
|
1706
|
+
test('UAT approval emits progress for assignee, notification, comment wait, and final approval wait', async () => {
|
|
1707
|
+
const events = [];
|
|
1708
|
+
try {
|
|
1709
|
+
process.env.JABBER_NOTIFY_SCRIPT = '/dev/null';
|
|
1710
|
+
process.env.POLL_INTERVAL_MS = '0';
|
|
1711
|
+
process.env.POLL_TIMEOUT_MS = '10';
|
|
1712
|
+
|
|
1713
|
+
const jira = makeApprovalJira('uat', {
|
|
1714
|
+
comments: [{ body: 'Approved, please proceed', author: { name: 'BK00619', displayName: 'James Yu' } }],
|
|
1715
|
+
});
|
|
1716
|
+
const result = await executeTool(
|
|
1717
|
+
'auto_grayrelease',
|
|
1718
|
+
{ issueKey: 'CID-101', autoVerify: false },
|
|
1719
|
+
{
|
|
1720
|
+
jira,
|
|
1721
|
+
notifier: mockNotifier,
|
|
1722
|
+
progress: (event) => events.push(event),
|
|
1723
|
+
},
|
|
1724
|
+
);
|
|
1725
|
+
|
|
1726
|
+
assert.ok(!result.content[0].text.startsWith('❌'), result.content[0].text);
|
|
1727
|
+
assert.ok(events.some((event) => event.title === '指派 GrayRelease 簽核人' && event.detail.includes('James Yu')));
|
|
1728
|
+
assert.ok(events.some((event) => event.title === '發送 GrayRelease 簽核通知' && event.detail.includes('James Yu')));
|
|
1729
|
+
assert.ok(events.some((event) => event.title === '等待 Jira comment' && event.attempts === 1));
|
|
1730
|
+
assert.ok(events.some((event) => event.title === '指派 GrayRelease 簽核人' && event.detail.includes('Solar Chen')));
|
|
1731
|
+
assert.ok(events.some((event) => event.phase === 'polling' && event.targetStatus === 'WAIT DEPLOY'));
|
|
1732
|
+
} finally {
|
|
1733
|
+
restoreEnv();
|
|
1734
|
+
}
|
|
1735
|
+
});
|
|
1736
|
+
|
|
1502
1737
|
test('WAIT APPROVAL fails when GrayRelease environment field is missing', async () => {
|
|
1503
1738
|
const transitions = [];
|
|
1504
1739
|
const jira = {
|
|
1505
1740
|
getIssueFields: async () => ({
|
|
1506
|
-
customfield_13443: {value: 'CWA'},
|
|
1741
|
+
customfield_13443: { value: 'CWA' },
|
|
1507
1742
|
}),
|
|
1508
|
-
getIssue: async () => ({fields: {status: {name: 'WAIT APPROVAL'}, summary: 'GrayRelease'}}),
|
|
1743
|
+
getIssue: async () => ({ fields: { status: { name: 'WAIT APPROVAL' }, summary: 'GrayRelease' } }),
|
|
1509
1744
|
transitionByName: async (_issueKey, transitionName) => {
|
|
1510
1745
|
transitions.push(transitionName);
|
|
1511
1746
|
},
|
|
@@ -1513,8 +1748,8 @@ describe('auto_grayrelease — approval payload handling', () => {
|
|
|
1513
1748
|
|
|
1514
1749
|
const result = await executeTool(
|
|
1515
1750
|
'auto_grayrelease',
|
|
1516
|
-
{issueKey: 'CID-102', autoVerify: false},
|
|
1517
|
-
{jira, notifier: mockNotifier},
|
|
1751
|
+
{ issueKey: 'CID-102', autoVerify: false },
|
|
1752
|
+
{ jira, notifier: mockNotifier },
|
|
1518
1753
|
);
|
|
1519
1754
|
|
|
1520
1755
|
assert.ok(result.content[0].text.startsWith('❌'), 'missing environment should fail closed');
|
|
@@ -1549,23 +1784,130 @@ describe('auto_grayrelease — build/deploy result fields', () => {
|
|
|
1549
1784
|
}
|
|
1550
1785
|
}
|
|
1551
1786
|
|
|
1552
|
-
test('
|
|
1787
|
+
test('get_grayrelease_status normalizes Jira display status names', async () => {
|
|
1788
|
+
const jira = {
|
|
1789
|
+
getIssueFields: async () => ({
|
|
1790
|
+
customfield_13436: { value: 'dev' },
|
|
1791
|
+
customfield_13443: { value: 'IBK' },
|
|
1792
|
+
}),
|
|
1793
|
+
getIssue: async () => ({ fields: { status: { name: 'Wait for Build' }, summary: 'GrayRelease' } }),
|
|
1794
|
+
getTransitions: async () => [
|
|
1795
|
+
{ id: '1', name: 'Apply to approval', to: { name: 'Wait Approval' } },
|
|
1796
|
+
{ id: '2', name: 'GrayRelease Build', to: { name: 'Wait for Build' } },
|
|
1797
|
+
],
|
|
1798
|
+
};
|
|
1799
|
+
|
|
1800
|
+
const result = await executeTool(
|
|
1801
|
+
'get_grayrelease_status',
|
|
1802
|
+
{ issueKey: 'CID-822' },
|
|
1803
|
+
{ jira, notifier: mockNotifier },
|
|
1804
|
+
);
|
|
1805
|
+
|
|
1806
|
+
assert.ok(!result.isError, result.content[0].text);
|
|
1807
|
+
const output = JSON.parse(result.content[0].text);
|
|
1808
|
+
assert.equal(output.currentStatus, 'Wait for Build');
|
|
1809
|
+
assert.equal(output.normalizedStatus, 'WAIT FOR BUILD');
|
|
1810
|
+
assert.deepEqual(output.nextSteps, ['GrayRelease Build']);
|
|
1811
|
+
});
|
|
1812
|
+
|
|
1813
|
+
test('deploy_grayrelease advances Wait for Build via approval without rebuilding', async () => {
|
|
1814
|
+
useFastPolling();
|
|
1815
|
+
let status = 'Wait for Build';
|
|
1816
|
+
const transitions = [];
|
|
1817
|
+
const jira = {
|
|
1818
|
+
getIssueFields: async (_issueKey, fieldNames = []) => {
|
|
1819
|
+
if (fieldNames.includes('customfield_13433')) {
|
|
1820
|
+
return { customfield_13433: 'pass' };
|
|
1821
|
+
}
|
|
1822
|
+
return {
|
|
1823
|
+
customfield_13436: { value: 'dev' },
|
|
1824
|
+
customfield_13443: { value: 'IBK' },
|
|
1825
|
+
};
|
|
1826
|
+
},
|
|
1827
|
+
getIssue: async () => ({ fields: { status: { name: status }, summary: 'GrayRelease' } }),
|
|
1828
|
+
getTransitions: async () => {
|
|
1829
|
+
if (status === 'Wait for Build') {
|
|
1830
|
+
return [
|
|
1831
|
+
{ id: '1', name: 'Apply to approval', to: { name: 'Wait Approval' } },
|
|
1832
|
+
{ id: '2', name: 'GrayRelease Build', to: { name: 'Wait for Build' } },
|
|
1833
|
+
];
|
|
1834
|
+
}
|
|
1835
|
+
return [];
|
|
1836
|
+
},
|
|
1837
|
+
searchIssues: async () => [{ fields: { customfield_13436: { value: 'stg' } } }],
|
|
1838
|
+
transitionByName: async (_issueKey, transitionName) => {
|
|
1839
|
+
transitions.push(transitionName);
|
|
1840
|
+
if (transitionName === 'Apply to approval') status = 'Wait Approval';
|
|
1841
|
+
if (transitionName === 'Approve') status = 'Wait Deploy';
|
|
1842
|
+
if (transitionName === 'To Verify') status = 'VERIFY';
|
|
1843
|
+
},
|
|
1844
|
+
};
|
|
1845
|
+
|
|
1846
|
+
try {
|
|
1847
|
+
const result = await executeTool(
|
|
1848
|
+
'deploy_grayrelease',
|
|
1849
|
+
{ issueKey: 'CID-822' },
|
|
1850
|
+
{ jira, notifier: mockNotifier },
|
|
1851
|
+
);
|
|
1852
|
+
|
|
1853
|
+
assert.ok(!result.isError, result.content[0].text);
|
|
1854
|
+
const output = JSON.parse(result.content[0].text);
|
|
1855
|
+
assert.equal(output.finalStatus, 'VERIFY');
|
|
1856
|
+
assert.deepEqual(transitions, [
|
|
1857
|
+
'Apply to approval',
|
|
1858
|
+
'Approve',
|
|
1859
|
+
'GrayRelease Deploy',
|
|
1860
|
+
'To Verify',
|
|
1861
|
+
]);
|
|
1862
|
+
} finally {
|
|
1863
|
+
restoreEnv();
|
|
1864
|
+
}
|
|
1865
|
+
});
|
|
1866
|
+
|
|
1867
|
+
test('deploy_grayrelease stops in VERIFY without rebuilding', async () => {
|
|
1868
|
+
const transitions = [];
|
|
1869
|
+
const jira = {
|
|
1870
|
+
getIssueFields: async () => ({
|
|
1871
|
+
customfield_13436: { value: 'dev' },
|
|
1872
|
+
customfield_13443: { value: 'IBK' },
|
|
1873
|
+
}),
|
|
1874
|
+
getIssue: async () => ({ fields: { status: { name: 'VERIFY' }, summary: 'GrayRelease' } }),
|
|
1875
|
+
transitionByName: async (_issueKey, transitionName) => {
|
|
1876
|
+
transitions.push(transitionName);
|
|
1877
|
+
},
|
|
1878
|
+
};
|
|
1879
|
+
|
|
1880
|
+
const result = await executeTool(
|
|
1881
|
+
'deploy_grayrelease',
|
|
1882
|
+
{ issueKey: 'CID-822' },
|
|
1883
|
+
{ jira, notifier: mockNotifier },
|
|
1884
|
+
);
|
|
1885
|
+
|
|
1886
|
+
assert.ok(!result.isError, result.content[0].text);
|
|
1887
|
+
const output = JSON.parse(result.content[0].text);
|
|
1888
|
+
assert.equal(output.finalStatus, 'VERIFY');
|
|
1889
|
+
assert.deepEqual(transitions, []);
|
|
1890
|
+
assert.match(output.log.join('\n'), /若需重 build 請明確執行 build\/rebuild/);
|
|
1891
|
+
});
|
|
1892
|
+
|
|
1893
|
+
test('Build waits until customfield_13432 becomes pass before Apply to approval', async () => {
|
|
1553
1894
|
useFastPolling();
|
|
1895
|
+
const events = [];
|
|
1554
1896
|
let status = 'WAIT FOR BUILD';
|
|
1555
1897
|
let buildResultCalls = 0;
|
|
1556
1898
|
const transitions = [];
|
|
1557
1899
|
const jira = {
|
|
1558
1900
|
getIssueFields: async (_issueKey, fieldNames = []) => {
|
|
1559
|
-
if (fieldNames.includes('
|
|
1901
|
+
if (fieldNames.includes('customfield_13432')) {
|
|
1560
1902
|
buildResultCalls++;
|
|
1561
|
-
return {
|
|
1903
|
+
return { customfield_13432: buildResultCalls >= 3 ? 'pass' : 'starting' };
|
|
1562
1904
|
}
|
|
1563
1905
|
return {
|
|
1564
|
-
customfield_13436: {value: 'dev'},
|
|
1565
|
-
customfield_13443: {value: 'CWA'},
|
|
1906
|
+
customfield_13436: { value: 'dev' },
|
|
1907
|
+
customfield_13443: { value: 'CWA' },
|
|
1566
1908
|
};
|
|
1567
1909
|
},
|
|
1568
|
-
getIssue: async () => ({fields: {status: {name: status}, summary: 'GrayRelease'}}),
|
|
1910
|
+
getIssue: async () => ({ fields: { status: { name: status }, summary: 'GrayRelease' } }),
|
|
1569
1911
|
transitionByName: async (_issueKey, transitionName) => {
|
|
1570
1912
|
transitions.push(transitionName);
|
|
1571
1913
|
if (transitionName === 'Apply to approval') {
|
|
@@ -1577,36 +1919,41 @@ describe('auto_grayrelease — build/deploy result fields', () => {
|
|
|
1577
1919
|
try {
|
|
1578
1920
|
const result = await executeTool(
|
|
1579
1921
|
'auto_grayrelease',
|
|
1580
|
-
{issueKey: 'CID-200', autoVerify: false},
|
|
1581
|
-
{jira, notifier: mockNotifier},
|
|
1922
|
+
{ issueKey: 'CID-200', autoVerify: false },
|
|
1923
|
+
{ jira, notifier: mockNotifier, progress: (event) => events.push(event) },
|
|
1582
1924
|
);
|
|
1583
1925
|
|
|
1584
1926
|
assert.ok(!result.content[0].text.startsWith('❌'), 'Build flow should succeed');
|
|
1585
1927
|
assert.equal(buildResultCalls, 3);
|
|
1586
1928
|
assert.deepEqual(transitions, ['GrayRelease Build', 'Apply to approval']);
|
|
1929
|
+
assert.ok(events.some((event) => (
|
|
1930
|
+
event.phase === 'polling' &&
|
|
1931
|
+
event.title === '等待 GrayRelease Build 結果' &&
|
|
1932
|
+
event.attempts === 3
|
|
1933
|
+
)));
|
|
1587
1934
|
} finally {
|
|
1588
1935
|
restoreEnv();
|
|
1589
1936
|
}
|
|
1590
1937
|
});
|
|
1591
1938
|
|
|
1592
|
-
test('Deploy waits until
|
|
1939
|
+
test('Deploy waits until customfield_13433 becomes pass before To Verify', async () => {
|
|
1593
1940
|
useFastPolling();
|
|
1594
1941
|
let status = 'WAIT DEPLOY';
|
|
1595
1942
|
let deployResultCalls = 0;
|
|
1596
1943
|
const transitions = [];
|
|
1597
1944
|
const jira = {
|
|
1598
1945
|
getIssueFields: async (_issueKey, fieldNames = []) => {
|
|
1599
|
-
if (fieldNames.includes('
|
|
1946
|
+
if (fieldNames.includes('customfield_13433')) {
|
|
1600
1947
|
deployResultCalls++;
|
|
1601
|
-
return {
|
|
1948
|
+
return { customfield_13433: deployResultCalls >= 3 ? 'pass' : 'starting' };
|
|
1602
1949
|
}
|
|
1603
1950
|
return {
|
|
1604
|
-
customfield_13436: {value: 'stg'},
|
|
1605
|
-
customfield_13443: {value: 'CWA'},
|
|
1951
|
+
customfield_13436: { value: 'stg' },
|
|
1952
|
+
customfield_13443: { value: 'CWA' },
|
|
1606
1953
|
};
|
|
1607
1954
|
},
|
|
1608
|
-
getIssue: async () => ({fields: {status: {name: status}, summary: 'GrayRelease'}}),
|
|
1609
|
-
searchIssues: async () => [{fields: {customfield_13436: {value: 'stg'}}}],
|
|
1955
|
+
getIssue: async () => ({ fields: { status: { name: status }, summary: 'GrayRelease' } }),
|
|
1956
|
+
searchIssues: async () => [{ fields: { customfield_13436: { value: 'stg' } } }],
|
|
1610
1957
|
transitionByName: async (_issueKey, transitionName) => {
|
|
1611
1958
|
transitions.push(transitionName);
|
|
1612
1959
|
if (transitionName === 'To Verify') {
|
|
@@ -1618,8 +1965,8 @@ describe('auto_grayrelease — build/deploy result fields', () => {
|
|
|
1618
1965
|
try {
|
|
1619
1966
|
const result = await executeTool(
|
|
1620
1967
|
'auto_grayrelease',
|
|
1621
|
-
{issueKey: 'CID-201', autoVerify: false},
|
|
1622
|
-
{jira, notifier: mockNotifier},
|
|
1968
|
+
{ issueKey: 'CID-201', autoVerify: false },
|
|
1969
|
+
{ jira, notifier: mockNotifier },
|
|
1623
1970
|
);
|
|
1624
1971
|
|
|
1625
1972
|
assert.ok(!result.content[0].text.startsWith('❌'), 'Deploy flow should succeed');
|
|
@@ -1636,23 +1983,23 @@ describe('auto_grayrelease — build/deploy result fields', () => {
|
|
|
1636
1983
|
const transitions = [];
|
|
1637
1984
|
const jira = {
|
|
1638
1985
|
getIssueFields: async (_issueKey, fieldNames = []) => {
|
|
1639
|
-
if (fieldNames.includes('
|
|
1640
|
-
return {
|
|
1986
|
+
if (fieldNames.includes('customfield_13433')) {
|
|
1987
|
+
return { customfield_13433: 'pass' };
|
|
1641
1988
|
}
|
|
1642
1989
|
return {
|
|
1643
|
-
customfield_13436: {value: 'stg'},
|
|
1644
|
-
customfield_13443: {value: 'IBK'},
|
|
1990
|
+
customfield_13436: { value: 'stg' },
|
|
1991
|
+
customfield_13443: { value: 'IBK' },
|
|
1645
1992
|
};
|
|
1646
1993
|
},
|
|
1647
|
-
getIssue: async () => ({fields: {status: {name: status}, summary: 'GrayRelease'}}),
|
|
1994
|
+
getIssue: async () => ({ fields: { status: { name: status }, summary: 'GrayRelease' } }),
|
|
1648
1995
|
getComments: async () => [
|
|
1649
1996
|
{
|
|
1650
1997
|
body: "Trigger update IBK's instance_group to [ING]NonPRD_ExecutionNode success, please wait about 3 mins before trigger deploy.",
|
|
1651
|
-
author: {displayName: 'cid jira worker'},
|
|
1998
|
+
author: { displayName: 'cid jira worker' },
|
|
1652
1999
|
created: new Date().toISOString(),
|
|
1653
2000
|
},
|
|
1654
2001
|
],
|
|
1655
|
-
searchIssues: async () => [{fields: {customfield_13436: {value: 'prd'}}}],
|
|
2002
|
+
searchIssues: async () => [{ fields: { customfield_13436: { value: 'prd' } } }],
|
|
1656
2003
|
transitionByName: async (_issueKey, transitionName) => {
|
|
1657
2004
|
transitions.push(transitionName);
|
|
1658
2005
|
if (transitionName === 'To Verify') {
|
|
@@ -1664,8 +2011,8 @@ describe('auto_grayrelease — build/deploy result fields', () => {
|
|
|
1664
2011
|
try {
|
|
1665
2012
|
const result = await executeTool(
|
|
1666
2013
|
'auto_grayrelease',
|
|
1667
|
-
{issueKey: 'CID-202', autoVerify: false},
|
|
1668
|
-
{jira, notifier: mockNotifier},
|
|
2014
|
+
{ issueKey: 'CID-202', autoVerify: false },
|
|
2015
|
+
{ jira, notifier: mockNotifier },
|
|
1669
2016
|
);
|
|
1670
2017
|
|
|
1671
2018
|
assert.ok(!result.content[0].text.startsWith('❌'), 'Deploy flow should succeed');
|
|
@@ -1683,15 +2030,15 @@ describe('auto_grayrelease — build/deploy result fields', () => {
|
|
|
1683
2030
|
const transitions = [];
|
|
1684
2031
|
const jira = {
|
|
1685
2032
|
getIssueFields: async (_issueKey, fieldNames = []) => {
|
|
1686
|
-
if (fieldNames.includes('
|
|
1687
|
-
return {
|
|
2033
|
+
if (fieldNames.includes('customfield_13433')) {
|
|
2034
|
+
return { customfield_13433: 'pass' };
|
|
1688
2035
|
}
|
|
1689
2036
|
return {
|
|
1690
|
-
customfield_13436: {value: 'stg'},
|
|
1691
|
-
customfield_13443: {value: 'IBK'},
|
|
2037
|
+
customfield_13436: { value: 'stg' },
|
|
2038
|
+
customfield_13443: { value: 'IBK' },
|
|
1692
2039
|
};
|
|
1693
2040
|
},
|
|
1694
|
-
getIssue: async () => ({fields: {status: {name: status}, summary: 'GrayRelease'}}),
|
|
2041
|
+
getIssue: async () => ({ fields: { status: { name: status }, summary: 'GrayRelease' } }),
|
|
1695
2042
|
getComments: async () => {
|
|
1696
2043
|
getCommentsCalls++;
|
|
1697
2044
|
if (getCommentsCalls < 2) {
|
|
@@ -1700,14 +2047,14 @@ describe('auto_grayrelease — build/deploy result fields', () => {
|
|
|
1700
2047
|
return [
|
|
1701
2048
|
{
|
|
1702
2049
|
body: "Trigger update IBK's instance_group to [ING]NonPRD_ExecutionNode success, please wait about 3 mins before trigger deploy.",
|
|
1703
|
-
author: {displayName: 'CID Jira Worker'},
|
|
2050
|
+
author: { displayName: 'CID Jira Worker' },
|
|
1704
2051
|
created: new Date().toISOString(),
|
|
1705
2052
|
},
|
|
1706
2053
|
];
|
|
1707
2054
|
},
|
|
1708
2055
|
searchIssues: async () => {
|
|
1709
2056
|
searchIssuesCalls++;
|
|
1710
|
-
return [{fields: {customfield_13436: {value: 'prd'}}}];
|
|
2057
|
+
return [{ fields: { customfield_13436: { value: 'prd' } } }];
|
|
1711
2058
|
},
|
|
1712
2059
|
transitionByName: async (_issueKey, transitionName) => {
|
|
1713
2060
|
transitions.push(transitionName);
|
|
@@ -1720,8 +2067,8 @@ describe('auto_grayrelease — build/deploy result fields', () => {
|
|
|
1720
2067
|
try {
|
|
1721
2068
|
const result = await executeTool(
|
|
1722
2069
|
'auto_grayrelease',
|
|
1723
|
-
{issueKey: 'CID-203', autoVerify: false},
|
|
1724
|
-
{jira, notifier: mockNotifier},
|
|
2070
|
+
{ issueKey: 'CID-203', autoVerify: false },
|
|
2071
|
+
{ jira, notifier: mockNotifier },
|
|
1725
2072
|
);
|
|
1726
2073
|
|
|
1727
2074
|
assert.ok(!result.content[0].text.startsWith('❌'), 'Deploy flow should succeed');
|
|
@@ -1761,7 +2108,7 @@ describe('prepare_cd_deployment', () => {
|
|
|
1761
2108
|
return {};
|
|
1762
2109
|
},
|
|
1763
2110
|
getIssue: async () => ({ fields: { status: { name: resultStatus }, summary: 'CD test' } }),
|
|
1764
|
-
addComment: async () => {},
|
|
2111
|
+
addComment: async () => { },
|
|
1765
2112
|
};
|
|
1766
2113
|
}
|
|
1767
2114
|
|
|
@@ -1890,12 +2237,12 @@ describe('prepare_cd_deployment', () => {
|
|
|
1890
2237
|
|
|
1891
2238
|
test('找不到任何 deploy transition → 回傳錯誤', async () => {
|
|
1892
2239
|
const jira = {
|
|
1893
|
-
updateIssue: async () => {},
|
|
2240
|
+
updateIssue: async () => { },
|
|
1894
2241
|
getTransitions: async () => [
|
|
1895
2242
|
{ id: '1', name: 'Some Other Transition', to: { name: 'Other' } },
|
|
1896
2243
|
],
|
|
1897
2244
|
getIssue: async () => ({ fields: { status: { name: 'Approved' }, summary: 'CD test' } }),
|
|
1898
|
-
addComment: async () => {},
|
|
2245
|
+
addComment: async () => { },
|
|
1899
2246
|
};
|
|
1900
2247
|
const result = await executeTool(
|
|
1901
2248
|
'prepare_cd_deployment',
|
|
@@ -1927,7 +2274,7 @@ describe('prepare_cd_deployment', () => {
|
|
|
1927
2274
|
let callCount = 0;
|
|
1928
2275
|
const calls = { transitionById: [] };
|
|
1929
2276
|
const jira = {
|
|
1930
|
-
updateIssue: async () => {},
|
|
2277
|
+
updateIssue: async () => { },
|
|
1931
2278
|
getTransitions: async () => {
|
|
1932
2279
|
callCount++;
|
|
1933
2280
|
// findDeployTrans(1) → 無;pre-transition check(2) → Accept;findDeployTrans after Accept(3) → deploy
|
|
@@ -1946,7 +2293,7 @@ describe('prepare_cd_deployment', () => {
|
|
|
1946
2293
|
return {};
|
|
1947
2294
|
},
|
|
1948
2295
|
getIssue: async () => ({ fields: { status: { name: 'Prepare For Deploy' }, summary: 'CD' } }),
|
|
1949
|
-
addComment: async () => {},
|
|
2296
|
+
addComment: async () => { },
|
|
1950
2297
|
};
|
|
1951
2298
|
const result = await executeTool(
|
|
1952
2299
|
'prepare_cd_deployment',
|
|
@@ -1977,7 +2324,7 @@ describe('prepare_cd_deployment', () => {
|
|
|
1977
2324
|
let status = 'TO DO';
|
|
1978
2325
|
const calls = { transitionById: [] };
|
|
1979
2326
|
const jira = {
|
|
1980
|
-
updateIssue: async () => {},
|
|
2327
|
+
updateIssue: async () => { },
|
|
1981
2328
|
getTransitions: async () => {
|
|
1982
2329
|
callCount++;
|
|
1983
2330
|
if (callCount === 1) {
|
|
@@ -1999,7 +2346,7 @@ describe('prepare_cd_deployment', () => {
|
|
|
1999
2346
|
return {};
|
|
2000
2347
|
},
|
|
2001
2348
|
getIssue: async () => ({ fields: { status: { name: status }, summary: 'CD' } }),
|
|
2002
|
-
addComment: async () => {},
|
|
2349
|
+
addComment: async () => { },
|
|
2003
2350
|
};
|
|
2004
2351
|
const result = await executeTool(
|
|
2005
2352
|
'prepare_cd_deployment',
|
|
@@ -2020,7 +2367,7 @@ describe('prepare_cd_deployment', () => {
|
|
|
2020
2367
|
let status = 'Prepare For Deploy';
|
|
2021
2368
|
const calls = { transitionById: [] };
|
|
2022
2369
|
const jira = {
|
|
2023
|
-
updateIssue: async () => {},
|
|
2370
|
+
updateIssue: async () => { },
|
|
2024
2371
|
getTransitions: async () => {
|
|
2025
2372
|
callCount++;
|
|
2026
2373
|
if (callCount === 1) {
|
|
@@ -2042,7 +2389,7 @@ describe('prepare_cd_deployment', () => {
|
|
|
2042
2389
|
return {};
|
|
2043
2390
|
},
|
|
2044
2391
|
getIssue: async () => ({ fields: { status: { name: status }, summary: 'CD' } }),
|
|
2045
|
-
addComment: async () => {},
|
|
2392
|
+
addComment: async () => { },
|
|
2046
2393
|
};
|
|
2047
2394
|
const result = await executeTool(
|
|
2048
2395
|
'prepare_cd_deployment',
|
|
@@ -2076,7 +2423,7 @@ describe('wait_to_dev', () => {
|
|
|
2076
2423
|
calls.push({ key, id });
|
|
2077
2424
|
},
|
|
2078
2425
|
getIssue: async () => ({ fields: { status: { name: 'Wait To DEV' }, summary: 'CI' } }),
|
|
2079
|
-
addComment: async () => {},
|
|
2426
|
+
addComment: async () => { },
|
|
2080
2427
|
};
|
|
2081
2428
|
|
|
2082
2429
|
const result = await executeTool(
|
|
@@ -2099,7 +2446,7 @@ describe('wait_to_dev', () => {
|
|
|
2099
2446
|
calls.push({ key, id });
|
|
2100
2447
|
},
|
|
2101
2448
|
getIssue: async () => ({ fields: { status: { name: 'Wait To DEV' }, summary: 'CI' } }),
|
|
2102
|
-
addComment: async () => {},
|
|
2449
|
+
addComment: async () => { },
|
|
2103
2450
|
};
|
|
2104
2451
|
|
|
2105
2452
|
const result = await executeTool(
|
|
@@ -2153,7 +2500,7 @@ describe('trigger_deployment', () => {
|
|
|
2153
2500
|
getIssue: async (key) => ({
|
|
2154
2501
|
fields: { status: { name: finalStatus }, summary: `[MOCK] ${key}` },
|
|
2155
2502
|
}),
|
|
2156
|
-
addComment: async () => {},
|
|
2503
|
+
addComment: async () => { },
|
|
2157
2504
|
};
|
|
2158
2505
|
}
|
|
2159
2506
|
|