jira-pat 1.0.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/AGENTS.md +218 -0
- package/README.md +64 -0
- package/backend/.env.example +1 -0
- package/backend/__tests__/getJiraClient.test.js +57 -0
- package/backend/__tests__/issues.test.js +565 -0
- package/backend/__tests__/jiraService.test.js +1127 -0
- package/backend/__tests__/projects.test.js +256 -0
- package/backend/coverage/clover.xml +426 -0
- package/backend/coverage/coverage-final.json +4 -0
- package/backend/coverage/lcov-report/base.css +224 -0
- package/backend/coverage/lcov-report/block-navigation.js +87 -0
- package/backend/coverage/lcov-report/favicon.png +0 -0
- package/backend/coverage/lcov-report/index.html +131 -0
- package/backend/coverage/lcov-report/prettify.css +1 -0
- package/backend/coverage/lcov-report/prettify.js +2 -0
- package/backend/coverage/lcov-report/routes/index.html +131 -0
- package/backend/coverage/lcov-report/routes/issues.js.html +823 -0
- package/backend/coverage/lcov-report/routes/projects.js.html +190 -0
- package/backend/coverage/lcov-report/service/index.html +116 -0
- package/backend/coverage/lcov-report/service/jiraService.js.html +1663 -0
- package/backend/coverage/lcov-report/sort-arrow-sprite.png +0 -0
- package/backend/coverage/lcov-report/sorter.js +210 -0
- package/backend/coverage/lcov.info +707 -0
- package/backend/index.js +38 -0
- package/backend/jest.config.js +11 -0
- package/backend/package-lock.json +5636 -0
- package/backend/package.json +28 -0
- package/backend/routes/issues.js +246 -0
- package/backend/routes/projects.js +35 -0
- package/backend/service/jiraService.js +526 -0
- package/bin/jira.js +92 -0
- package/frontend/.env.example +1 -0
- package/frontend/coverage/base.css +224 -0
- package/frontend/coverage/block-navigation.js +87 -0
- package/frontend/coverage/clover.xml +559 -0
- package/frontend/coverage/components/CreateIssueModal.jsx.html +592 -0
- package/frontend/coverage/components/IssueDetailPanel.jsx.html +1633 -0
- package/frontend/coverage/components/IssueDrawer.jsx.html +550 -0
- package/frontend/coverage/components/IssueTable.jsx.html +571 -0
- package/frontend/coverage/components/SkeletonComponents.jsx.html +223 -0
- package/frontend/coverage/components/ToastContainer.jsx.html +142 -0
- package/frontend/coverage/components/index.html +191 -0
- package/frontend/coverage/coverage-final.json +14 -0
- package/frontend/coverage/favicon.png +0 -0
- package/frontend/coverage/hooks/index.html +161 -0
- package/frontend/coverage/hooks/useFocusTrap.js.html +262 -0
- package/frontend/coverage/hooks/useIssueDrawer.js.html +1000 -0
- package/frontend/coverage/hooks/useIssuesList.js.html +175 -0
- package/frontend/coverage/hooks/useToasts.js.html +142 -0
- package/frontend/coverage/index.html +161 -0
- package/frontend/coverage/prettify.css +1 -0
- package/frontend/coverage/prettify.js +2 -0
- package/frontend/coverage/services/api.js.html +547 -0
- package/frontend/coverage/services/index.html +116 -0
- package/frontend/coverage/sort-arrow-sprite.png +0 -0
- package/frontend/coverage/sorter.js +210 -0
- package/frontend/coverage/utils/index.html +131 -0
- package/frontend/coverage/utils/issueHelpers.jsx.html +334 -0
- package/frontend/coverage/utils/sanitize.js.html +166 -0
- package/frontend/index.html +13 -0
- package/frontend/package-lock.json +3436 -0
- package/frontend/package.json +30 -0
- package/frontend/src/App.jsx +447 -0
- package/frontend/src/__tests__/components/CreateIssueModal.test.jsx +375 -0
- package/frontend/src/__tests__/components/IssueDetailPanel.test.jsx +962 -0
- package/frontend/src/__tests__/components/IssueDrawer.test.jsx +240 -0
- package/frontend/src/__tests__/components/IssueTable.test.jsx +423 -0
- package/frontend/src/__tests__/components/ToastContainer.test.jsx +196 -0
- package/frontend/src/__tests__/hooks/useFocusTrap.test.js +197 -0
- package/frontend/src/__tests__/hooks/useIssueDrawer.test.js +1053 -0
- package/frontend/src/__tests__/hooks/useIssuesList.test.js +175 -0
- package/frontend/src/__tests__/hooks/useToasts.test.js +110 -0
- package/frontend/src/__tests__/services/api.test.js +568 -0
- package/frontend/src/__tests__/setup.js +54 -0
- package/frontend/src/__tests__/utils/issueHelpers.test.jsx +336 -0
- package/frontend/src/__tests__/utils/sanitize.test.js +238 -0
- package/frontend/src/components/CreateIssueModal.jsx +169 -0
- package/frontend/src/components/ErrorBoundary.jsx +52 -0
- package/frontend/src/components/IssueDetailPanel.jsx +517 -0
- package/frontend/src/components/IssueDrawer.jsx +155 -0
- package/frontend/src/components/IssueTable.jsx +162 -0
- package/frontend/src/components/SkeletonComponents.jsx +46 -0
- package/frontend/src/components/StandaloneIssuePage.jsx +176 -0
- package/frontend/src/components/ToastContainer.jsx +19 -0
- package/frontend/src/hooks/useFocusTrap.js +59 -0
- package/frontend/src/hooks/useIssueDrawer.js +305 -0
- package/frontend/src/hooks/useIssuesList.js +30 -0
- package/frontend/src/hooks/useToasts.js +19 -0
- package/frontend/src/index.css +2070 -0
- package/frontend/src/main.jsx +13 -0
- package/frontend/src/services/api.js +154 -0
- package/frontend/src/utils/issueHelpers.jsx +84 -0
- package/frontend/src/utils/sanitize.js +27 -0
- package/frontend/vite.config.js +15 -0
- package/package.json +19 -0
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
const express = require('express');
|
|
2
|
+
const request = require('supertest');
|
|
3
|
+
const projectsRouter = require('../routes/projects');
|
|
4
|
+
|
|
5
|
+
jest.mock('../service/jiraService');
|
|
6
|
+
const jiraService = require('../service/jiraService');
|
|
7
|
+
|
|
8
|
+
const createApp = () => {
|
|
9
|
+
const app = express();
|
|
10
|
+
app.use(express.json());
|
|
11
|
+
app.use('/api/projects', projectsRouter);
|
|
12
|
+
return app;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
describe('projects routes', () => {
|
|
16
|
+
let app;
|
|
17
|
+
|
|
18
|
+
beforeEach(() => {
|
|
19
|
+
jest.clearAllMocks();
|
|
20
|
+
app = createApp();
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
describe('GET /api/projects', () => {
|
|
24
|
+
it('should fetch all projects', async () => {
|
|
25
|
+
const mockProjects = [
|
|
26
|
+
{ key: 'TEST', name: 'Test Project', id: '100' },
|
|
27
|
+
{ key: 'PROD', name: 'Production', id: '200' }
|
|
28
|
+
];
|
|
29
|
+
jiraService.getProjects.mockResolvedValue(mockProjects);
|
|
30
|
+
|
|
31
|
+
const response = await request(app).get('/api/projects');
|
|
32
|
+
|
|
33
|
+
expect(response.status).toBe(200);
|
|
34
|
+
expect(response.body).toEqual(mockProjects);
|
|
35
|
+
expect(jiraService.getProjects).toHaveBeenCalledTimes(1);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it('should return empty array when no projects', async () => {
|
|
39
|
+
jiraService.getProjects.mockResolvedValue([]);
|
|
40
|
+
|
|
41
|
+
const response = await request(app).get('/api/projects');
|
|
42
|
+
|
|
43
|
+
expect(response.status).toBe(200);
|
|
44
|
+
expect(response.body).toEqual([]);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it('should handle Jira API errors', async () => {
|
|
48
|
+
jiraService.getProjects.mockRejectedValue(
|
|
49
|
+
new Error('Jira API Error: 403 - Forbidden')
|
|
50
|
+
);
|
|
51
|
+
|
|
52
|
+
const response = await request(app).get('/api/projects');
|
|
53
|
+
|
|
54
|
+
expect(response.status).toBe(500);
|
|
55
|
+
expect(response.body.error).toBe('Failed to fetch projects');
|
|
56
|
+
expect(response.body.details).toContain('403');
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it('should handle network errors', async () => {
|
|
60
|
+
jiraService.getProjects.mockRejectedValue(
|
|
61
|
+
new Error('connect ECONNREFUSED')
|
|
62
|
+
);
|
|
63
|
+
|
|
64
|
+
const response = await request(app).get('/api/projects');
|
|
65
|
+
|
|
66
|
+
expect(response.status).toBe(500);
|
|
67
|
+
expect(response.body.details).toContain('ECONNREFUSED');
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it('should handle authentication errors', async () => {
|
|
71
|
+
jiraService.getProjects.mockRejectedValue(
|
|
72
|
+
new Error('Jira API Error: 401 - Unauthorized')
|
|
73
|
+
);
|
|
74
|
+
|
|
75
|
+
const response = await request(app).get('/api/projects');
|
|
76
|
+
|
|
77
|
+
expect(response.status).toBe(500);
|
|
78
|
+
expect(response.body.error).toBe('Failed to fetch projects');
|
|
79
|
+
});
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
describe('GET /api/projects/:projectKey/issuetypes', () => {
|
|
83
|
+
it('should fetch issue types for project', async () => {
|
|
84
|
+
const mockTypes = [
|
|
85
|
+
{ id: '1', name: 'Bug', description: 'Bug description' },
|
|
86
|
+
{ id: '2', name: 'Story', description: 'Story description' },
|
|
87
|
+
{ id: '3', name: 'Task', description: 'Task description' }
|
|
88
|
+
];
|
|
89
|
+
jiraService.getIssueTypes.mockResolvedValue(mockTypes);
|
|
90
|
+
|
|
91
|
+
const response = await request(app)
|
|
92
|
+
.get('/api/projects/TEST/issuetypes');
|
|
93
|
+
|
|
94
|
+
expect(response.status).toBe(200);
|
|
95
|
+
expect(response.body).toEqual(mockTypes);
|
|
96
|
+
expect(jiraService.getIssueTypes).toHaveBeenCalledWith('TEST');
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
it('should return issue types with all properties', async () => {
|
|
100
|
+
const mockTypes = [
|
|
101
|
+
{
|
|
102
|
+
id: '10001',
|
|
103
|
+
name: 'Epic',
|
|
104
|
+
description: 'An epic',
|
|
105
|
+
iconUrl: 'https://example.com/icon.png',
|
|
106
|
+
subtask: false
|
|
107
|
+
}
|
|
108
|
+
];
|
|
109
|
+
jiraService.getIssueTypes.mockResolvedValue(mockTypes);
|
|
110
|
+
|
|
111
|
+
const response = await request(app)
|
|
112
|
+
.get('/api/projects/PROJ/issuetypes');
|
|
113
|
+
|
|
114
|
+
expect(response.status).toBe(200);
|
|
115
|
+
expect(response.body[0].name).toBe('Epic');
|
|
116
|
+
expect(response.body[0].subtask).toBe(false);
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
it('should handle Jira API errors', async () => {
|
|
120
|
+
jiraService.getIssueTypes.mockRejectedValue(
|
|
121
|
+
new Error('Project not found')
|
|
122
|
+
);
|
|
123
|
+
|
|
124
|
+
const response = await request(app)
|
|
125
|
+
.get('/api/projects/INVALID/issuetypes');
|
|
126
|
+
|
|
127
|
+
expect(response.status).toBe(500);
|
|
128
|
+
expect(response.body.error).toBe('Failed to fetch issue types');
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
it('should pass project key to service', async () => {
|
|
132
|
+
jiraService.getIssueTypes.mockResolvedValue([]);
|
|
133
|
+
|
|
134
|
+
await request(app).get('/api/projects/MYPROJ/issuetypes');
|
|
135
|
+
|
|
136
|
+
expect(jiraService.getIssueTypes).toHaveBeenCalledWith('MYPROJ');
|
|
137
|
+
});
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
describe('GET /api/projects/:projectKey/versions', () => {
|
|
141
|
+
it('should fetch versions for project', async () => {
|
|
142
|
+
const mockVersions = [
|
|
143
|
+
{ id: '1', name: 'v1.0.0', released: true },
|
|
144
|
+
{ id: '2', name: 'v2.0.0', released: false },
|
|
145
|
+
{ id: '3', name: 'v2.1.0', released: false }
|
|
146
|
+
];
|
|
147
|
+
jiraService.getProjectVersions.mockResolvedValue(mockVersions);
|
|
148
|
+
|
|
149
|
+
const response = await request(app)
|
|
150
|
+
.get('/api/projects/TEST/versions');
|
|
151
|
+
|
|
152
|
+
expect(response.status).toBe(200);
|
|
153
|
+
expect(response.body).toEqual(mockVersions);
|
|
154
|
+
expect(jiraService.getProjectVersions).toHaveBeenCalledWith('TEST');
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
it('should return empty array when no versions', async () => {
|
|
158
|
+
jiraService.getProjectVersions.mockResolvedValue([]);
|
|
159
|
+
|
|
160
|
+
const response = await request(app)
|
|
161
|
+
.get('/api/projects/EMPTY/versions');
|
|
162
|
+
|
|
163
|
+
expect(response.status).toBe(200);
|
|
164
|
+
expect(response.body).toEqual([]);
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
it('should include version details', async () => {
|
|
168
|
+
const mockVersions = [
|
|
169
|
+
{
|
|
170
|
+
id: '10000',
|
|
171
|
+
name: 'Sprint 1',
|
|
172
|
+
released: false,
|
|
173
|
+
releaseDate: '2024-01-15',
|
|
174
|
+
description: 'First sprint'
|
|
175
|
+
}
|
|
176
|
+
];
|
|
177
|
+
jiraService.getProjectVersions.mockResolvedValue(mockVersions);
|
|
178
|
+
|
|
179
|
+
const response = await request(app)
|
|
180
|
+
.get('/api/projects/TEST/versions');
|
|
181
|
+
|
|
182
|
+
expect(response.body[0].releaseDate).toBe('2024-01-15');
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
it('should handle Jira API errors', async () => {
|
|
186
|
+
jiraService.getProjectVersions.mockRejectedValue(
|
|
187
|
+
new Error('Version API unavailable')
|
|
188
|
+
);
|
|
189
|
+
|
|
190
|
+
const response = await request(app)
|
|
191
|
+
.get('/api/projects/TEST/versions');
|
|
192
|
+
|
|
193
|
+
expect(response.status).toBe(500);
|
|
194
|
+
expect(response.body.error).toBe('Failed to fetch versions');
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
it('should pass project key to service', async () => {
|
|
198
|
+
jiraService.getProjectVersions.mockResolvedValue([]);
|
|
199
|
+
|
|
200
|
+
await request(app).get('/api/projects/PROJ/versions');
|
|
201
|
+
|
|
202
|
+
expect(jiraService.getProjectVersions).toHaveBeenCalledWith('PROJ');
|
|
203
|
+
});
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
describe('Route parameter handling', () => {
|
|
207
|
+
it('should handle special characters in project key', async () => {
|
|
208
|
+
jiraService.getIssueTypes.mockResolvedValue([]);
|
|
209
|
+
|
|
210
|
+
const response = await request(app)
|
|
211
|
+
.get('/api/projects/TEST%2DPROJ/issuetypes');
|
|
212
|
+
|
|
213
|
+
expect(response.status).toBe(200);
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
it('should handle numeric project keys', async () => {
|
|
217
|
+
jiraService.getProjectVersions.mockResolvedValue([]);
|
|
218
|
+
|
|
219
|
+
const response = await request(app).get('/api/projects/12345/versions');
|
|
220
|
+
expect(response.status).toBe(200);
|
|
221
|
+
});
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
describe('Error handling', () => {
|
|
225
|
+
it('should handle service timeout errors', async () => {
|
|
226
|
+
const timeoutError = new Error('Request timeout');
|
|
227
|
+
timeoutError.code = 'ETIMEDOUT';
|
|
228
|
+
jiraService.getProjects.mockRejectedValue(timeoutError);
|
|
229
|
+
|
|
230
|
+
const response = await request(app).get('/api/projects');
|
|
231
|
+
|
|
232
|
+
expect(response.status).toBe(500);
|
|
233
|
+
expect(response.body.error).toBe('Failed to fetch projects');
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
it('should handle connection refused errors', async () => {
|
|
237
|
+
const connError = new Error('connect ECONNREFUSED');
|
|
238
|
+
connError.code = 'ECONNREFUSED';
|
|
239
|
+
jiraService.getProjects.mockRejectedValue(connError);
|
|
240
|
+
|
|
241
|
+
const response = await request(app).get('/api/projects');
|
|
242
|
+
|
|
243
|
+
expect(response.status).toBe(500);
|
|
244
|
+
expect(response.body.details).toContain('ECONNREFUSED');
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
it('should return proper error structure', async () => {
|
|
248
|
+
jiraService.getProjects.mockRejectedValue(new Error('Test error'));
|
|
249
|
+
|
|
250
|
+
const response = await request(app).get('/api/projects');
|
|
251
|
+
|
|
252
|
+
expect(response.body).toHaveProperty('error');
|
|
253
|
+
expect(response.body).toHaveProperty('details');
|
|
254
|
+
});
|
|
255
|
+
});
|
|
256
|
+
});
|