jira-ai 0.2.11 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +10 -0
- package/dist/commands/create-task.js +61 -0
- package/dist/lib/jira-client.js +33 -0
- package/package.json +1 -1
- package/settings.yaml +2 -1
- package/src/cli.ts +11 -0
- package/src/commands/create-task.ts +73 -0
- package/src/lib/jira-client.ts +42 -0
- package/tests/create-task.test.ts +199 -0
package/dist/cli.js
CHANGED
|
@@ -16,6 +16,7 @@ const list_issue_types_1 = require("./commands/list-issue-types");
|
|
|
16
16
|
const run_jql_1 = require("./commands/run-jql");
|
|
17
17
|
const update_description_1 = require("./commands/update-description");
|
|
18
18
|
const add_comment_1 = require("./commands/add-comment");
|
|
19
|
+
const create_task_1 = require("./commands/create-task");
|
|
19
20
|
const about_1 = require("./commands/about");
|
|
20
21
|
const auth_1 = require("./commands/auth");
|
|
21
22
|
const settings_1 = require("./lib/settings");
|
|
@@ -95,6 +96,15 @@ program
|
|
|
95
96
|
.requiredOption('--file-path <path>', 'Path to Markdown file')
|
|
96
97
|
.requiredOption('--issue-key <key>', 'Jira issue key (e.g., PS-123)')
|
|
97
98
|
.action(withPermission('add-comment', add_comment_1.addCommentCommand));
|
|
99
|
+
// Create task command
|
|
100
|
+
program
|
|
101
|
+
.command('create-task')
|
|
102
|
+
.description('Create a new Jira issue')
|
|
103
|
+
.requiredOption('--title <title>', 'Issue title/summary')
|
|
104
|
+
.requiredOption('--project <project>', 'Project key (e.g., PROJ)')
|
|
105
|
+
.requiredOption('--issue-type <type>', 'Issue type (e.g., Task, Epic, Subtask)')
|
|
106
|
+
.option('--parent <key>', 'Parent issue key (required for subtasks)')
|
|
107
|
+
.action(withPermission('create-task', create_task_1.createTaskCommand));
|
|
98
108
|
// About command (always allowed)
|
|
99
109
|
program
|
|
100
110
|
.command('about')
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.createTaskCommand = createTaskCommand;
|
|
7
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
8
|
+
const ora_1 = __importDefault(require("ora"));
|
|
9
|
+
const jira_client_1 = require("../lib/jira-client");
|
|
10
|
+
async function createTaskCommand(options) {
|
|
11
|
+
const { title, project, issueType, parent } = options;
|
|
12
|
+
// Validate required fields
|
|
13
|
+
if (!title || title.trim() === '') {
|
|
14
|
+
console.error(chalk_1.default.red('\nError: Title is required (use --title)'));
|
|
15
|
+
process.exit(1);
|
|
16
|
+
}
|
|
17
|
+
if (!project || project.trim() === '') {
|
|
18
|
+
console.error(chalk_1.default.red('\nError: Project is required (use --project)'));
|
|
19
|
+
process.exit(1);
|
|
20
|
+
}
|
|
21
|
+
if (!issueType || issueType.trim() === '') {
|
|
22
|
+
console.error(chalk_1.default.red('\nError: Issue type is required (use --issue-type)'));
|
|
23
|
+
process.exit(1);
|
|
24
|
+
}
|
|
25
|
+
// Create issue with spinner
|
|
26
|
+
const spinner = (0, ora_1.default)(`Creating ${issueType} in project ${project}...`).start();
|
|
27
|
+
try {
|
|
28
|
+
const result = await (0, jira_client_1.createIssue)(project, title, issueType, parent);
|
|
29
|
+
spinner.succeed(chalk_1.default.green(`Issue created successfully: ${result.key}`));
|
|
30
|
+
console.log(chalk_1.default.gray(`\nTitle: ${title}`));
|
|
31
|
+
console.log(chalk_1.default.gray(`Project: ${project}`));
|
|
32
|
+
console.log(chalk_1.default.gray(`Issue Type: ${issueType}`));
|
|
33
|
+
if (parent) {
|
|
34
|
+
console.log(chalk_1.default.gray(`Parent: ${parent}`));
|
|
35
|
+
}
|
|
36
|
+
console.log(chalk_1.default.cyan(`\nIssue Key: ${result.key}`));
|
|
37
|
+
}
|
|
38
|
+
catch (error) {
|
|
39
|
+
spinner.fail(chalk_1.default.red('Failed to create issue'));
|
|
40
|
+
console.error(chalk_1.default.red('\nError: ' + (error instanceof Error ? error.message : 'Unknown error')));
|
|
41
|
+
// Provide helpful hints based on error
|
|
42
|
+
if (error instanceof Error) {
|
|
43
|
+
if (error.message.includes('project') || error.message.includes('Project')) {
|
|
44
|
+
console.log(chalk_1.default.yellow('\nHint: Check that the project key is correct'));
|
|
45
|
+
console.log(chalk_1.default.yellow('Use "jira-ai projects" to see available projects'));
|
|
46
|
+
}
|
|
47
|
+
else if (error.message.includes('issue type') || error.message.includes('issuetype')) {
|
|
48
|
+
console.log(chalk_1.default.yellow('\nHint: Check that the issue type is correct'));
|
|
49
|
+
console.log(chalk_1.default.yellow(`Use "jira-ai list-issue-types ${project}" to see available issue types`));
|
|
50
|
+
}
|
|
51
|
+
else if (error.message.includes('parent') || error.message.includes('Parent')) {
|
|
52
|
+
console.log(chalk_1.default.yellow('\nHint: Check that the parent issue key is correct'));
|
|
53
|
+
console.log(chalk_1.default.yellow('Parent issues are required for subtasks'));
|
|
54
|
+
}
|
|
55
|
+
else if (error.message.includes('403')) {
|
|
56
|
+
console.log(chalk_1.default.yellow('\nHint: You may not have permission to create issues in this project'));
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
process.exit(1);
|
|
60
|
+
}
|
|
61
|
+
}
|
package/dist/lib/jira-client.js
CHANGED
|
@@ -10,6 +10,7 @@ exports.searchIssuesByJql = searchIssuesByJql;
|
|
|
10
10
|
exports.updateIssueDescription = updateIssueDescription;
|
|
11
11
|
exports.addIssueComment = addIssueComment;
|
|
12
12
|
exports.getProjectIssueTypes = getProjectIssueTypes;
|
|
13
|
+
exports.createIssue = createIssue;
|
|
13
14
|
const jira_js_1 = require("jira.js");
|
|
14
15
|
const utils_1 = require("./utils");
|
|
15
16
|
const auth_storage_1 = require("./auth-storage");
|
|
@@ -270,3 +271,35 @@ async function getProjectIssueTypes(projectIdOrKey) {
|
|
|
270
271
|
hierarchyLevel: issueType.hierarchyLevel || 0,
|
|
271
272
|
})) || [];
|
|
272
273
|
}
|
|
274
|
+
/**
|
|
275
|
+
* Create a new issue
|
|
276
|
+
* @param projectKey - The project key (e.g., "PROJ")
|
|
277
|
+
* @param summary - The issue title/summary
|
|
278
|
+
* @param issueTypeName - The issue type name (e.g., "Task", "Epic", "Subtask")
|
|
279
|
+
* @param parentKey - Optional parent issue key for subtasks
|
|
280
|
+
*/
|
|
281
|
+
async function createIssue(projectKey, summary, issueTypeName, parentKey) {
|
|
282
|
+
const client = getJiraClient();
|
|
283
|
+
const fields = {
|
|
284
|
+
project: {
|
|
285
|
+
key: projectKey,
|
|
286
|
+
},
|
|
287
|
+
summary,
|
|
288
|
+
issuetype: {
|
|
289
|
+
name: issueTypeName,
|
|
290
|
+
},
|
|
291
|
+
};
|
|
292
|
+
// Add parent field if this is a subtask
|
|
293
|
+
if (parentKey) {
|
|
294
|
+
fields.parent = {
|
|
295
|
+
key: parentKey,
|
|
296
|
+
};
|
|
297
|
+
}
|
|
298
|
+
const response = await client.issues.createIssue({
|
|
299
|
+
fields,
|
|
300
|
+
});
|
|
301
|
+
return {
|
|
302
|
+
key: response.key || '',
|
|
303
|
+
id: response.id || '',
|
|
304
|
+
};
|
|
305
|
+
}
|
package/package.json
CHANGED
package/settings.yaml
CHANGED
|
@@ -11,7 +11,7 @@ projects:
|
|
|
11
11
|
# - all
|
|
12
12
|
|
|
13
13
|
# Commands: List of allowed commands (use "all" to allow all commands)
|
|
14
|
-
# Available commands: me, projects, task-with-details, project-statuses, list-issue-types, run-jql, update-description, add-comment, about
|
|
14
|
+
# Available commands: me, projects, task-with-details, project-statuses, list-issue-types, run-jql, update-description, add-comment, create-task, about
|
|
15
15
|
commands:
|
|
16
16
|
- me
|
|
17
17
|
- projects
|
|
@@ -23,3 +23,4 @@ commands:
|
|
|
23
23
|
- list-issue-types
|
|
24
24
|
- update-description
|
|
25
25
|
- add-comment
|
|
26
|
+
- create-task
|
package/src/cli.ts
CHANGED
|
@@ -12,6 +12,7 @@ import { listIssueTypesCommand } from './commands/list-issue-types';
|
|
|
12
12
|
import { runJqlCommand } from './commands/run-jql';
|
|
13
13
|
import { updateDescriptionCommand } from './commands/update-description';
|
|
14
14
|
import { addCommentCommand } from './commands/add-comment';
|
|
15
|
+
import { createTaskCommand } from './commands/create-task';
|
|
15
16
|
import { aboutCommand } from './commands/about';
|
|
16
17
|
import { authCommand } from './commands/auth';
|
|
17
18
|
import { isCommandAllowed, getAllowedCommands } from './lib/settings';
|
|
@@ -107,6 +108,16 @@ program
|
|
|
107
108
|
.requiredOption('--issue-key <key>', 'Jira issue key (e.g., PS-123)')
|
|
108
109
|
.action(withPermission('add-comment', addCommentCommand));
|
|
109
110
|
|
|
111
|
+
// Create task command
|
|
112
|
+
program
|
|
113
|
+
.command('create-task')
|
|
114
|
+
.description('Create a new Jira issue')
|
|
115
|
+
.requiredOption('--title <title>', 'Issue title/summary')
|
|
116
|
+
.requiredOption('--project <project>', 'Project key (e.g., PROJ)')
|
|
117
|
+
.requiredOption('--issue-type <type>', 'Issue type (e.g., Task, Epic, Subtask)')
|
|
118
|
+
.option('--parent <key>', 'Parent issue key (required for subtasks)')
|
|
119
|
+
.action(withPermission('create-task', createTaskCommand));
|
|
120
|
+
|
|
110
121
|
// About command (always allowed)
|
|
111
122
|
program
|
|
112
123
|
.command('about')
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import ora from 'ora';
|
|
3
|
+
import { createIssue } from '../lib/jira-client';
|
|
4
|
+
|
|
5
|
+
export async function createTaskCommand(
|
|
6
|
+
options: {
|
|
7
|
+
title: string;
|
|
8
|
+
project: string;
|
|
9
|
+
issueType: string;
|
|
10
|
+
parent?: string;
|
|
11
|
+
}
|
|
12
|
+
): Promise<void> {
|
|
13
|
+
const { title, project, issueType, parent } = options;
|
|
14
|
+
|
|
15
|
+
// Validate required fields
|
|
16
|
+
if (!title || title.trim() === '') {
|
|
17
|
+
console.error(chalk.red('\nError: Title is required (use --title)'));
|
|
18
|
+
process.exit(1);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
if (!project || project.trim() === '') {
|
|
22
|
+
console.error(chalk.red('\nError: Project is required (use --project)'));
|
|
23
|
+
process.exit(1);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
if (!issueType || issueType.trim() === '') {
|
|
27
|
+
console.error(chalk.red('\nError: Issue type is required (use --issue-type)'));
|
|
28
|
+
process.exit(1);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Create issue with spinner
|
|
32
|
+
const spinner = ora(`Creating ${issueType} in project ${project}...`).start();
|
|
33
|
+
|
|
34
|
+
try {
|
|
35
|
+
const result = await createIssue(project, title, issueType, parent);
|
|
36
|
+
|
|
37
|
+
spinner.succeed(chalk.green(`Issue created successfully: ${result.key}`));
|
|
38
|
+
console.log(chalk.gray(`\nTitle: ${title}`));
|
|
39
|
+
console.log(chalk.gray(`Project: ${project}`));
|
|
40
|
+
console.log(chalk.gray(`Issue Type: ${issueType}`));
|
|
41
|
+
if (parent) {
|
|
42
|
+
console.log(chalk.gray(`Parent: ${parent}`));
|
|
43
|
+
}
|
|
44
|
+
console.log(chalk.cyan(`\nIssue Key: ${result.key}`));
|
|
45
|
+
} catch (error) {
|
|
46
|
+
spinner.fail(chalk.red('Failed to create issue'));
|
|
47
|
+
console.error(
|
|
48
|
+
chalk.red(
|
|
49
|
+
'\nError: ' + (error instanceof Error ? error.message : 'Unknown error')
|
|
50
|
+
)
|
|
51
|
+
);
|
|
52
|
+
|
|
53
|
+
// Provide helpful hints based on error
|
|
54
|
+
if (error instanceof Error) {
|
|
55
|
+
if (error.message.includes('project') || error.message.includes('Project')) {
|
|
56
|
+
console.log(chalk.yellow('\nHint: Check that the project key is correct'));
|
|
57
|
+
console.log(chalk.yellow('Use "jira-ai projects" to see available projects'));
|
|
58
|
+
} else if (error.message.includes('issue type') || error.message.includes('issuetype')) {
|
|
59
|
+
console.log(chalk.yellow('\nHint: Check that the issue type is correct'));
|
|
60
|
+
console.log(chalk.yellow(`Use "jira-ai list-issue-types ${project}" to see available issue types`));
|
|
61
|
+
} else if (error.message.includes('parent') || error.message.includes('Parent')) {
|
|
62
|
+
console.log(chalk.yellow('\nHint: Check that the parent issue key is correct'));
|
|
63
|
+
console.log(chalk.yellow('Parent issues are required for subtasks'));
|
|
64
|
+
} else if (error.message.includes('403')) {
|
|
65
|
+
console.log(
|
|
66
|
+
chalk.yellow('\nHint: You may not have permission to create issues in this project')
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
process.exit(1);
|
|
72
|
+
}
|
|
73
|
+
}
|
package/src/lib/jira-client.ts
CHANGED
|
@@ -383,3 +383,45 @@ export async function getProjectIssueTypes(projectIdOrKey: string): Promise<Issu
|
|
|
383
383
|
hierarchyLevel: issueType.hierarchyLevel || 0,
|
|
384
384
|
})) || [];
|
|
385
385
|
}
|
|
386
|
+
|
|
387
|
+
/**
|
|
388
|
+
* Create a new issue
|
|
389
|
+
* @param projectKey - The project key (e.g., "PROJ")
|
|
390
|
+
* @param summary - The issue title/summary
|
|
391
|
+
* @param issueTypeName - The issue type name (e.g., "Task", "Epic", "Subtask")
|
|
392
|
+
* @param parentKey - Optional parent issue key for subtasks
|
|
393
|
+
*/
|
|
394
|
+
export async function createIssue(
|
|
395
|
+
projectKey: string,
|
|
396
|
+
summary: string,
|
|
397
|
+
issueTypeName: string,
|
|
398
|
+
parentKey?: string
|
|
399
|
+
): Promise<{ key: string; id: string }> {
|
|
400
|
+
const client = getJiraClient();
|
|
401
|
+
|
|
402
|
+
const fields: any = {
|
|
403
|
+
project: {
|
|
404
|
+
key: projectKey,
|
|
405
|
+
},
|
|
406
|
+
summary,
|
|
407
|
+
issuetype: {
|
|
408
|
+
name: issueTypeName,
|
|
409
|
+
},
|
|
410
|
+
};
|
|
411
|
+
|
|
412
|
+
// Add parent field if this is a subtask
|
|
413
|
+
if (parentKey) {
|
|
414
|
+
fields.parent = {
|
|
415
|
+
key: parentKey,
|
|
416
|
+
};
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
const response = await client.issues.createIssue({
|
|
420
|
+
fields,
|
|
421
|
+
});
|
|
422
|
+
|
|
423
|
+
return {
|
|
424
|
+
key: response.key || '',
|
|
425
|
+
id: response.id || '',
|
|
426
|
+
};
|
|
427
|
+
}
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
import { createTaskCommand } from '../src/commands/create-task';
|
|
2
|
+
import * as jiraClient from '../src/lib/jira-client';
|
|
3
|
+
|
|
4
|
+
// Mock dependencies
|
|
5
|
+
jest.mock('../src/lib/jira-client');
|
|
6
|
+
jest.mock('../src/lib/utils');
|
|
7
|
+
jest.mock('ora', () => {
|
|
8
|
+
return jest.fn(() => ({
|
|
9
|
+
start: jest.fn().mockReturnThis(),
|
|
10
|
+
succeed: jest.fn().mockReturnThis(),
|
|
11
|
+
fail: jest.fn().mockReturnThis()
|
|
12
|
+
}));
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
const mockJiraClient = jiraClient as jest.Mocked<typeof jiraClient>;
|
|
16
|
+
|
|
17
|
+
describe('Create Task Command', () => {
|
|
18
|
+
const mockOptions = {
|
|
19
|
+
title: 'Test Task Title',
|
|
20
|
+
project: 'TEST',
|
|
21
|
+
issueType: 'Task',
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
const mockResponse = {
|
|
25
|
+
key: 'TEST-123',
|
|
26
|
+
id: '10001',
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
beforeEach(() => {
|
|
30
|
+
jest.clearAllMocks();
|
|
31
|
+
console.log = jest.fn();
|
|
32
|
+
console.error = jest.fn();
|
|
33
|
+
|
|
34
|
+
// Setup default mock
|
|
35
|
+
mockJiraClient.createIssue = jest.fn().mockResolvedValue(mockResponse);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it('should successfully create a task', async () => {
|
|
39
|
+
await createTaskCommand(mockOptions);
|
|
40
|
+
|
|
41
|
+
expect(mockJiraClient.createIssue).toHaveBeenCalledWith(
|
|
42
|
+
'TEST',
|
|
43
|
+
'Test Task Title',
|
|
44
|
+
'Task',
|
|
45
|
+
undefined
|
|
46
|
+
);
|
|
47
|
+
expect(console.log).toHaveBeenCalledWith(expect.stringContaining('TEST-123'));
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it('should create a task with parent issue', async () => {
|
|
51
|
+
const optionsWithParent = {
|
|
52
|
+
...mockOptions,
|
|
53
|
+
parent: 'TEST-100',
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
await createTaskCommand(optionsWithParent);
|
|
57
|
+
|
|
58
|
+
expect(mockJiraClient.createIssue).toHaveBeenCalledWith(
|
|
59
|
+
'TEST',
|
|
60
|
+
'Test Task Title',
|
|
61
|
+
'Task',
|
|
62
|
+
'TEST-100'
|
|
63
|
+
);
|
|
64
|
+
expect(console.log).toHaveBeenCalledWith(expect.stringContaining('Parent: TEST-100'));
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it('should exit with error when title is empty', async () => {
|
|
68
|
+
const processExitSpy = jest.spyOn(process, 'exit').mockImplementation(() => {
|
|
69
|
+
throw new Error('Process exit');
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
await expect(
|
|
73
|
+
createTaskCommand({ ...mockOptions, title: '' })
|
|
74
|
+
).rejects.toThrow('Process exit');
|
|
75
|
+
|
|
76
|
+
expect(console.error).toHaveBeenCalledWith(
|
|
77
|
+
expect.stringContaining('Title is required')
|
|
78
|
+
);
|
|
79
|
+
expect(processExitSpy).toHaveBeenCalledWith(1);
|
|
80
|
+
|
|
81
|
+
processExitSpy.mockRestore();
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
it('should exit with error when project is empty', async () => {
|
|
85
|
+
const processExitSpy = jest.spyOn(process, 'exit').mockImplementation(() => {
|
|
86
|
+
throw new Error('Process exit');
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
await expect(
|
|
90
|
+
createTaskCommand({ ...mockOptions, project: '' })
|
|
91
|
+
).rejects.toThrow('Process exit');
|
|
92
|
+
|
|
93
|
+
expect(console.error).toHaveBeenCalledWith(
|
|
94
|
+
expect.stringContaining('Project is required')
|
|
95
|
+
);
|
|
96
|
+
expect(processExitSpy).toHaveBeenCalledWith(1);
|
|
97
|
+
|
|
98
|
+
processExitSpy.mockRestore();
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
it('should exit with error when issue type is empty', async () => {
|
|
102
|
+
const processExitSpy = jest.spyOn(process, 'exit').mockImplementation(() => {
|
|
103
|
+
throw new Error('Process exit');
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
await expect(
|
|
107
|
+
createTaskCommand({ ...mockOptions, issueType: '' })
|
|
108
|
+
).rejects.toThrow('Process exit');
|
|
109
|
+
|
|
110
|
+
expect(console.error).toHaveBeenCalledWith(
|
|
111
|
+
expect.stringContaining('Issue type is required')
|
|
112
|
+
);
|
|
113
|
+
expect(processExitSpy).toHaveBeenCalledWith(1);
|
|
114
|
+
|
|
115
|
+
processExitSpy.mockRestore();
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
it('should exit with error and hint when project not found', async () => {
|
|
119
|
+
const apiError = new Error('Project does not exist');
|
|
120
|
+
mockJiraClient.createIssue = jest.fn().mockRejectedValue(apiError);
|
|
121
|
+
const processExitSpy = jest.spyOn(process, 'exit').mockImplementation(() => {
|
|
122
|
+
throw new Error('Process exit');
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
await expect(createTaskCommand(mockOptions)).rejects.toThrow('Process exit');
|
|
126
|
+
|
|
127
|
+
expect(console.error).toHaveBeenCalledWith(
|
|
128
|
+
expect.stringContaining('Project does not exist')
|
|
129
|
+
);
|
|
130
|
+
expect(console.log).toHaveBeenCalledWith(
|
|
131
|
+
expect.stringContaining('Check that the project key is correct')
|
|
132
|
+
);
|
|
133
|
+
expect(processExitSpy).toHaveBeenCalledWith(1);
|
|
134
|
+
|
|
135
|
+
processExitSpy.mockRestore();
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
it('should exit with error and hint when issue type is invalid', async () => {
|
|
139
|
+
const apiError = new Error('Invalid issue type specified');
|
|
140
|
+
mockJiraClient.createIssue = jest.fn().mockRejectedValue(apiError);
|
|
141
|
+
const processExitSpy = jest.spyOn(process, 'exit').mockImplementation(() => {
|
|
142
|
+
throw new Error('Process exit');
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
await expect(createTaskCommand(mockOptions)).rejects.toThrow('Process exit');
|
|
146
|
+
|
|
147
|
+
expect(console.error).toHaveBeenCalledWith(
|
|
148
|
+
expect.stringContaining('Invalid issue type specified')
|
|
149
|
+
);
|
|
150
|
+
expect(console.log).toHaveBeenCalledWith(
|
|
151
|
+
expect.stringContaining('Check that the issue type is correct')
|
|
152
|
+
);
|
|
153
|
+
expect(processExitSpy).toHaveBeenCalledWith(1);
|
|
154
|
+
|
|
155
|
+
processExitSpy.mockRestore();
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
it('should exit with error and hint when parent issue is invalid', async () => {
|
|
159
|
+
const apiError = new Error('Parent issue not found');
|
|
160
|
+
mockJiraClient.createIssue = jest.fn().mockRejectedValue(apiError);
|
|
161
|
+
const processExitSpy = jest.spyOn(process, 'exit').mockImplementation(() => {
|
|
162
|
+
throw new Error('Process exit');
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
await expect(
|
|
166
|
+
createTaskCommand({ ...mockOptions, parent: 'TEST-999' })
|
|
167
|
+
).rejects.toThrow('Process exit');
|
|
168
|
+
|
|
169
|
+
expect(console.error).toHaveBeenCalledWith(
|
|
170
|
+
expect.stringContaining('Parent issue not found')
|
|
171
|
+
);
|
|
172
|
+
expect(console.log).toHaveBeenCalledWith(
|
|
173
|
+
expect.stringContaining('Check that the parent issue key is correct')
|
|
174
|
+
);
|
|
175
|
+
expect(processExitSpy).toHaveBeenCalledWith(1);
|
|
176
|
+
|
|
177
|
+
processExitSpy.mockRestore();
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
it('should exit with error and hint when permission denied (403)', async () => {
|
|
181
|
+
const apiError = new Error('Permission denied (403)');
|
|
182
|
+
mockJiraClient.createIssue = jest.fn().mockRejectedValue(apiError);
|
|
183
|
+
const processExitSpy = jest.spyOn(process, 'exit').mockImplementation(() => {
|
|
184
|
+
throw new Error('Process exit');
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
await expect(createTaskCommand(mockOptions)).rejects.toThrow('Process exit');
|
|
188
|
+
|
|
189
|
+
expect(console.error).toHaveBeenCalledWith(
|
|
190
|
+
expect.stringContaining('Permission denied (403)')
|
|
191
|
+
);
|
|
192
|
+
expect(console.log).toHaveBeenCalledWith(
|
|
193
|
+
expect.stringContaining('You may not have permission to create issues')
|
|
194
|
+
);
|
|
195
|
+
expect(processExitSpy).toHaveBeenCalledWith(1);
|
|
196
|
+
|
|
197
|
+
processExitSpy.mockRestore();
|
|
198
|
+
});
|
|
199
|
+
});
|