@skyramp/mcp 0.2.8 → 0.2.10-rc.1
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 +1 -8
- package/build/prompts/enhance-assertions/uiAssertionsPrompt.js +28 -1
- package/build/prompts/initialize-workspace/initializeWorkspacePrompt.js +1 -0
- package/build/prompts/personas.d.ts +2 -2
- package/build/prompts/personas.js +2 -2
- package/build/prompts/sut-setup/modes/adaptWorkflowPrompt.js +3 -1
- package/build/prompts/sut-setup/modes/dockerComposePrompt.js +6 -1
- package/build/prompts/sut-setup/shared.d.ts +20 -0
- package/build/prompts/sut-setup/shared.js +67 -7
- package/build/prompts/test-maintenance/actionsInstructions.js +5 -1
- package/build/prompts/test-maintenance/drift-analysis-prompt.d.ts +15 -2
- package/build/prompts/test-maintenance/drift-analysis-prompt.js +63 -3
- package/build/prompts/test-maintenance/driftAnalysisSections.js +7 -25
- package/build/prompts/test-maintenance/driftAnalysisShared.d.ts +20 -0
- package/build/prompts/test-maintenance/driftAnalysisShared.js +80 -0
- package/build/prompts/test-maintenance/uiDriftAnalysisSections.d.ts +32 -0
- package/build/prompts/test-maintenance/uiDriftAnalysisSections.js +212 -0
- package/build/prompts/test-recommendation/recommendationShared.js +1 -1
- package/build/prompts/test-recommendation/scopeAssessment.js +12 -2
- package/build/prompts/testbot/testbot-prompts.js +10 -9
- package/build/resources/testbotResource.js +1 -1
- package/build/services/TestDiscoveryService.d.ts +12 -10
- package/build/services/TestDiscoveryService.js +137 -83
- package/build/services/TestDiscoveryService.test.js +370 -15
- package/build/services/TestExecutionService.d.ts +1 -1
- package/build/services/TestExecutionService.js +7 -7
- package/build/services/TestExecutionService.test.js +4 -1
- package/build/tools/executeSkyrampTestTool.js +40 -71
- package/build/tools/submitReportTool.d.ts +25 -25
- package/build/tools/submitReportTool.js +123 -34
- package/build/tools/submitReportTool.test.js +413 -94
- package/build/tools/test-management/actionsTool.js +177 -77
- package/build/tools/test-management/actionsTool.test.d.ts +1 -0
- package/build/tools/test-management/actionsTool.test.js +297 -0
- package/build/tools/test-management/analyzeChangesTool.d.ts +9 -0
- package/build/tools/test-management/analyzeChangesTool.js +74 -169
- package/build/tools/test-management/analyzeChangesTool.test.js +34 -3
- package/build/tools/test-management/analyzeTestHealthTool.js +63 -22
- package/build/tools/test-management/analyzeTestHealthTool.test.d.ts +1 -0
- package/build/tools/test-management/analyzeTestHealthTool.test.js +268 -0
- package/build/tools/workspace/initializeWorkspaceTool.js +37 -24
- package/build/tools/workspace/initializeWorkspaceTool.test.js +9 -4
- package/build/types/FrontendIntegration.d.ts +25 -0
- package/build/types/FrontendIntegration.js +19 -0
- package/build/types/RepositoryAnalysis.d.ts +8 -8
- package/build/types/TestAnalysis.d.ts +18 -49
- package/build/types/TestAnalysis.js +0 -28
- package/build/types/TestTypes.d.ts +3 -1
- package/build/types/TestTypes.js +3 -1
- package/build/types/TestbotReport.d.ts +1 -1
- package/build/utils/AnalysisStateManager.d.ts +16 -22
- package/build/utils/docker.test.js +1 -1
- package/build/utils/featureFlags.d.ts +1 -1
- package/build/utils/featureFlags.js +1 -1
- package/build/utils/frontendIntegration.d.ts +9 -0
- package/build/utils/frontendIntegration.js +237 -0
- package/build/utils/frontendIntegration.test.d.ts +1 -0
- package/build/utils/frontendIntegration.test.js +229 -0
- package/build/utils/pr-comment-parser.d.ts +3 -3
- package/build/utils/pr-comment-parser.js +6 -6
- package/build/utils/pr-comment-parser.test.js +3 -3
- package/build/utils/repoScanner.d.ts +7 -0
- package/build/utils/repoScanner.js +14 -6
- package/build/utils/repoScanner.test.js +9 -9
- package/build/utils/routeParsers.d.ts +32 -0
- package/build/utils/routeParsers.js +205 -2
- package/build/utils/routeParsers.test.js +279 -29
- package/build/utils/sourceRouteExtractor.js +174 -5
- package/build/utils/sourceRouteExtractor.test.js +173 -0
- package/build/utils/utils.d.ts +11 -0
- package/build/utils/utils.js +19 -0
- package/build/utils/utils.test.js +23 -1
- package/build/utils/versions.d.ts +3 -3
- package/build/utils/versions.js +1 -1
- package/build/workspace/workspace.d.ts +21 -21
- package/build/workspace/workspace.js +7 -4
- package/build/workspace/workspace.test.js +65 -6
- package/node_modules/playwright/lib/mcp/browser/tools/files.js +8 -3
- package/node_modules/playwright/lib/mcp/browser/tools/wait.js +1 -1
- package/node_modules/playwright/lib/mcp/skyramp/exportTool.js +10 -1
- package/node_modules/playwright/lib/mcp/skyramp/traceRecordingBackend.js +33 -9
- package/node_modules/playwright/lib/mcp/test/skyRampExport.js +7 -1
- package/node_modules/playwright/lib/mcp/test/skyRampExport.test.js +44 -0
- package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/{index.B_7ywgmr.js → index.d4gkVSou.js} +1 -1
- package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/index.html +1 -1
- package/node_modules/playwright/node_modules/playwright-core/package.json +1 -1
- package/node_modules/playwright/package.json +1 -1
- package/package.json +3 -3
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
// @ts-nocheck - Jest ESM type inference issues
|
|
2
|
+
import { jest, describe, it, expect, beforeEach } from "@jest/globals";
|
|
3
|
+
jest.unstable_mockModule("@modelcontextprotocol/sdk/server/mcp.js", () => ({
|
|
4
|
+
McpServer: jest.fn(),
|
|
5
|
+
}));
|
|
6
|
+
jest.unstable_mockModule("@modelcontextprotocol/sdk/types.js", () => ({
|
|
7
|
+
CallToolResult: {},
|
|
8
|
+
}));
|
|
9
|
+
jest.unstable_mockModule("../../utils/logger.js", () => ({
|
|
10
|
+
logger: {
|
|
11
|
+
info: jest.fn(),
|
|
12
|
+
debug: jest.fn(),
|
|
13
|
+
error: jest.fn(),
|
|
14
|
+
warning: jest.fn(),
|
|
15
|
+
},
|
|
16
|
+
}));
|
|
17
|
+
jest.unstable_mockModule("../../services/AnalyticsService.js", () => ({
|
|
18
|
+
AnalyticsService: { pushMCPToolEvent: jest.fn() },
|
|
19
|
+
}));
|
|
20
|
+
const mockBuildDriftAnalysisPrompt = jest.fn().mockImplementation((_stateFile, apiTests, ui) => {
|
|
21
|
+
const parts = [];
|
|
22
|
+
if ((apiTests?.length ?? 0) > 0 || !ui)
|
|
23
|
+
parts.push("<drift_analysis_rules>API drift</drift_analysis_rules>");
|
|
24
|
+
if (ui)
|
|
25
|
+
parts.push("<ui_drift_analysis_rules>UI drift</ui_drift_analysis_rules>");
|
|
26
|
+
return parts.join("\n\n");
|
|
27
|
+
});
|
|
28
|
+
jest.unstable_mockModule("../../prompts/test-maintenance/drift-analysis-prompt.js", () => ({
|
|
29
|
+
buildDriftAnalysisPrompt: mockBuildDriftAnalysisPrompt,
|
|
30
|
+
}));
|
|
31
|
+
const mockFromStatePath = jest.fn();
|
|
32
|
+
const mockCleanupOldFiles = jest.fn().mockImplementation(() => Promise.resolve());
|
|
33
|
+
jest.unstable_mockModule("../../utils/AnalysisStateManager.js", () => ({
|
|
34
|
+
StateManager: {
|
|
35
|
+
fromStatePath: mockFromStatePath,
|
|
36
|
+
cleanupOldFiles: mockCleanupOldFiles,
|
|
37
|
+
},
|
|
38
|
+
TestSource: { Skyramp: "skyramp", External: "external" },
|
|
39
|
+
}));
|
|
40
|
+
jest.unstable_mockModule("../../utils/utils.js", () => ({
|
|
41
|
+
toolError: (msg) => ({ content: [{ type: "text", text: `Error: ${msg}` }] }),
|
|
42
|
+
}));
|
|
43
|
+
const { registerAnalyzeTestHealthTool } = await import("./analyzeTestHealthTool.js");
|
|
44
|
+
// ---------------------------------------------------------------------------
|
|
45
|
+
// Helpers
|
|
46
|
+
// ---------------------------------------------------------------------------
|
|
47
|
+
function makeFakeServer() {
|
|
48
|
+
let capturedHandler = null;
|
|
49
|
+
let capturedSchema = null;
|
|
50
|
+
const server = {
|
|
51
|
+
registerTool: jest.fn((_name, config, handler) => {
|
|
52
|
+
capturedSchema = config.inputSchema;
|
|
53
|
+
capturedHandler = handler;
|
|
54
|
+
}),
|
|
55
|
+
getHandler: () => capturedHandler,
|
|
56
|
+
getSchema: () => capturedSchema,
|
|
57
|
+
};
|
|
58
|
+
return server;
|
|
59
|
+
}
|
|
60
|
+
function makeStateManager(stateData) {
|
|
61
|
+
return {
|
|
62
|
+
getStatePath: jest.fn().mockReturnValue("/tmp/skyramp-analysis-test.json"),
|
|
63
|
+
readData: jest.fn().mockResolvedValue(stateData),
|
|
64
|
+
readFullState: jest.fn().mockResolvedValue({ metadata: { repositoryPath: "/repo" } }),
|
|
65
|
+
// Multi-repo API added in SKYR-3786 — analyzeTestHealthTool now uses these.
|
|
66
|
+
readRepoData: jest.fn().mockResolvedValue(stateData),
|
|
67
|
+
getRepoRepositoryPath: jest.fn().mockResolvedValue("/repo"),
|
|
68
|
+
listRelatedRepos: jest.fn().mockResolvedValue([]),
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
// ---------------------------------------------------------------------------
|
|
72
|
+
// Tests
|
|
73
|
+
// ---------------------------------------------------------------------------
|
|
74
|
+
describe("analyzeTestHealthTool — blueprintCaptured schema", () => {
|
|
75
|
+
it("registers blueprintCaptured as an optional boolean parameter", () => {
|
|
76
|
+
const server = makeFakeServer();
|
|
77
|
+
registerAnalyzeTestHealthTool(server);
|
|
78
|
+
const schema = server.getSchema();
|
|
79
|
+
expect(schema).toHaveProperty("blueprintCaptured");
|
|
80
|
+
expect(schema.blueprintCaptured.safeParse(undefined).success).toBe(true);
|
|
81
|
+
expect(schema.blueprintCaptured.safeParse(true).success).toBe(true);
|
|
82
|
+
});
|
|
83
|
+
});
|
|
84
|
+
describe("analyzeTestHealthTool — UI drift gate logic", () => {
|
|
85
|
+
let server;
|
|
86
|
+
beforeEach(() => {
|
|
87
|
+
jest.clearAllMocks();
|
|
88
|
+
server = makeFakeServer();
|
|
89
|
+
registerAnalyzeTestHealthTool(server);
|
|
90
|
+
});
|
|
91
|
+
it("includes UI drift when uiContext has changedFrontendFiles and a UI test", async () => {
|
|
92
|
+
const mockStateManager = makeStateManager({
|
|
93
|
+
existingTests: [
|
|
94
|
+
{ testFile: "/repo/tests/cart.spec.ts", source: "skyramp", testType: "ui", framework: "playwright-ui" },
|
|
95
|
+
],
|
|
96
|
+
uiContext: {
|
|
97
|
+
changedFrontendFiles: ["/repo/src/components/Cart.tsx"],
|
|
98
|
+
candidateUiPages: [],
|
|
99
|
+
},
|
|
100
|
+
});
|
|
101
|
+
mockFromStatePath.mockReturnValue(mockStateManager);
|
|
102
|
+
const result = await server.getHandler()({ stateFile: "/tmp/state.json", blueprintCaptured: true });
|
|
103
|
+
expect(result).not.toHaveProperty("isError", true);
|
|
104
|
+
expect(mockBuildDriftAnalysisPrompt.mock.calls[0][2]).toBeDefined();
|
|
105
|
+
const testsArg = mockBuildDriftAnalysisPrompt.mock.calls[0][2].tests;
|
|
106
|
+
expect(testsArg).toContainEqual(expect.objectContaining({ testFile: "/repo/tests/cart.spec.ts" }));
|
|
107
|
+
});
|
|
108
|
+
it("passes blueprintCaptured=true to buildUiDriftAnalysisPrompt", async () => {
|
|
109
|
+
const mockStateManager = makeStateManager({
|
|
110
|
+
existingTests: [{ testFile: "/repo/tests/cart.spec.ts", source: "skyramp", testType: "ui", framework: "playwright-ui" }],
|
|
111
|
+
uiContext: { changedFrontendFiles: ["/repo/src/Cart.tsx"], candidateUiPages: [] },
|
|
112
|
+
});
|
|
113
|
+
mockFromStatePath.mockReturnValue(mockStateManager);
|
|
114
|
+
await server.getHandler()({ stateFile: "/tmp/state.json", blueprintCaptured: true });
|
|
115
|
+
const blueprintArg = mockBuildDriftAnalysisPrompt.mock.calls[0][2].blueprintCaptured;
|
|
116
|
+
expect(blueprintArg).toBe(true);
|
|
117
|
+
});
|
|
118
|
+
it("passes blueprintCaptured=false to buildUiDriftAnalysisPrompt", async () => {
|
|
119
|
+
const mockStateManager = makeStateManager({
|
|
120
|
+
existingTests: [{ testFile: "/repo/tests/cart.spec.ts", source: "skyramp", testType: "ui", framework: "playwright-ui" }],
|
|
121
|
+
uiContext: { changedFrontendFiles: ["/repo/src/Cart.tsx"], candidateUiPages: [] },
|
|
122
|
+
});
|
|
123
|
+
mockFromStatePath.mockReturnValue(mockStateManager);
|
|
124
|
+
await server.getHandler()({ stateFile: "/tmp/state.json", blueprintCaptured: false });
|
|
125
|
+
expect(mockBuildDriftAnalysisPrompt.mock.calls[0][2]).toBeDefined();
|
|
126
|
+
const blueprintArg = mockBuildDriftAnalysisPrompt.mock.calls[0][2].blueprintCaptured;
|
|
127
|
+
expect(blueprintArg).toBe(false);
|
|
128
|
+
});
|
|
129
|
+
it("excludes UI drift when uiContext is absent", async () => {
|
|
130
|
+
const mockStateManager = makeStateManager({ existingTests: [] });
|
|
131
|
+
mockFromStatePath.mockReturnValue(mockStateManager);
|
|
132
|
+
await server.getHandler()({ stateFile: "/tmp/state.json", blueprintCaptured: true });
|
|
133
|
+
expect(mockBuildDriftAnalysisPrompt.mock.calls[0][2]).toBeUndefined();
|
|
134
|
+
});
|
|
135
|
+
it("excludes UI drift when changedFrontendFiles is empty", async () => {
|
|
136
|
+
const mockStateManager = makeStateManager({
|
|
137
|
+
existingTests: [{ testFile: "/repo/tests/cart.spec.ts", source: "skyramp", testType: "ui" }],
|
|
138
|
+
uiContext: { changedFrontendFiles: [], candidateUiPages: [] },
|
|
139
|
+
});
|
|
140
|
+
mockFromStatePath.mockReturnValue(mockStateManager);
|
|
141
|
+
await server.getHandler()({ stateFile: "/tmp/state.json", blueprintCaptured: true });
|
|
142
|
+
expect(mockBuildDriftAnalysisPrompt.mock.calls[0][2]).toBeUndefined();
|
|
143
|
+
});
|
|
144
|
+
it("includes UI drift (empty test list) when frontend changed but no UI tests found — grep pre-scan will discover them", async () => {
|
|
145
|
+
const mockStateManager = makeStateManager({
|
|
146
|
+
existingTests: [
|
|
147
|
+
{ testFile: "/repo/tests/products_contract_test.py", source: "skyramp", testType: "contract", framework: "pytest" },
|
|
148
|
+
{ testFile: "/repo/tests/orders_smoke_test.py", source: "skyramp", testType: "smoke", framework: "pytest" },
|
|
149
|
+
],
|
|
150
|
+
uiContext: { changedFrontendFiles: ["/repo/src/Cart.tsx"], candidateUiPages: [] },
|
|
151
|
+
});
|
|
152
|
+
mockFromStatePath.mockReturnValue(mockStateManager);
|
|
153
|
+
await server.getHandler()({ stateFile: "/tmp/state.json", blueprintCaptured: true });
|
|
154
|
+
// UI drift fires even with 0 UI tests — grep pre-scan discovers component tests dynamically
|
|
155
|
+
const uiParam = mockBuildDriftAnalysisPrompt.mock.calls[0][2];
|
|
156
|
+
expect(uiParam).toBeDefined();
|
|
157
|
+
expect(uiParam.tests).toHaveLength(0);
|
|
158
|
+
expect(uiParam.changedFrontendFiles).toEqual(["/repo/src/Cart.tsx"]);
|
|
159
|
+
});
|
|
160
|
+
it("includes UI drift for RTL tests (framework=rtl, testType=unknown)", async () => {
|
|
161
|
+
const mockStateManager = makeStateManager({
|
|
162
|
+
existingTests: [
|
|
163
|
+
{ testFile: "/repo/src/components/Cart.test.tsx", source: "external", testType: "unknown", framework: "rtl" },
|
|
164
|
+
{ testFile: "/repo/tests/orders_smoke_test.py", source: "skyramp", testType: "smoke", framework: "pytest" },
|
|
165
|
+
],
|
|
166
|
+
repositoryAnalysis: { relevantExternalTestPaths: ["src/components/Cart.test.tsx"] },
|
|
167
|
+
uiContext: { changedFrontendFiles: ["/repo/src/components/Cart.tsx"], candidateUiPages: [] },
|
|
168
|
+
});
|
|
169
|
+
mockFromStatePath.mockReturnValue(mockStateManager);
|
|
170
|
+
mockFromStatePath.mock.results[0]?.value?.readFullState?.mockResolvedValue({
|
|
171
|
+
metadata: { repositoryPath: "/repo" },
|
|
172
|
+
});
|
|
173
|
+
await server.getHandler()({ stateFile: "/tmp/state.json", blueprintCaptured: false });
|
|
174
|
+
expect(mockBuildDriftAnalysisPrompt.mock.calls[0][2]).toBeDefined();
|
|
175
|
+
const testsArg = mockBuildDriftAnalysisPrompt.mock.calls[0][2].tests;
|
|
176
|
+
expect(testsArg).toHaveLength(1);
|
|
177
|
+
expect(testsArg[0]).toMatchObject({ testFile: "/repo/src/components/Cart.test.tsx" });
|
|
178
|
+
});
|
|
179
|
+
it("excludes RTL test from API drift prompt when UI drift is running", async () => {
|
|
180
|
+
const mockStateManager = makeStateManager({
|
|
181
|
+
existingTests: [
|
|
182
|
+
{ testFile: "/repo/src/components/Cart.test.tsx", source: "external", testType: "unknown", framework: "rtl" },
|
|
183
|
+
{ testFile: "/repo/tests/orders_smoke_test.py", source: "skyramp", testType: "smoke", framework: "pytest" },
|
|
184
|
+
],
|
|
185
|
+
repositoryAnalysis: { relevantExternalTestPaths: ["src/components/Cart.test.tsx"] },
|
|
186
|
+
uiContext: { changedFrontendFiles: ["/repo/src/components/Cart.tsx"], candidateUiPages: [] },
|
|
187
|
+
});
|
|
188
|
+
mockFromStatePath.mockReturnValue(mockStateManager);
|
|
189
|
+
await server.getHandler()({ stateFile: "/tmp/state.json", blueprintCaptured: false });
|
|
190
|
+
const apiTestsArg = mockBuildDriftAnalysisPrompt.mock.calls[0][1];
|
|
191
|
+
expect(apiTestsArg).toHaveLength(1);
|
|
192
|
+
expect(apiTestsArg[0]).toMatchObject({ testFile: "/repo/tests/orders_smoke_test.py" });
|
|
193
|
+
});
|
|
194
|
+
it("passes only UI tests to buildUiDriftAnalysisPrompt when mixed test types exist", async () => {
|
|
195
|
+
const mockStateManager = makeStateManager({
|
|
196
|
+
existingTests: [
|
|
197
|
+
{ testFile: "/repo/tests/cart.spec.ts", source: "skyramp", testType: "ui", framework: "playwright-ui" },
|
|
198
|
+
{ testFile: "/repo/tests/products_contract.py", source: "skyramp", testType: "contract", framework: "pytest" },
|
|
199
|
+
{ testFile: "/repo/tests/smoke.py", source: "skyramp", testType: "smoke", framework: "pytest" },
|
|
200
|
+
],
|
|
201
|
+
uiContext: { changedFrontendFiles: ["/repo/src/Cart.tsx"], candidateUiPages: [] },
|
|
202
|
+
});
|
|
203
|
+
mockFromStatePath.mockReturnValue(mockStateManager);
|
|
204
|
+
await server.getHandler()({ stateFile: "/tmp/state.json", blueprintCaptured: true });
|
|
205
|
+
expect(mockBuildDriftAnalysisPrompt.mock.calls[0][2]).toBeDefined();
|
|
206
|
+
const testsArg = mockBuildDriftAnalysisPrompt.mock.calls[0][2].tests;
|
|
207
|
+
expect(testsArg).toHaveLength(1);
|
|
208
|
+
expect(testsArg[0]).toMatchObject({ testFile: "/repo/tests/cart.spec.ts" });
|
|
209
|
+
});
|
|
210
|
+
it("excludes UI tests from API drift prompt when UI drift is also running", async () => {
|
|
211
|
+
const mockStateManager = makeStateManager({
|
|
212
|
+
existingTests: [
|
|
213
|
+
{ testFile: "/repo/tests/cart.spec.ts", source: "skyramp", testType: "ui", framework: "playwright-ui" },
|
|
214
|
+
{ testFile: "/repo/tests/products_contract.py", source: "skyramp", testType: "contract", framework: "pytest" },
|
|
215
|
+
],
|
|
216
|
+
uiContext: { changedFrontendFiles: ["/repo/src/Cart.tsx"], candidateUiPages: [] },
|
|
217
|
+
});
|
|
218
|
+
mockFromStatePath.mockReturnValue(mockStateManager);
|
|
219
|
+
await server.getHandler()({ stateFile: "/tmp/state.json", blueprintCaptured: true });
|
|
220
|
+
const apiTestsArg = mockBuildDriftAnalysisPrompt.mock.calls[0][1];
|
|
221
|
+
expect(apiTestsArg).toHaveLength(1);
|
|
222
|
+
expect(apiTestsArg[0]).toMatchObject({ testFile: "/repo/tests/products_contract.py" });
|
|
223
|
+
});
|
|
224
|
+
it("includes UI tests in API drift prompt when no frontend changes", async () => {
|
|
225
|
+
const mockStateManager = makeStateManager({
|
|
226
|
+
existingTests: [
|
|
227
|
+
{ testFile: "/repo/tests/cart.spec.ts", source: "skyramp", testType: "ui", framework: "playwright-ui" },
|
|
228
|
+
{ testFile: "/repo/tests/products_contract.py", source: "skyramp", testType: "contract", framework: "pytest" },
|
|
229
|
+
],
|
|
230
|
+
});
|
|
231
|
+
mockFromStatePath.mockReturnValue(mockStateManager);
|
|
232
|
+
await server.getHandler()({ stateFile: "/tmp/state.json" });
|
|
233
|
+
// No frontend changes → no UI drift section → UI tests fall through to API drift
|
|
234
|
+
// so they are not silently dropped on API-only PRs.
|
|
235
|
+
const apiTestsArg = mockBuildDriftAnalysisPrompt.mock.calls[0][1];
|
|
236
|
+
expect(apiTestsArg).toHaveLength(2);
|
|
237
|
+
expect(apiTestsArg).toContainEqual(expect.objectContaining({ testFile: "/repo/tests/cart.spec.ts" }));
|
|
238
|
+
expect(apiTestsArg).toContainEqual(expect.objectContaining({ testFile: "/repo/tests/products_contract.py" }));
|
|
239
|
+
});
|
|
240
|
+
});
|
|
241
|
+
describe("analyzeTestHealthTool — prompt concatenation", () => {
|
|
242
|
+
let server;
|
|
243
|
+
beforeEach(() => {
|
|
244
|
+
jest.clearAllMocks();
|
|
245
|
+
server = makeFakeServer();
|
|
246
|
+
registerAnalyzeTestHealthTool(server);
|
|
247
|
+
});
|
|
248
|
+
it("concatenates API and UI drift prompts when both are included", async () => {
|
|
249
|
+
const mockStateManager = makeStateManager({
|
|
250
|
+
existingTests: [
|
|
251
|
+
{ testFile: "/repo/tests/cart.spec.ts", source: "skyramp", testType: "ui", framework: "playwright-ui" },
|
|
252
|
+
{ testFile: "/repo/tests/contract.py", source: "skyramp", testType: "contract", framework: "pytest" },
|
|
253
|
+
],
|
|
254
|
+
uiContext: { changedFrontendFiles: ["/repo/src/Cart.tsx"], candidateUiPages: [] },
|
|
255
|
+
});
|
|
256
|
+
mockFromStatePath.mockReturnValue(mockStateManager);
|
|
257
|
+
const result = await server.getHandler()({ stateFile: "/tmp/state.json", blueprintCaptured: true });
|
|
258
|
+
expect(result.structuredContent?.prompt).toContain("API drift");
|
|
259
|
+
expect(result.structuredContent?.prompt).toContain("UI drift");
|
|
260
|
+
});
|
|
261
|
+
it("returns only API drift prompt when uiContext is absent", async () => {
|
|
262
|
+
const mockStateManager = makeStateManager({ existingTests: [] });
|
|
263
|
+
mockFromStatePath.mockReturnValue(mockStateManager);
|
|
264
|
+
const result = await server.getHandler()({ stateFile: "/tmp/state.json" });
|
|
265
|
+
expect(result.structuredContent?.prompt).toContain("API drift");
|
|
266
|
+
expect(result.structuredContent?.prompt).not.toContain("UI drift");
|
|
267
|
+
});
|
|
268
|
+
});
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
-
import { WorkspaceConfigManager, serviceSchema } from "../../workspace/workspace.js";
|
|
3
|
-
import { createRequire } from "module";
|
|
2
|
+
import { WorkspaceConfigManager, serviceSchema, } from "../../workspace/workspace.js";
|
|
4
3
|
import fs from "fs/promises";
|
|
5
4
|
import yaml from "js-yaml";
|
|
6
5
|
import { logger } from "../../utils/logger.js";
|
|
@@ -8,8 +7,6 @@ import { AnalyticsService } from "../../services/AnalyticsService.js";
|
|
|
8
7
|
import { SKYRAMP_IMAGE_VERSION } from "../../utils/versions.js";
|
|
9
8
|
import { validateAndConsumeScanToken } from "./initScanWorkspaceTool.js";
|
|
10
9
|
import { upsertServicesByRepo } from "./serviceUpsert.js";
|
|
11
|
-
const require = createRequire(import.meta.url);
|
|
12
|
-
const MCP_VERSION = require("../../../package.json").version || "";
|
|
13
10
|
function getExecutorVersion() {
|
|
14
11
|
return SKYRAMP_IMAGE_VERSION;
|
|
15
12
|
}
|
|
@@ -20,19 +17,18 @@ const TOOL_NAME = "skyramp_init_workspace";
|
|
|
20
17
|
// set (incl. cookie/session/token) and the multi-repo `repository` field, so input
|
|
21
18
|
// services are validated against it directly — no local schema extension needed.
|
|
22
19
|
const initializeWorkspaceSchema = z.object({
|
|
23
|
-
workspacePath: z
|
|
24
|
-
.string()
|
|
25
|
-
.describe("Path to workspace directory"),
|
|
20
|
+
workspacePath: z.string().describe("Path to workspace directory"),
|
|
26
21
|
services: z
|
|
27
22
|
.array(serviceSchema)
|
|
28
23
|
.describe("Array of services discovered by skyramp_init_scan. Must contain at least one service."),
|
|
29
24
|
scanToken: z
|
|
30
25
|
.string()
|
|
31
|
-
.
|
|
26
|
+
.optional()
|
|
27
|
+
.describe("Token returned by skyramp_init_scan. Required on a fresh init (no existing .skyramp/workspace.yml) to confirm the repo was scanned before initializing. Omit when editing an existing workspace.yml — pair `force: true` with the full updated `services` array instead."),
|
|
32
28
|
force: z
|
|
33
29
|
.boolean()
|
|
34
30
|
.default(false)
|
|
35
|
-
.describe("Set to true to overwrite an existing workspace
|
|
31
|
+
.describe("Set to true to overwrite an existing workspace.yml. Use for edits (e.g. updating `runtimeDetails.serverStartCommand` after generating SUT files): pass the full updated `services` array — this is an overwrite, not a merge, so any field not present is dropped. Also use when recovering from a validation failure. Required when `scanToken` is omitted. Default is false."),
|
|
36
32
|
merge: z
|
|
37
33
|
.boolean()
|
|
38
34
|
.default(false)
|
|
@@ -54,8 +50,11 @@ async function writeWorkspaceConfig(manager, config) {
|
|
|
54
50
|
}
|
|
55
51
|
export function registerInitializeWorkspaceTool(server) {
|
|
56
52
|
server.registerTool(TOOL_NAME, {
|
|
57
|
-
description: "
|
|
58
|
-
"
|
|
53
|
+
description: "Write or update .skyramp/workspace.yml. Always use this tool for any change to that file — do NOT use Edit/Write directly. " +
|
|
54
|
+
"1. Fresh init: PREREQUISITE: Call skyramp_init_scan first to get a scanToken and discover services. " +
|
|
55
|
+
"Then call this tool to write .skyramp/workspace.yml with the discovered services and scanToken. " +
|
|
56
|
+
"2. Edit existing workspace.yml: omit scanToken, set force: true, and pass the FULL updated services array (overwrite, not merge — fields not in the array are dropped). " +
|
|
57
|
+
"The services schema is strict: any field not in the schema (e.g. inventing `serverStopCommand` on `runtimeDetails`) fails validation and the call is rejected.",
|
|
59
58
|
inputSchema: initializeWorkspaceSchema.shape,
|
|
60
59
|
_meta: {
|
|
61
60
|
keywords: [
|
|
@@ -93,17 +92,33 @@ export function registerInitializeWorkspaceTool(server) {
|
|
|
93
92
|
isError: false,
|
|
94
93
|
};
|
|
95
94
|
}
|
|
96
|
-
//
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
95
|
+
// scanToken is required for fresh init; for edits (force:true on an
|
|
96
|
+
// existing workspace.yml) the caller supplies the full services array
|
|
97
|
+
// directly and we skip the token check.
|
|
98
|
+
const isEdit = alreadyExists && params.force;
|
|
99
|
+
if (!isEdit) {
|
|
100
|
+
if (!params.scanToken) {
|
|
101
|
+
return {
|
|
102
|
+
content: [
|
|
103
|
+
{
|
|
104
|
+
type: "text",
|
|
105
|
+
text: "scanToken is required for a fresh init. Call skyramp_init_scan first to get a scanToken and scanning instructions. To edit an existing workspace.yml, set force: true and omit scanToken.",
|
|
106
|
+
},
|
|
107
|
+
],
|
|
108
|
+
isError: true,
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
if (!validateAndConsumeScanToken(params.scanToken)) {
|
|
112
|
+
return {
|
|
113
|
+
content: [
|
|
114
|
+
{
|
|
115
|
+
type: "text",
|
|
116
|
+
text: "Invalid or expired scanToken. Call skyramp_init_scan first to get a valid scanToken and scanning instructions.",
|
|
117
|
+
},
|
|
118
|
+
],
|
|
119
|
+
isError: true,
|
|
120
|
+
};
|
|
121
|
+
}
|
|
107
122
|
}
|
|
108
123
|
if (!params.services || params.services.length === 0) {
|
|
109
124
|
return {
|
|
@@ -123,14 +138,12 @@ export function registerInitializeWorkspaceTool(server) {
|
|
|
123
138
|
if (params.merge && alreadyExists) {
|
|
124
139
|
config = await manager.read();
|
|
125
140
|
config = await manager.updateMetadata({
|
|
126
|
-
mcpVersion: MCP_VERSION,
|
|
127
141
|
executorVersion: getExecutorVersion(),
|
|
128
142
|
});
|
|
129
143
|
}
|
|
130
144
|
else {
|
|
131
145
|
config = await manager.initialize();
|
|
132
146
|
config = await manager.updateMetadata({
|
|
133
|
-
mcpVersion: MCP_VERSION,
|
|
134
147
|
executorVersion: getExecutorVersion(),
|
|
135
148
|
});
|
|
136
149
|
}
|
|
@@ -36,7 +36,9 @@ describe("initializeWorkspaceTool", () => {
|
|
|
36
36
|
// Build a mock WorkspaceConfigManager
|
|
37
37
|
mockManager = {
|
|
38
38
|
exists: jest.fn().mockResolvedValue(false),
|
|
39
|
-
getConfigPath: jest
|
|
39
|
+
getConfigPath: jest
|
|
40
|
+
.fn()
|
|
41
|
+
.mockReturnValue(path.join(tmpDir, ".skyramp/workspace.yml")),
|
|
40
42
|
initialize: jest.fn().mockResolvedValue({ workspace: {}, services: [] }),
|
|
41
43
|
updateMetadata: jest.fn().mockImplementation(async (data) => ({
|
|
42
44
|
workspace: {},
|
|
@@ -95,7 +97,6 @@ describe("initializeWorkspaceTool", () => {
|
|
|
95
97
|
expect(result.content[0].text).toContain("Workspace initialized");
|
|
96
98
|
expect(mockManager.initialize).toHaveBeenCalled();
|
|
97
99
|
expect(mockManager.updateMetadata).toHaveBeenCalledWith(expect.objectContaining({
|
|
98
|
-
mcpVersion: expect.any(String),
|
|
99
100
|
executorVersion: expect.any(String),
|
|
100
101
|
}));
|
|
101
102
|
});
|
|
@@ -103,7 +104,9 @@ describe("initializeWorkspaceTool", () => {
|
|
|
103
104
|
mockManager.exists.mockResolvedValue(true);
|
|
104
105
|
const result = await handler({
|
|
105
106
|
workspacePath: tmpDir,
|
|
106
|
-
services: [
|
|
107
|
+
services: [
|
|
108
|
+
{ serviceName: "svc", language: "python", testDirectory: "tests" },
|
|
109
|
+
],
|
|
107
110
|
scanToken: "valid-token",
|
|
108
111
|
force: false,
|
|
109
112
|
});
|
|
@@ -114,7 +117,9 @@ describe("initializeWorkspaceTool", () => {
|
|
|
114
117
|
validateAndConsumeScanToken.mockReturnValue(false);
|
|
115
118
|
const result = await handler({
|
|
116
119
|
workspacePath: tmpDir,
|
|
117
|
-
services: [
|
|
120
|
+
services: [
|
|
121
|
+
{ serviceName: "svc", language: "python", testDirectory: "tests" },
|
|
122
|
+
],
|
|
118
123
|
scanToken: "bad-token",
|
|
119
124
|
force: false,
|
|
120
125
|
});
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Frontend component integration check types — SKYR-3855.
|
|
3
|
+
*
|
|
4
|
+
* Produced by `checkFrontendFileIntegration` (src/utils/frontendIntegration.ts)
|
|
5
|
+
* and surfaced to the agent as `uiContext.frontendFileIntegration` in the
|
|
6
|
+
* `skyramp_analyze_changes` output and state file.
|
|
7
|
+
*/
|
|
8
|
+
/** String enum so the JSON wire format (state file, tool output) stays unchanged. */
|
|
9
|
+
export declare enum IntegrationReason {
|
|
10
|
+
/** Framework route/page/entrypoint file — reachable by convention, grep skipped. */
|
|
11
|
+
RouteOrEntrypoint = "route-or-entrypoint",
|
|
12
|
+
/** At least one production file imports/references the component's module path. */
|
|
13
|
+
Imported = "imported",
|
|
14
|
+
/** No production importer found — the component has no DOM presence in the running app. */
|
|
15
|
+
NoImporters = "no-importers",
|
|
16
|
+
/** The check could not run (missing file, scan failure) — reported integrated (fail-open). */
|
|
17
|
+
ScanError = "scan-error"
|
|
18
|
+
}
|
|
19
|
+
export interface FrontendFileIntegration {
|
|
20
|
+
file: string;
|
|
21
|
+
integrated: boolean;
|
|
22
|
+
/** Production files that import/reference the component, relative to repositoryPath. Capped at 10. */
|
|
23
|
+
importers: string[];
|
|
24
|
+
reason: IntegrationReason;
|
|
25
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Frontend component integration check types — SKYR-3855.
|
|
3
|
+
*
|
|
4
|
+
* Produced by `checkFrontendFileIntegration` (src/utils/frontendIntegration.ts)
|
|
5
|
+
* and surfaced to the agent as `uiContext.frontendFileIntegration` in the
|
|
6
|
+
* `skyramp_analyze_changes` output and state file.
|
|
7
|
+
*/
|
|
8
|
+
/** String enum so the JSON wire format (state file, tool output) stays unchanged. */
|
|
9
|
+
export var IntegrationReason;
|
|
10
|
+
(function (IntegrationReason) {
|
|
11
|
+
/** Framework route/page/entrypoint file — reachable by convention, grep skipped. */
|
|
12
|
+
IntegrationReason["RouteOrEntrypoint"] = "route-or-entrypoint";
|
|
13
|
+
/** At least one production file imports/references the component's module path. */
|
|
14
|
+
IntegrationReason["Imported"] = "imported";
|
|
15
|
+
/** No production importer found — the component has no DOM presence in the running app. */
|
|
16
|
+
IntegrationReason["NoImporters"] = "no-importers";
|
|
17
|
+
/** The check could not run (missing file, scan failure) — reported integrated (fail-open). */
|
|
18
|
+
IntegrationReason["ScanError"] = "scan-error";
|
|
19
|
+
})(IntegrationReason || (IntegrationReason = {}));
|
|
@@ -1195,7 +1195,7 @@ export declare const draftedScenarioSchema: z.ZodObject<{
|
|
|
1195
1195
|
description: string;
|
|
1196
1196
|
priority: "high" | "medium" | "low";
|
|
1197
1197
|
scenarioName: string;
|
|
1198
|
-
category: "
|
|
1198
|
+
category: "new_endpoint" | "bug_caught" | "business_rule" | "security_boundary" | "data_integrity" | "breaking_change" | "auth" | "error_handling" | "workflow" | "data_validation" | "crud";
|
|
1199
1199
|
steps: {
|
|
1200
1200
|
path: string;
|
|
1201
1201
|
method: string;
|
|
@@ -1232,7 +1232,7 @@ export declare const draftedScenarioSchema: z.ZodObject<{
|
|
|
1232
1232
|
description: string;
|
|
1233
1233
|
priority: "high" | "medium" | "low";
|
|
1234
1234
|
scenarioName: string;
|
|
1235
|
-
category: "
|
|
1235
|
+
category: "new_endpoint" | "bug_caught" | "business_rule" | "security_boundary" | "data_integrity" | "breaking_change" | "auth" | "error_handling" | "workflow" | "data_validation" | "crud";
|
|
1236
1236
|
steps: {
|
|
1237
1237
|
path: string;
|
|
1238
1238
|
method: string;
|
|
@@ -1644,7 +1644,7 @@ export declare const repositoryAnalysisSchema: z.ZodObject<{
|
|
|
1644
1644
|
description: string;
|
|
1645
1645
|
priority: "high" | "medium" | "low";
|
|
1646
1646
|
scenarioName: string;
|
|
1647
|
-
category: "
|
|
1647
|
+
category: "new_endpoint" | "bug_caught" | "business_rule" | "security_boundary" | "data_integrity" | "breaking_change" | "auth" | "error_handling" | "workflow" | "data_validation" | "crud";
|
|
1648
1648
|
steps: {
|
|
1649
1649
|
path: string;
|
|
1650
1650
|
method: string;
|
|
@@ -1681,7 +1681,7 @@ export declare const repositoryAnalysisSchema: z.ZodObject<{
|
|
|
1681
1681
|
description: string;
|
|
1682
1682
|
priority: "high" | "medium" | "low";
|
|
1683
1683
|
scenarioName: string;
|
|
1684
|
-
category: "
|
|
1684
|
+
category: "new_endpoint" | "bug_caught" | "business_rule" | "security_boundary" | "data_integrity" | "breaking_change" | "auth" | "error_handling" | "workflow" | "data_validation" | "crud";
|
|
1685
1685
|
steps: {
|
|
1686
1686
|
path: string;
|
|
1687
1687
|
method: string;
|
|
@@ -1724,7 +1724,7 @@ export declare const repositoryAnalysisSchema: z.ZodObject<{
|
|
|
1724
1724
|
description: string;
|
|
1725
1725
|
priority: "high" | "medium" | "low";
|
|
1726
1726
|
scenarioName: string;
|
|
1727
|
-
category: "
|
|
1727
|
+
category: "new_endpoint" | "bug_caught" | "business_rule" | "security_boundary" | "data_integrity" | "breaking_change" | "auth" | "error_handling" | "workflow" | "data_validation" | "crud";
|
|
1728
1728
|
steps: {
|
|
1729
1729
|
path: string;
|
|
1730
1730
|
method: string;
|
|
@@ -1767,7 +1767,7 @@ export declare const repositoryAnalysisSchema: z.ZodObject<{
|
|
|
1767
1767
|
description: string;
|
|
1768
1768
|
priority: "high" | "medium" | "low";
|
|
1769
1769
|
scenarioName: string;
|
|
1770
|
-
category: "
|
|
1770
|
+
category: "new_endpoint" | "bug_caught" | "business_rule" | "security_boundary" | "data_integrity" | "breaking_change" | "auth" | "error_handling" | "workflow" | "data_validation" | "crud";
|
|
1771
1771
|
steps: {
|
|
1772
1772
|
path: string;
|
|
1773
1773
|
method: string;
|
|
@@ -2629,7 +2629,7 @@ export declare const repositoryAnalysisSchema: z.ZodObject<{
|
|
|
2629
2629
|
description: string;
|
|
2630
2630
|
priority: "high" | "medium" | "low";
|
|
2631
2631
|
scenarioName: string;
|
|
2632
|
-
category: "
|
|
2632
|
+
category: "new_endpoint" | "bug_caught" | "business_rule" | "security_boundary" | "data_integrity" | "breaking_change" | "auth" | "error_handling" | "workflow" | "data_validation" | "crud";
|
|
2633
2633
|
steps: {
|
|
2634
2634
|
path: string;
|
|
2635
2635
|
method: string;
|
|
@@ -2831,7 +2831,7 @@ export declare const repositoryAnalysisSchema: z.ZodObject<{
|
|
|
2831
2831
|
description: string;
|
|
2832
2832
|
priority: "high" | "medium" | "low";
|
|
2833
2833
|
scenarioName: string;
|
|
2834
|
-
category: "
|
|
2834
|
+
category: "new_endpoint" | "bug_caught" | "business_rule" | "security_boundary" | "data_integrity" | "breaking_change" | "auth" | "error_handling" | "workflow" | "data_validation" | "crud";
|
|
2835
2835
|
steps: {
|
|
2836
2836
|
path: string;
|
|
2837
2837
|
method: string;
|
|
@@ -1,45 +1,10 @@
|
|
|
1
1
|
import { TestExecutionResult } from "./TestExecution.js";
|
|
2
|
+
import { TestType } from "./TestTypes.js";
|
|
2
3
|
export declare enum RecommendationPriority {
|
|
3
4
|
High = "high",
|
|
4
5
|
Medium = "medium",
|
|
5
6
|
Low = "low"
|
|
6
7
|
}
|
|
7
|
-
export declare enum IssueSeverity {
|
|
8
|
-
Low = "low",
|
|
9
|
-
Medium = "medium",
|
|
10
|
-
High = "high",
|
|
11
|
-
Critical = "critical"
|
|
12
|
-
}
|
|
13
|
-
export declare enum DriftChangeType {
|
|
14
|
-
EndpointAdded = "endpoint_added",
|
|
15
|
-
EndpointRemoved = "endpoint_removed",
|
|
16
|
-
EndpointRenamed = "endpoint_renamed",
|
|
17
|
-
EndpointModified = "endpoint_modified",
|
|
18
|
-
AuthenticationChanged = "authentication_changed",
|
|
19
|
-
SchemaChanges = "schema_changes",
|
|
20
|
-
RouteChanged = "route_changed",
|
|
21
|
-
RouteAdded = "route_added",
|
|
22
|
-
RouteRemoved = "route_removed",
|
|
23
|
-
UiComponentAdded = "ui_component_added",
|
|
24
|
-
UiComponentRemoved = "ui_component_removed",
|
|
25
|
-
UiComponentModified = "ui_component_modified",
|
|
26
|
-
UiComponentRestructured = "ui_component_restructured",
|
|
27
|
-
DependencyChanged = "dependency_changed",
|
|
28
|
-
FunctionChanged = "function_changed",
|
|
29
|
-
ClassChanged = "class_changed",
|
|
30
|
-
BreakingChange = "breaking_change",
|
|
31
|
-
CodeChange = "code_change"
|
|
32
|
-
}
|
|
33
|
-
export interface DriftChange {
|
|
34
|
-
type: DriftChangeType;
|
|
35
|
-
file: string;
|
|
36
|
-
description: string;
|
|
37
|
-
severity: IssueSeverity;
|
|
38
|
-
details?: string;
|
|
39
|
-
}
|
|
40
|
-
export interface AffectedFiles {
|
|
41
|
-
files: string[];
|
|
42
|
-
}
|
|
43
8
|
/** Origin of a test file — whether it was generated by Skyramp or is user/third-party maintained. */
|
|
44
9
|
export declare enum TestSource {
|
|
45
10
|
Skyramp = "skyramp",
|
|
@@ -59,12 +24,23 @@ export declare enum EstimatedWork {
|
|
|
59
24
|
Medium = "Medium",
|
|
60
25
|
Large = "Large"
|
|
61
26
|
}
|
|
62
|
-
/**
|
|
63
|
-
|
|
64
|
-
|
|
27
|
+
/** The maintenance-decision fields shared by every stage of the test-maintenance
|
|
28
|
+
* pipeline (skyramp_actions' recommendations, the state's maintenanceVerdicts, and
|
|
29
|
+
* the report's testMaintenance rows) — one canonical shape instead of each stage
|
|
30
|
+
* redeclaring the same five fields under its own name. */
|
|
31
|
+
export interface MaintenanceActionCore {
|
|
32
|
+
testFilePath: string;
|
|
65
33
|
action: DriftAction;
|
|
66
|
-
|
|
34
|
+
testType: TestType;
|
|
35
|
+
endpoint: string;
|
|
67
36
|
rationale: string;
|
|
37
|
+
}
|
|
38
|
+
/** Normalized internal recommendation built from LLM-supplied args.recommendations. */
|
|
39
|
+
export interface DriftRecommendation extends MaintenanceActionCore {
|
|
40
|
+
/** When selectors are abstracted into a page object, pomFile is the POM path to edit.
|
|
41
|
+
* testFilePath remains the spec (for catalog lookup and report); pomFile is what gets patched. */
|
|
42
|
+
pomFile?: string;
|
|
43
|
+
priority: RecommendationPriority;
|
|
68
44
|
estimatedWork: EstimatedWork;
|
|
69
45
|
updateInstructions: string;
|
|
70
46
|
renamedEndpoints: Array<{
|
|
@@ -80,18 +56,11 @@ export interface DriftRecommendation {
|
|
|
80
56
|
*/
|
|
81
57
|
export interface TestAnalysisResult {
|
|
82
58
|
testFile: string;
|
|
83
|
-
testType:
|
|
59
|
+
testType: TestType;
|
|
84
60
|
language: string;
|
|
85
61
|
framework: string;
|
|
86
|
-
apiSchema?: string;
|
|
87
62
|
apiEndpoint?: string;
|
|
88
|
-
|
|
89
|
-
* with existing state files. */
|
|
90
|
-
source?: TestSource;
|
|
91
|
-
drift?: {
|
|
92
|
-
changes: DriftChange[];
|
|
93
|
-
affectedFiles: AffectedFiles;
|
|
94
|
-
};
|
|
63
|
+
source: TestSource;
|
|
95
64
|
executionBefore?: TestExecutionResult;
|
|
96
65
|
executionAfter?: TestExecutionResult;
|
|
97
66
|
}
|