@skyramp/mcp 0.0.30 → 0.0.32
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/build/index.js +10 -5
- package/build/prompts/code-reuse.js +101 -0
- package/build/prompts/fix-error-prompt.js +29 -0
- package/build/prompts/modularization/integration-test-modularization.js +92 -0
- package/build/prompts/modularization/ui-test-modularization.js +177 -0
- package/build/prompts/testGenerationPrompt.js +76 -0
- package/build/services/ModularizationService.js +26 -5
- package/build/services/ScenarioGenerationService.js +167 -0
- package/build/services/TestGenerationService.js +31 -5
- package/build/tools/__tests__/codeReuseTool.test.js +266 -0
- package/build/tools/codeReuseTool.js +45 -0
- package/build/tools/executeSkyrampTestTool.js +1 -1
- package/build/tools/fixErrorTool.js +1 -1
- package/build/tools/generateContractRestTool.js +2 -2
- package/build/tools/generateE2ERestTool.js +3 -2
- package/build/tools/generateFuzzRestTool.js +2 -2
- package/build/tools/generateIntegrationRestTool.js +7 -2
- package/build/tools/generateLoadRestTool.js +2 -2
- package/build/tools/generateScenarioRestTool.js +81 -0
- package/build/tools/generateSmokeRestTool.js +2 -2
- package/build/tools/generateUIRestTool.js +3 -2
- package/build/tools/modularizationTool.js +12 -5
- package/build/tools/startTraceCollectionTool.js +4 -4
- package/build/tools/stopTraceCollectionTool.js +11 -5
- package/build/types/TestTypes.js +20 -0
- package/build/utils/utils.js +19 -0
- package/build/utils/utils.test.js +34 -1
- package/package.json +2 -2
- package/build/prompts/modularization-prompts.js +0 -281
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
import { logger } from "../utils/logger.js";
|
|
2
|
+
import fs from "fs";
|
|
3
|
+
import path from "path";
|
|
4
|
+
export class ScenarioGenerationService {
|
|
5
|
+
async parseScenario(params) {
|
|
6
|
+
try {
|
|
7
|
+
logger.info("Parsing scenario into API requests", {
|
|
8
|
+
scenarioName: params.scenarioName,
|
|
9
|
+
});
|
|
10
|
+
// Check if we have API schema to work with
|
|
11
|
+
if (!params.apiSchema) {
|
|
12
|
+
return {
|
|
13
|
+
content: [
|
|
14
|
+
{
|
|
15
|
+
type: "text",
|
|
16
|
+
text: "Please provide an API schema so that I can parse the scenario and map it to specific API endpoints.",
|
|
17
|
+
},
|
|
18
|
+
],
|
|
19
|
+
isError: true,
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
// Generate a single trace request from the scenario
|
|
23
|
+
const traceRequest = this.generateTraceRequestFromInput(params);
|
|
24
|
+
if (!traceRequest) {
|
|
25
|
+
return {
|
|
26
|
+
content: [
|
|
27
|
+
{
|
|
28
|
+
type: "text",
|
|
29
|
+
text: "Could not generate a trace request from the provided scenario.",
|
|
30
|
+
},
|
|
31
|
+
],
|
|
32
|
+
isError: true,
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
// Handle file writing
|
|
36
|
+
//add hyphen to the scenario name
|
|
37
|
+
//make file in tmp directory
|
|
38
|
+
const scenarioName = params.scenarioName.replace(/ /g, "-").toLowerCase();
|
|
39
|
+
const fileName = `scenario_${scenarioName}.json`;
|
|
40
|
+
const filePath = path.join(params.outputDir, fileName);
|
|
41
|
+
try {
|
|
42
|
+
// Check if file exists to determine if we should append or create new
|
|
43
|
+
let existingRequests = [];
|
|
44
|
+
if (fs.existsSync(filePath)) {
|
|
45
|
+
try {
|
|
46
|
+
const existingContent = fs.readFileSync(filePath, "utf8");
|
|
47
|
+
existingRequests = JSON.parse(existingContent);
|
|
48
|
+
if (!Array.isArray(existingRequests)) {
|
|
49
|
+
existingRequests = [];
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
catch (parseError) {
|
|
53
|
+
// If file exists but can't be parsed, start fresh
|
|
54
|
+
existingRequests = [];
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
// Add the new request to the array
|
|
58
|
+
existingRequests.push(traceRequest);
|
|
59
|
+
// Write the updated array to the file
|
|
60
|
+
fs.writeFileSync(filePath, JSON.stringify(existingRequests, null, 2), "utf8");
|
|
61
|
+
logger.info("Trace request added to file", {
|
|
62
|
+
filePath,
|
|
63
|
+
totalRequests: existingRequests.length,
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
catch (error) {
|
|
67
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
68
|
+
return {
|
|
69
|
+
content: [
|
|
70
|
+
{
|
|
71
|
+
type: "text",
|
|
72
|
+
text: `Error writing trace file: ${errorMessage}`,
|
|
73
|
+
},
|
|
74
|
+
],
|
|
75
|
+
isError: true,
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
return {
|
|
79
|
+
content: [
|
|
80
|
+
{
|
|
81
|
+
type: "text",
|
|
82
|
+
text: `**Trace Request Generated**
|
|
83
|
+
|
|
84
|
+
**Original Scenario:** "${params.scenarioName}"
|
|
85
|
+
|
|
86
|
+
**File Updated:** ${filePath}
|
|
87
|
+
|
|
88
|
+
**Generated Trace Request:**
|
|
89
|
+
|
|
90
|
+
\`\`\`json
|
|
91
|
+
${JSON.stringify(traceRequest, null, 2)}
|
|
92
|
+
\`\`\`
|
|
93
|
+
|
|
94
|
+
**File Details:**
|
|
95
|
+
- Output file: ${fileName}
|
|
96
|
+
- Full path: ${filePath}
|
|
97
|
+
- Action: Request added to trace file
|
|
98
|
+
|
|
99
|
+
**Next Steps:**
|
|
100
|
+
- Call this tool again with the next step in your scenario to add more requests
|
|
101
|
+
- Use the complete JSON file as input for integration or load test generation`,
|
|
102
|
+
},
|
|
103
|
+
],
|
|
104
|
+
isError: false,
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
catch (error) {
|
|
108
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
109
|
+
return {
|
|
110
|
+
content: [
|
|
111
|
+
{
|
|
112
|
+
type: "text",
|
|
113
|
+
text: `Error generating trace request: ${errorMessage}`,
|
|
114
|
+
},
|
|
115
|
+
],
|
|
116
|
+
isError: true,
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
generateTraceRequestFromInput(params) {
|
|
121
|
+
const destination = this.extractDestinationFromSchema(params.apiSchema);
|
|
122
|
+
// Use AI-provided parameters instead of parsing
|
|
123
|
+
const timestamp = new Date().toISOString();
|
|
124
|
+
const method = params.method;
|
|
125
|
+
const path = params.path;
|
|
126
|
+
const statusCode = params.statusCode ||
|
|
127
|
+
(method === "POST" ? 201 : method === "DELETE" ? 204 : 200);
|
|
128
|
+
const requestBody = params.requestBody ||
|
|
129
|
+
(method === "GET" || method === "DELETE" ? "" : "{}");
|
|
130
|
+
const responseBody = params.responseBody || (method === "DELETE" ? "" : "{}");
|
|
131
|
+
// Hardcode source IP and port for NLP consistency
|
|
132
|
+
const source = "192.168.65.1:39998";
|
|
133
|
+
return {
|
|
134
|
+
Source: source,
|
|
135
|
+
Destination: destination,
|
|
136
|
+
RequestBody: requestBody,
|
|
137
|
+
ResponseBody: responseBody,
|
|
138
|
+
RequestHeaders: {
|
|
139
|
+
"Content-Type": ["application/json"],
|
|
140
|
+
Authorization: ["Bearer demo-token"],
|
|
141
|
+
},
|
|
142
|
+
ResponseHeaders: {
|
|
143
|
+
"Content-Type": ["application/json"],
|
|
144
|
+
},
|
|
145
|
+
Method: method,
|
|
146
|
+
Path: path,
|
|
147
|
+
QueryParams: {},
|
|
148
|
+
StatusCode: statusCode,
|
|
149
|
+
Port: 443,
|
|
150
|
+
Timestamp: timestamp,
|
|
151
|
+
Scheme: "https",
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
extractDestinationFromSchema(apiSchema) {
|
|
155
|
+
// If it's a URL, extract destination; if it's a file path, use default
|
|
156
|
+
if (apiSchema.startsWith("http")) {
|
|
157
|
+
try {
|
|
158
|
+
const url = new URL(apiSchema);
|
|
159
|
+
return url.host;
|
|
160
|
+
}
|
|
161
|
+
catch {
|
|
162
|
+
return "api.example.com";
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
return "api.example.com";
|
|
166
|
+
}
|
|
167
|
+
}
|
|
@@ -28,20 +28,46 @@ export class TestGenerationService {
|
|
|
28
28
|
}
|
|
29
29
|
const result = await this.executeGeneration(generateOptions);
|
|
30
30
|
const testType = this.getTestType();
|
|
31
|
-
const shouldModularize = testType === "ui" || testType === "e2e";
|
|
32
31
|
const languageSteps = getLanguageSteps({
|
|
33
32
|
language: params.language || "",
|
|
34
33
|
testType: testType,
|
|
35
34
|
framework: params.framework,
|
|
36
35
|
});
|
|
36
|
+
// Orchestrate code optimization workflow based on flags
|
|
37
|
+
let postGenerationMessage = "";
|
|
38
|
+
if (params.codeReuse && params.modularizeCode) {
|
|
39
|
+
// Both enabled: Run in sequence (reuse first, then modularize)
|
|
40
|
+
postGenerationMessage += `
|
|
41
|
+
✅ Test generated successfully!
|
|
42
|
+
|
|
43
|
+
📋 **Optimization Workflow:**
|
|
44
|
+
1. **Code Reuse Analysis** - Will identify and extract reusable code patterns
|
|
45
|
+
2. **Code Modularization** - Will then organize code for better maintainability
|
|
46
|
+
|
|
47
|
+
⏭️ Next: Running code reuse analysis with skyramp_code_reuse tool...
|
|
48
|
+
`;
|
|
49
|
+
}
|
|
50
|
+
else if (params.codeReuse) {
|
|
51
|
+
// Only code reuse
|
|
52
|
+
postGenerationMessage += `
|
|
53
|
+
✅ Test generated successfully!
|
|
54
|
+
|
|
55
|
+
⏭️ Next: Running code reuse analysis with skyramp_code_reuse tool...
|
|
56
|
+
`;
|
|
57
|
+
}
|
|
58
|
+
else if (params.modularizeCode) {
|
|
59
|
+
// Only modularization
|
|
60
|
+
postGenerationMessage += `
|
|
61
|
+
✅ Test generated successfully!
|
|
62
|
+
|
|
63
|
+
⏭️ Next: Running modularization with skyramp_modularization tool...
|
|
64
|
+
`;
|
|
65
|
+
}
|
|
37
66
|
return {
|
|
38
67
|
content: [
|
|
39
68
|
{
|
|
40
69
|
type: "text",
|
|
41
|
-
text: `${
|
|
42
|
-
? "Generated the test and WILL NOW MODULARIZE THE CODE USING THE FOLLOWING MODULARIZATION PRINCIPLES USING skyramp_modularization TOOL"
|
|
43
|
-
: "Generated the test and DO NOT MODULARIZE THE CODE"}
|
|
44
|
-
${result}
|
|
70
|
+
text: `${postGenerationMessage}${result}
|
|
45
71
|
|
|
46
72
|
${languageSteps}`,
|
|
47
73
|
},
|
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, jest } from "@jest/globals";
|
|
2
|
+
import { registerCodeReuseTool } from "../codeReuseTool.js";
|
|
3
|
+
import { getCodeReusePrompt } from "../../prompts/code-reuse.js";
|
|
4
|
+
// Mock the logger
|
|
5
|
+
jest.mock("../../utils/logger", () => ({
|
|
6
|
+
logger: {
|
|
7
|
+
info: jest.fn(),
|
|
8
|
+
error: jest.fn(),
|
|
9
|
+
warn: jest.fn(),
|
|
10
|
+
debug: jest.fn(),
|
|
11
|
+
},
|
|
12
|
+
}));
|
|
13
|
+
// Mock the prompt function
|
|
14
|
+
jest.mock("../../prompts/code-reuse", () => ({
|
|
15
|
+
getCodeReusePrompt: jest.fn(),
|
|
16
|
+
}));
|
|
17
|
+
describe("Code Reuse Tool", () => {
|
|
18
|
+
let mockServer;
|
|
19
|
+
let mockRegisterTool;
|
|
20
|
+
beforeEach(() => {
|
|
21
|
+
jest.clearAllMocks();
|
|
22
|
+
mockRegisterTool = jest.fn();
|
|
23
|
+
mockServer = {
|
|
24
|
+
registerTool: mockRegisterTool,
|
|
25
|
+
};
|
|
26
|
+
getCodeReusePrompt.mockReturnValue("Mocked prompt content");
|
|
27
|
+
});
|
|
28
|
+
describe("Tool Registration", () => {
|
|
29
|
+
it("should register the code reuse tool with correct configuration", () => {
|
|
30
|
+
registerCodeReuseTool(mockServer);
|
|
31
|
+
expect(mockRegisterTool).toHaveBeenCalledWith("skyramp_code_reuse", expect.objectContaining({
|
|
32
|
+
description: expect.stringContaining("Analyzes code for reuse opportunities"),
|
|
33
|
+
inputSchema: expect.objectContaining({
|
|
34
|
+
testFile: expect.any(Object),
|
|
35
|
+
language: expect.any(Object),
|
|
36
|
+
framework: expect.any(Object),
|
|
37
|
+
modularizeCode: expect.any(Object),
|
|
38
|
+
prompt: expect.any(Object),
|
|
39
|
+
}),
|
|
40
|
+
annotations: expect.objectContaining({
|
|
41
|
+
keywords: expect.arrayContaining([
|
|
42
|
+
"code reuse",
|
|
43
|
+
"duplicate code",
|
|
44
|
+
"helper functions",
|
|
45
|
+
"import",
|
|
46
|
+
"refactor",
|
|
47
|
+
]),
|
|
48
|
+
}),
|
|
49
|
+
}), expect.any(Function));
|
|
50
|
+
});
|
|
51
|
+
});
|
|
52
|
+
describe("Tool Execution", () => {
|
|
53
|
+
let toolHandler;
|
|
54
|
+
beforeEach(() => {
|
|
55
|
+
registerCodeReuseTool(mockServer);
|
|
56
|
+
toolHandler = mockRegisterTool.mock.calls[0][2];
|
|
57
|
+
});
|
|
58
|
+
it("should handle valid input parameters correctly", async () => {
|
|
59
|
+
const params = {
|
|
60
|
+
testFile: "/path/to/test.spec.ts",
|
|
61
|
+
language: "typescript",
|
|
62
|
+
framework: "playwright",
|
|
63
|
+
modularizeCode: true,
|
|
64
|
+
prompt: "Test code content to analyze",
|
|
65
|
+
};
|
|
66
|
+
const result = await toolHandler(params);
|
|
67
|
+
expect(getCodeReusePrompt).toHaveBeenCalledWith(params.testFile, params.language, params.modularizeCode);
|
|
68
|
+
expect(result).toEqual({
|
|
69
|
+
content: [
|
|
70
|
+
{
|
|
71
|
+
type: "text",
|
|
72
|
+
text: expect.stringContaining("Mocked prompt content"),
|
|
73
|
+
},
|
|
74
|
+
],
|
|
75
|
+
});
|
|
76
|
+
expect(result.content[0].text).toContain(params.testFile);
|
|
77
|
+
expect(result.content[0].text).toContain(params.language);
|
|
78
|
+
expect(result.content[0].text).toContain(params.framework);
|
|
79
|
+
expect(result.content[0].text).toContain(params.prompt);
|
|
80
|
+
});
|
|
81
|
+
it("should include analysis instructions in the output", async () => {
|
|
82
|
+
const params = {
|
|
83
|
+
testFile: "/path/to/test.spec.ts",
|
|
84
|
+
language: "typescript",
|
|
85
|
+
framework: "playwright",
|
|
86
|
+
modularizeCode: false,
|
|
87
|
+
prompt: "Code to analyze",
|
|
88
|
+
};
|
|
89
|
+
const result = await toolHandler(params);
|
|
90
|
+
const outputText = result.content[0].text;
|
|
91
|
+
expect(outputText).toContain("ANALYSIS INSTRUCTIONS");
|
|
92
|
+
expect(outputText).toContain("search for existing test files using glob_file_search");
|
|
93
|
+
expect(outputText).toContain("Read existing test files to identify reusable helper functions");
|
|
94
|
+
expect(outputText).toContain("Import and reuse existing functions instead of duplicating code");
|
|
95
|
+
expect(outputText).toContain("DO NOT modify existing utility files");
|
|
96
|
+
});
|
|
97
|
+
it("should handle different frameworks", async () => {
|
|
98
|
+
const frameworks = ["playwright", "jest", "pytest", "junit"];
|
|
99
|
+
for (const framework of frameworks) {
|
|
100
|
+
const params = {
|
|
101
|
+
testFile: "/path/to/test.spec.ts",
|
|
102
|
+
language: "typescript",
|
|
103
|
+
framework,
|
|
104
|
+
modularizeCode: false,
|
|
105
|
+
prompt: "Test framework code",
|
|
106
|
+
};
|
|
107
|
+
const result = await toolHandler(params);
|
|
108
|
+
expect(result.content[0].text).toContain(`Framework: ${framework}`);
|
|
109
|
+
}
|
|
110
|
+
});
|
|
111
|
+
it("should handle modularizeCode flag variations", async () => {
|
|
112
|
+
const modularizeOptions = [true, false];
|
|
113
|
+
for (const modularizeCode of modularizeOptions) {
|
|
114
|
+
const params = {
|
|
115
|
+
testFile: "/path/to/test.spec.ts",
|
|
116
|
+
language: "typescript",
|
|
117
|
+
framework: "playwright",
|
|
118
|
+
modularizeCode,
|
|
119
|
+
prompt: "Code content",
|
|
120
|
+
};
|
|
121
|
+
await toolHandler(params);
|
|
122
|
+
expect(getCodeReusePrompt).toHaveBeenCalledWith(params.testFile, params.language, modularizeCode);
|
|
123
|
+
}
|
|
124
|
+
});
|
|
125
|
+
it("should format output with proper sections", async () => {
|
|
126
|
+
const params = {
|
|
127
|
+
testFile: "/path/to/integration.test.ts",
|
|
128
|
+
language: "typescript",
|
|
129
|
+
framework: "playwright",
|
|
130
|
+
modularizeCode: true,
|
|
131
|
+
prompt: "Integration test code with potential reuse opportunities",
|
|
132
|
+
};
|
|
133
|
+
const result = await toolHandler(params);
|
|
134
|
+
const outputText = result.content[0].text;
|
|
135
|
+
// Check for proper section formatting
|
|
136
|
+
expect(outputText).toContain("CODE TO ANALYZE");
|
|
137
|
+
expect(outputText).toContain("ANALYSIS INSTRUCTIONS");
|
|
138
|
+
expect(outputText).toMatch(/═+/); // Section separators
|
|
139
|
+
expect(outputText).toContain("📝"); // Emoji indicators
|
|
140
|
+
expect(outputText).toContain("🎯"); // Analysis emoji
|
|
141
|
+
});
|
|
142
|
+
it("should include specific reuse categories in instructions", async () => {
|
|
143
|
+
const params = {
|
|
144
|
+
testFile: "/path/to/ui.test.ts",
|
|
145
|
+
language: "typescript",
|
|
146
|
+
framework: "playwright",
|
|
147
|
+
modularizeCode: true,
|
|
148
|
+
prompt: "UI test with authentication and form filling",
|
|
149
|
+
};
|
|
150
|
+
const result = await toolHandler(params);
|
|
151
|
+
const outputText = result.content[0].text;
|
|
152
|
+
expect(outputText).toContain("authentication");
|
|
153
|
+
expect(outputText).toContain("HTTP request");
|
|
154
|
+
expect(outputText).toContain("data setup");
|
|
155
|
+
expect(outputText).toContain("validation");
|
|
156
|
+
expect(outputText).toContain("utility functions");
|
|
157
|
+
});
|
|
158
|
+
});
|
|
159
|
+
describe("Input Validation", () => {
|
|
160
|
+
let toolHandler;
|
|
161
|
+
beforeEach(() => {
|
|
162
|
+
registerCodeReuseTool(mockServer);
|
|
163
|
+
toolHandler = mockRegisterTool.mock.calls[0][2];
|
|
164
|
+
});
|
|
165
|
+
it("should handle minimal required parameters", async () => {
|
|
166
|
+
const minimalParams = {
|
|
167
|
+
testFile: "/path/to/test.ts",
|
|
168
|
+
language: "typescript",
|
|
169
|
+
framework: "jest",
|
|
170
|
+
modularizeCode: false,
|
|
171
|
+
prompt: "Basic test code",
|
|
172
|
+
};
|
|
173
|
+
const result = await toolHandler(minimalParams);
|
|
174
|
+
expect(result.content).toHaveLength(1);
|
|
175
|
+
expect(result.content[0].type).toBe("text");
|
|
176
|
+
});
|
|
177
|
+
it("should preserve all input parameters in output", async () => {
|
|
178
|
+
const params = {
|
|
179
|
+
testFile: "/complex/path/to/advanced.test.ts",
|
|
180
|
+
language: "typescript",
|
|
181
|
+
framework: "playwright",
|
|
182
|
+
modularizeCode: true,
|
|
183
|
+
prompt: "Complex test code with multiple helper functions and duplicate patterns",
|
|
184
|
+
};
|
|
185
|
+
const result = await toolHandler(params);
|
|
186
|
+
const outputText = result.content[0].text;
|
|
187
|
+
expect(outputText).toContain(params.testFile);
|
|
188
|
+
expect(outputText).toContain(params.language);
|
|
189
|
+
expect(outputText).toContain(params.framework);
|
|
190
|
+
expect(outputText).toContain(params.prompt);
|
|
191
|
+
});
|
|
192
|
+
});
|
|
193
|
+
describe("Error Handling", () => {
|
|
194
|
+
let toolHandler;
|
|
195
|
+
beforeEach(() => {
|
|
196
|
+
registerCodeReuseTool(mockServer);
|
|
197
|
+
toolHandler = mockRegisterTool.mock.calls[0][2];
|
|
198
|
+
});
|
|
199
|
+
it("should handle errors in prompt generation gracefully", async () => {
|
|
200
|
+
getCodeReusePrompt.mockImplementation(() => {
|
|
201
|
+
throw new Error("Prompt generation failed");
|
|
202
|
+
});
|
|
203
|
+
const params = {
|
|
204
|
+
testFile: "/path/to/test.ts",
|
|
205
|
+
language: "typescript",
|
|
206
|
+
framework: "jest",
|
|
207
|
+
modularizeCode: false,
|
|
208
|
+
prompt: "Test code",
|
|
209
|
+
};
|
|
210
|
+
await expect(toolHandler(params)).rejects.toThrow("Prompt generation failed");
|
|
211
|
+
});
|
|
212
|
+
});
|
|
213
|
+
describe("Integration with Prompt System", () => {
|
|
214
|
+
let toolHandler;
|
|
215
|
+
beforeEach(() => {
|
|
216
|
+
registerCodeReuseTool(mockServer);
|
|
217
|
+
toolHandler = mockRegisterTool.mock.calls[0][2];
|
|
218
|
+
});
|
|
219
|
+
it("should call getCodeReusePrompt with correct parameters", async () => {
|
|
220
|
+
const params = {
|
|
221
|
+
testFile: "/src/tests/api.test.ts",
|
|
222
|
+
language: "typescript",
|
|
223
|
+
framework: "jest",
|
|
224
|
+
modularizeCode: true,
|
|
225
|
+
prompt: "API test code",
|
|
226
|
+
};
|
|
227
|
+
await toolHandler(params);
|
|
228
|
+
expect(getCodeReusePrompt).toHaveBeenCalledTimes(1);
|
|
229
|
+
expect(getCodeReusePrompt).toHaveBeenCalledWith(params.testFile, params.language, params.modularizeCode);
|
|
230
|
+
});
|
|
231
|
+
it("should integrate prompt content with tool-specific instructions", async () => {
|
|
232
|
+
const mockPromptContent = "Custom analysis instructions from prompt";
|
|
233
|
+
getCodeReusePrompt.mockReturnValue(mockPromptContent);
|
|
234
|
+
const params = {
|
|
235
|
+
testFile: "/test/example.ts",
|
|
236
|
+
language: "typescript",
|
|
237
|
+
framework: "playwright",
|
|
238
|
+
modularizeCode: false,
|
|
239
|
+
prompt: "Example code",
|
|
240
|
+
};
|
|
241
|
+
const result = await toolHandler(params);
|
|
242
|
+
const outputText = result.content[0].text;
|
|
243
|
+
expect(outputText).toContain(mockPromptContent);
|
|
244
|
+
expect(outputText).toContain("Read test file /test/example.ts");
|
|
245
|
+
});
|
|
246
|
+
});
|
|
247
|
+
describe("Tool Metadata", () => {
|
|
248
|
+
it("should have appropriate keywords for discoverability", () => {
|
|
249
|
+
registerCodeReuseTool(mockServer);
|
|
250
|
+
const toolConfig = mockRegisterTool.mock.calls[0][1];
|
|
251
|
+
const keywords = toolConfig.annotations.keywords;
|
|
252
|
+
expect(keywords).toContain("code reuse");
|
|
253
|
+
expect(keywords).toContain("duplicate code");
|
|
254
|
+
expect(keywords).toContain("helper functions");
|
|
255
|
+
expect(keywords).toContain("import");
|
|
256
|
+
expect(keywords).toContain("refactor");
|
|
257
|
+
});
|
|
258
|
+
it("should have a descriptive tool description", () => {
|
|
259
|
+
registerCodeReuseTool(mockServer);
|
|
260
|
+
const toolConfig = mockRegisterTool.mock.calls[0][1];
|
|
261
|
+
expect(toolConfig.description).toContain("Analyzes code for reuse opportunities");
|
|
262
|
+
expect(toolConfig.description).toContain("duplicate code patterns");
|
|
263
|
+
expect(toolConfig.description).toContain("importing and reusing existing helper functions");
|
|
264
|
+
});
|
|
265
|
+
});
|
|
266
|
+
});
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { logger } from "../utils/logger.js";
|
|
3
|
+
import { getCodeReusePrompt } from "../prompts/code-reuse.js";
|
|
4
|
+
import { codeRefactoringSchema, languageSchema } from "../types/TestTypes.js";
|
|
5
|
+
const codeReuseSchema = z.object({
|
|
6
|
+
testFile: z
|
|
7
|
+
.string()
|
|
8
|
+
.describe("The test file to analyze for code reuse opportunities"),
|
|
9
|
+
language: languageSchema.shape.language,
|
|
10
|
+
framework: languageSchema.shape.framework,
|
|
11
|
+
modularizeCode: codeRefactoringSchema.shape.modularizeCode,
|
|
12
|
+
prompt: z
|
|
13
|
+
.string()
|
|
14
|
+
.describe("The code content to analyze and optimize for code reuse"),
|
|
15
|
+
});
|
|
16
|
+
export function registerCodeReuseTool(server) {
|
|
17
|
+
server.registerTool("skyramp_code_reuse", {
|
|
18
|
+
description: `Analyzes code for reuse opportunities and enforces code reuse principles. This tool helps identify duplicate code patterns and provides guidance on importing and reusing existing helper functions from other test files.`,
|
|
19
|
+
inputSchema: codeReuseSchema.shape,
|
|
20
|
+
annotations: {
|
|
21
|
+
keywords: [
|
|
22
|
+
"code reuse",
|
|
23
|
+
"duplicate code",
|
|
24
|
+
"helper functions",
|
|
25
|
+
"import",
|
|
26
|
+
"refactor",
|
|
27
|
+
],
|
|
28
|
+
},
|
|
29
|
+
}, async (params) => {
|
|
30
|
+
logger.info("Analyzing code for reuse opportunities", {
|
|
31
|
+
testFile: params.testFile,
|
|
32
|
+
language: params.language,
|
|
33
|
+
framework: params.framework,
|
|
34
|
+
});
|
|
35
|
+
const codeReusePrompt = getCodeReusePrompt(params.testFile, params.language, params.modularizeCode);
|
|
36
|
+
return {
|
|
37
|
+
content: [
|
|
38
|
+
{
|
|
39
|
+
type: "text",
|
|
40
|
+
text: codeReusePrompt,
|
|
41
|
+
},
|
|
42
|
+
],
|
|
43
|
+
};
|
|
44
|
+
});
|
|
45
|
+
}
|
|
@@ -5,7 +5,7 @@ import { Writable } from "stream";
|
|
|
5
5
|
import { logger } from "../utils/logger.js";
|
|
6
6
|
import { stripVTControlCharacters } from "util";
|
|
7
7
|
import fs from "fs";
|
|
8
|
-
const EXECUTOR_DOCKER_IMAGE = "skyramp/executor:v1.2.
|
|
8
|
+
const EXECUTOR_DOCKER_IMAGE = "skyramp/executor:v1.2.24";
|
|
9
9
|
const DOCKER_PLATFORM = "linux/amd64";
|
|
10
10
|
export function registerExecuteSkyrampTestTool(server) {
|
|
11
11
|
server.registerTool("skyramp_execute_test", {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { logger } from "../utils/logger.js";
|
|
3
|
-
import { getFixErrorsPrompt } from "../prompts/
|
|
3
|
+
import { getFixErrorsPrompt } from "../prompts/fix-error-prompt.js";
|
|
4
4
|
import { languageSchema } from "../types/TestTypes.js";
|
|
5
5
|
import { getLanguageSteps } from "../utils/language-helper.js";
|
|
6
6
|
const fixErrorSchema = z.object({
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
-
import { baseTestSchema } from "../types/TestTypes.js";
|
|
2
|
+
import { baseTestSchema, TestType } from "../types/TestTypes.js";
|
|
3
3
|
import { TestGenerationService, } from "../services/TestGenerationService.js";
|
|
4
4
|
const contractTestSchema = {
|
|
5
5
|
...baseTestSchema,
|
|
@@ -14,7 +14,7 @@ const contractTestSchema = {
|
|
|
14
14
|
};
|
|
15
15
|
export class ContractTestService extends TestGenerationService {
|
|
16
16
|
getTestType() {
|
|
17
|
-
return
|
|
17
|
+
return TestType.CONTRACT;
|
|
18
18
|
}
|
|
19
19
|
buildGenerationOptions(params) {
|
|
20
20
|
return {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
-
import { baseSchema, baseTraceSchema } from "../types/TestTypes.js";
|
|
2
|
+
import { baseSchema, baseTraceSchema, TestType, codeRefactoringSchema, } from "../types/TestTypes.js";
|
|
3
3
|
import { TestGenerationService, } from "../services/TestGenerationService.js";
|
|
4
4
|
const e2eTestSchema = {
|
|
5
5
|
...baseSchema.shape,
|
|
@@ -7,10 +7,11 @@ const e2eTestSchema = {
|
|
|
7
7
|
playwrightInput: z
|
|
8
8
|
.string()
|
|
9
9
|
.describe("MUST be absolute path to the playwright input file like /path/to/playwright-ui-test.zip and MUST be a zip file captured using start_trace_collection tool"),
|
|
10
|
+
...codeRefactoringSchema.shape,
|
|
10
11
|
};
|
|
11
12
|
export class E2ETestService extends TestGenerationService {
|
|
12
13
|
getTestType() {
|
|
13
|
-
return
|
|
14
|
+
return TestType.E2E;
|
|
14
15
|
}
|
|
15
16
|
buildGenerationOptions(params) {
|
|
16
17
|
return {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { baseTestSchema } from "../types/TestTypes.js";
|
|
1
|
+
import { baseTestSchema, TestType } from "../types/TestTypes.js";
|
|
2
2
|
import { TestGenerationService, } from "../services/TestGenerationService.js";
|
|
3
3
|
import { z } from "zod";
|
|
4
4
|
const fuzzTestSchema = {
|
|
@@ -10,7 +10,7 @@ const fuzzTestSchema = {
|
|
|
10
10
|
};
|
|
11
11
|
export class FuzzTestService extends TestGenerationService {
|
|
12
12
|
getTestType() {
|
|
13
|
-
return
|
|
13
|
+
return TestType.FUZZ;
|
|
14
14
|
}
|
|
15
15
|
buildGenerationOptions(params) {
|
|
16
16
|
return {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
-
import { baseTestSchema, baseTraceSchema, } from "../types/TestTypes.js";
|
|
2
|
+
import { baseTestSchema, baseTraceSchema, TestType, codeRefactoringSchema, } from "../types/TestTypes.js";
|
|
3
3
|
import { TestGenerationService, } from "../services/TestGenerationService.js";
|
|
4
4
|
const integrationTestSchema = z
|
|
5
5
|
.object({
|
|
@@ -8,17 +8,22 @@ const integrationTestSchema = z
|
|
|
8
8
|
include: baseTraceSchema.shape.include.optional(),
|
|
9
9
|
exclude: baseTraceSchema.shape.exclude.optional(),
|
|
10
10
|
endpointURL: baseTestSchema.endpointURL.default(""),
|
|
11
|
+
...codeRefactoringSchema.shape,
|
|
12
|
+
rawTrace: z
|
|
13
|
+
.string()
|
|
14
|
+
.describe("Raw trace data read from the scenario file to be used for test generation, this is not generated by skramp_start_trace_collection tool"),
|
|
11
15
|
})
|
|
12
16
|
.omit({ method: true }).shape;
|
|
13
17
|
export class IntegrationTestService extends TestGenerationService {
|
|
14
18
|
getTestType() {
|
|
15
|
-
return
|
|
19
|
+
return TestType.INTEGRATION;
|
|
16
20
|
}
|
|
17
21
|
buildGenerationOptions(params) {
|
|
18
22
|
return {
|
|
19
23
|
...super.buildBaseGenerationOptions(params),
|
|
20
24
|
responseData: params.responseData,
|
|
21
25
|
playwrightInput: params.playwrightInput,
|
|
26
|
+
rawTrace: params.rawTrace,
|
|
22
27
|
};
|
|
23
28
|
}
|
|
24
29
|
async handleApiAnalysis(params, generateOptions) {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
-
import { baseTestSchema, baseTraceSchema, } from "../types/TestTypes.js";
|
|
2
|
+
import { baseTestSchema, baseTraceSchema, TestType, } from "../types/TestTypes.js";
|
|
3
3
|
import { TestGenerationService, } from "../services/TestGenerationService.js";
|
|
4
4
|
const loadTestSchema = {
|
|
5
5
|
...baseTestSchema,
|
|
@@ -34,7 +34,7 @@ const loadTestSchema = {
|
|
|
34
34
|
};
|
|
35
35
|
export class LoadTestService extends TestGenerationService {
|
|
36
36
|
getTestType() {
|
|
37
|
-
return
|
|
37
|
+
return TestType.LOAD;
|
|
38
38
|
}
|
|
39
39
|
buildGenerationOptions(params) {
|
|
40
40
|
return {
|