@nbakka/mcp-appium 2.0.93 → 2.0.94
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/lib/review-ui/index.html +59 -132
- package/lib/server.js +148 -191
- package/package.json +1 -1
package/lib/review-ui/index.html
CHANGED
|
@@ -33,20 +33,11 @@
|
|
|
33
33
|
padding-bottom: 10px;
|
|
34
34
|
border-bottom: 2px solid;
|
|
35
35
|
}
|
|
36
|
-
.new {
|
|
37
|
-
background-color: #e8f5e8;
|
|
38
|
-
border-color: #4caf50;
|
|
39
|
-
}
|
|
36
|
+
.new { background-color: #e8f5e8; border-color: #4caf50; }
|
|
40
37
|
.new h2 { border-bottom-color: #4caf50; color: #2e7d32; }
|
|
41
|
-
.modify {
|
|
42
|
-
background-color: #fff8e1;
|
|
43
|
-
border-color: #ff9800;
|
|
44
|
-
}
|
|
38
|
+
.modify { background-color: #fff8e1; border-color: #ff9800; }
|
|
45
39
|
.modify h2 { border-bottom-color: #ff9800; color: #f57c00; }
|
|
46
|
-
.remove {
|
|
47
|
-
background-color: #ffebee;
|
|
48
|
-
border-color: #f44336;
|
|
49
|
-
}
|
|
40
|
+
.remove { background-color: #ffebee; border-color: #f44336; }
|
|
50
41
|
.remove h2 { border-bottom-color: #f44336; color: #c62828; }
|
|
51
42
|
.test-case {
|
|
52
43
|
margin: 15px 0;
|
|
@@ -75,35 +66,18 @@
|
|
|
75
66
|
font-size: 0.85em;
|
|
76
67
|
margin-left: 5px;
|
|
77
68
|
}
|
|
78
|
-
.edit-btn {
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
}
|
|
82
|
-
.
|
|
83
|
-
background-color: #4caf50;
|
|
84
|
-
color: white;
|
|
85
|
-
}
|
|
86
|
-
.cancel-btn-inline {
|
|
87
|
-
background-color: #757575;
|
|
88
|
-
color: white;
|
|
89
|
-
}
|
|
90
|
-
.delete-btn {
|
|
91
|
-
background-color: #f44336;
|
|
92
|
-
color: white;
|
|
93
|
-
}
|
|
94
|
-
.delete-btn:hover {
|
|
95
|
-
background-color: #d32f2f;
|
|
96
|
-
}
|
|
69
|
+
.edit-btn { background-color: #2196f3; color: white; }
|
|
70
|
+
.save-btn { background-color: #4caf50; color: white; }
|
|
71
|
+
.cancel-btn-inline { background-color: #757575; color: white; }
|
|
72
|
+
.delete-btn { background-color: #f44336; color: white; }
|
|
73
|
+
.delete-btn:hover { background-color: #d32f2f; }
|
|
97
74
|
.original-text {
|
|
98
75
|
color: #999;
|
|
99
76
|
text-decoration: line-through;
|
|
100
77
|
font-style: italic;
|
|
101
78
|
margin-bottom: 10px;
|
|
102
79
|
}
|
|
103
|
-
.modified-text {
|
|
104
|
-
color: #000;
|
|
105
|
-
font-weight: 500;
|
|
106
|
-
}
|
|
80
|
+
.modified-text { color: #000; font-weight: 500; }
|
|
107
81
|
.editable-area {
|
|
108
82
|
width: 100%;
|
|
109
83
|
min-height: 60px;
|
|
@@ -114,10 +88,7 @@
|
|
|
114
88
|
font-size: 14px;
|
|
115
89
|
resize: vertical;
|
|
116
90
|
}
|
|
117
|
-
.editable-area:focus {
|
|
118
|
-
outline: none;
|
|
119
|
-
border-color: #2196f3;
|
|
120
|
-
}
|
|
91
|
+
.editable-area:focus { outline: none; border-color: #2196f3; }
|
|
121
92
|
.button-container {
|
|
122
93
|
text-align: center;
|
|
123
94
|
margin-top: 40px;
|
|
@@ -136,22 +107,10 @@
|
|
|
136
107
|
letter-spacing: 0.5px;
|
|
137
108
|
transition: all 0.3s ease;
|
|
138
109
|
}
|
|
139
|
-
.approve {
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
}
|
|
143
|
-
.approve:hover {
|
|
144
|
-
background-color: #45a049;
|
|
145
|
-
transform: translateY(-2px);
|
|
146
|
-
}
|
|
147
|
-
.cancel {
|
|
148
|
-
background-color: #f44336;
|
|
149
|
-
color: white;
|
|
150
|
-
}
|
|
151
|
-
.cancel:hover {
|
|
152
|
-
background-color: #da190b;
|
|
153
|
-
transform: translateY(-2px);
|
|
154
|
-
}
|
|
110
|
+
.approve { background-color: #4caf50; color: white; }
|
|
111
|
+
.approve:hover { background-color: #45a049; transform: translateY(-2px); }
|
|
112
|
+
.cancel { background-color: #f44336; color: white; }
|
|
113
|
+
.cancel:hover { background-color: #da190b; transform: translateY(-2px); }
|
|
155
114
|
.status {
|
|
156
115
|
margin-top: 20px;
|
|
157
116
|
padding: 15px;
|
|
@@ -203,22 +162,18 @@
|
|
|
203
162
|
|
|
204
163
|
<script>
|
|
205
164
|
let sessionId;
|
|
206
|
-
let testCases;
|
|
165
|
+
let testCases = { new: [], modify: [], remove: [] };
|
|
207
166
|
let editingStates = {};
|
|
208
167
|
|
|
209
168
|
async function loadTestCases() {
|
|
210
169
|
try {
|
|
211
170
|
const response = await fetch('/api/testcases');
|
|
212
|
-
if (!response.ok) {
|
|
213
|
-
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
214
|
-
}
|
|
171
|
+
if (!response.ok) throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
215
172
|
const data = await response.json();
|
|
216
173
|
sessionId = data.sessionId;
|
|
217
174
|
testCases = data.testCases;
|
|
218
|
-
|
|
219
175
|
document.getElementById('loading').style.display = 'none';
|
|
220
176
|
document.getElementById('content').style.display = 'block';
|
|
221
|
-
|
|
222
177
|
renderTestCases();
|
|
223
178
|
} catch (error) {
|
|
224
179
|
document.getElementById('loading').innerHTML = `<div class="status error">Error loading test cases: ${error.message}</div>`;
|
|
@@ -297,7 +252,7 @@
|
|
|
297
252
|
function renderRemoveCases() {
|
|
298
253
|
const container = document.getElementById('remove-cases');
|
|
299
254
|
if (testCases.remove && testCases.remove.length > 0) {
|
|
300
|
-
container.innerHTML = testCases.remove.map(
|
|
255
|
+
container.innerHTML = testCases.remove.map(tc => `
|
|
301
256
|
<div class="test-case">
|
|
302
257
|
<div class="test-case-header">
|
|
303
258
|
<span class="test-case-id">ID: ${tc.id}</span>
|
|
@@ -318,8 +273,7 @@
|
|
|
318
273
|
function toggleEdit(type, index) {
|
|
319
274
|
const displayDiv = document.getElementById(`${type}-${index}-display`);
|
|
320
275
|
const editDiv = document.getElementById(`${type}-${index}-edit`);
|
|
321
|
-
|
|
322
|
-
if (displayDiv.style.display === 'block') {
|
|
276
|
+
if (displayDiv && editDiv && displayDiv.style.display === 'block') {
|
|
323
277
|
displayDiv.style.display = 'none';
|
|
324
278
|
editDiv.style.display = 'block';
|
|
325
279
|
editingStates[`${type}-${index}`] = true;
|
|
@@ -329,16 +283,11 @@
|
|
|
329
283
|
function saveEdit(type, index) {
|
|
330
284
|
if (type === 'new') {
|
|
331
285
|
const newDesc = document.getElementById(`new-${index}-desc`).value.trim();
|
|
332
|
-
if (newDesc)
|
|
333
|
-
testCases.new[index].description = newDesc;
|
|
334
|
-
}
|
|
286
|
+
if (newDesc) testCases.new[index].description = newDesc;
|
|
335
287
|
} else if (type === 'modify') {
|
|
336
288
|
const newMod = document.getElementById(`modify-${index}-mod`).value.trim();
|
|
337
|
-
if (newMod)
|
|
338
|
-
testCases.modify[index].modified = newMod;
|
|
339
|
-
}
|
|
289
|
+
if (newMod) testCases.modify[index].modified = newMod;
|
|
340
290
|
}
|
|
341
|
-
|
|
342
291
|
cancelEdit(type, index);
|
|
343
292
|
renderTestCases();
|
|
344
293
|
showStatus('Changes saved successfully!', 'success');
|
|
@@ -347,10 +296,11 @@
|
|
|
347
296
|
function cancelEdit(type, index) {
|
|
348
297
|
const displayDiv = document.getElementById(`${type}-${index}-display`);
|
|
349
298
|
const editDiv = document.getElementById(`${type}-${index}-edit`);
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
299
|
+
if (displayDiv && editDiv) {
|
|
300
|
+
displayDiv.style.display = 'block';
|
|
301
|
+
editDiv.style.display = 'none';
|
|
302
|
+
delete editingStates[`${type}-${index}`];
|
|
303
|
+
}
|
|
354
304
|
}
|
|
355
305
|
|
|
356
306
|
function showStatus(message, type) {
|
|
@@ -358,10 +308,35 @@
|
|
|
358
308
|
statusDiv.textContent = message;
|
|
359
309
|
statusDiv.className = `status ${type}`;
|
|
360
310
|
statusDiv.style.display = 'block';
|
|
311
|
+
setTimeout(() => { statusDiv.style.display = 'none'; }, 3000);
|
|
312
|
+
}
|
|
361
313
|
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
314
|
+
async function deleteTestCase(type, index) {
|
|
315
|
+
if (!['new', 'modify'].includes(type)) return;
|
|
316
|
+
const confirmation = confirm("Are you sure you want to delete this test case?");
|
|
317
|
+
if (!confirmation) return;
|
|
318
|
+
const item = testCases[type][index];
|
|
319
|
+
if (!item) return;
|
|
320
|
+
try {
|
|
321
|
+
showStatus('Deleting test case...', 'info');
|
|
322
|
+
const response = await fetch(`/delete/${sessionId}`, {
|
|
323
|
+
method: 'POST',
|
|
324
|
+
headers: { 'Content-Type': 'application/json' },
|
|
325
|
+
body: JSON.stringify({ type, id: item.id })
|
|
326
|
+
});
|
|
327
|
+
if (!response.ok) throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
328
|
+
const updated = await response.json();
|
|
329
|
+
if (updated.testCases) {
|
|
330
|
+
testCases = updated.testCases;
|
|
331
|
+
} else {
|
|
332
|
+
// fallback local update
|
|
333
|
+
testCases[type] = testCases[type].filter(tc => tc.id !== item.id);
|
|
334
|
+
}
|
|
335
|
+
renderTestCases();
|
|
336
|
+
showStatus('Test case deleted successfully!', 'success');
|
|
337
|
+
} catch (error) {
|
|
338
|
+
showStatus(`Error deleting test case: ${error.message}`, 'error');
|
|
339
|
+
}
|
|
365
340
|
}
|
|
366
341
|
|
|
367
342
|
async function approveTestCases() {
|
|
@@ -372,17 +347,10 @@
|
|
|
372
347
|
headers: { 'Content-Type': 'application/json' },
|
|
373
348
|
body: JSON.stringify(testCases)
|
|
374
349
|
});
|
|
375
|
-
|
|
376
|
-
if (!response.ok) {
|
|
377
|
-
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
378
|
-
}
|
|
379
|
-
|
|
350
|
+
if (!response.ok) throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
380
351
|
const result = await response.json();
|
|
381
352
|
showStatus(result.message || 'Test cases approved successfully!', 'success');
|
|
382
|
-
|
|
383
|
-
setTimeout(() => {
|
|
384
|
-
window.close();
|
|
385
|
-
}, 2000);
|
|
353
|
+
setTimeout(() => window.close(), 1200);
|
|
386
354
|
} catch (error) {
|
|
387
355
|
showStatus(`Error approving test cases: ${error.message}`, 'error');
|
|
388
356
|
}
|
|
@@ -391,58 +359,17 @@
|
|
|
391
359
|
async function cancelReview() {
|
|
392
360
|
try {
|
|
393
361
|
showStatus('Cancelling review...', 'info');
|
|
394
|
-
const response = await fetch(`/cancel/${sessionId}`, {
|
|
395
|
-
|
|
396
|
-
});
|
|
397
|
-
|
|
398
|
-
if (!response.ok) {
|
|
399
|
-
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
400
|
-
}
|
|
401
|
-
|
|
362
|
+
const response = await fetch(`/cancel/${sessionId}`, { method: 'POST' });
|
|
363
|
+
if (!response.ok) throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
402
364
|
const result = await response.json();
|
|
403
365
|
showStatus(result.message || 'Review cancelled', 'info');
|
|
404
|
-
|
|
405
|
-
setTimeout(() => {
|
|
406
|
-
window.close();
|
|
407
|
-
}, 2000);
|
|
366
|
+
setTimeout(() => window.close(), 1500);
|
|
408
367
|
} catch (error) {
|
|
409
368
|
showStatus(`Error cancelling review: ${error.message}`, 'error');
|
|
410
369
|
}
|
|
411
370
|
}
|
|
412
371
|
|
|
413
|
-
async function deleteTestCase(type, index) {
|
|
414
|
-
const confirmation = confirm("Are you sure you want to delete this test case?");
|
|
415
|
-
if (!confirmation) return;
|
|
416
|
-
|
|
417
|
-
try {
|
|
418
|
-
showStatus('Deleting test case...', 'info');
|
|
419
|
-
const response = await fetch(`/delete/${sessionId}`, {
|
|
420
|
-
method: 'POST',
|
|
421
|
-
headers: { 'Content-Type': 'application/json' },
|
|
422
|
-
body: JSON.stringify({ type, index })
|
|
423
|
-
});
|
|
424
|
-
|
|
425
|
-
if (!response.ok) {
|
|
426
|
-
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
427
|
-
}
|
|
428
|
-
|
|
429
|
-
// Remove the deleted test case from the local state first
|
|
430
|
-
if (type === 'new') {
|
|
431
|
-
testCases.new.splice(index, 1);
|
|
432
|
-
} else if (type === 'modify') {
|
|
433
|
-
testCases.modify.splice(index, 1);
|
|
434
|
-
}
|
|
435
|
-
|
|
436
|
-
// Re-render the UI to update indexes and remove the element
|
|
437
|
-
renderTestCases();
|
|
438
|
-
showStatus('Test case deleted successfully!', 'success');
|
|
439
|
-
} catch (error) {
|
|
440
|
-
showStatus(`Error deleting test case: ${error.message}`, 'error');
|
|
441
|
-
}
|
|
442
|
-
}
|
|
443
|
-
|
|
444
|
-
// Load test cases when page loads
|
|
445
372
|
window.onload = loadTestCases;
|
|
446
373
|
</script>
|
|
447
374
|
</body>
|
|
448
|
-
</html>
|
|
375
|
+
</html>
|
package/lib/server.js
CHANGED
|
@@ -798,219 +798,176 @@ tool(
|
|
|
798
798
|
let approvalSessions = new Map();
|
|
799
799
|
|
|
800
800
|
tool(
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
if (tc.length === 4 && tc[2] === 'Modify') {
|
|
822
|
-
// Modify: [original, modified, "Modify", tcmsId]
|
|
823
|
-
parsed.modify.push({
|
|
824
|
-
id: tc[3],
|
|
825
|
-
original: tc[0],
|
|
826
|
-
modified: tc[1]
|
|
827
|
-
});
|
|
828
|
-
} else if (tc.length === 3 && tc[1] === 'Remove') {
|
|
829
|
-
// Remove: [description, "Remove", tcmsId]
|
|
830
|
-
parsed.remove.push({
|
|
831
|
-
id: tc[2],
|
|
832
|
-
description: tc[0]
|
|
833
|
-
});
|
|
834
|
-
} else if (tc.length === 2 && tc[1] === 'New') {
|
|
835
|
-
// New: [description, "New"]
|
|
836
|
-
parsed.new.push({
|
|
837
|
-
description: tc[0],
|
|
838
|
-
id: `NEW-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`
|
|
839
|
-
});
|
|
840
|
-
}
|
|
841
|
-
});
|
|
842
|
-
|
|
843
|
-
return parsed;
|
|
844
|
-
}
|
|
845
|
-
|
|
846
|
-
// Use the improved parsing function
|
|
847
|
-
const parsedTestCases = parseTestCases(testCases);
|
|
848
|
-
|
|
849
|
-
// Remove the old parsing logic and console.log statements that cause JSON errors
|
|
850
|
-
// Initialize session state
|
|
851
|
-
approvalSessions.set(sessionId, {
|
|
852
|
-
status: 'pending',
|
|
853
|
-
testCases: parsedTestCases,
|
|
854
|
-
originalTestCases: JSON.parse(JSON.stringify(parsedTestCases)),
|
|
855
|
-
server: null,
|
|
856
|
-
startTime: Date.now()
|
|
801
|
+
"review_testcases",
|
|
802
|
+
"Open test cases in browser for manual approval.",
|
|
803
|
+
{
|
|
804
|
+
testCases: z.string().array().array().describe("test cases array generated by tool generate_testcases_from_ticket_data")
|
|
805
|
+
},
|
|
806
|
+
async ({ testCases }) => {
|
|
807
|
+
try {
|
|
808
|
+
const sessionId = Date.now().toString();
|
|
809
|
+
|
|
810
|
+
function parseTestCases(testCasesArray) {
|
|
811
|
+
const parsed = { new: [], modify: [], remove: [] };
|
|
812
|
+
testCasesArray.forEach(tc => {
|
|
813
|
+
if (tc.length === 4 && tc[2] === 'Modify') {
|
|
814
|
+
parsed.modify.push({ id: tc[3], original: tc[0], modified: tc[1] });
|
|
815
|
+
} else if (tc.length === 3 && tc[1] === 'Remove') {
|
|
816
|
+
parsed.remove.push({ id: tc[2], description: tc[0] });
|
|
817
|
+
} else if (tc.length === 2 && tc[1] === 'New') {
|
|
818
|
+
parsed.new.push({
|
|
819
|
+
description: tc[0],
|
|
820
|
+
id: `NEW-${Date.now()}-${Math.random().toString(36).slice(2)}`
|
|
857
821
|
});
|
|
822
|
+
}
|
|
823
|
+
});
|
|
824
|
+
return parsed;
|
|
825
|
+
}
|
|
858
826
|
|
|
859
|
-
|
|
860
|
-
const port = 3001;
|
|
861
|
-
|
|
862
|
-
app.use(express.json());
|
|
863
|
-
app.use(express.static(path.join(__dirname, 'review-ui')));
|
|
864
|
-
|
|
865
|
-
app.get("/", (req, res) => {
|
|
866
|
-
res.sendFile(path.join(__dirname, 'review-ui', 'index.html'));
|
|
867
|
-
});
|
|
827
|
+
const parsedTestCases = parseTestCases(testCases);
|
|
868
828
|
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
testCases: session.testCases
|
|
877
|
-
});
|
|
878
|
-
});
|
|
879
|
-
|
|
880
|
-
app.post(`/approve/${sessionId}`, (req, res) => {
|
|
881
|
-
const session = approvalSessions.get(sessionId);
|
|
882
|
-
if (session) {
|
|
883
|
-
session.status = 'approved';
|
|
884
|
-
session.finalTestCases = req.body;
|
|
885
|
-
approvalSessions.set(sessionId, session);
|
|
886
|
-
}
|
|
887
|
-
res.json({ status: 'approved', message: 'Test cases approved successfully!' });
|
|
888
|
-
});
|
|
829
|
+
approvalSessions.set(sessionId, {
|
|
830
|
+
status: 'pending',
|
|
831
|
+
testCases: parsedTestCases,
|
|
832
|
+
originalTestCases: JSON.parse(JSON.stringify(parsedTestCases)),
|
|
833
|
+
server: null,
|
|
834
|
+
startTime: Date.now()
|
|
835
|
+
});
|
|
889
836
|
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
if (session) {
|
|
893
|
-
session.status = 'cancelled';
|
|
894
|
-
approvalSessions.set(sessionId, session);
|
|
895
|
-
}
|
|
896
|
-
res.json({ status: 'cancelled', message: 'Review cancelled' });
|
|
897
|
-
});
|
|
837
|
+
const app = express();
|
|
838
|
+
const port = 3001;
|
|
898
839
|
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
if (!session) {
|
|
902
|
-
return res.status(404).json({ error: 'Session not found' });
|
|
903
|
-
}
|
|
840
|
+
app.use(express.json());
|
|
841
|
+
app.use(express.static(path.join(__dirname, 'review-ui')));
|
|
904
842
|
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
if (type === 'new' && session.testCases.new && index < session.testCases.new.length) {
|
|
909
|
-
session.testCases.new.splice(index, 1);
|
|
910
|
-
} else if (type === 'modify' && session.testCases.modify && index < session.testCases.modify.length) {
|
|
911
|
-
session.testCases.modify.splice(index, 1);
|
|
912
|
-
} else {
|
|
913
|
-
return res.status(400).json({ error: 'Invalid type or index' });
|
|
914
|
-
}
|
|
915
|
-
|
|
916
|
-
approvalSessions.set(sessionId, session);
|
|
917
|
-
res.json({ status: 'deleted', message: 'Test case deleted successfully' });
|
|
918
|
-
} catch (error) {
|
|
919
|
-
res.status(500).json({ error: 'Failed to delete test case' });
|
|
920
|
-
}
|
|
921
|
-
});
|
|
843
|
+
app.get("/", (_req, res) =>
|
|
844
|
+
res.sendFile(path.join(__dirname, 'review-ui', 'index.html'))
|
|
845
|
+
);
|
|
922
846
|
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
} catch (openError) {
|
|
929
|
-
// Silent fail for browser opening
|
|
930
|
-
}
|
|
931
|
-
});
|
|
847
|
+
app.get("/api/testcases", (_req, res) => {
|
|
848
|
+
const session = approvalSessions.get(sessionId);
|
|
849
|
+
if (!session) return res.status(404).json({ error: 'Session not found' });
|
|
850
|
+
res.json({ sessionId, testCases: session.testCases });
|
|
851
|
+
});
|
|
932
852
|
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
setTimeout(() => {
|
|
938
|
-
const session = approvalSessions.get(sessionId);
|
|
939
|
-
if (session && session.status === 'pending') {
|
|
940
|
-
session.status = 'timeout';
|
|
941
|
-
if (session.server) {
|
|
942
|
-
session.server.close();
|
|
943
|
-
}
|
|
944
|
-
approvalSessions.set(sessionId, session);
|
|
945
|
-
}
|
|
946
|
-
}, 300000);
|
|
947
|
-
|
|
948
|
-
// Return clean JSON response
|
|
949
|
-
return JSON.stringify({
|
|
950
|
-
status: "review_started",
|
|
951
|
-
sessionId: sessionId,
|
|
952
|
-
message: "Test case review interface opened in browser. Use check_approval_status tool to poll for approval.",
|
|
953
|
-
testCasesCount: testCases.length,
|
|
954
|
-
browserUrl: `http://localhost:${port}`,
|
|
955
|
-
instructions: "Poll using check_approval_status tool continuously for max 10 times"
|
|
956
|
-
});
|
|
853
|
+
app.post(`/delete/${sessionId}`, (req, res) => {
|
|
854
|
+
const session = approvalSessions.get(sessionId);
|
|
855
|
+
if (!session) return res.status(404).json({ error: 'Session not found' });
|
|
957
856
|
|
|
958
|
-
}
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
message: `Error setting up review interface: ${err.message}`
|
|
962
|
-
});
|
|
857
|
+
const { type, id } = req.body;
|
|
858
|
+
if (!['new', 'modify'].includes(type) || !id) {
|
|
859
|
+
return res.status(400).json({ error: 'Invalid delete request' });
|
|
963
860
|
}
|
|
964
|
-
|
|
965
|
-
);
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
},
|
|
973
|
-
async ({ sessionId }) => {
|
|
974
|
-
await new Promise(resolve => setTimeout(resolve, 25000));
|
|
861
|
+
try {
|
|
862
|
+
session.testCases[type] = session.testCases[type].filter(tc => tc.id !== id);
|
|
863
|
+
approvalSessions.set(sessionId, session);
|
|
864
|
+
return res.json({ status: 'deleted', testCases: session.testCases });
|
|
865
|
+
} catch {
|
|
866
|
+
return res.status(500).json({ error: 'Failed to delete test case' });
|
|
867
|
+
}
|
|
868
|
+
});
|
|
975
869
|
|
|
870
|
+
app.post(`/approve/${sessionId}`, (req, res) => {
|
|
976
871
|
const session = approvalSessions.get(sessionId);
|
|
977
|
-
if (
|
|
978
|
-
|
|
872
|
+
if (session) {
|
|
873
|
+
// overwrite authoritative copy with the submitted version
|
|
874
|
+
session.testCases = req.body;
|
|
875
|
+
session.finalTestCases = req.body;
|
|
876
|
+
session.status = 'approved';
|
|
877
|
+
approvalSessions.set(sessionId, session);
|
|
979
878
|
}
|
|
879
|
+
res.json({ status: 'approved', message: 'Test cases approved successfully!' });
|
|
880
|
+
});
|
|
980
881
|
|
|
981
|
-
|
|
982
|
-
const
|
|
882
|
+
app.post(`/cancel/${sessionId}`, (_req, res) => {
|
|
883
|
+
const session = approvalSessions.get(sessionId);
|
|
884
|
+
if (session) {
|
|
885
|
+
session.status = 'cancelled';
|
|
886
|
+
approvalSessions.set(sessionId, session);
|
|
887
|
+
}
|
|
888
|
+
res.json({ status: 'cancelled', message: 'Review cancelled' });
|
|
889
|
+
});
|
|
983
890
|
|
|
984
|
-
|
|
985
|
-
|
|
891
|
+
const serverInstance = app.listen(port, async () => {
|
|
892
|
+
try {
|
|
893
|
+
const { default: open } = await import('open');
|
|
894
|
+
await open(`http://localhost:${port}`);
|
|
895
|
+
} catch { /* silent */ }
|
|
896
|
+
});
|
|
986
897
|
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
898
|
+
const stored = approvalSessions.get(sessionId);
|
|
899
|
+
stored.server = serverInstance;
|
|
900
|
+
approvalSessions.set(sessionId, stored);
|
|
901
|
+
|
|
902
|
+
// timeout safeguard
|
|
903
|
+
setTimeout(() => {
|
|
904
|
+
const s = approvalSessions.get(sessionId);
|
|
905
|
+
if (s && s.status === 'pending') {
|
|
906
|
+
s.status = 'timeout';
|
|
907
|
+
s.server?.close();
|
|
908
|
+
approvalSessions.set(sessionId, s);
|
|
909
|
+
}
|
|
910
|
+
}, 300000);
|
|
911
|
+
|
|
912
|
+
return {
|
|
913
|
+
status: "review_started",
|
|
914
|
+
sessionId,
|
|
915
|
+
message: "Test case review interface opened.",
|
|
916
|
+
testCasesCount: testCases.length,
|
|
917
|
+
browserUrl: `http://localhost:${port}`,
|
|
918
|
+
instructions: "Use check_approval_status tool to poll."
|
|
919
|
+
};
|
|
920
|
+
} catch (err) {
|
|
921
|
+
return { status: "error", message: `Error setting up review interface: ${err.message}` };
|
|
922
|
+
}
|
|
923
|
+
}
|
|
924
|
+
);
|
|
991
925
|
|
|
992
|
-
return `✅ Test cases approved successfully! Elapsed time: ${elapsedTime}s\n\nApproved test cases:\n${JSON.stringify(approvedTestCases, null, 2)}`;
|
|
993
|
-
} else if (session.status === 'cancelled') {
|
|
994
|
-
if (session.server) {
|
|
995
|
-
session.server.close();
|
|
996
|
-
}
|
|
997
|
-
approvalSessions.delete(sessionId);
|
|
998
926
|
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
927
|
+
tool(
|
|
928
|
+
"check_approval_status",
|
|
929
|
+
"Check the approval status of test cases review session (short-circuits if already approved)",
|
|
930
|
+
{ sessionId: z.string().describe("Session ID from review_testcases") },
|
|
931
|
+
async ({ sessionId }) => {
|
|
932
|
+
const session = approvalSessions.get(sessionId);
|
|
933
|
+
if (!session) return `❌ Session not found. Invalid session ID: ${sessionId}`;
|
|
934
|
+
|
|
935
|
+
const now = Date.now();
|
|
936
|
+
const elapsed = Math.floor((now - session.startTime) / 1000);
|
|
937
|
+
|
|
938
|
+
if (session.status === 'approved') {
|
|
939
|
+
const approvedTestCases = session.finalTestCases || session.testCases;
|
|
940
|
+
session.server?.close();
|
|
941
|
+
approvalSessions.delete(sessionId);
|
|
942
|
+
return `✅ Test cases approved. Elapsed: ${elapsed}s\n${JSON.stringify(approvedTestCases, null, 2)}`;
|
|
943
|
+
}
|
|
1005
944
|
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
945
|
+
// only wait if still pending
|
|
946
|
+
await new Promise(r => setTimeout(r, 5000));
|
|
947
|
+
|
|
948
|
+
const refreshed = approvalSessions.get(sessionId);
|
|
949
|
+
if (!refreshed) return `❌ Session expired`;
|
|
950
|
+
|
|
951
|
+
if (refreshed.status === 'approved') {
|
|
952
|
+
const approvedTestCases = refreshed.finalTestCases || refreshed.testCases;
|
|
953
|
+
refreshed.server?.close();
|
|
954
|
+
approvalSessions.delete(sessionId);
|
|
955
|
+
return `✅ Test cases approved. Elapsed: ${elapsed + 5}s\n${JSON.stringify(approvedTestCases, null, 2)}`;
|
|
956
|
+
} else if (refreshed.status === 'cancelled') {
|
|
957
|
+
refreshed.server?.close();
|
|
958
|
+
approvalSessions.delete(sessionId);
|
|
959
|
+
return `❌ Review cancelled. Elapsed: ${elapsed + 5}s`;
|
|
960
|
+
} else if (refreshed.status === 'timeout' || elapsed > 300) {
|
|
961
|
+
refreshed.server?.close();
|
|
962
|
+
approvalSessions.delete(sessionId);
|
|
963
|
+
return `⏰ Review session timed out. Elapsed: ${elapsed + 5}s`;
|
|
1011
964
|
}
|
|
965
|
+
|
|
966
|
+
return `⏳ Still pending. Elapsed: ${elapsed + 5}s Remaining: ${Math.max(0, 300 - (elapsed + 5))}s`;
|
|
967
|
+
}
|
|
1012
968
|
);
|
|
1013
969
|
|
|
970
|
+
|
|
1014
971
|
return server;
|
|
1015
972
|
};
|
|
1016
973
|
|