@skyramp/mcp 0.3.2-rc.pom-3 → 0.3.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/commands/commandLibrary.d.ts +1 -0
- package/build/commands/commandLibrary.js +19 -13
- package/build/commands/localDevTestChangesCommand.d.ts +15 -0
- package/build/commands/localDevTestChangesCommand.js +201 -0
- package/build/index.js +80 -6
- package/build/prompts/code-reuse.js +3 -0
- package/build/prompts/enhance-assertions/sharedAssertionRules.d.ts +1 -0
- package/build/prompts/enhance-assertions/sharedAssertionRules.js +17 -0
- package/build/prompts/initialize-workspace/initializeWorkspacePrompt.js +11 -9
- package/build/prompts/local-dev/local-dev-plan.d.ts +35 -0
- package/build/prompts/local-dev/local-dev-plan.js +429 -0
- package/build/prompts/local-dev/local-dev-prompts.d.ts +4 -0
- package/build/prompts/local-dev/local-dev-prompts.js +190 -0
- package/build/prompts/pom-aware-code-reuse.js +12 -0
- package/build/prompts/prompt-utils.d.ts +8 -0
- package/build/prompts/prompt-utils.js +33 -0
- package/build/prompts/sut-setup/modes/adaptWorkflowPrompt.js +37 -14
- package/build/prompts/sut-setup/shared.js +16 -12
- package/build/prompts/test-recommendation/analysisOutputPrompt.js +21 -29
- package/build/prompts/test-recommendation/scopeAssessment.d.ts +24 -7
- package/build/prompts/test-recommendation/scopeAssessment.js +111 -12
- package/build/prompts/test-recommendation/test-recommendation-prompt.js +41 -4
- package/build/prompts/testbot/testbot-prompts.d.ts +0 -5
- package/build/prompts/testbot/testbot-prompts.js +32 -52
- package/build/recommendation/planRanker.d.ts +15 -2
- package/build/recommendation/planRanker.js +76 -5
- package/build/resources/testbotResource.js +2 -1
- package/build/services/AnalyticsService.d.ts +1 -1
- package/build/services/TestExecutionService.d.ts +2 -1
- package/build/services/TestExecutionService.js +8 -3
- package/build/services/TestGenerationService.d.ts +2 -2
- package/build/services/TestGenerationService.js +39 -21
- package/build/services/containerEnv.js +3 -1
- package/build/tool-phases.js +6 -0
- package/build/tools/code-refactor/codeReuseTool.js +43 -4
- package/build/tools/code-refactor/enhanceAssertionsTool.js +68 -18
- package/build/tools/code-refactor/reuse-outcome.d.ts +109 -0
- package/build/tools/code-refactor/reuse-outcome.js +158 -0
- package/build/tools/code-refactor/reuse-state.d.ts +45 -0
- package/build/tools/code-refactor/reuse-state.js +140 -0
- package/build/tools/enrichTestWithMocksTool.d.ts +28 -0
- package/build/tools/enrichTestWithMocksTool.js +726 -0
- package/build/tools/executeSkyrampTestTool.d.ts +11 -0
- package/build/tools/executeSkyrampTestTool.js +62 -21
- package/build/tools/generate-tests/batchMockGenerationTool.d.ts +106 -0
- package/build/tools/generate-tests/batchMockGenerationTool.js +545 -0
- package/build/tools/generate-tests/generateBatchScenarioRestTool.js +25 -23
- package/build/tools/generate-tests/generateContractRestTool.js +2 -2
- package/build/tools/generate-tests/generateE2ERestTool.d.ts +1 -1
- package/build/tools/generate-tests/generateE2ERestTool.js +1 -1
- package/build/tools/generate-tests/generateLoadRestTool.d.ts +1 -1
- package/build/tools/generate-tests/generateLoadRestTool.js +1 -1
- package/build/tools/generate-tests/generateMockRestTool.d.ts +179 -6
- package/build/tools/generate-tests/generateMockRestTool.js +391 -22
- package/build/tools/generate-tests/loadTestSchema.d.ts +1 -1
- package/build/tools/generate-tests/planGuard.js +2 -22
- package/build/tools/generateEnrichedIntegrationTestTool.d.ts +25 -0
- package/build/tools/generateEnrichedIntegrationTestTool.js +235 -0
- package/build/tools/localDevWorkerComposeTool.d.ts +24 -0
- package/build/tools/localDevWorkerComposeTool.js +264 -0
- package/build/tools/one-click/oneClickTool.d.ts +13 -0
- package/build/tools/one-click/oneClickTool.js +195 -24
- package/build/tools/preflightMockCheckTool.d.ts +2 -0
- package/build/tools/preflightMockCheckTool.js +96 -0
- package/build/tools/queryProxyMocksTool.d.ts +70 -0
- package/build/tools/queryProxyMocksTool.js +522 -0
- package/build/tools/runExistingTestsTool.d.ts +31 -0
- package/build/tools/runExistingTestsTool.js +214 -13
- package/build/tools/submitReportTool.js +38 -3
- package/build/tools/test-management/analyzeChangesTool.d.ts +2 -1
- package/build/tools/test-management/analyzeChangesTool.js +63 -40
- package/build/tools/test-management/registerTestPlanTool.js +55 -7
- package/build/tools/trace/startTraceCollectionTool.js +3 -3
- package/build/types/FrontendIntegration.d.ts +3 -0
- package/build/types/FrontendIntegration.js +3 -0
- package/build/types/OneClickCommands.d.ts +1 -1
- package/build/types/Recommendation.d.ts +20 -0
- package/build/types/Recommendation.js +32 -6
- package/build/types/RepositoryAnalysis.d.ts +131 -14
- package/build/types/RepositoryAnalysis.js +16 -2
- package/build/types/ReuseOutcome.d.ts +63 -0
- package/build/types/ReuseOutcome.js +33 -0
- package/build/types/TestExecution.d.ts +1 -0
- package/build/types/TestTypes.d.ts +25 -7
- package/build/types/TestTypes.js +22 -6
- package/build/types/TestbotReport.d.ts +7 -0
- package/build/types/index.d.ts +2 -0
- package/build/types/index.js +1 -0
- package/build/utils/AnalysisStateManager.d.ts +26 -0
- package/build/utils/AnalysisStateManager.js +31 -1
- package/build/utils/analyze-openapi.js +18 -1
- package/build/utils/branchDiff.d.ts +17 -1
- package/build/utils/branchDiff.js +99 -14
- package/build/utils/featureFlags.d.ts +31 -0
- package/build/utils/featureFlags.js +37 -0
- package/build/utils/frontendIntegration.js +8 -1
- package/build/utils/grpcMockValidation.d.ts +1 -0
- package/build/utils/grpcMockValidation.js +49 -0
- package/build/utils/httpMethodValidation.d.ts +4 -0
- package/build/utils/httpMethodValidation.js +15 -0
- package/build/utils/logger.js +1 -1
- package/build/utils/mockCompatibility.d.ts +49 -0
- package/build/utils/mockCompatibility.js +82 -0
- package/build/utils/pom-scope/pom-files.js +7 -0
- package/build/utils/pom-verify/bindings.js +11 -1
- package/build/utils/pom-verify/re-exports.d.ts +10 -0
- package/build/utils/pom-verify/re-exports.js +61 -0
- package/build/utils/pom-verify/resolve.d.ts +4 -1
- package/build/utils/pom-verify/resolve.js +7 -4
- package/build/utils/pom-verify/verify.d.ts +5 -0
- package/build/utils/pom-verify/verify.js +25 -3
- package/build/utils/progress.js +10 -5
- package/build/utils/proxy-terminal.js +3 -3
- package/build/utils/routeParsers.d.ts +3 -9
- package/build/utils/routeParsers.js +79 -4
- package/build/utils/utils.js +2 -2
- package/build/utils/versions.d.ts +4 -3
- package/build/utils/versions.js +3 -1
- package/build/utils/workspaceAuth.d.ts +46 -0
- package/build/utils/workspaceAuth.js +156 -1
- package/build/workspace/testSuites.d.ts +2 -1
- package/build/workspace/testSuites.js +1 -1
- package/build/workspace/workspace.d.ts +108 -32
- package/build/workspace/workspace.js +32 -4
- package/package.json +5 -2
- package/build/adapters/jestAdapter.test.d.ts +0 -1
- package/build/adapters/jestAdapter.test.js +0 -93
- package/build/adapters/mochaAdapter.test.d.ts +0 -1
- package/build/adapters/mochaAdapter.test.js +0 -63
- package/build/adapters/playwrightAdapter.test.d.ts +0 -1
- package/build/adapters/playwrightAdapter.test.js +0 -169
- package/build/adapters/pytestAdapter.test.d.ts +0 -1
- package/build/adapters/pytestAdapter.test.js +0 -90
- package/build/prompts/code-reuse.test.d.ts +0 -1
- package/build/prompts/code-reuse.test.js +0 -62
- package/build/prompts/pom-aware-code-reuse.test.d.ts +0 -1
- package/build/prompts/pom-aware-code-reuse.test.js +0 -11
- package/build/prompts/test-maintenance/drift-analysis-prompt.test.d.ts +0 -1
- package/build/prompts/test-maintenance/drift-analysis-prompt.test.js +0 -51
- package/build/prompts/test-recommendation/analysisOutputPrompt.test.d.ts +0 -1
- package/build/prompts/test-recommendation/analysisOutputPrompt.test.js +0 -264
- package/build/prompts/test-recommendation/mergeEnrichedScenarios.test.d.ts +0 -1
- package/build/prompts/test-recommendation/mergeEnrichedScenarios.test.js +0 -126
- package/build/prompts/test-recommendation/promptPlan.test.d.ts +0 -1
- package/build/prompts/test-recommendation/promptPlan.test.js +0 -336
- package/build/prompts/test-recommendation/scopeAssessment.test.d.ts +0 -1
- package/build/prompts/test-recommendation/scopeAssessment.test.js +0 -371
- package/build/prompts/test-recommendation/test-recommendation-prompt.test.d.ts +0 -1
- package/build/prompts/test-recommendation/test-recommendation-prompt.test.js +0 -1925
- package/build/prompts/testbot/testbot-prompts.test.d.ts +0 -1
- package/build/prompts/testbot/testbot-prompts.test.js +0 -451
- package/build/recommendation/budgeters/diversityBalancedBudgeter.test.d.ts +0 -1
- package/build/recommendation/budgeters/diversityBalancedBudgeter.test.js +0 -138
- package/build/recommendation/budgeters/fixedNBudgeter.test.d.ts +0 -1
- package/build/recommendation/budgeters/fixedNBudgeter.test.js +0 -66
- package/build/recommendation/discriminators.test.d.ts +0 -1
- package/build/recommendation/discriminators.test.js +0 -324
- package/build/recommendation/diversity.test.d.ts +0 -1
- package/build/recommendation/diversity.test.js +0 -77
- package/build/recommendation/planRanker.test.d.ts +0 -1
- package/build/recommendation/planRanker.test.js +0 -110
- package/build/resources/testbotResource.test.d.ts +0 -1
- package/build/resources/testbotResource.test.js +0 -44
- package/build/services/AnalyticsService.test.d.ts +0 -1
- package/build/services/AnalyticsService.test.js +0 -86
- package/build/services/ScenarioGenerationService.integration.test.d.ts +0 -1
- package/build/services/ScenarioGenerationService.integration.test.js +0 -162
- package/build/services/ScenarioGenerationService.test.d.ts +0 -1
- package/build/services/ScenarioGenerationService.test.js +0 -414
- package/build/services/TestDiscoveryService.test.d.ts +0 -1
- package/build/services/TestDiscoveryService.test.js +0 -983
- package/build/services/TestExecutionService.test.d.ts +0 -1
- package/build/services/TestExecutionService.test.js +0 -1032
- package/build/services/TestGenerationService.test.d.ts +0 -1
- package/build/services/TestGenerationService.test.js +0 -578
- package/build/tool-phase-coverage.test.d.ts +0 -1
- package/build/tool-phase-coverage.test.js +0 -49
- package/build/tools/code-refactor/codeReuseTool.test.d.ts +0 -1
- package/build/tools/code-refactor/codeReuseTool.test.js +0 -572
- package/build/tools/generate-tests/generateBatchScenarioRestTool.test.d.ts +0 -1
- package/build/tools/generate-tests/generateBatchScenarioRestTool.test.js +0 -433
- package/build/tools/generate-tests/generateContractRestTool.test.d.ts +0 -1
- package/build/tools/generate-tests/generateContractRestTool.test.js +0 -142
- package/build/tools/generate-tests/generateIntegrationRestTool.test.d.ts +0 -1
- package/build/tools/generate-tests/generateIntegrationRestTool.test.js +0 -159
- package/build/tools/generate-tests/generateLoadRestTool.test.d.ts +0 -1
- package/build/tools/generate-tests/generateLoadRestTool.test.js +0 -169
- package/build/tools/generate-tests/generateUIRestTool.test.d.ts +0 -1
- package/build/tools/generate-tests/generateUIRestTool.test.js +0 -32
- package/build/tools/generate-tests/planGuard.test.d.ts +0 -1
- package/build/tools/generate-tests/planGuard.test.js +0 -185
- package/build/tools/generate-tests/scenarioLint.test.d.ts +0 -1
- package/build/tools/generate-tests/scenarioLint.test.js +0 -100
- package/build/tools/runExistingTestsTool.test.d.ts +0 -1
- package/build/tools/runExistingTestsTool.test.js +0 -379
- package/build/tools/submitReportTool.test.d.ts +0 -1
- package/build/tools/submitReportTool.test.js +0 -1428
- package/build/tools/test-management/actionsTool.test.d.ts +0 -1
- package/build/tools/test-management/actionsTool.test.js +0 -436
- package/build/tools/test-management/analyzeChangesTool.test.d.ts +0 -1
- package/build/tools/test-management/analyzeChangesTool.test.js +0 -522
- package/build/tools/test-management/analyzeTestHealthTool.test.d.ts +0 -1
- package/build/tools/test-management/analyzeTestHealthTool.test.js +0 -385
- package/build/tools/test-management/registerTestPlanTool.test.d.ts +0 -1
- package/build/tools/test-management/registerTestPlanTool.test.js +0 -296
- package/build/tools/trace/resolveSaveStoragePath.test.d.ts +0 -1
- package/build/tools/trace/resolveSaveStoragePath.test.js +0 -17
- package/build/tools/trace/resolveSessionPaths.test.d.ts +0 -1
- package/build/tools/trace/resolveSessionPaths.test.js +0 -104
- package/build/tools/trace/sessionState.test.d.ts +0 -1
- package/build/tools/trace/sessionState.test.js +0 -17
- package/build/tools/workspace/initializeWorkspaceTool.test.d.ts +0 -1
- package/build/tools/workspace/initializeWorkspaceTool.test.js +0 -139
- package/build/tools/workspace/serviceUpsert.test.d.ts +0 -1
- package/build/tools/workspace/serviceUpsert.test.js +0 -50
- package/build/utils/AnalysisStateManager.test.d.ts +0 -1
- package/build/utils/AnalysisStateManager.test.js +0 -134
- package/build/utils/dartRouteExtractor.test.d.ts +0 -1
- package/build/utils/dartRouteExtractor.test.js +0 -307
- package/build/utils/docker.test.d.ts +0 -1
- package/build/utils/docker.test.js +0 -114
- package/build/utils/featureFlags.test.d.ts +0 -1
- package/build/utils/featureFlags.test.js +0 -81
- package/build/utils/fileWalk.test.d.ts +0 -1
- package/build/utils/fileWalk.test.js +0 -252
- package/build/utils/frontendIntegration.test.d.ts +0 -1
- package/build/utils/frontendIntegration.test.js +0 -229
- package/build/utils/frontendSelectors.test.d.ts +0 -1
- package/build/utils/frontendSelectors.test.js +0 -118
- package/build/utils/gitStaging.test.d.ts +0 -1
- package/build/utils/gitStaging.test.js +0 -111
- package/build/utils/httpDefaults.test.d.ts +0 -1
- package/build/utils/httpDefaults.test.js +0 -21
- package/build/utils/importerHop.test.d.ts +0 -1
- package/build/utils/importerHop.test.js +0 -469
- package/build/utils/pathAffinityClassification.test.d.ts +0 -1
- package/build/utils/pathAffinityClassification.test.js +0 -208
- package/build/utils/planMatchKeys.test.d.ts +0 -1
- package/build/utils/planMatchKeys.test.js +0 -123
- package/build/utils/pom-scope/index.test.d.ts +0 -1
- package/build/utils/pom-scope/index.test.js +0 -278
- package/build/utils/pom-scope/pom-files.test.d.ts +0 -1
- package/build/utils/pom-scope/pom-files.test.js +0 -29
- package/build/utils/pom-scope/scoring.test.d.ts +0 -1
- package/build/utils/pom-scope/scoring.test.js +0 -39
- package/build/utils/pom-scope/selector-extractor.test.d.ts +0 -1
- package/build/utils/pom-scope/selector-extractor.test.js +0 -67
- package/build/utils/pom-verify/bindings.test.d.ts +0 -1
- package/build/utils/pom-verify/bindings.test.js +0 -164
- package/build/utils/pom-verify/calls.test.d.ts +0 -1
- package/build/utils/pom-verify/calls.test.js +0 -61
- package/build/utils/pom-verify/resolve.test.d.ts +0 -1
- package/build/utils/pom-verify/resolve.test.js +0 -114
- package/build/utils/pom-verify/verify.test.d.ts +0 -1
- package/build/utils/pom-verify/verify.test.js +0 -374
- package/build/utils/pr-comment-parser.test.d.ts +0 -1
- package/build/utils/pr-comment-parser.test.js +0 -428
- package/build/utils/progress.test.d.ts +0 -1
- package/build/utils/progress.test.js +0 -37
- package/build/utils/projectMetadata.test.d.ts +0 -1
- package/build/utils/projectMetadata.test.js +0 -172
- package/build/utils/pythonMountPrefixes.test.d.ts +0 -1
- package/build/utils/pythonMountPrefixes.test.js +0 -113
- package/build/utils/repoScanner.test.d.ts +0 -1
- package/build/utils/repoScanner.test.js +0 -190
- package/build/utils/reportVerification.test.d.ts +0 -1
- package/build/utils/reportVerification.test.js +0 -185
- package/build/utils/routeParsers.test.d.ts +0 -1
- package/build/utils/routeParsers.test.js +0 -1011
- package/build/utils/scenarioDrafting.test.d.ts +0 -1
- package/build/utils/scenarioDrafting.test.js +0 -876
- package/build/utils/sourceRouteExtractor.test.d.ts +0 -1
- package/build/utils/sourceRouteExtractor.test.js +0 -738
- package/build/utils/telemetry.test.d.ts +0 -1
- package/build/utils/telemetry.test.js +0 -70
- package/build/utils/trace-parser.test.d.ts +0 -6
- package/build/utils/trace-parser.test.js +0 -140
- package/build/utils/uiPageEnumerator.test.d.ts +0 -1
- package/build/utils/uiPageEnumerator.test.js +0 -824
- package/build/utils/utils.test.d.ts +0 -1
- package/build/utils/utils.test.js +0 -103
- package/build/utils/walkerCharacterization.test.d.ts +0 -1
- package/build/utils/walkerCharacterization.test.js +0 -233
- package/build/utils/workspaceAuth.test.d.ts +0 -1
- package/build/utils/workspaceAuth.test.js +0 -260
- package/build/workspace/testSuites.test.d.ts +0 -1
- package/build/workspace/testSuites.test.js +0 -23
- package/build/workspace/workspace.test.d.ts +0 -1
- package/build/workspace/workspace.test.js +0 -264
|
@@ -1,296 +0,0 @@
|
|
|
1
|
-
// @ts-nocheck - Jest ESM type inference issues
|
|
2
|
-
import { jest } from "@jest/globals";
|
|
3
|
-
import * as fs from "fs/promises";
|
|
4
|
-
import * as path from "path";
|
|
5
|
-
import * as os from "os";
|
|
6
|
-
import { z } from "zod";
|
|
7
|
-
jest.unstable_mockModule("../../utils/logger.js", () => ({
|
|
8
|
-
logger: { info: jest.fn(), error: jest.fn(), warning: jest.fn(), debug: jest.fn() },
|
|
9
|
-
}));
|
|
10
|
-
const { registerRegisterTestPlanTool } = await import("./registerTestPlanTool.js");
|
|
11
|
-
const { computeCandidateId, DiscriminatorKind, CandidateSource } = await import("../../types/Recommendation.js");
|
|
12
|
-
function captureToolHandler() {
|
|
13
|
-
let handler;
|
|
14
|
-
let inputSchema;
|
|
15
|
-
const fakeServer = {
|
|
16
|
-
registerTool: (_name, opts, fn) => {
|
|
17
|
-
handler = fn;
|
|
18
|
-
inputSchema = opts.inputSchema;
|
|
19
|
-
},
|
|
20
|
-
};
|
|
21
|
-
registerRegisterTestPlanTool(fakeServer);
|
|
22
|
-
// Mirrors the real MCP SDK's registerTool: validates args against inputSchema via Zod
|
|
23
|
-
// before invoking the handler, so tests exercise the real tool contract.
|
|
24
|
-
return async (params) => {
|
|
25
|
-
const parsed = z.object(inputSchema).safeParse(params);
|
|
26
|
-
if (!parsed.success) {
|
|
27
|
-
return { isError: true, content: [{ type: "text", text: parsed.error.message }] };
|
|
28
|
-
}
|
|
29
|
-
return handler(parsed.data);
|
|
30
|
-
};
|
|
31
|
-
}
|
|
32
|
-
async function writeStateFile(dir, overrides = {}) {
|
|
33
|
-
const stateFile = path.join(dir, "analyze-changes-state.json");
|
|
34
|
-
await fs.writeFile(stateFile, JSON.stringify({
|
|
35
|
-
existingTests: [],
|
|
36
|
-
analysisScope: "current_branch_diff",
|
|
37
|
-
newEndpoints: [],
|
|
38
|
-
repositoryAnalysis: {},
|
|
39
|
-
...overrides,
|
|
40
|
-
metadata: {
|
|
41
|
-
sessionId: "test-session",
|
|
42
|
-
stateType: "analysis",
|
|
43
|
-
repositoryPath: dir,
|
|
44
|
-
createdAt: "2026-01-01T00:00:00.000Z",
|
|
45
|
-
updatedAt: "2026-01-01T00:00:00.000Z",
|
|
46
|
-
},
|
|
47
|
-
}), "utf-8");
|
|
48
|
-
return stateFile;
|
|
49
|
-
}
|
|
50
|
-
async function readStateFile(stateFile) {
|
|
51
|
-
return JSON.parse(await fs.readFile(stateFile, "utf-8"));
|
|
52
|
-
}
|
|
53
|
-
const AGGREGATE_ANCHOR = "order_count = len(existing_orders)";
|
|
54
|
-
function multiRecordCandidate(overrides = {}) {
|
|
55
|
-
return {
|
|
56
|
-
scenarioName: "orders_multi_record_aggregate",
|
|
57
|
-
description: "Create two orders then assert the aggregate count.",
|
|
58
|
-
category: "business_rule",
|
|
59
|
-
priority: "high",
|
|
60
|
-
testType: "integration",
|
|
61
|
-
steps: [
|
|
62
|
-
{
|
|
63
|
-
order: 1,
|
|
64
|
-
method: "POST",
|
|
65
|
-
path: "/api/v1/orders",
|
|
66
|
-
description: "Create first order",
|
|
67
|
-
interactionType: "success",
|
|
68
|
-
expectedStatusCode: 201,
|
|
69
|
-
},
|
|
70
|
-
{
|
|
71
|
-
order: 2,
|
|
72
|
-
method: "GET",
|
|
73
|
-
path: "/api/v1/orders/summary",
|
|
74
|
-
description: "Assert order_count is 2",
|
|
75
|
-
interactionType: "success",
|
|
76
|
-
expectedStatusCode: 200,
|
|
77
|
-
expectedResponseFields: ["order_count == 2"],
|
|
78
|
-
},
|
|
79
|
-
],
|
|
80
|
-
...overrides,
|
|
81
|
-
};
|
|
82
|
-
}
|
|
83
|
-
describe("skyramp_register_test_plan — SKYR-3879 Path B", () => {
|
|
84
|
-
let handler;
|
|
85
|
-
let tmpDirs = [];
|
|
86
|
-
beforeAll(() => {
|
|
87
|
-
handler = captureToolHandler();
|
|
88
|
-
});
|
|
89
|
-
afterEach(async () => {
|
|
90
|
-
for (const dir of tmpDirs) {
|
|
91
|
-
await fs.rm(dir, { recursive: true, force: true });
|
|
92
|
-
}
|
|
93
|
-
tmpDirs = [];
|
|
94
|
-
});
|
|
95
|
-
async function mkTmpDir() {
|
|
96
|
-
const dir = await fs.mkdtemp(path.join(os.tmpdir(), "register-plan-test-"));
|
|
97
|
-
tmpDirs.push(dir);
|
|
98
|
-
return dir;
|
|
99
|
-
}
|
|
100
|
-
it("errors when stateFile does not exist", async () => {
|
|
101
|
-
const dir = await mkTmpDir();
|
|
102
|
-
const result = await handler({
|
|
103
|
-
stateFile: path.join(dir, "does-not-exist.json"),
|
|
104
|
-
candidates: [],
|
|
105
|
-
});
|
|
106
|
-
expect(result.isError).toBe(true);
|
|
107
|
-
expect(result.content[0].text).toContain("State file is empty or invalid");
|
|
108
|
-
});
|
|
109
|
-
it("rejects a relative stateFile path (schema refine)", async () => {
|
|
110
|
-
const result = await handler({ stateFile: "relative/path.json", candidates: [] });
|
|
111
|
-
expect(result.isError).toBe(true);
|
|
112
|
-
});
|
|
113
|
-
it("rejects an empty candidate union instead of persisting an empty plan", async () => {
|
|
114
|
-
const dir = await mkTmpDir();
|
|
115
|
-
const stateFile = await writeStateFile(dir, { diffText: "" });
|
|
116
|
-
const result = await handler({ stateFile, candidates: [] });
|
|
117
|
-
expect(result.isError).toBe(true);
|
|
118
|
-
expect(result.content[0].text).toContain("No candidates submitted");
|
|
119
|
-
const persisted = JSON.parse(await fs.readFile(stateFile, "utf-8"));
|
|
120
|
-
expect(persisted.approvedPlan).toBeUndefined();
|
|
121
|
-
});
|
|
122
|
-
it("promotes a verified discriminator candidate into GENERATE ahead of an unverified one", async () => {
|
|
123
|
-
const dir = await mkTmpDir();
|
|
124
|
-
const stateFile = await writeStateFile(dir, {
|
|
125
|
-
diffText: `--- a/orders.py\n+++ b/orders.py\n+${AGGREGATE_ANCHOR}\n`,
|
|
126
|
-
planBudgetContext: {
|
|
127
|
-
maxGenerate: 2,
|
|
128
|
-
maxTotal: 3,
|
|
129
|
-
isUIOnlyPR: false,
|
|
130
|
-
hasFrontendChanges: false,
|
|
131
|
-
externalCoverageKeys: [],
|
|
132
|
-
},
|
|
133
|
-
});
|
|
134
|
-
const result = await handler({
|
|
135
|
-
stateFile,
|
|
136
|
-
candidates: [
|
|
137
|
-
multiRecordCandidate({
|
|
138
|
-
discriminator: { kind: DiscriminatorKind.MULTI_RECORD_AGGREGATE, changedCodeAnchor: AGGREGATE_ANCHOR },
|
|
139
|
-
}),
|
|
140
|
-
{
|
|
141
|
-
scenarioName: "products_crud",
|
|
142
|
-
description: "Basic CRUD happy path.",
|
|
143
|
-
category: "crud",
|
|
144
|
-
priority: "low",
|
|
145
|
-
testType: "contract",
|
|
146
|
-
steps: [
|
|
147
|
-
{
|
|
148
|
-
order: 1,
|
|
149
|
-
method: "GET",
|
|
150
|
-
path: "/api/v1/products",
|
|
151
|
-
description: "List products",
|
|
152
|
-
interactionType: "success",
|
|
153
|
-
expectedStatusCode: 200,
|
|
154
|
-
},
|
|
155
|
-
],
|
|
156
|
-
},
|
|
157
|
-
],
|
|
158
|
-
});
|
|
159
|
-
expect(result.isError).toBeFalsy();
|
|
160
|
-
expect(result.content[0].text).toContain("orders_multi_record_aggregate");
|
|
161
|
-
expect(result.content[0].text).toContain("verified discriminator: multi_record_aggregate");
|
|
162
|
-
const persisted = await readStateFile(stateFile);
|
|
163
|
-
expect(persisted.approvedPlan.generate[0].scenarioName).toBe("orders_multi_record_aggregate");
|
|
164
|
-
expect(persisted.approvedPlan.generate[0].verifiedDiscriminator).toBe(DiscriminatorKind.MULTI_RECORD_AGGREGATE);
|
|
165
|
-
expect(persisted.approvedPlan.demotions).toEqual([]);
|
|
166
|
-
});
|
|
167
|
-
it("demotes a gamed discriminator claim (no structural evidence) but keeps the candidate registered", async () => {
|
|
168
|
-
const dir = await mkTmpDir();
|
|
169
|
-
const stateFile = await writeStateFile(dir, {
|
|
170
|
-
diffText: `--- a/orders.py\n+++ b/orders.py\n+${AGGREGATE_ANCHOR}\n`,
|
|
171
|
-
planBudgetContext: {
|
|
172
|
-
maxGenerate: 2,
|
|
173
|
-
maxTotal: 3,
|
|
174
|
-
isUIOnlyPR: false,
|
|
175
|
-
hasFrontendChanges: false,
|
|
176
|
-
externalCoverageKeys: [],
|
|
177
|
-
},
|
|
178
|
-
});
|
|
179
|
-
const result = await handler({
|
|
180
|
-
stateFile,
|
|
181
|
-
candidates: [
|
|
182
|
-
multiRecordCandidate({
|
|
183
|
-
// No aggregate assertion — steps.length === 2 but the second step doesn't
|
|
184
|
-
// assert a count. A gamed claim: declares the discriminator anyway.
|
|
185
|
-
steps: [
|
|
186
|
-
{
|
|
187
|
-
order: 1,
|
|
188
|
-
method: "POST",
|
|
189
|
-
path: "/api/v1/orders",
|
|
190
|
-
description: "Create order",
|
|
191
|
-
interactionType: "success",
|
|
192
|
-
expectedStatusCode: 201,
|
|
193
|
-
},
|
|
194
|
-
],
|
|
195
|
-
discriminator: { kind: DiscriminatorKind.MULTI_RECORD_AGGREGATE, changedCodeAnchor: AGGREGATE_ANCHOR },
|
|
196
|
-
}),
|
|
197
|
-
],
|
|
198
|
-
});
|
|
199
|
-
expect(result.isError).toBeFalsy();
|
|
200
|
-
expect(result.content[0].text).toContain("Demotions (1)");
|
|
201
|
-
expect(result.content[0].text).toContain("multi_record_aggregate unverified");
|
|
202
|
-
const persisted = await readStateFile(stateFile);
|
|
203
|
-
expect(persisted.approvedPlan.demotions).toHaveLength(1);
|
|
204
|
-
expect(persisted.approvedPlan.demotions[0].reason).toContain("multi_record_aggregate unverified");
|
|
205
|
-
// The candidate itself is NOT dropped — it must still appear somewhere in the plan.
|
|
206
|
-
const allScenarioNames = [...persisted.approvedPlan.generate, ...persisted.approvedPlan.additional].map((item) => item.scenarioName);
|
|
207
|
-
expect(allScenarioNames).toContain("orders_multi_record_aggregate");
|
|
208
|
-
// And it must NOT carry a verifiedDiscriminator.
|
|
209
|
-
const item = [...persisted.approvedPlan.generate, ...persisted.approvedPlan.additional].find((i) => i.scenarioName === "orders_multi_record_aggregate");
|
|
210
|
-
expect(item.verifiedDiscriminator).toBeUndefined();
|
|
211
|
-
});
|
|
212
|
-
it("merges agent candidates with pre-seeded server candidates from analyze_changes", async () => {
|
|
213
|
-
const dir = await mkTmpDir();
|
|
214
|
-
const serverScenario = {
|
|
215
|
-
scenarioName: "server_seeded_scenario",
|
|
216
|
-
description: "Pre-ranked by the server's own classification.",
|
|
217
|
-
category: "business_rule",
|
|
218
|
-
priority: "high",
|
|
219
|
-
steps: [
|
|
220
|
-
{
|
|
221
|
-
order: 1,
|
|
222
|
-
method: "GET",
|
|
223
|
-
path: "/api/v1/reports",
|
|
224
|
-
description: "Fetch report",
|
|
225
|
-
interactionType: "success",
|
|
226
|
-
expectedStatusCode: 200,
|
|
227
|
-
},
|
|
228
|
-
],
|
|
229
|
-
chainingKeys: [],
|
|
230
|
-
requiresAuth: false,
|
|
231
|
-
estimatedComplexity: "simple",
|
|
232
|
-
testType: "contract",
|
|
233
|
-
};
|
|
234
|
-
const serverCandidateId = computeCandidateId(serverScenario);
|
|
235
|
-
const stateFile = await writeStateFile(dir, {
|
|
236
|
-
diffText: "",
|
|
237
|
-
repositoryAnalysis: { scenarios: [serverScenario] },
|
|
238
|
-
planBudgetContext: {
|
|
239
|
-
maxGenerate: 5,
|
|
240
|
-
maxTotal: 5,
|
|
241
|
-
isUIOnlyPR: false,
|
|
242
|
-
hasFrontendChanges: false,
|
|
243
|
-
externalCoverageKeys: [],
|
|
244
|
-
},
|
|
245
|
-
approvedPlan: {
|
|
246
|
-
planId: "prior-plan",
|
|
247
|
-
createdAt: "2026-01-01T00:00:00.000Z",
|
|
248
|
-
generate: [
|
|
249
|
-
{
|
|
250
|
-
candidateId: serverCandidateId,
|
|
251
|
-
scenarioName: "server_seeded_scenario",
|
|
252
|
-
testType: "contract",
|
|
253
|
-
category: "business_rule",
|
|
254
|
-
source: CandidateSource.SERVER,
|
|
255
|
-
matchKeys: [`name:server-seeded-scenario`, `ep:contract:GET:/api/v1/reports`],
|
|
256
|
-
},
|
|
257
|
-
],
|
|
258
|
-
additional: [],
|
|
259
|
-
demotions: [],
|
|
260
|
-
},
|
|
261
|
-
});
|
|
262
|
-
const result = await handler({
|
|
263
|
-
stateFile,
|
|
264
|
-
candidates: [
|
|
265
|
-
{
|
|
266
|
-
scenarioName: "agent_drafted_scenario",
|
|
267
|
-
description: "Agent-drafted candidate this run.",
|
|
268
|
-
category: "workflow",
|
|
269
|
-
priority: "medium",
|
|
270
|
-
testType: "integration",
|
|
271
|
-
steps: [
|
|
272
|
-
{
|
|
273
|
-
order: 1,
|
|
274
|
-
method: "POST",
|
|
275
|
-
path: "/api/v1/carts",
|
|
276
|
-
description: "Create cart",
|
|
277
|
-
interactionType: "success",
|
|
278
|
-
expectedStatusCode: 201,
|
|
279
|
-
},
|
|
280
|
-
],
|
|
281
|
-
},
|
|
282
|
-
],
|
|
283
|
-
});
|
|
284
|
-
expect(result.isError).toBeFalsy();
|
|
285
|
-
const persisted = await readStateFile(stateFile);
|
|
286
|
-
const names = persisted.approvedPlan.generate.map((item) => item.scenarioName);
|
|
287
|
-
expect(names).toContain("server_seeded_scenario");
|
|
288
|
-
expect(names).toContain("agent_drafted_scenario");
|
|
289
|
-
const serverItem = persisted.approvedPlan.generate.find((item) => item.scenarioName === "server_seeded_scenario");
|
|
290
|
-
expect(serverItem.source).toBe("server");
|
|
291
|
-
const agentItem = persisted.approvedPlan.generate.find((item) => item.scenarioName === "agent_drafted_scenario");
|
|
292
|
-
expect(agentItem.source).toBe("agent");
|
|
293
|
-
// New plan replaces the old planId (fresh registration, not an append).
|
|
294
|
-
expect(persisted.approvedPlan.planId).not.toBe("prior-plan");
|
|
295
|
-
});
|
|
296
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
import path from "path";
|
|
2
|
-
import { resolveSaveStoragePath } from "./resolveSaveStoragePath.js";
|
|
3
|
-
describe("resolveSaveStoragePath", () => {
|
|
4
|
-
const outputDir = "/abs/output";
|
|
5
|
-
it("joins a bare filename with outputDir", () => {
|
|
6
|
-
expect(resolveSaveStoragePath("auth.json", outputDir)).toBe(path.join(outputDir, "auth.json"));
|
|
7
|
-
});
|
|
8
|
-
it("returns an absolute path unchanged", () => {
|
|
9
|
-
expect(resolveSaveStoragePath("/tmp/storage.json", outputDir)).toBe("/tmp/storage.json");
|
|
10
|
-
});
|
|
11
|
-
it("returns a relative path with separators unchanged", () => {
|
|
12
|
-
expect(resolveSaveStoragePath("subdir/storage.json", outputDir)).toBe("subdir/storage.json");
|
|
13
|
-
});
|
|
14
|
-
it("returns input unchanged when outputDir is undefined", () => {
|
|
15
|
-
expect(resolveSaveStoragePath("auth.json", undefined)).toBe("auth.json");
|
|
16
|
-
});
|
|
17
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,104 +0,0 @@
|
|
|
1
|
-
import path from "path";
|
|
2
|
-
import { SESSION_STORAGE_FILENAME } from "../../types/TestTypes.js";
|
|
3
|
-
import { resolveSessionPaths } from "./resolveSessionPaths.js";
|
|
4
|
-
describe("resolveSessionPaths", () => {
|
|
5
|
-
const outputDir = "/abs/output";
|
|
6
|
-
const defaultPath = path.join(outputDir, SESSION_STORAGE_FILENAME);
|
|
7
|
-
const baseArgs = {
|
|
8
|
-
loadOverride: undefined,
|
|
9
|
-
saveOverride: undefined,
|
|
10
|
-
outputDir,
|
|
11
|
-
sessionExists: false,
|
|
12
|
-
};
|
|
13
|
-
describe("mode=auto", () => {
|
|
14
|
-
it("does NOT save when no session exists — no implicit credential storage (SKYR-3793)", () => {
|
|
15
|
-
// Storing the auth session must be opt-in (sessionMode=capture or an
|
|
16
|
-
// explicit save override). Plain auto with no existing session must not
|
|
17
|
-
// write credentials the user never asked to store.
|
|
18
|
-
expect(resolveSessionPaths({ ...baseArgs, mode: "auto" })).toEqual({});
|
|
19
|
-
});
|
|
20
|
-
it("reuses (loads) the default path when a session exists, without saving", () => {
|
|
21
|
-
expect(resolveSessionPaths({ ...baseArgs, mode: "auto", sessionExists: true })).toEqual({ loadPath: defaultPath });
|
|
22
|
-
});
|
|
23
|
-
it("respects an explicit load override when given (auto + sessionExists)", () => {
|
|
24
|
-
expect(resolveSessionPaths({
|
|
25
|
-
...baseArgs,
|
|
26
|
-
mode: "auto",
|
|
27
|
-
sessionExists: true,
|
|
28
|
-
loadOverride: "/elsewhere/session.json",
|
|
29
|
-
})).toEqual({ loadPath: "/elsewhere/session.json" });
|
|
30
|
-
});
|
|
31
|
-
it("respects an explicit save override (auto + no session)", () => {
|
|
32
|
-
expect(resolveSessionPaths({
|
|
33
|
-
...baseArgs,
|
|
34
|
-
mode: "auto",
|
|
35
|
-
saveOverride: "auth.json",
|
|
36
|
-
})).toEqual({ savePath: path.join(outputDir, "auth.json") });
|
|
37
|
-
});
|
|
38
|
-
});
|
|
39
|
-
describe("mode=capture", () => {
|
|
40
|
-
it("always saves at the default path, never loads", () => {
|
|
41
|
-
expect(resolveSessionPaths({ ...baseArgs, mode: "capture" })).toEqual({
|
|
42
|
-
savePath: defaultPath,
|
|
43
|
-
});
|
|
44
|
-
});
|
|
45
|
-
it("ignores a load override (capture is a clean slate)", () => {
|
|
46
|
-
expect(resolveSessionPaths({
|
|
47
|
-
...baseArgs,
|
|
48
|
-
mode: "capture",
|
|
49
|
-
loadOverride: "/elsewhere/session.json",
|
|
50
|
-
})).toEqual({ savePath: defaultPath });
|
|
51
|
-
});
|
|
52
|
-
it("honors an explicit save override", () => {
|
|
53
|
-
expect(resolveSessionPaths({
|
|
54
|
-
...baseArgs,
|
|
55
|
-
mode: "capture",
|
|
56
|
-
saveOverride: "/tmp/custom.json",
|
|
57
|
-
})).toEqual({ savePath: "/tmp/custom.json" });
|
|
58
|
-
});
|
|
59
|
-
it("still captures even when a session file already exists (we're recapturing)", () => {
|
|
60
|
-
expect(resolveSessionPaths({
|
|
61
|
-
...baseArgs,
|
|
62
|
-
mode: "capture",
|
|
63
|
-
sessionExists: true,
|
|
64
|
-
})).toEqual({ savePath: defaultPath });
|
|
65
|
-
});
|
|
66
|
-
});
|
|
67
|
-
describe("mode=reuse", () => {
|
|
68
|
-
it("loads the default path, does not save (preserves the session file)", () => {
|
|
69
|
-
expect(resolveSessionPaths({ ...baseArgs, mode: "reuse" })).toEqual({
|
|
70
|
-
loadPath: defaultPath,
|
|
71
|
-
});
|
|
72
|
-
});
|
|
73
|
-
it("honors an explicit load override", () => {
|
|
74
|
-
expect(resolveSessionPaths({
|
|
75
|
-
...baseArgs,
|
|
76
|
-
mode: "reuse",
|
|
77
|
-
loadOverride: "/elsewhere/session.json",
|
|
78
|
-
})).toEqual({ loadPath: "/elsewhere/session.json" });
|
|
79
|
-
});
|
|
80
|
-
it("allows an explicit save override even in reuse (caller wants to refresh)", () => {
|
|
81
|
-
expect(resolveSessionPaths({
|
|
82
|
-
...baseArgs,
|
|
83
|
-
mode: "reuse",
|
|
84
|
-
saveOverride: "/tmp/refreshed.json",
|
|
85
|
-
})).toEqual({ loadPath: defaultPath, savePath: "/tmp/refreshed.json" });
|
|
86
|
-
});
|
|
87
|
-
});
|
|
88
|
-
describe("mode=ignore", () => {
|
|
89
|
-
it("loads and saves nothing by default", () => {
|
|
90
|
-
expect(resolveSessionPaths({ ...baseArgs, mode: "ignore" })).toEqual({});
|
|
91
|
-
});
|
|
92
|
-
it("honors explicit overrides only", () => {
|
|
93
|
-
expect(resolveSessionPaths({
|
|
94
|
-
...baseArgs,
|
|
95
|
-
mode: "ignore",
|
|
96
|
-
loadOverride: "/in/session.json",
|
|
97
|
-
saveOverride: "/out/session.json",
|
|
98
|
-
})).toEqual({
|
|
99
|
-
loadPath: "/in/session.json",
|
|
100
|
-
savePath: "/out/session.json",
|
|
101
|
-
});
|
|
102
|
-
});
|
|
103
|
-
});
|
|
104
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
import { consumeSavedSessionPath, setSavedSessionPath, } from "./sessionState.js";
|
|
2
|
-
describe("sessionState", () => {
|
|
3
|
-
afterEach(() => {
|
|
4
|
-
// Ensure module-level state is cleared between tests.
|
|
5
|
-
setSavedSessionPath(undefined);
|
|
6
|
-
});
|
|
7
|
-
it("returns the set value on first consume and undefined on the second", () => {
|
|
8
|
-
setSavedSessionPath("/abs/session.json");
|
|
9
|
-
expect(consumeSavedSessionPath()).toBe("/abs/session.json");
|
|
10
|
-
expect(consumeSavedSessionPath()).toBeUndefined();
|
|
11
|
-
});
|
|
12
|
-
it("setting undefined clears any prior value", () => {
|
|
13
|
-
setSavedSessionPath("/abs/session.json");
|
|
14
|
-
setSavedSessionPath(undefined);
|
|
15
|
-
expect(consumeSavedSessionPath()).toBeUndefined();
|
|
16
|
-
});
|
|
17
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,139 +0,0 @@
|
|
|
1
|
-
// @ts-nocheck - Jest ESM type inference issues
|
|
2
|
-
import { jest } from "@jest/globals";
|
|
3
|
-
import { z } from "zod";
|
|
4
|
-
import * as fs from "fs/promises";
|
|
5
|
-
import * as path from "path";
|
|
6
|
-
import * as os from "os";
|
|
7
|
-
// WorkspaceConfigManager + serviceSchema now live in the local workspace module
|
|
8
|
-
// (SKYR-3794 moved them out of @skyramp/skyramp). serviceSchema must be a real
|
|
9
|
-
// zod schema because `z.array(serviceSchema)` runs at module import time.
|
|
10
|
-
jest.unstable_mockModule("../../workspace/workspace.js", () => ({
|
|
11
|
-
WorkspaceConfigManager: jest.fn(),
|
|
12
|
-
serviceSchema: z.any(),
|
|
13
|
-
}));
|
|
14
|
-
jest.unstable_mockModule("js-yaml", () => ({
|
|
15
|
-
default: { dump: jest.fn() },
|
|
16
|
-
}));
|
|
17
|
-
jest.unstable_mockModule("../../utils/logger.js", () => ({
|
|
18
|
-
logger: { info: jest.fn(), error: jest.fn() },
|
|
19
|
-
}));
|
|
20
|
-
jest.unstable_mockModule("../../services/AnalyticsService.js", () => ({
|
|
21
|
-
AnalyticsService: { pushMCPToolEvent: jest.fn().mockResolvedValue(undefined) },
|
|
22
|
-
}));
|
|
23
|
-
jest.unstable_mockModule("./initScanWorkspaceTool.js", () => ({
|
|
24
|
-
validateAndConsumeScanToken: jest.fn(),
|
|
25
|
-
}));
|
|
26
|
-
const { registerInitializeWorkspaceTool } = await import("./initializeWorkspaceTool.js");
|
|
27
|
-
const { validateAndConsumeScanToken } = await import("./initScanWorkspaceTool.js");
|
|
28
|
-
const { WorkspaceConfigManager } = await import("../../workspace/workspace.js");
|
|
29
|
-
const yaml = (await import("js-yaml")).default;
|
|
30
|
-
describe("initializeWorkspaceTool", () => {
|
|
31
|
-
let tmpDir;
|
|
32
|
-
let handler;
|
|
33
|
-
let mockManager;
|
|
34
|
-
beforeEach(async () => {
|
|
35
|
-
tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), "init-workspace-test-"));
|
|
36
|
-
// Build a mock WorkspaceConfigManager
|
|
37
|
-
mockManager = {
|
|
38
|
-
exists: jest.fn().mockResolvedValue(false),
|
|
39
|
-
getConfigPath: jest
|
|
40
|
-
.fn()
|
|
41
|
-
.mockReturnValue(path.join(tmpDir, ".skyramp/workspace.yml")),
|
|
42
|
-
initialize: jest.fn().mockResolvedValue({ workspace: {}, services: [] }),
|
|
43
|
-
updateMetadata: jest.fn().mockImplementation(async (data) => ({
|
|
44
|
-
workspace: {},
|
|
45
|
-
services: [],
|
|
46
|
-
metadata: { ...data, updatedAt: "2024-01-01T00:00:00.000Z" },
|
|
47
|
-
})),
|
|
48
|
-
};
|
|
49
|
-
WorkspaceConfigManager.mockImplementation(() => mockManager);
|
|
50
|
-
// Mock validateAndConsumeScanToken to accept any token
|
|
51
|
-
validateAndConsumeScanToken.mockReturnValue(true);
|
|
52
|
-
// Mock yaml.dump to return a simple string
|
|
53
|
-
yaml.dump.mockReturnValue("services: []\n");
|
|
54
|
-
// Register the tool and capture the handler
|
|
55
|
-
const mockServer = {
|
|
56
|
-
registerTool: jest.fn((_name, _opts, fn) => {
|
|
57
|
-
handler = fn;
|
|
58
|
-
}),
|
|
59
|
-
};
|
|
60
|
-
registerInitializeWorkspaceTool(mockServer);
|
|
61
|
-
});
|
|
62
|
-
afterEach(async () => {
|
|
63
|
-
await fs.rm(tmpDir, { recursive: true, force: true });
|
|
64
|
-
jest.clearAllMocks();
|
|
65
|
-
});
|
|
66
|
-
it("registers the tool with the expected schema shape", () => {
|
|
67
|
-
const mockServer = {
|
|
68
|
-
registerTool: jest.fn(),
|
|
69
|
-
};
|
|
70
|
-
registerInitializeWorkspaceTool(mockServer);
|
|
71
|
-
expect(mockServer.registerTool).toHaveBeenCalledWith("skyramp_init_workspace", expect.objectContaining({
|
|
72
|
-
description: expect.stringContaining("PREREQUISITE"),
|
|
73
|
-
inputSchema: expect.objectContaining({
|
|
74
|
-
workspacePath: expect.any(Object),
|
|
75
|
-
services: expect.any(Object),
|
|
76
|
-
scanToken: expect.any(Object),
|
|
77
|
-
force: expect.any(Object),
|
|
78
|
-
}),
|
|
79
|
-
}), expect.any(Function));
|
|
80
|
-
});
|
|
81
|
-
it("successfully initializes workspace with valid input in merge mode", async () => {
|
|
82
|
-
// Create the parent directory
|
|
83
|
-
await fs.mkdir(path.join(tmpDir, ".skyramp"), { recursive: true });
|
|
84
|
-
const result = await handler({
|
|
85
|
-
workspacePath: tmpDir,
|
|
86
|
-
services: [
|
|
87
|
-
{
|
|
88
|
-
serviceName: "test-service",
|
|
89
|
-
language: "python",
|
|
90
|
-
testDirectory: "tests",
|
|
91
|
-
},
|
|
92
|
-
],
|
|
93
|
-
scanToken: "valid-token",
|
|
94
|
-
force: false,
|
|
95
|
-
});
|
|
96
|
-
expect(result.isError).toBeFalsy();
|
|
97
|
-
expect(result.content[0].text).toContain("Workspace initialized");
|
|
98
|
-
expect(mockManager.initialize).toHaveBeenCalled();
|
|
99
|
-
expect(mockManager.updateMetadata).toHaveBeenCalledWith(expect.objectContaining({
|
|
100
|
-
executorVersion: expect.any(String),
|
|
101
|
-
}));
|
|
102
|
-
});
|
|
103
|
-
it("returns error when workspace already exists without force flag", async () => {
|
|
104
|
-
mockManager.exists.mockResolvedValue(true);
|
|
105
|
-
const result = await handler({
|
|
106
|
-
workspacePath: tmpDir,
|
|
107
|
-
services: [
|
|
108
|
-
{ serviceName: "svc", language: "python", testDirectory: "tests" },
|
|
109
|
-
],
|
|
110
|
-
scanToken: "valid-token",
|
|
111
|
-
force: false,
|
|
112
|
-
});
|
|
113
|
-
expect(result.isError).toBe(false);
|
|
114
|
-
expect(result.content[0].text).toContain("already initialized");
|
|
115
|
-
});
|
|
116
|
-
it("returns error when scanToken is invalid", async () => {
|
|
117
|
-
validateAndConsumeScanToken.mockReturnValue(false);
|
|
118
|
-
const result = await handler({
|
|
119
|
-
workspacePath: tmpDir,
|
|
120
|
-
services: [
|
|
121
|
-
{ serviceName: "svc", language: "python", testDirectory: "tests" },
|
|
122
|
-
],
|
|
123
|
-
scanToken: "bad-token",
|
|
124
|
-
force: false,
|
|
125
|
-
});
|
|
126
|
-
expect(result.isError).toBe(true);
|
|
127
|
-
expect(result.content[0].text).toContain("Invalid or expired scanToken");
|
|
128
|
-
});
|
|
129
|
-
it("returns error when no services provided", async () => {
|
|
130
|
-
const result = await handler({
|
|
131
|
-
workspacePath: tmpDir,
|
|
132
|
-
services: [],
|
|
133
|
-
scanToken: "valid-token",
|
|
134
|
-
force: false,
|
|
135
|
-
});
|
|
136
|
-
expect(result.isError).toBe(true);
|
|
137
|
-
expect(result.content[0].text).toContain("No services provided");
|
|
138
|
-
});
|
|
139
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
import { upsertServicesByRepo } from "./serviceUpsert.js";
|
|
2
|
-
// upsertServicesByRepo is the merge=true core: it upserts incoming services into
|
|
3
|
-
// the existing list keyed on the composite (repository, serviceName), so a
|
|
4
|
-
// same-named service from a different repo is a distinct entry rather than an
|
|
5
|
-
// overwrite. This is what makes the unified multi-repo workspace.yml correct.
|
|
6
|
-
describe("upsertServicesByRepo", () => {
|
|
7
|
-
const svc = (serviceName, repository, testDirectory) => ({ serviceName, repository, testDirectory });
|
|
8
|
-
it("keeps same-named services from different repos as distinct entries", () => {
|
|
9
|
-
const existing = [svc("api", undefined, "tests/primary")];
|
|
10
|
-
const result = upsertServicesByRepo(existing, [
|
|
11
|
-
svc("api", "letsramp/api-insight", "tests/py"),
|
|
12
|
-
]);
|
|
13
|
-
expect(result).toHaveLength(2);
|
|
14
|
-
expect(result.find((s) => !s.repository)?.testDirectory).toBe("tests/primary");
|
|
15
|
-
expect(result.find((s) => s.repository === "letsramp/api-insight")?.testDirectory).toBe("tests/py");
|
|
16
|
-
});
|
|
17
|
-
it("updates only the matching (repository, serviceName) entry in place", () => {
|
|
18
|
-
const existing = [
|
|
19
|
-
svc("api", undefined, "tests/primary"),
|
|
20
|
-
svc("api", "letsramp/api-insight", "tests/py"),
|
|
21
|
-
];
|
|
22
|
-
// Re-register the related repo's service with a new testDirectory.
|
|
23
|
-
const result = upsertServicesByRepo(existing, [
|
|
24
|
-
svc("api", "letsramp/api-insight", "tests/py-v2"),
|
|
25
|
-
]);
|
|
26
|
-
expect(result).toHaveLength(2);
|
|
27
|
-
// Primary untouched.
|
|
28
|
-
expect(result.find((s) => !s.repository)?.testDirectory).toBe("tests/primary");
|
|
29
|
-
// Related repo's entry updated in place (not duplicated).
|
|
30
|
-
const related = result.filter((s) => s.repository === "letsramp/api-insight");
|
|
31
|
-
expect(related).toHaveLength(1);
|
|
32
|
-
expect(related[0].testDirectory).toBe("tests/py-v2");
|
|
33
|
-
});
|
|
34
|
-
it("treats an absent repository as the primary (normalized to '')", () => {
|
|
35
|
-
const existing = [svc("api", undefined, "tests/primary")];
|
|
36
|
-
// A second primary write (no repository) upserts the SAME entry.
|
|
37
|
-
const result = upsertServicesByRepo(existing, [svc("api", undefined, "tests/primary-v2")]);
|
|
38
|
-
expect(result).toHaveLength(1);
|
|
39
|
-
expect(result[0].testDirectory).toBe("tests/primary-v2");
|
|
40
|
-
});
|
|
41
|
-
it("appends brand-new services from multiple repos", () => {
|
|
42
|
-
const result = upsertServicesByRepo([], [
|
|
43
|
-
svc("web", "letsramp/frontend", "tests/web"),
|
|
44
|
-
svc("api", "letsramp/backend", "tests/api"),
|
|
45
|
-
svc("api", undefined, "tests/primary"),
|
|
46
|
-
]);
|
|
47
|
-
expect(result).toHaveLength(3);
|
|
48
|
-
expect(new Set(result.map((s) => `${s.repository ?? ""}|${s.serviceName}`))).toEqual(new Set(["letsramp/frontend|web", "letsramp/backend|api", "|api"]));
|
|
49
|
-
});
|
|
50
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|