wopee-mcp 1.20.0 → 1.23.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/README.md +36 -1
- package/build/prompts/fetch_test_results/index.js +65 -0
- package/build/prompts/index.js +2 -1
- package/build/prompts/shared/types.js +1 -0
- package/build/tools/index.js +2 -0
- package/build/tools/shared/gql-queries.js +22 -0
- package/build/tools/shared/handlers.js +6 -6
- package/build/tools/shared/helpers.js +11 -0
- package/build/tools/shared/types.js +6 -0
- package/build/tools/wopee_create_blank_suite/index.js +29 -23
- package/build/tools/wopee_dispatch_agent/index.js +7 -6
- package/build/tools/wopee_dispatch_analysis/index.js +6 -5
- package/build/tools/wopee_fetch_analysis_suites/index.js +29 -23
- package/build/tools/wopee_fetch_executed_test_cases/factory.js +13 -0
- package/build/tools/wopee_fetch_executed_test_cases/index.js +43 -0
- package/build/tools/wopee_fetch_executed_test_cases/schema.js +18 -0
- package/build/utils/requestClient.js +46 -26
- package/build/utils/withRetry.js +24 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -298,7 +298,7 @@ Dispatches an autonomous testing agent to execute test cases for a selected suit
|
|
|
298
298
|
- `testCases` - Array of test case objects to execute, each containing:
|
|
299
299
|
- `testCaseId` - The ID of the test case
|
|
300
300
|
- `userStoryId` - The ID of the associated user story
|
|
301
|
-
- **Returns:**
|
|
301
|
+
- **Returns:** Array of executed test case objects with their initial execution state (uuid, executionStatus, agentReportStatus, codeReportStatus, etc.)
|
|
302
302
|
|
|
303
303
|
**Example Usage:**
|
|
304
304
|
|
|
@@ -306,6 +306,27 @@ Dispatches an autonomous testing agent to execute test cases for a selected suit
|
|
|
306
306
|
Dispatch agent for my latest suite's user story US001 and test case TC003
|
|
307
307
|
```
|
|
308
308
|
|
|
309
|
+
### Test Results
|
|
310
|
+
|
|
311
|
+
#### `wopee_fetch_executed_test_cases`
|
|
312
|
+
|
|
313
|
+
Fetches executed test cases and their results for a given analysis suite. Use this to check the status and reports of dispatched agent runs.
|
|
314
|
+
|
|
315
|
+
- **Parameters:**
|
|
316
|
+
- `suiteUuid` - The UUID of the analysis suite to fetch results for
|
|
317
|
+
- `analysisIdentifier` _(optional)_ - Analysis identifier to narrow results (e.g. `A068`)
|
|
318
|
+
- **Returns:** Array of results grouped by user story, each containing executed test cases with execution status, agent report, agent report status, code report, and code report status
|
|
319
|
+
|
|
320
|
+
**Example Usage:**
|
|
321
|
+
|
|
322
|
+
```
|
|
323
|
+
Fetch test results for suite <suiteUuid>
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
```
|
|
327
|
+
Show me the executed test cases for my latest analysis suite
|
|
328
|
+
```
|
|
329
|
+
|
|
309
330
|
## Typical Workflow
|
|
310
331
|
|
|
311
332
|
1. **Start with a suite:**
|
|
@@ -326,6 +347,20 @@ Dispatch agent for my latest suite's user story US001 and test case TC003
|
|
|
326
347
|
4. **Run tests:**
|
|
327
348
|
- Use `wopee_dispatch_agent` to execute test cases with the autonomous testing agent
|
|
328
349
|
|
|
350
|
+
5. **Check results:**
|
|
351
|
+
- Use `wopee_fetch_executed_test_cases` to check the status and reports of dispatched agent runs
|
|
352
|
+
- Or use the `fetch-test-results` prompt for a formatted summary of all test results
|
|
353
|
+
|
|
354
|
+
## Available Prompts
|
|
355
|
+
|
|
356
|
+
### `fetch-project-summary`
|
|
357
|
+
|
|
358
|
+
Fetches analysis suites and their user stories/test cases, then displays a formatted summary with two markdown tables: a suite overview and a detailed test case breakdown.
|
|
359
|
+
|
|
360
|
+
### `fetch-test-results`
|
|
361
|
+
|
|
362
|
+
Fetches analysis suites and their executed test case results, then displays formatted markdown tables showing execution status, agent report status, and code report status for each test case. Also surfaces failed report details.
|
|
363
|
+
|
|
329
364
|
## Notes
|
|
330
365
|
|
|
331
366
|
- Most tools require a `suiteUuid`. Always start by fetching or creating a suite.
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { PromptName } from "../shared/types.js";
|
|
2
|
+
export const fetchTestResults = {
|
|
3
|
+
name: PromptName.FETCH_TEST_RESULTS,
|
|
4
|
+
config: {
|
|
5
|
+
title: "Fetch test results",
|
|
6
|
+
description: "Fetch and display test execution results for analysis suites",
|
|
7
|
+
},
|
|
8
|
+
handler: () => ({
|
|
9
|
+
messages: [
|
|
10
|
+
{
|
|
11
|
+
role: "user",
|
|
12
|
+
content: {
|
|
13
|
+
type: "text",
|
|
14
|
+
text: `Please, fetch my analysis suites using 'wopee_fetch_analysis_suites' tool. If there are more than 5 suites, ask the user to select which suite(s) they want to see results for.
|
|
15
|
+
|
|
16
|
+
Then, for each selected suite, fetch the executed test cases using 'wopee_fetch_executed_test_cases' tool with the suite's uuid and analysisIdentifier.
|
|
17
|
+
|
|
18
|
+
After fetching all of the necessary data, summarize and display readable markdown tables as an output in the same chat conversation. Do NOT create, write, or save any files to disk - only display the formatted tables in your response.
|
|
19
|
+
|
|
20
|
+
Display the following tables:
|
|
21
|
+
|
|
22
|
+
1. **Suite Overview Table** - one row per suite showing:
|
|
23
|
+
- Suite name
|
|
24
|
+
- Analysis identifier
|
|
25
|
+
- Execution status
|
|
26
|
+
- Suite running status
|
|
27
|
+
|
|
28
|
+
### Example:
|
|
29
|
+
| Suite Name | Identifier | Execution Status | Running Status |
|
|
30
|
+
|------------|------------|------------------|----------------|
|
|
31
|
+
| Analysis - A068 | A068 | FINISHED | IDLE |
|
|
32
|
+
| Analysis - A067 | A067 | IN_PROGRESS | IN_PROGRESS |
|
|
33
|
+
|
|
34
|
+
___
|
|
35
|
+
|
|
36
|
+
2. **Test Case Results Table** - for each suite that has executed test cases:
|
|
37
|
+
- Suite name
|
|
38
|
+
- User story ID
|
|
39
|
+
- Test case ID
|
|
40
|
+
- Execution status
|
|
41
|
+
- Agent report status (PASSED/FAILED or N/A)
|
|
42
|
+
- Code report status (PASSED/FAILED or N/A)
|
|
43
|
+
|
|
44
|
+
### Example:
|
|
45
|
+
| Suite Name | User Story | Test Case | Execution | Agent Status | Code Status |
|
|
46
|
+
|------------|------------|-----------|-----------|--------------|-------------|
|
|
47
|
+
| Analysis - A068 | US001 | TC001 | FINISHED | PASSED | PASSED |
|
|
48
|
+
| | US001 | TC002 | FINISHED | PASSED | FAILED |
|
|
49
|
+
| | US002 | TC001 | FINISHED | FAILED | N/A |
|
|
50
|
+
| Analysis - A067 | US001 | TC001 | IN_PROGRESS | N/A | N/A |
|
|
51
|
+
|
|
52
|
+
___
|
|
53
|
+
|
|
54
|
+
3. If any test case has a FAILED agent or code report status, display the report content below the tables under a "Failed Reports" section, showing the suite name, test case ID, and the report text.
|
|
55
|
+
|
|
56
|
+
## Important Notes:
|
|
57
|
+
- If a test case's executionStatus is IN_PROGRESS, note that results are not yet available.
|
|
58
|
+
- Use "N/A" for report statuses that are null (not yet available).
|
|
59
|
+
- Group test cases by user story within each suite.
|
|
60
|
+
`,
|
|
61
|
+
},
|
|
62
|
+
},
|
|
63
|
+
],
|
|
64
|
+
}),
|
|
65
|
+
};
|
package/build/prompts/index.js
CHANGED
package/build/tools/index.js
CHANGED
|
@@ -5,9 +5,11 @@ import { wopeeDispatchAgent } from "./wopee_dispatch_agent/index.js";
|
|
|
5
5
|
import { wopeeDispatchAnalysis } from "./wopee_dispatch_analysis/index.js";
|
|
6
6
|
import { wopeeCreateBlankSuite } from "./wopee_create_blank_suite/index.js";
|
|
7
7
|
import { wopeeFetchAnalysisSuites } from "./wopee_fetch_analysis_suites/index.js";
|
|
8
|
+
import { wopeeFetchExecutedTestCases } from "./wopee_fetch_executed_test_cases/index.js";
|
|
8
9
|
export const TOOLS = [
|
|
9
10
|
wopeeCreateBlankSuite,
|
|
10
11
|
wopeeFetchAnalysisSuites,
|
|
12
|
+
wopeeFetchExecutedTestCases,
|
|
11
13
|
wopeeDispatchAnalysis,
|
|
12
14
|
wopeeDispatchAgent,
|
|
13
15
|
wopeeFetchArtifact,
|
|
@@ -155,6 +155,28 @@ export const GenerateReusableTestCaseSteps = `
|
|
|
155
155
|
generateReusableTestCaseSteps(input: $input)
|
|
156
156
|
}
|
|
157
157
|
`;
|
|
158
|
+
export const FetchExecutedTestCases = `
|
|
159
|
+
query FetchExecutedTestCases($input: FetchExecutedTestCasesInput!) {
|
|
160
|
+
fetchExecutedTestCases(input: $input) {
|
|
161
|
+
userStoryId
|
|
162
|
+
executedTestCases {
|
|
163
|
+
uuid
|
|
164
|
+
suiteUuid
|
|
165
|
+
analysisSuiteUuid
|
|
166
|
+
analysisIdentifier
|
|
167
|
+
userStoryId
|
|
168
|
+
testCaseId
|
|
169
|
+
executionStatus
|
|
170
|
+
agentReport
|
|
171
|
+
agentReportStatus
|
|
172
|
+
codeReport
|
|
173
|
+
codeReportStatus
|
|
174
|
+
createdAt
|
|
175
|
+
updatedAt
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
`;
|
|
158
180
|
export const CreateBlankAnalysisSuite = `
|
|
159
181
|
mutation CreateBlankAnalysisSuite($projectUuid: ID!) {
|
|
160
182
|
createBlankAnalysisSuite(projectUuid: $projectUuid) {
|
|
@@ -15,12 +15,12 @@ export async function fetchArtifact(input) {
|
|
|
15
15
|
const result = await requestClient(FetchArtifact, {
|
|
16
16
|
input: parsedInput,
|
|
17
17
|
});
|
|
18
|
-
if (!result
|
|
18
|
+
if (!result?.fetchArtifact?.content)
|
|
19
19
|
return {
|
|
20
20
|
content: [
|
|
21
21
|
{
|
|
22
22
|
type: "text",
|
|
23
|
-
text: "Failed to fetch file",
|
|
23
|
+
text: "Failed to fetch file: no content returned",
|
|
24
24
|
},
|
|
25
25
|
],
|
|
26
26
|
};
|
|
@@ -54,12 +54,12 @@ export async function generateAIDataFile(input) {
|
|
|
54
54
|
const generationResult = await requestClient(query, {
|
|
55
55
|
input: parsedInput,
|
|
56
56
|
});
|
|
57
|
-
if (!generationResult
|
|
57
|
+
if (!generationResult?.[dataKey])
|
|
58
58
|
return {
|
|
59
59
|
content: [
|
|
60
60
|
{
|
|
61
61
|
type: "text",
|
|
62
|
-
text: `Failed to generate ${description}`,
|
|
62
|
+
text: `Failed to generate ${description}: no result returned`,
|
|
63
63
|
},
|
|
64
64
|
],
|
|
65
65
|
};
|
|
@@ -86,12 +86,12 @@ export async function updateArtifact(input) {
|
|
|
86
86
|
const updateFileResult = await requestClient(UpdateArtifact, {
|
|
87
87
|
input: parsedInput,
|
|
88
88
|
});
|
|
89
|
-
if (!updateFileResult
|
|
89
|
+
if (!updateFileResult?.updateArtifact)
|
|
90
90
|
return {
|
|
91
91
|
content: [
|
|
92
92
|
{
|
|
93
93
|
type: "text",
|
|
94
|
-
text: "Failed to update file",
|
|
94
|
+
text: "Failed to update file: operation returned false",
|
|
95
95
|
},
|
|
96
96
|
],
|
|
97
97
|
};
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { GenerateAppContext, GenerateGeneralUserStories, GenerateReusableTestCases, GenerateReusableTestCaseSteps, GenerateTestCases, GenerateTestCaseSteps, GenerateUserStoriesWithTestCases, } from "./gql-queries.js";
|
|
3
3
|
import { ArtifactType, GenerateArtifactType } from "./types.js";
|
|
4
|
+
import { RequestError } from "../../utils/requestClient.js";
|
|
4
5
|
export function _convertToArtifactType(type) {
|
|
5
6
|
switch (type) {
|
|
6
7
|
case GenerateArtifactType.APP_CONTEXT:
|
|
@@ -69,6 +70,16 @@ export function _parseGenerateArtifactType(type) {
|
|
|
69
70
|
}
|
|
70
71
|
export function _parseError(error) {
|
|
71
72
|
console.error(error instanceof z.ZodError ? error.issues : error);
|
|
73
|
+
if (error instanceof RequestError) {
|
|
74
|
+
return {
|
|
75
|
+
content: [
|
|
76
|
+
{
|
|
77
|
+
type: "text",
|
|
78
|
+
text: `[${error.code}] ${error.message}${error.retryable ? " (retryable)" : ""}`,
|
|
79
|
+
},
|
|
80
|
+
],
|
|
81
|
+
};
|
|
82
|
+
}
|
|
72
83
|
return {
|
|
73
84
|
content: [
|
|
74
85
|
{
|
|
@@ -2,6 +2,7 @@ export var ToolName;
|
|
|
2
2
|
(function (ToolName) {
|
|
3
3
|
ToolName["WOPEE_CREATE_BLANK_SUITE"] = "wopee_create_blank_suite";
|
|
4
4
|
ToolName["WOPEE_FETCH_ANALYSIS_SUITES"] = "wopee_fetch_analysis_suites";
|
|
5
|
+
ToolName["WOPEE_FETCH_EXECUTED_TEST_CASES"] = "wopee_fetch_executed_test_cases";
|
|
5
6
|
ToolName["WOPEE_DISPATCH_ANALYSIS"] = "wopee_dispatch_analysis";
|
|
6
7
|
ToolName["WOPEE_DISPATCH_AGENT"] = "wopee_dispatch_agent";
|
|
7
8
|
ToolName["WOPEE_FETCH_ARTIFACT"] = "wopee_fetch_artifact";
|
|
@@ -56,3 +57,8 @@ export var GenerationStatus;
|
|
|
56
57
|
GenerationStatus["FINISHED"] = "FINISHED";
|
|
57
58
|
GenerationStatus["FAILED"] = "FAILED";
|
|
58
59
|
})(GenerationStatus || (GenerationStatus = {}));
|
|
60
|
+
export var ReportStatus;
|
|
61
|
+
(function (ReportStatus) {
|
|
62
|
+
ReportStatus["PASSED"] = "PASSED";
|
|
63
|
+
ReportStatus["FAILED"] = "FAILED";
|
|
64
|
+
})(ReportStatus || (ReportStatus = {}));
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { getConfig } from "../../utils/getConfig.js";
|
|
2
2
|
import { requestClient } from "../../utils/requestClient.js";
|
|
3
|
+
import { _parseError } from "../shared/helpers.js";
|
|
3
4
|
import { ToolName } from "../shared/types.js";
|
|
4
5
|
import { CreateBlankAnalysisSuite } from "../shared/gql-queries.js";
|
|
5
6
|
export const wopeeCreateBlankSuite = {
|
|
@@ -9,35 +10,40 @@ export const wopeeCreateBlankSuite = {
|
|
|
9
10
|
description: "Create a blank analysis suite for a project",
|
|
10
11
|
},
|
|
11
12
|
handler: async () => {
|
|
12
|
-
|
|
13
|
-
|
|
13
|
+
try {
|
|
14
|
+
const { WOPEE_PROJECT_UUID } = getConfig();
|
|
15
|
+
if (!WOPEE_PROJECT_UUID)
|
|
16
|
+
return {
|
|
17
|
+
content: [
|
|
18
|
+
{
|
|
19
|
+
type: "text",
|
|
20
|
+
text: "WOPEE_PROJECT_UUID is not set",
|
|
21
|
+
},
|
|
22
|
+
],
|
|
23
|
+
};
|
|
24
|
+
const result = await requestClient(CreateBlankAnalysisSuite, {
|
|
25
|
+
projectUuid: WOPEE_PROJECT_UUID,
|
|
26
|
+
});
|
|
27
|
+
if (!result?.createBlankAnalysisSuite)
|
|
28
|
+
return {
|
|
29
|
+
content: [
|
|
30
|
+
{
|
|
31
|
+
type: "text",
|
|
32
|
+
text: "Failed to create blank suite: no data returned",
|
|
33
|
+
},
|
|
34
|
+
],
|
|
35
|
+
};
|
|
14
36
|
return {
|
|
15
37
|
content: [
|
|
16
38
|
{
|
|
17
39
|
type: "text",
|
|
18
|
-
text:
|
|
40
|
+
text: JSON.stringify(result.createBlankAnalysisSuite, null, 2),
|
|
19
41
|
},
|
|
20
42
|
],
|
|
21
43
|
};
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
return {
|
|
27
|
-
content: [
|
|
28
|
-
{
|
|
29
|
-
type: "text",
|
|
30
|
-
text: "Failed to fetch analysis suites",
|
|
31
|
-
},
|
|
32
|
-
],
|
|
33
|
-
};
|
|
34
|
-
return {
|
|
35
|
-
content: [
|
|
36
|
-
{
|
|
37
|
-
type: "text",
|
|
38
|
-
text: JSON.stringify(result.createBlankAnalysisSuite, null, 2),
|
|
39
|
-
},
|
|
40
|
-
],
|
|
41
|
-
};
|
|
44
|
+
}
|
|
45
|
+
catch (error) {
|
|
46
|
+
return _parseError(error);
|
|
47
|
+
}
|
|
42
48
|
},
|
|
43
49
|
};
|
|
@@ -4,26 +4,27 @@ import { _parseError } from "../shared/helpers.js";
|
|
|
4
4
|
import { createDispatchAgentInput } from "./factory.js";
|
|
5
5
|
import { DispatchAgent } from "../shared/gql-queries.js";
|
|
6
6
|
import { requestClient } from "../../utils/requestClient.js";
|
|
7
|
+
import { withRetry } from "../../utils/withRetry.js";
|
|
7
8
|
export const wopeeDispatchAgent = {
|
|
8
9
|
name: ToolName.WOPEE_DISPATCH_AGENT,
|
|
9
10
|
config: {
|
|
10
11
|
title: "Dispatch agent",
|
|
11
|
-
description: "Dispatch agent testing for selected suite's test cases",
|
|
12
|
+
description: "Dispatch agent testing for selected suite's test cases. Note: there is a 10-second per-project rate limit between dispatches; concurrent calls will auto-retry with backoff.",
|
|
12
13
|
inputSchema: WopeeDispatchAgentInputSchema.shape,
|
|
13
14
|
},
|
|
14
15
|
handler: async (input) => {
|
|
15
16
|
try {
|
|
16
17
|
const dispatchAgentInput = createDispatchAgentInput(input);
|
|
17
18
|
const parsedInput = DispatchAgentInputSchema.parse(dispatchAgentInput);
|
|
18
|
-
const result = await requestClient(DispatchAgent, {
|
|
19
|
+
const result = await withRetry(() => requestClient(DispatchAgent, {
|
|
19
20
|
input: parsedInput,
|
|
20
|
-
});
|
|
21
|
-
if (!result
|
|
21
|
+
}));
|
|
22
|
+
if (!result?.dispatchAgent?.length)
|
|
22
23
|
return {
|
|
23
24
|
content: [
|
|
24
25
|
{
|
|
25
26
|
type: "text",
|
|
26
|
-
text: "Failed to dispatch agent",
|
|
27
|
+
text: "Failed to dispatch agent: no dispatch result returned",
|
|
27
28
|
},
|
|
28
29
|
],
|
|
29
30
|
};
|
|
@@ -31,7 +32,7 @@ export const wopeeDispatchAgent = {
|
|
|
31
32
|
content: [
|
|
32
33
|
{
|
|
33
34
|
type: "text",
|
|
34
|
-
text:
|
|
35
|
+
text: JSON.stringify(result.dispatchAgent, null, 2),
|
|
35
36
|
},
|
|
36
37
|
],
|
|
37
38
|
};
|
|
@@ -3,27 +3,28 @@ import { DispatchAnalysisInputSchema, WopeeDispatchAnalysisInputSchema, } from "
|
|
|
3
3
|
import { createDispatchAnalysisInput } from "./factory.js";
|
|
4
4
|
import { DispatchAnalysis } from "../shared/gql-queries.js";
|
|
5
5
|
import { requestClient } from "../../utils/requestClient.js";
|
|
6
|
+
import { withRetry } from "../../utils/withRetry.js";
|
|
6
7
|
import { ToolName } from "../shared/types.js";
|
|
7
8
|
export const wopeeDispatchAnalysis = {
|
|
8
9
|
name: ToolName.WOPEE_DISPATCH_ANALYSIS,
|
|
9
10
|
config: {
|
|
10
11
|
title: "Dispatch analysis",
|
|
11
|
-
description: "Create and dispatch analysis/crawling suite for a project",
|
|
12
|
+
description: "Create and dispatch analysis/crawling suite for a project. Note: there is a 10-second per-project rate limit between dispatches; concurrent calls will auto-retry with backoff.",
|
|
12
13
|
inputSchema: WopeeDispatchAnalysisInputSchema.shape,
|
|
13
14
|
},
|
|
14
15
|
handler: async (input) => {
|
|
15
16
|
try {
|
|
16
17
|
const rawInput = createDispatchAnalysisInput(input);
|
|
17
18
|
const parsedInput = DispatchAnalysisInputSchema.parse(rawInput);
|
|
18
|
-
const result = await requestClient(DispatchAnalysis, {
|
|
19
|
+
const result = await withRetry(() => requestClient(DispatchAnalysis, {
|
|
19
20
|
input: parsedInput,
|
|
20
|
-
});
|
|
21
|
-
if (!result
|
|
21
|
+
}));
|
|
22
|
+
if (!result?.dispatchAnalysis)
|
|
22
23
|
return {
|
|
23
24
|
content: [
|
|
24
25
|
{
|
|
25
26
|
type: "text",
|
|
26
|
-
text: "Failed to dispatch agent",
|
|
27
|
+
text: "Failed to dispatch agent: no analysis suite returned",
|
|
27
28
|
},
|
|
28
29
|
],
|
|
29
30
|
};
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { getConfig } from "../../utils/getConfig.js";
|
|
2
2
|
import { requestClient } from "../../utils/requestClient.js";
|
|
3
|
+
import { _parseError } from "../shared/helpers.js";
|
|
3
4
|
import { ToolName } from "../shared/types.js";
|
|
4
5
|
import { FetchAnalysisSuites } from "../shared/gql-queries.js";
|
|
5
6
|
export const wopeeFetchAnalysisSuites = {
|
|
@@ -9,35 +10,40 @@ export const wopeeFetchAnalysisSuites = {
|
|
|
9
10
|
description: "Fetch project's analysis suites from Woopee",
|
|
10
11
|
},
|
|
11
12
|
handler: async () => {
|
|
12
|
-
|
|
13
|
-
|
|
13
|
+
try {
|
|
14
|
+
const { WOPEE_PROJECT_UUID } = getConfig();
|
|
15
|
+
if (!WOPEE_PROJECT_UUID)
|
|
16
|
+
return {
|
|
17
|
+
content: [
|
|
18
|
+
{
|
|
19
|
+
type: "text",
|
|
20
|
+
text: "WOPEE_PROJECT_UUID is not set",
|
|
21
|
+
},
|
|
22
|
+
],
|
|
23
|
+
};
|
|
24
|
+
const result = await requestClient(FetchAnalysisSuites, {
|
|
25
|
+
projectUuid: WOPEE_PROJECT_UUID,
|
|
26
|
+
});
|
|
27
|
+
if (!result?.fetchAnalysisSuites)
|
|
28
|
+
return {
|
|
29
|
+
content: [
|
|
30
|
+
{
|
|
31
|
+
type: "text",
|
|
32
|
+
text: "Failed to fetch analysis suites: no data returned",
|
|
33
|
+
},
|
|
34
|
+
],
|
|
35
|
+
};
|
|
14
36
|
return {
|
|
15
37
|
content: [
|
|
16
38
|
{
|
|
17
39
|
type: "text",
|
|
18
|
-
text:
|
|
40
|
+
text: JSON.stringify(result.fetchAnalysisSuites, null, 2),
|
|
19
41
|
},
|
|
20
42
|
],
|
|
21
43
|
};
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
return {
|
|
27
|
-
content: [
|
|
28
|
-
{
|
|
29
|
-
type: "text",
|
|
30
|
-
text: "Failed to fetch analysis suites",
|
|
31
|
-
},
|
|
32
|
-
],
|
|
33
|
-
};
|
|
34
|
-
return {
|
|
35
|
-
content: [
|
|
36
|
-
{
|
|
37
|
-
type: "text",
|
|
38
|
-
text: JSON.stringify(result.fetchAnalysisSuites, null, 2),
|
|
39
|
-
},
|
|
40
|
-
],
|
|
41
|
-
};
|
|
44
|
+
}
|
|
45
|
+
catch (error) {
|
|
46
|
+
return _parseError(error);
|
|
47
|
+
}
|
|
42
48
|
},
|
|
43
49
|
};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { getConfig } from "../../utils/getConfig.js";
|
|
2
|
+
export const createFetchExecutedTestCasesInput = (input) => {
|
|
3
|
+
const { WOPEE_PROJECT_UUID } = getConfig();
|
|
4
|
+
if (!WOPEE_PROJECT_UUID)
|
|
5
|
+
throw new Error("WOPEE_PROJECT_UUID is not set");
|
|
6
|
+
return {
|
|
7
|
+
projectUuid: WOPEE_PROJECT_UUID,
|
|
8
|
+
analysisSuiteUuid: input.suiteUuid,
|
|
9
|
+
...(input.analysisIdentifier
|
|
10
|
+
? { analysisIdentifier: input.analysisIdentifier }
|
|
11
|
+
: {}),
|
|
12
|
+
};
|
|
13
|
+
};
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { WopeeFetchExecutedTestCasesInputSchema, FetchExecutedTestCasesInputSchema, } from "./schema.js";
|
|
2
|
+
import { ToolName } from "../shared/types.js";
|
|
3
|
+
import { _parseError } from "../shared/helpers.js";
|
|
4
|
+
import { createFetchExecutedTestCasesInput } from "./factory.js";
|
|
5
|
+
import { FetchExecutedTestCases } from "../shared/gql-queries.js";
|
|
6
|
+
import { requestClient } from "../../utils/requestClient.js";
|
|
7
|
+
export const wopeeFetchExecutedTestCases = {
|
|
8
|
+
name: ToolName.WOPEE_FETCH_EXECUTED_TEST_CASES,
|
|
9
|
+
config: {
|
|
10
|
+
title: "Fetch executed test cases",
|
|
11
|
+
description: "Fetch executed test cases and their results (agent report, code report, execution status) for a given analysis suite.",
|
|
12
|
+
inputSchema: WopeeFetchExecutedTestCasesInputSchema.shape,
|
|
13
|
+
},
|
|
14
|
+
handler: async (input) => {
|
|
15
|
+
try {
|
|
16
|
+
const factoryInput = createFetchExecutedTestCasesInput(input);
|
|
17
|
+
const parsedInput = FetchExecutedTestCasesInputSchema.parse(factoryInput);
|
|
18
|
+
const result = await requestClient(FetchExecutedTestCases, {
|
|
19
|
+
input: parsedInput,
|
|
20
|
+
});
|
|
21
|
+
if (!result?.fetchExecutedTestCases)
|
|
22
|
+
return {
|
|
23
|
+
content: [
|
|
24
|
+
{
|
|
25
|
+
type: "text",
|
|
26
|
+
text: "Failed to fetch executed test cases: no data returned",
|
|
27
|
+
},
|
|
28
|
+
],
|
|
29
|
+
};
|
|
30
|
+
return {
|
|
31
|
+
content: [
|
|
32
|
+
{
|
|
33
|
+
type: "text",
|
|
34
|
+
text: JSON.stringify(result.fetchExecutedTestCases, null, 2),
|
|
35
|
+
},
|
|
36
|
+
],
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
catch (error) {
|
|
40
|
+
return _parseError(error);
|
|
41
|
+
}
|
|
42
|
+
},
|
|
43
|
+
};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
export const WopeeFetchExecutedTestCasesInputSchema = z.object({
|
|
3
|
+
suiteUuid: z
|
|
4
|
+
.string({
|
|
5
|
+
description: "UUID of the analysis suite to fetch executed test cases for",
|
|
6
|
+
})
|
|
7
|
+
.min(1, "Suite UUID is required"),
|
|
8
|
+
analysisIdentifier: z
|
|
9
|
+
.string({
|
|
10
|
+
description: "Analysis identifier of the suite (ex. A068). Can be found in the analysis suite data.",
|
|
11
|
+
})
|
|
12
|
+
.optional(),
|
|
13
|
+
});
|
|
14
|
+
export const FetchExecutedTestCasesInputSchema = z.object({
|
|
15
|
+
projectUuid: z.string().min(1, "Project UUID is required"),
|
|
16
|
+
analysisSuiteUuid: z.string().min(1, "Suite UUID is required"),
|
|
17
|
+
analysisIdentifier: z.string().optional(),
|
|
18
|
+
});
|
|
@@ -1,37 +1,57 @@
|
|
|
1
1
|
import { getConfig } from "./getConfig.js";
|
|
2
|
+
export class RequestError extends Error {
|
|
3
|
+
code;
|
|
4
|
+
status;
|
|
5
|
+
retryable;
|
|
6
|
+
constructor(message, code, options) {
|
|
7
|
+
super(message);
|
|
8
|
+
this.name = "RequestError";
|
|
9
|
+
this.code = code;
|
|
10
|
+
this.status = options?.status ?? null;
|
|
11
|
+
this.retryable = options?.retryable ?? false;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
function classifyGraphQLErrors(errors) {
|
|
15
|
+
const combined = errors.map((e) => e.message).join("; ");
|
|
16
|
+
const lower = combined.toLowerCase();
|
|
17
|
+
if (lower.includes("rate limit")) {
|
|
18
|
+
return new RequestError(combined, "RATE_LIMITED", { retryable: true });
|
|
19
|
+
}
|
|
20
|
+
if (lower.includes("usage limit") || lower.includes("usage_limit")) {
|
|
21
|
+
return new RequestError(combined, "USAGE_LIMIT_EXCEEDED");
|
|
22
|
+
}
|
|
23
|
+
return new RequestError(combined, "GRAPHQL_ERROR");
|
|
24
|
+
}
|
|
2
25
|
export const requestClient = async (query, variables) => {
|
|
3
26
|
const { WOPEE_API_URL, WOPEE_API_KEY } = getConfig();
|
|
27
|
+
if (!WOPEE_API_KEY) {
|
|
28
|
+
throw new RequestError("WOPEE_API_KEY is not set", "AUTH_ERROR");
|
|
29
|
+
}
|
|
30
|
+
const headers = {
|
|
31
|
+
api_key: WOPEE_API_KEY,
|
|
32
|
+
"Content-Type": "application/json",
|
|
33
|
+
};
|
|
34
|
+
let response;
|
|
4
35
|
try {
|
|
5
|
-
|
|
6
|
-
console.error("[REQUEST_CLIENT_ERROR]: WOPEE_API_KEY is not set");
|
|
7
|
-
return null;
|
|
8
|
-
}
|
|
9
|
-
const headers = {
|
|
10
|
-
api_key: WOPEE_API_KEY,
|
|
11
|
-
"Content-Type": "application/json",
|
|
12
|
-
};
|
|
13
|
-
const response = await fetch(`${WOPEE_API_URL}`, {
|
|
36
|
+
response = await fetch(`${WOPEE_API_URL}`, {
|
|
14
37
|
method: "POST",
|
|
15
38
|
headers,
|
|
16
|
-
body: JSON.stringify({
|
|
17
|
-
query,
|
|
18
|
-
variables,
|
|
19
|
-
}),
|
|
39
|
+
body: JSON.stringify({ query, variables }),
|
|
20
40
|
});
|
|
21
|
-
if (!response.ok) {
|
|
22
|
-
const errorText = await response.text();
|
|
23
|
-
console.error("[REQUEST_CLIENT_ERROR]:", errorText);
|
|
24
|
-
return null;
|
|
25
|
-
}
|
|
26
|
-
const result = (await response.json());
|
|
27
|
-
if (result.errors && result.errors.length > 0) {
|
|
28
|
-
console.error("GraphQL errors:", result.errors);
|
|
29
|
-
return null;
|
|
30
|
-
}
|
|
31
|
-
return result.data || null;
|
|
32
41
|
}
|
|
33
42
|
catch (error) {
|
|
34
|
-
|
|
35
|
-
|
|
43
|
+
throw new RequestError(`Network error: ${error instanceof Error ? error.message : String(error)}`, "NETWORK_ERROR", { retryable: true });
|
|
44
|
+
}
|
|
45
|
+
if (!response.ok) {
|
|
46
|
+
const errorText = await response.text().catch(() => "");
|
|
47
|
+
const status = response.status;
|
|
48
|
+
const isRateLimit = status === 429 || errorText.toLowerCase().includes("rate limit");
|
|
49
|
+
const isRetryable = isRateLimit || status >= 500;
|
|
50
|
+
throw new RequestError(`HTTP ${status}: ${errorText || response.statusText}`, isRateLimit ? "RATE_LIMITED" : "HTTP_ERROR", { status, retryable: isRetryable });
|
|
51
|
+
}
|
|
52
|
+
const result = (await response.json());
|
|
53
|
+
if (result.errors && result.errors.length > 0) {
|
|
54
|
+
throw classifyGraphQLErrors(result.errors);
|
|
36
55
|
}
|
|
56
|
+
return result.data;
|
|
37
57
|
};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { RequestError } from "./requestClient.js";
|
|
2
|
+
export async function withRetry(fn, options) {
|
|
3
|
+
const maxRetries = options?.maxRetries ?? 3;
|
|
4
|
+
const baseDelayMs = options?.baseDelayMs ?? 2000;
|
|
5
|
+
let lastError;
|
|
6
|
+
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
|
7
|
+
try {
|
|
8
|
+
return await fn();
|
|
9
|
+
}
|
|
10
|
+
catch (error) {
|
|
11
|
+
if (error instanceof RequestError &&
|
|
12
|
+
error.retryable &&
|
|
13
|
+
attempt < maxRetries) {
|
|
14
|
+
lastError = error;
|
|
15
|
+
const delay = baseDelayMs * Math.pow(2, attempt);
|
|
16
|
+
console.error(`[RETRY] Attempt ${attempt + 1}/${maxRetries} failed (${error.code}): ${error.message}. Retrying in ${delay}ms...`);
|
|
17
|
+
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
18
|
+
continue;
|
|
19
|
+
}
|
|
20
|
+
throw error;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
throw lastError;
|
|
24
|
+
}
|
package/package.json
CHANGED