@skyramp/mcp 0.3.1 → 0.3.2-rc.pom-2
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 +2 -1
- package/build/prompts/code-reuse.d.ts +7 -1
- package/build/prompts/code-reuse.js +8 -5
- package/build/prompts/code-reuse.test.d.ts +1 -0
- package/build/prompts/code-reuse.test.js +62 -0
- package/build/prompts/pom-aware-code-reuse.d.ts +6 -1
- package/build/prompts/pom-aware-code-reuse.js +100 -53
- package/build/prompts/pom-aware-code-reuse.test.d.ts +1 -0
- package/build/prompts/pom-aware-code-reuse.test.js +11 -0
- package/build/prompts/test-recommendation/diffExecutionPlan.d.ts +4 -2
- package/build/prompts/test-recommendation/diffExecutionPlan.js +11 -65
- package/build/prompts/test-recommendation/fullRepoCatalog.d.ts +2 -2
- package/build/prompts/test-recommendation/recommendationSections.js +5 -2
- package/build/prompts/test-recommendation/scopeAssessment.js +1 -1
- package/build/prompts/test-recommendation/test-recommendation-prompt.d.ts +26 -1
- package/build/prompts/test-recommendation/test-recommendation-prompt.js +68 -56
- package/build/prompts/test-recommendation/test-recommendation-prompt.test.js +48 -11
- package/build/prompts/testbot/testbot-prompts.d.ts +1 -1
- package/build/prompts/testbot/testbot-prompts.js +67 -21
- package/build/prompts/testbot/testbot-prompts.test.js +44 -0
- package/build/recommendation/budgeters/diversityBalancedBudgeter.d.ts +7 -0
- package/build/recommendation/budgeters/diversityBalancedBudgeter.js +71 -0
- package/build/recommendation/budgeters/diversityBalancedBudgeter.test.d.ts +1 -0
- package/build/recommendation/budgeters/diversityBalancedBudgeter.test.js +75 -0
- package/build/recommendation/budgeters/fixedNBudgeter.d.ts +7 -0
- package/build/recommendation/budgeters/fixedNBudgeter.js +11 -0
- package/build/recommendation/budgeters/fixedNBudgeter.test.d.ts +1 -0
- package/build/recommendation/budgeters/fixedNBudgeter.test.js +66 -0
- package/build/recommendation/budgeters/shared.d.ts +19 -0
- package/build/recommendation/budgeters/shared.js +66 -0
- package/build/recommendation/discriminators.d.ts +31 -0
- package/build/recommendation/discriminators.js +355 -0
- package/build/recommendation/discriminators.test.d.ts +1 -0
- package/build/recommendation/discriminators.test.js +324 -0
- package/build/recommendation/diversity.d.ts +47 -0
- package/build/recommendation/diversity.js +101 -0
- package/build/recommendation/diversity.test.d.ts +1 -0
- package/build/recommendation/diversity.test.js +77 -0
- package/build/recommendation/planRanker.d.ts +50 -0
- package/build/recommendation/planRanker.js +67 -0
- package/build/recommendation/planRanker.test.d.ts +1 -0
- package/build/recommendation/planRanker.test.js +110 -0
- package/build/recommendation/testFixtures.d.ts +25 -0
- package/build/recommendation/testFixtures.js +45 -0
- package/build/resources/testbotResource.js +4 -1
- package/build/services/ScenarioGenerationService.d.ts +5 -0
- package/build/services/ScenarioGenerationService.js +16 -1
- package/build/services/ScenarioGenerationService.test.js +44 -0
- package/build/services/TestExecutionService.d.ts +15 -1
- package/build/services/TestExecutionService.js +210 -55
- package/build/services/TestExecutionService.test.js +397 -0
- package/build/services/TestGenerationService.js +19 -1
- package/build/services/TestGenerationService.test.js +58 -0
- package/build/tool-phases.js +1 -0
- package/build/toolNames.d.ts +19 -0
- package/build/toolNames.js +19 -0
- package/build/tools/code-refactor/codeReuseTool.d.ts +7 -0
- package/build/tools/code-refactor/codeReuseTool.js +130 -4
- package/build/tools/code-refactor/codeReuseTool.test.d.ts +1 -0
- package/build/tools/code-refactor/codeReuseTool.test.js +290 -0
- package/build/tools/executeSkyrampTestTool.js +8 -2
- package/build/tools/generate-tests/generateBatchScenarioRestTool.d.ts +6 -1
- package/build/tools/generate-tests/generateBatchScenarioRestTool.js +110 -17
- package/build/tools/generate-tests/generateBatchScenarioRestTool.test.js +147 -0
- package/build/tools/generate-tests/generateContractRestTool.js +11 -1
- package/build/tools/generate-tests/generateIntegrationRestTool.js +24 -1
- package/build/tools/generate-tests/generateIntegrationRestTool.test.d.ts +1 -0
- package/build/tools/generate-tests/generateIntegrationRestTool.test.js +159 -0
- package/build/tools/generate-tests/planGuard.d.ts +13 -0
- package/build/tools/generate-tests/planGuard.js +78 -0
- package/build/tools/generate-tests/planGuard.test.d.ts +1 -0
- package/build/tools/generate-tests/planGuard.test.js +185 -0
- package/build/tools/generate-tests/scenarioFileIdentity.d.ts +10 -0
- package/build/tools/generate-tests/scenarioFileIdentity.js +46 -0
- package/build/tools/generate-tests/scenarioLint.d.ts +30 -0
- package/build/tools/generate-tests/scenarioLint.js +150 -0
- package/build/tools/generate-tests/scenarioLint.test.d.ts +1 -0
- package/build/tools/generate-tests/scenarioLint.test.js +100 -0
- package/build/tools/submitReportTool.js +78 -0
- package/build/tools/submitReportTool.test.js +255 -0
- package/build/tools/test-management/analyzeChangesTool.js +55 -2
- package/build/tools/test-management/analyzeChangesTool.test.js +12 -0
- package/build/tools/test-management/index.d.ts +1 -0
- package/build/tools/test-management/index.js +1 -0
- package/build/tools/test-management/registerTestPlanTool.d.ts +2 -0
- package/build/tools/test-management/registerTestPlanTool.js +329 -0
- package/build/tools/test-management/registerTestPlanTool.test.d.ts +1 -0
- package/build/tools/test-management/registerTestPlanTool.test.js +296 -0
- package/build/types/Recommendation.d.ts +97 -0
- package/build/types/Recommendation.js +48 -0
- package/build/types/RepositoryAnalysis.d.ts +14 -14
- package/build/types/TestExecution.d.ts +2 -0
- package/build/types/TestRecommendation.d.ts +12 -1
- package/build/types/TestRecommendation.js +26 -11
- package/build/types/TestTypes.js +1 -1
- package/build/utils/AnalysisStateManager.d.ts +47 -0
- package/build/utils/docker.test.js +1 -1
- package/build/utils/planMatchKeys.d.ts +61 -0
- package/build/utils/planMatchKeys.js +125 -0
- package/build/utils/pom-scope/import-expansion.d.ts +5 -0
- package/build/utils/pom-scope/import-expansion.js +32 -0
- package/build/utils/pom-scope/index.d.ts +39 -0
- package/build/utils/pom-scope/index.js +120 -0
- package/build/utils/pom-scope/index.test.d.ts +1 -0
- package/build/utils/pom-scope/index.test.js +239 -0
- package/build/utils/pom-scope/pom-files.d.ts +3 -0
- package/build/utils/pom-scope/pom-files.js +48 -0
- package/build/utils/pom-scope/pom-files.test.d.ts +1 -0
- package/build/utils/pom-scope/pom-files.test.js +29 -0
- package/build/utils/pom-scope/scoring.d.ts +20 -0
- package/build/utils/pom-scope/scoring.js +45 -0
- package/build/utils/pom-scope/scoring.test.d.ts +1 -0
- package/build/utils/pom-scope/scoring.test.js +39 -0
- package/build/utils/pom-scope/selector-extractor.d.ts +7 -0
- package/build/utils/pom-scope/selector-extractor.js +57 -0
- package/build/utils/pom-scope/selector-extractor.test.d.ts +1 -0
- package/build/utils/pom-scope/selector-extractor.test.js +67 -0
- package/build/utils/pom-verify/__fixtures__/af-style/asset-list.page.d.ts +5 -0
- package/build/utils/pom-verify/__fixtures__/af-style/asset-list.page.js +5 -0
- package/build/utils/pom-verify/__fixtures__/af-style/pageobjects/asset-list-page.d.ts +5 -0
- package/build/utils/pom-verify/__fixtures__/af-style/pageobjects/asset-list-page.js +9 -0
- package/build/utils/pom-verify/__fixtures__/af-style/report.iframe.page.d.ts +4 -0
- package/build/utils/pom-verify/__fixtures__/af-style/report.iframe.page.js +4 -0
- package/build/utils/pom-verify/__fixtures__/af-style/workflow-footer.page.d.ts +4 -0
- package/build/utils/pom-verify/__fixtures__/af-style/workflow-footer.page.js +4 -0
- package/build/utils/pom-verify/bindings.d.ts +19 -0
- package/build/utils/pom-verify/bindings.js +161 -0
- package/build/utils/pom-verify/bindings.test.d.ts +1 -0
- package/build/utils/pom-verify/bindings.test.js +164 -0
- package/build/utils/pom-verify/calls.d.ts +16 -0
- package/build/utils/pom-verify/calls.js +42 -0
- package/build/utils/pom-verify/calls.test.d.ts +1 -0
- package/build/utils/pom-verify/calls.test.js +61 -0
- package/build/utils/pom-verify/index.d.ts +4 -0
- package/build/utils/pom-verify/index.js +4 -0
- package/build/utils/pom-verify/resolve.d.ts +7 -0
- package/build/utils/pom-verify/resolve.js +27 -0
- package/build/utils/pom-verify/resolve.test.d.ts +1 -0
- package/build/utils/pom-verify/resolve.test.js +68 -0
- package/build/utils/pom-verify/strip.d.ts +9 -0
- package/build/utils/pom-verify/strip.js +89 -0
- package/build/utils/pom-verify/verify.d.ts +14 -0
- package/build/utils/pom-verify/verify.js +158 -0
- package/build/utils/pom-verify/verify.test.d.ts +1 -0
- package/build/utils/pom-verify/verify.test.js +325 -0
- package/build/utils/reportVerification.d.ts +61 -0
- package/build/utils/reportVerification.js +104 -0
- package/build/utils/reportVerification.test.d.ts +1 -0
- package/build/utils/reportVerification.test.js +185 -0
- package/build/utils/scenarioDrafting.js +5 -5
- package/build/utils/versions.d.ts +3 -3
- package/build/utils/versions.js +1 -1
- package/build/utils/workspaceAuth.d.ts +9 -1
- package/build/utils/workspaceAuth.js +25 -5
- package/build/utils/workspaceAuth.test.js +48 -0
- package/build/workspace/workspace.d.ts +20 -0
- package/build/workspace/workspace.js +4 -0
- package/build/workspace/workspace.test.js +10 -0
- package/node_modules/playwright/lib/mcp/skyramp/traceRecordingBackend.js +77 -8
- package/node_modules/playwright/node_modules/playwright-core/lib/generated/injectedScriptSource.js +1 -1
- package/node_modules/playwright/node_modules/playwright-core/lib/generated/pollingRecorderSource.js +1 -1
- package/node_modules/playwright/node_modules/playwright-core/lib/utils/isomorphic/volatileDate.js +101 -0
- package/node_modules/playwright/node_modules/playwright-core/lib/utils.js +2 -0
- package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/assets/{codeMirrorModule-aszq5EdG.js → codeMirrorModule-Bzd72-bG.js} +1 -1
- package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/assets/{defaultSettingsView-BxS7Jm4s.js → defaultSettingsView-DzxTioTK.js} +101 -101
- package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/{index.D4JTTy4R.js → index.BGc30U3S.js} +1 -1
- package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/index.html +2 -2
- package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/{uiMode.DaRMQKOI.js → uiMode.IaDrb29A.js} +1 -1
- package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/uiMode.html +2 -2
- package/node_modules/playwright/node_modules/playwright-core/package.json +1 -1
- package/node_modules/playwright/node_modules/playwright-core/src/generated/injectedScriptSource.ts +1 -1
- package/node_modules/playwright/node_modules/playwright-core/src/generated/pollingRecorderSource.ts +1 -1
- package/node_modules/playwright/node_modules/playwright-core/src/utils/isomorphic/volatileDate.ts +131 -0
- package/node_modules/playwright/node_modules/playwright-core/src/utils.ts +1 -0
- package/node_modules/playwright/package.json +1 -1
- package/package.json +3 -3
- package/node_modules/playwright/node_modules/playwright-core/.DS_Store +0 -0
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
// @ts-nocheck - Jest ESM type inference issues
|
|
2
2
|
import { jest } from "@jest/globals";
|
|
3
|
+
import * as os from "os";
|
|
4
|
+
import * as path from "path";
|
|
3
5
|
import { buildContainerEnv, rewriteLocalhostForDocker } from "./containerEnv.js";
|
|
4
6
|
// Mock dockerode before importing TestExecutionService
|
|
5
7
|
const mockRun = jest.fn();
|
|
@@ -54,6 +56,32 @@ jest.unstable_mockModule("../utils/logger.js", () => ({
|
|
|
54
56
|
warning: jest.fn(),
|
|
55
57
|
},
|
|
56
58
|
}));
|
|
59
|
+
// Real temp directory trees for tests that need actual directory traversal
|
|
60
|
+
// (findNestedNodeModules / findExcludedPaths walk the real fs, delegated to
|
|
61
|
+
// via mockReaddirSync — see each describe block's beforeEach). Every root
|
|
62
|
+
// created here is tracked and removed after its test so these don't leak
|
|
63
|
+
// into the OS temp dir across runs.
|
|
64
|
+
const makeTreeRoots = [];
|
|
65
|
+
function makeTree(files) {
|
|
66
|
+
const root = actualFs.mkdtempSync(path.join(os.tmpdir(), "nnm-"));
|
|
67
|
+
makeTreeRoots.push(root);
|
|
68
|
+
for (const [rel, content] of Object.entries(files)) {
|
|
69
|
+
const abs = path.join(root, rel);
|
|
70
|
+
if (content === null)
|
|
71
|
+
actualFs.mkdirSync(abs, { recursive: true });
|
|
72
|
+
else {
|
|
73
|
+
actualFs.mkdirSync(path.dirname(abs), { recursive: true });
|
|
74
|
+
actualFs.writeFileSync(abs, content);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
return root;
|
|
78
|
+
}
|
|
79
|
+
afterEach(() => {
|
|
80
|
+
while (makeTreeRoots.length > 0) {
|
|
81
|
+
const root = makeTreeRoots.pop();
|
|
82
|
+
actualFs.rmSync(root, { recursive: true, force: true });
|
|
83
|
+
}
|
|
84
|
+
});
|
|
57
85
|
describe("rewriteLocalhostForDocker", () => {
|
|
58
86
|
it("rewrites http://localhost:port", () => {
|
|
59
87
|
expect(rewriteLocalhostForDocker("http://localhost:8000")).toBe("http://host.docker.internal:8000");
|
|
@@ -633,3 +661,372 @@ describe("TestExecutionService.executeTest - Video capture for browser tests", (
|
|
|
633
661
|
expect(result.videoPath).toBeUndefined();
|
|
634
662
|
});
|
|
635
663
|
});
|
|
664
|
+
// SKYR-3819: tests delivered to a cross-repo test repository (testsRepoDir) live
|
|
665
|
+
// outside workspacePath. The executor must mount the test repo under a reserved
|
|
666
|
+
// /home/user subdir — inside the generated Playwright config's testDir — and
|
|
667
|
+
// anchor the container test path there, or the runner reports "No tests found" /
|
|
668
|
+
// "file or directory not found".
|
|
669
|
+
describe("TestExecutionService.executeTest - cross-repo test repo mounts", () => {
|
|
670
|
+
let TestExecutionService;
|
|
671
|
+
let TEST_REPO_CONTAINER_SUBDIR;
|
|
672
|
+
let EXECUTOR_DOCKER_IMAGE;
|
|
673
|
+
beforeAll(async () => {
|
|
674
|
+
const mod = await import("./TestExecutionService.js");
|
|
675
|
+
TestExecutionService = mod.TestExecutionService;
|
|
676
|
+
TEST_REPO_CONTAINER_SUBDIR = mod.TEST_REPO_CONTAINER_SUBDIR;
|
|
677
|
+
EXECUTOR_DOCKER_IMAGE = mod.EXECUTOR_DOCKER_IMAGE;
|
|
678
|
+
});
|
|
679
|
+
const dirent = (name, dir = false) => ({
|
|
680
|
+
name,
|
|
681
|
+
isFile: () => !dir,
|
|
682
|
+
isDirectory: () => dir,
|
|
683
|
+
});
|
|
684
|
+
beforeEach(() => {
|
|
685
|
+
jest.clearAllMocks();
|
|
686
|
+
mockListImages.mockResolvedValue([{ RepoTags: [EXECUTOR_DOCKER_IMAGE] }]);
|
|
687
|
+
const mockContainer = { remove: jest.fn().mockResolvedValue(undefined) };
|
|
688
|
+
mockRun.mockResolvedValue([{ StatusCode: 0 }, mockContainer]);
|
|
689
|
+
// Path-aware directory layout:
|
|
690
|
+
// /workspace → SUT repo (src/)
|
|
691
|
+
// /test-repo → cross-repo test repo (tests/, node_modules/, package.json)
|
|
692
|
+
mockReaddirSync.mockImplementation((dir, options) => {
|
|
693
|
+
const listing = {
|
|
694
|
+
"/workspace": [dirent("src", true)],
|
|
695
|
+
"/workspace/src": [],
|
|
696
|
+
"/test-repo": [dirent("tests", true), dirent("node_modules", true), dirent("package.json")],
|
|
697
|
+
"/test-repo/tests": [dirent("orders_test.py")],
|
|
698
|
+
};
|
|
699
|
+
const entries = listing[dir] ?? [];
|
|
700
|
+
return options?.withFileTypes ? entries : entries.map((e) => e.name);
|
|
701
|
+
});
|
|
702
|
+
});
|
|
703
|
+
it("mounts test repo entries under the reserved /home/user subdir with host mirrors", async () => {
|
|
704
|
+
const service = new TestExecutionService();
|
|
705
|
+
await service.executeTest({
|
|
706
|
+
testFile: "/test-repo/tests/orders_test.py",
|
|
707
|
+
workspacePath: "/workspace",
|
|
708
|
+
testRepoPath: "/test-repo",
|
|
709
|
+
language: "python",
|
|
710
|
+
testType: "integration",
|
|
711
|
+
});
|
|
712
|
+
expect(mockRun).toHaveBeenCalledTimes(1);
|
|
713
|
+
const mounts = mockRun.mock.calls[0][3].HostConfig.Mounts;
|
|
714
|
+
expect(mounts).toContainEqual(expect.objectContaining({
|
|
715
|
+
Source: "/test-repo/tests",
|
|
716
|
+
Target: `/home/user/${TEST_REPO_CONTAINER_SUBDIR}/tests`,
|
|
717
|
+
}));
|
|
718
|
+
// Host-absolute mirror so absolute references embedded in tests resolve
|
|
719
|
+
expect(mounts).toContainEqual(expect.objectContaining({ Source: "/test-repo/tests", Target: "/test-repo/tests" }));
|
|
720
|
+
});
|
|
721
|
+
it("anchors the container test path inside the reserved subdir", async () => {
|
|
722
|
+
const service = new TestExecutionService();
|
|
723
|
+
await service.executeTest({
|
|
724
|
+
testFile: "/test-repo/tests/orders_test.py",
|
|
725
|
+
workspacePath: "/workspace",
|
|
726
|
+
testRepoPath: "/test-repo",
|
|
727
|
+
language: "python",
|
|
728
|
+
testType: "integration",
|
|
729
|
+
});
|
|
730
|
+
const command = mockRun.mock.calls[0][1];
|
|
731
|
+
expect(command).toContain(`/home/user/${TEST_REPO_CONTAINER_SUBDIR}/tests/orders_test.py`);
|
|
732
|
+
});
|
|
733
|
+
it("applies mount exclusions and null shadows to the test repo", async () => {
|
|
734
|
+
const service = new TestExecutionService();
|
|
735
|
+
await service.executeTest({
|
|
736
|
+
testFile: "/test-repo/tests/orders_test.py",
|
|
737
|
+
workspacePath: "/workspace",
|
|
738
|
+
testRepoPath: "/test-repo",
|
|
739
|
+
language: "python",
|
|
740
|
+
testType: "integration",
|
|
741
|
+
});
|
|
742
|
+
const mounts = mockRun.mock.calls[0][3].HostConfig.Mounts;
|
|
743
|
+
// node_modules is never mounted
|
|
744
|
+
expect(mounts.filter((m) => m.Source === "/test-repo/node_modules")).toHaveLength(0);
|
|
745
|
+
// package.json is shadowed with the empty-JSON file at both targets
|
|
746
|
+
const pkgShadows = mounts.filter((m) => m.Target === `/home/user/${TEST_REPO_CONTAINER_SUBDIR}/package.json` ||
|
|
747
|
+
m.Target === "/test-repo/package.json");
|
|
748
|
+
expect(pkgShadows).toHaveLength(2);
|
|
749
|
+
for (const m of pkgShadows) {
|
|
750
|
+
expect(m.Source).toMatch(/skyramp-empty\.json$/);
|
|
751
|
+
}
|
|
752
|
+
});
|
|
753
|
+
it("throws an actionable error when testFile is outside the workspace and no testRepoPath is known", async () => {
|
|
754
|
+
const service = new TestExecutionService();
|
|
755
|
+
await expect(service.executeTest({
|
|
756
|
+
testFile: "/test-repo/tests/orders_test.py",
|
|
757
|
+
workspacePath: "/workspace",
|
|
758
|
+
language: "python",
|
|
759
|
+
testType: "integration",
|
|
760
|
+
})).rejects.toThrow(/outside the workspace/);
|
|
761
|
+
});
|
|
762
|
+
it("does not add test repo mounts when testFile is inside the workspace", async () => {
|
|
763
|
+
mockReaddirSync.mockImplementation((dir, options) => {
|
|
764
|
+
const listing = {
|
|
765
|
+
"/workspace": [dirent("tests", true)],
|
|
766
|
+
"/workspace/tests": [dirent("orders_test.py")],
|
|
767
|
+
};
|
|
768
|
+
const entries = listing[dir] ?? [];
|
|
769
|
+
return options?.withFileTypes ? entries : entries.map((e) => e.name);
|
|
770
|
+
});
|
|
771
|
+
const service = new TestExecutionService();
|
|
772
|
+
await service.executeTest({
|
|
773
|
+
testFile: "/workspace/tests/orders_test.py",
|
|
774
|
+
workspacePath: "/workspace",
|
|
775
|
+
testRepoPath: "/test-repo",
|
|
776
|
+
language: "python",
|
|
777
|
+
testType: "integration",
|
|
778
|
+
});
|
|
779
|
+
const command = mockRun.mock.calls[0][1];
|
|
780
|
+
expect(command).toContain("/home/user/tests/orders_test.py");
|
|
781
|
+
const mounts = mockRun.mock.calls[0][3].HostConfig.Mounts;
|
|
782
|
+
expect(mounts.filter((m) => m.Target.includes(TEST_REPO_CONTAINER_SUBDIR))).toHaveLength(0);
|
|
783
|
+
});
|
|
784
|
+
});
|
|
785
|
+
// Copilot review on PR #623: guard hazardous or stale cross-repo roots.
|
|
786
|
+
describe("TestExecutionService.executeTest - cross-repo root guards", () => {
|
|
787
|
+
let TestExecutionService;
|
|
788
|
+
let EXECUTOR_DOCKER_IMAGE;
|
|
789
|
+
beforeAll(async () => {
|
|
790
|
+
const mod = await import("./TestExecutionService.js");
|
|
791
|
+
TestExecutionService = mod.TestExecutionService;
|
|
792
|
+
EXECUTOR_DOCKER_IMAGE = mod.EXECUTOR_DOCKER_IMAGE;
|
|
793
|
+
});
|
|
794
|
+
beforeEach(() => {
|
|
795
|
+
jest.clearAllMocks();
|
|
796
|
+
mockListImages.mockResolvedValue([{ RepoTags: [EXECUTOR_DOCKER_IMAGE] }]);
|
|
797
|
+
const mockContainer = { remove: jest.fn().mockResolvedValue(undefined) };
|
|
798
|
+
mockRun.mockResolvedValue([{ StatusCode: 0 }, mockContainer]);
|
|
799
|
+
});
|
|
800
|
+
afterEach(() => {
|
|
801
|
+
mockAccessSync.mockReset();
|
|
802
|
+
});
|
|
803
|
+
it("rejects a filesystem-root testRepoPath instead of mounting the host root", async () => {
|
|
804
|
+
const service = new TestExecutionService();
|
|
805
|
+
await expect(service.executeTest({
|
|
806
|
+
testFile: "/outside/tests/orders_test.py",
|
|
807
|
+
workspacePath: "/workspace",
|
|
808
|
+
testRepoPath: "/",
|
|
809
|
+
language: "python",
|
|
810
|
+
testType: "integration",
|
|
811
|
+
})).rejects.toThrow(/filesystem root/);
|
|
812
|
+
expect(mockRun).not.toHaveBeenCalled();
|
|
813
|
+
});
|
|
814
|
+
it("throws an actionable error when the cross-repo test repository is missing or unreadable", async () => {
|
|
815
|
+
mockAccessSync.mockImplementation((p) => {
|
|
816
|
+
if (String(p).startsWith("/test-repo")) {
|
|
817
|
+
throw new Error("EACCES");
|
|
818
|
+
}
|
|
819
|
+
});
|
|
820
|
+
const service = new TestExecutionService();
|
|
821
|
+
await expect(service.executeTest({
|
|
822
|
+
testFile: "/test-repo/tests/orders_test.py",
|
|
823
|
+
workspacePath: "/workspace",
|
|
824
|
+
testRepoPath: "/test-repo",
|
|
825
|
+
language: "python",
|
|
826
|
+
testType: "integration",
|
|
827
|
+
})).rejects.toThrow(/test repository path does not exist or is not readable/);
|
|
828
|
+
expect(mockRun).not.toHaveBeenCalled();
|
|
829
|
+
});
|
|
830
|
+
});
|
|
831
|
+
describe("findNestedNodeModules", () => {
|
|
832
|
+
let findNestedNodeModules;
|
|
833
|
+
beforeAll(async () => {
|
|
834
|
+
const mod = await import("./TestExecutionService.js");
|
|
835
|
+
findNestedNodeModules = mod.findNestedNodeModules;
|
|
836
|
+
});
|
|
837
|
+
beforeEach(() => {
|
|
838
|
+
// These tests need real directory traversal — delegate the readdirSync
|
|
839
|
+
// mock (used by TestExecutionService.ts's `fs` binding) back to the real
|
|
840
|
+
// fs so it reflects the on-disk tree built by makeTree() below.
|
|
841
|
+
mockReaddirSync.mockImplementation((...args) => actualFs.readdirSync(...args));
|
|
842
|
+
});
|
|
843
|
+
const relSorted = (root, abs) => abs.map((p) => path.relative(root, p).replace(/\\/g, "/")).sort();
|
|
844
|
+
it("returns nested node_modules but not the top-level one", () => {
|
|
845
|
+
const root = makeTree({
|
|
846
|
+
"node_modules/playwright/index.js": "x",
|
|
847
|
+
"tests/ui-testing/node_modules/@playwright/test/index.js": "x",
|
|
848
|
+
"tests/ui-testing/pages/dashboard-list.js": "x",
|
|
849
|
+
"packages/web/node_modules/react/index.js": "x",
|
|
850
|
+
});
|
|
851
|
+
expect(relSorted(root, findNestedNodeModules(root))).toEqual([
|
|
852
|
+
"packages/web/node_modules",
|
|
853
|
+
"tests/ui-testing/node_modules",
|
|
854
|
+
]);
|
|
855
|
+
});
|
|
856
|
+
it("never descends into a node_modules (no nested-inside-node_modules hits)", () => {
|
|
857
|
+
const root = makeTree({
|
|
858
|
+
"tests/node_modules/foo/node_modules/bar/index.js": "x",
|
|
859
|
+
});
|
|
860
|
+
expect(relSorted(root, findNestedNodeModules(root))).toEqual([
|
|
861
|
+
"tests/node_modules",
|
|
862
|
+
]);
|
|
863
|
+
});
|
|
864
|
+
it("returns empty when there are no nested node_modules", () => {
|
|
865
|
+
const root = makeTree({ "node_modules/a/index.js": "x", "src/app.js": "x" });
|
|
866
|
+
expect(findNestedNodeModules(root)).toEqual([]);
|
|
867
|
+
});
|
|
868
|
+
});
|
|
869
|
+
describe("TestExecutionService.executeTest - nested node_modules shadow + NODE_PATH wrap", () => {
|
|
870
|
+
let TestExecutionService;
|
|
871
|
+
let EXECUTOR_DOCKER_IMAGE;
|
|
872
|
+
beforeAll(async () => {
|
|
873
|
+
const mod = await import("./TestExecutionService.js");
|
|
874
|
+
TestExecutionService = mod.TestExecutionService;
|
|
875
|
+
EXECUTOR_DOCKER_IMAGE = mod.EXECUTOR_DOCKER_IMAGE;
|
|
876
|
+
});
|
|
877
|
+
beforeEach(() => {
|
|
878
|
+
jest.clearAllMocks();
|
|
879
|
+
mockListImages.mockResolvedValue([{ RepoTags: [EXECUTOR_DOCKER_IMAGE] }]);
|
|
880
|
+
const mockContainer = { remove: jest.fn().mockResolvedValue(undefined) };
|
|
881
|
+
mockRun.mockResolvedValue([{ StatusCode: 0 }, mockContainer]);
|
|
882
|
+
// Real directory traversal so findNestedNodeModules / the workspace-mount
|
|
883
|
+
// scan see the on-disk tree built by makeTree() below (same delegation as
|
|
884
|
+
// the findNestedNodeModules describe block above).
|
|
885
|
+
mockReaddirSync.mockImplementation((...args) => actualFs.readdirSync(...args));
|
|
886
|
+
// Real existsSync too, so the production `if (!fs.existsSync(pkgPath))
|
|
887
|
+
// continue` package-existence gate is genuinely exercised against the
|
|
888
|
+
// on-disk tree instead of the file-level always-true default.
|
|
889
|
+
mockExistsSync.mockImplementation((...args) => actualFs.existsSync(...args));
|
|
890
|
+
});
|
|
891
|
+
afterEach(() => {
|
|
892
|
+
// Restore the file-level always-true default for describes that follow.
|
|
893
|
+
mockExistsSync.mockReturnValue(true);
|
|
894
|
+
});
|
|
895
|
+
it("shadows a leaked nested @playwright/test and wraps the run command with NODE_PATH", async () => {
|
|
896
|
+
const root = makeTree({
|
|
897
|
+
"tests/node_modules/@playwright/test/index.js": "module.exports = {};",
|
|
898
|
+
"tests/sample.spec.ts": "test('noop', () => {});",
|
|
899
|
+
});
|
|
900
|
+
const service = new TestExecutionService();
|
|
901
|
+
await service.executeTest({
|
|
902
|
+
testFile: path.join(root, "tests/sample.spec.ts"),
|
|
903
|
+
workspacePath: root,
|
|
904
|
+
language: "typescript",
|
|
905
|
+
testType: "smoke",
|
|
906
|
+
});
|
|
907
|
+
expect(mockRun).toHaveBeenCalledTimes(1);
|
|
908
|
+
const dockerOptions = mockRun.mock.calls[0][3];
|
|
909
|
+
const mounts = dockerOptions.HostConfig.Mounts;
|
|
910
|
+
const shadowMount = mounts.find((m) => m.ReadOnly && m.Target.endsWith("tests/node_modules/@playwright/test"));
|
|
911
|
+
expect(shadowMount).toBeDefined();
|
|
912
|
+
// The fixture only creates a nested @playwright/test — no
|
|
913
|
+
// @skyramp/skyramp package exists on disk, so the existence gate
|
|
914
|
+
// (`if (!fs.existsSync(pkgPath)) continue`) must skip it. Asserting its
|
|
915
|
+
// absence here only proves the gate is live because existsSync is now
|
|
916
|
+
// delegated to the real fs above (it would false-pass against the
|
|
917
|
+
// always-true mock default).
|
|
918
|
+
const nonexistentPkgShadow = mounts.find((m) => m.Target.endsWith("tests/node_modules/@skyramp/skyramp"));
|
|
919
|
+
expect(nonexistentPkgShadow).toBeUndefined();
|
|
920
|
+
const command = mockRun.mock.calls[0][1];
|
|
921
|
+
expect(command.slice(0, 2)).toEqual(["bash", "-c"]);
|
|
922
|
+
expect(command[2]).toContain("NODE_PATH");
|
|
923
|
+
expect(command[2]).toContain("/root/runner.sh");
|
|
924
|
+
});
|
|
925
|
+
});
|
|
926
|
+
describe("TestExecutionService.executeTest - cross-repo context nested node_modules shadow", () => {
|
|
927
|
+
let TestExecutionService;
|
|
928
|
+
let TEST_REPO_CONTAINER_SUBDIR;
|
|
929
|
+
let EXECUTOR_DOCKER_IMAGE;
|
|
930
|
+
beforeAll(async () => {
|
|
931
|
+
const mod = await import("./TestExecutionService.js");
|
|
932
|
+
TestExecutionService = mod.TestExecutionService;
|
|
933
|
+
TEST_REPO_CONTAINER_SUBDIR = mod.TEST_REPO_CONTAINER_SUBDIR;
|
|
934
|
+
EXECUTOR_DOCKER_IMAGE = mod.EXECUTOR_DOCKER_IMAGE;
|
|
935
|
+
});
|
|
936
|
+
beforeEach(() => {
|
|
937
|
+
jest.clearAllMocks();
|
|
938
|
+
mockListImages.mockResolvedValue([{ RepoTags: [EXECUTOR_DOCKER_IMAGE] }]);
|
|
939
|
+
const mockContainer = { remove: jest.fn().mockResolvedValue(undefined) };
|
|
940
|
+
mockRun.mockResolvedValue([{ StatusCode: 0 }, mockContainer]);
|
|
941
|
+
// Real directory traversal so findNestedNodeModules sees the on-disk tree
|
|
942
|
+
// built by makeTree() below (same delegation as the sibling
|
|
943
|
+
// workspace-context describe block above).
|
|
944
|
+
mockReaddirSync.mockImplementation((...args) => actualFs.readdirSync(...args));
|
|
945
|
+
// Real existsSync too, so the production `if (!fs.existsSync(pkgPath))
|
|
946
|
+
// continue` package-existence gate is genuinely exercised against the
|
|
947
|
+
// on-disk tree instead of the file-level always-true default.
|
|
948
|
+
mockExistsSync.mockImplementation((...args) => actualFs.existsSync(...args));
|
|
949
|
+
});
|
|
950
|
+
afterEach(() => {
|
|
951
|
+
// Restore the file-level always-true default for describes that follow.
|
|
952
|
+
mockExistsSync.mockReturnValue(true);
|
|
953
|
+
});
|
|
954
|
+
it("shadows a leaked nested @playwright/test in the cross-repo test root, mirroring at the host path unconditionally", async () => {
|
|
955
|
+
const workspaceRoot = makeTree({});
|
|
956
|
+
const testRepoRoot = makeTree({
|
|
957
|
+
"e2e/node_modules/@playwright/test/index.js": "module.exports = {};",
|
|
958
|
+
"e2e/sample.spec.ts": "test('noop', () => {});",
|
|
959
|
+
});
|
|
960
|
+
const service = new TestExecutionService();
|
|
961
|
+
await service.executeTest({
|
|
962
|
+
testFile: path.join(testRepoRoot, "e2e/sample.spec.ts"),
|
|
963
|
+
workspacePath: workspaceRoot,
|
|
964
|
+
testRepoPath: testRepoRoot,
|
|
965
|
+
language: "typescript",
|
|
966
|
+
testType: "smoke",
|
|
967
|
+
});
|
|
968
|
+
expect(mockRun).toHaveBeenCalledTimes(1);
|
|
969
|
+
const mounts = mockRun.mock.calls[0][3].HostConfig.Mounts;
|
|
970
|
+
// Container-side shadow lives under the reserved test-repo subdir.
|
|
971
|
+
const containerShadow = mounts.find((m) => m.ReadOnly &&
|
|
972
|
+
m.Target.includes(TEST_REPO_CONTAINER_SUBDIR) &&
|
|
973
|
+
m.Target.endsWith("e2e/node_modules/@playwright/test"));
|
|
974
|
+
expect(containerShadow).toBeDefined();
|
|
975
|
+
// Cross-repo mounts always mirror at the host-absolute path too (unlike
|
|
976
|
+
// the workspace context, where mirroring is conditional on
|
|
977
|
+
// workspacePath !== containerMountPath) — assert that unconditional
|
|
978
|
+
// mirror explicitly, since it's what distinguishes this context.
|
|
979
|
+
const hostPkgPath = path.join(testRepoRoot, "e2e/node_modules/@playwright/test");
|
|
980
|
+
const hostMirrorShadow = mounts.find((m) => m.ReadOnly && m.Target === hostPkgPath);
|
|
981
|
+
expect(hostMirrorShadow).toBeDefined();
|
|
982
|
+
expect(hostMirrorShadow.Source).toBe(containerShadow.Source);
|
|
983
|
+
});
|
|
984
|
+
});
|
|
985
|
+
describe("TestExecutionService.executeTest - root-level Playwright config shadow", () => {
|
|
986
|
+
let TestExecutionService;
|
|
987
|
+
let EXECUTOR_DOCKER_IMAGE;
|
|
988
|
+
beforeAll(async () => {
|
|
989
|
+
const mod = await import("./TestExecutionService.js");
|
|
990
|
+
TestExecutionService = mod.TestExecutionService;
|
|
991
|
+
EXECUTOR_DOCKER_IMAGE = mod.EXECUTOR_DOCKER_IMAGE;
|
|
992
|
+
});
|
|
993
|
+
beforeEach(() => {
|
|
994
|
+
jest.clearAllMocks();
|
|
995
|
+
mockListImages.mockResolvedValue([{ RepoTags: [EXECUTOR_DOCKER_IMAGE] }]);
|
|
996
|
+
const mockContainer = { remove: jest.fn().mockResolvedValue(undefined) };
|
|
997
|
+
mockRun.mockResolvedValue([{ StatusCode: 0 }, mockContainer]);
|
|
998
|
+
// Real directory traversal so the entry-mount filter and the
|
|
999
|
+
// PLAYWRIGHT_CONFIG_FILES shadow scan see the on-disk tree built by
|
|
1000
|
+
// makeTree() below (same delegation as the sibling shadow describes above).
|
|
1001
|
+
mockReaddirSync.mockImplementation((...args) => actualFs.readdirSync(...args));
|
|
1002
|
+
mockExistsSync.mockImplementation((...args) => actualFs.existsSync(...args));
|
|
1003
|
+
});
|
|
1004
|
+
afterEach(() => {
|
|
1005
|
+
// Restore the file-level always-true default for describes that follow.
|
|
1006
|
+
mockExistsSync.mockReturnValue(true);
|
|
1007
|
+
});
|
|
1008
|
+
// Regression test: a root-level playwright.config.js used to win the
|
|
1009
|
+
// pushMount target-dedup race over its minimal-config shadow, because the
|
|
1010
|
+
// entry-mount filter excluded EXCLUDED_MOUNT_ITEMS/MOUNT_NULL_ITEMS but not
|
|
1011
|
+
// PLAYWRIGHT_CONFIG_FILES — so the repo's own root config (which may import
|
|
1012
|
+
// dotenv, or simply isn't the video-enabled config the executor expects)
|
|
1013
|
+
// leaked into the container instead of being shadowed.
|
|
1014
|
+
it("shadows a root-level playwright.config.js with the minimal/generated config instead of the workspace's own file", async () => {
|
|
1015
|
+
const root = makeTree({
|
|
1016
|
+
"playwright.config.js": "module.exports = { testDir: './tests' };",
|
|
1017
|
+
"tests/sample.spec.ts": "test('noop', () => {});",
|
|
1018
|
+
});
|
|
1019
|
+
const service = new TestExecutionService();
|
|
1020
|
+
await service.executeTest({
|
|
1021
|
+
testFile: path.join(root, "tests/sample.spec.ts"),
|
|
1022
|
+
workspacePath: root,
|
|
1023
|
+
language: "typescript",
|
|
1024
|
+
testType: "smoke",
|
|
1025
|
+
});
|
|
1026
|
+
expect(mockRun).toHaveBeenCalledTimes(1);
|
|
1027
|
+
const mounts = mockRun.mock.calls[0][3].HostConfig.Mounts;
|
|
1028
|
+
const configMount = mounts.find((m) => m.Target === path.join("/home/user", "playwright.config.js"));
|
|
1029
|
+
expect(configMount).toBeDefined();
|
|
1030
|
+
expect(configMount.Source).not.toBe(path.join(root, "playwright.config.js"));
|
|
1031
|
+
});
|
|
1032
|
+
});
|
|
@@ -2,7 +2,7 @@ import path from "path";
|
|
|
2
2
|
import fs from "fs";
|
|
3
3
|
import { SkyrampClient } from "@skyramp/skyramp";
|
|
4
4
|
import { analyzeOpenAPIWithGivenEndpoint } from "../utils/analyze-openapi.js";
|
|
5
|
-
import { isAuthorizationHeaderName, KNOWN_AUTH_HEADERS, getWorkspaceAuthConfig, getDefaultAuthHeader, getAuthScheme, WorkspaceAuthType, } from "../utils/workspaceAuth.js";
|
|
5
|
+
import { isAuthorizationHeaderName, KNOWN_AUTH_HEADERS, getWorkspaceAuthConfig, getDefaultAuthHeader, getAuthScheme, WorkspaceAuthType, getWorkspaceSkipTLSVerify, } from "../utils/workspaceAuth.js";
|
|
6
6
|
import { getPathParameterValidationError, OUTPUT_DIR_FIELD_NAME, PATH_PARAMS_FIELD_NAME, QUERY_PARAMS_FIELD_NAME, FORM_PARAMS_FIELD_NAME, validateParams, validatePath, validateRequestData, } from "../utils/utils.js";
|
|
7
7
|
import { getEntryPoint } from "../utils/telemetry.js";
|
|
8
8
|
import { getLanguageSteps } from "../utils/language-helper.js";
|
|
@@ -346,6 +346,24 @@ The generated test file remains unchanged and ready to use as-is.
|
|
|
346
346
|
logger.warning("Could not resolve auth from workspace config");
|
|
347
347
|
}
|
|
348
348
|
}
|
|
349
|
+
// Workspace-declared self-signed TLS (SKYR-3961): api.skipTLSVerify maps
|
|
350
|
+
// onto the generation pipeline's generateInsecure option (the tool param
|
|
351
|
+
// `insecure` is renamed by buildBaseGenerationOptions; SkyrampClient reads
|
|
352
|
+
// generateInsecure). The tool schema defaults insecure to false, so
|
|
353
|
+
// "false" is indistinguishable from "unspecified" — a workspace
|
|
354
|
+
// declaration only ever turns it ON, never off.
|
|
355
|
+
if (generateOptions.generateInsecure !== true) {
|
|
356
|
+
try {
|
|
357
|
+
const repoPath = generateOptions.outputDir || process.cwd();
|
|
358
|
+
if (await getWorkspaceSkipTLSVerify(repoPath)) {
|
|
359
|
+
logger.info("generateInsecure enabled from workspace skipTLSVerify (self-signed TLS SUT)");
|
|
360
|
+
generateOptions.generateInsecure = true;
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
catch {
|
|
364
|
+
logger.warning("Could not resolve skipTLSVerify from workspace config");
|
|
365
|
+
}
|
|
366
|
+
}
|
|
349
367
|
if (generateOptions.traceFilePath) {
|
|
350
368
|
const traceAuth = this.extractAuthFromTrace(generateOptions.traceFilePath, generateOptions.generateInclude, generateOptions.generateExclude);
|
|
351
369
|
if (traceAuth) {
|
|
@@ -518,3 +518,61 @@ describe("TestGenerationService — trace-based auth in executeGeneration", () =
|
|
|
518
518
|
expect(callArgs.authScheme).toBeUndefined();
|
|
519
519
|
});
|
|
520
520
|
});
|
|
521
|
+
describe("TestGenerationService — workspace skipTLSVerify auto-resolve (SKYR-3961)", () => {
|
|
522
|
+
let mockGenerateRestTest;
|
|
523
|
+
let wsDir;
|
|
524
|
+
beforeEach(() => {
|
|
525
|
+
mockGenerateRestTest = jest.fn().mockResolvedValue("Test generation success");
|
|
526
|
+
SkyrampClient.mockImplementation(() => ({
|
|
527
|
+
generateRestTest: mockGenerateRestTest,
|
|
528
|
+
}));
|
|
529
|
+
wsDir = fs.mkdtempSync(path.join(os.tmpdir(), "skyramp-ws-"));
|
|
530
|
+
});
|
|
531
|
+
afterEach(() => {
|
|
532
|
+
fs.rmSync(wsDir, { recursive: true, force: true });
|
|
533
|
+
});
|
|
534
|
+
function writeWorkspace(api) {
|
|
535
|
+
const dir = path.join(wsDir, ".skyramp");
|
|
536
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
537
|
+
fs.writeFileSync(path.join(dir, "workspace.yml"), [
|
|
538
|
+
"workspace:",
|
|
539
|
+
" repoName: org/sut",
|
|
540
|
+
"services:",
|
|
541
|
+
" - serviceName: sut",
|
|
542
|
+
" api:",
|
|
543
|
+
...Object.entries(api).map(([k, v]) => ` ${k}: ${v}`),
|
|
544
|
+
].join("\n"), "utf8");
|
|
545
|
+
}
|
|
546
|
+
it("sets insecure from workspace when the caller did not request it", async () => {
|
|
547
|
+
writeWorkspace({ baseUrl: "https://127.0.0.1:45643", skipTLSVerify: true });
|
|
548
|
+
const svc = new StubService();
|
|
549
|
+
svc.buildGenerationOptions = () => ({
|
|
550
|
+
authHeader: "",
|
|
551
|
+
authScheme: "",
|
|
552
|
+
outputDir: wsDir,
|
|
553
|
+
language: "python",
|
|
554
|
+
framework: "pytest",
|
|
555
|
+
});
|
|
556
|
+
svc.handleApiAnalysis = async () => null;
|
|
557
|
+
await svc.generateTest({ ...BASE });
|
|
558
|
+
const callArgs = mockGenerateRestTest.mock.calls[0][0];
|
|
559
|
+
// generateInsecure is the key SkyrampClient reads (buildBaseGenerationOptions
|
|
560
|
+
// maps the tool param `insecure` onto it).
|
|
561
|
+
expect(callArgs.generateInsecure).toBe(true);
|
|
562
|
+
});
|
|
563
|
+
it("leaves insecure unset when the workspace does not declare it", async () => {
|
|
564
|
+
writeWorkspace({ baseUrl: "https://127.0.0.1:45643" });
|
|
565
|
+
const svc = new StubService();
|
|
566
|
+
svc.buildGenerationOptions = () => ({
|
|
567
|
+
authHeader: "",
|
|
568
|
+
authScheme: "",
|
|
569
|
+
outputDir: wsDir,
|
|
570
|
+
language: "python",
|
|
571
|
+
framework: "pytest",
|
|
572
|
+
});
|
|
573
|
+
svc.handleApiAnalysis = async () => null;
|
|
574
|
+
await svc.generateTest({ ...BASE });
|
|
575
|
+
const callArgs = mockGenerateRestTest.mock.calls[0][0];
|
|
576
|
+
expect(callArgs.generateInsecure).not.toBe(true);
|
|
577
|
+
});
|
|
578
|
+
});
|
package/build/tool-phases.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
export const TOOL_PHASE_MAP = {
|
|
2
2
|
skyramp_recommend_tests: "analyzing",
|
|
3
3
|
skyramp_analyze_changes: "analyzing",
|
|
4
|
+
skyramp_register_test_plan: "analyzing",
|
|
4
5
|
skyramp_smoke_test_generation: "generating",
|
|
5
6
|
skyramp_fuzz_test_generation: "generating",
|
|
6
7
|
skyramp_contract_test_generation: "generating",
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Canonical MCP tool-name constants for tools that are referenced OUTSIDE
|
|
3
|
+
* their own module — e.g. the register-plan checkpoint names the generation
|
|
4
|
+
* tools in its returned plan text, and the plan guard's rejection messages
|
|
5
|
+
* reference the register tool.
|
|
6
|
+
*
|
|
7
|
+
* Lives in a leaf module (no imports) so any tool can reference another
|
|
8
|
+
* tool's name without pulling in that tool's module graph — the generation
|
|
9
|
+
* tools import planGuard, so planGuard importing a generation tool's local
|
|
10
|
+
* TOOL_NAME back would be an import cycle.
|
|
11
|
+
*
|
|
12
|
+
* Keep in sync with TOOL_PHASE_MAP (src/tool-phases.ts) when renaming.
|
|
13
|
+
*/
|
|
14
|
+
export declare const TOOL_CONTRACT_TEST_GENERATION = "skyramp_contract_test_generation";
|
|
15
|
+
export declare const TOOL_BATCH_SCENARIO_TEST_GENERATION = "skyramp_batch_scenario_test_generation";
|
|
16
|
+
export declare const TOOL_INTEGRATION_TEST_GENERATION = "skyramp_integration_test_generation";
|
|
17
|
+
export declare const TOOL_E2E_TEST_GENERATION = "skyramp_e2e_test_generation";
|
|
18
|
+
export declare const TOOL_UI_TEST_GENERATION = "skyramp_ui_test_generation";
|
|
19
|
+
export declare const TOOL_REGISTER_TEST_PLAN = "skyramp_register_test_plan";
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Canonical MCP tool-name constants for tools that are referenced OUTSIDE
|
|
3
|
+
* their own module — e.g. the register-plan checkpoint names the generation
|
|
4
|
+
* tools in its returned plan text, and the plan guard's rejection messages
|
|
5
|
+
* reference the register tool.
|
|
6
|
+
*
|
|
7
|
+
* Lives in a leaf module (no imports) so any tool can reference another
|
|
8
|
+
* tool's name without pulling in that tool's module graph — the generation
|
|
9
|
+
* tools import planGuard, so planGuard importing a generation tool's local
|
|
10
|
+
* TOOL_NAME back would be an import cycle.
|
|
11
|
+
*
|
|
12
|
+
* Keep in sync with TOOL_PHASE_MAP (src/tool-phases.ts) when renaming.
|
|
13
|
+
*/
|
|
14
|
+
export const TOOL_CONTRACT_TEST_GENERATION = "skyramp_contract_test_generation";
|
|
15
|
+
export const TOOL_BATCH_SCENARIO_TEST_GENERATION = "skyramp_batch_scenario_test_generation";
|
|
16
|
+
export const TOOL_INTEGRATION_TEST_GENERATION = "skyramp_integration_test_generation";
|
|
17
|
+
export const TOOL_E2E_TEST_GENERATION = "skyramp_e2e_test_generation";
|
|
18
|
+
export const TOOL_UI_TEST_GENERATION = "skyramp_ui_test_generation";
|
|
19
|
+
export const TOOL_REGISTER_TEST_PLAN = "skyramp_register_test_plan";
|
|
@@ -1,2 +1,9 @@
|
|
|
1
1
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
import { type ScopedPom } from "../../utils/pom-scope/index.js";
|
|
3
|
+
import { type VerifyResult } from "../../utils/pom-verify/index.js";
|
|
4
|
+
/** Renders a `VerifyResult` (from `verifyReuse`) as the text report returned to the agent. */
|
|
5
|
+
export declare function formatVerifyReport(r: VerifyResult): string;
|
|
6
|
+
/** Renders the zero-reuse compliance-gate failure: a spec with 0 verified POM calls whose raw
|
|
7
|
+
* selectors nonetheless overlap tier-1 POM candidates — i.e. reuse was skipped, not unavailable. */
|
|
8
|
+
export declare function formatZeroReuseGateFailure(tier1: ScopedPom[]): string;
|
|
2
9
|
export declare function registerCodeReuseTool(server: McpServer): void;
|