@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,86 +0,0 @@
|
|
|
1
|
-
// @ts-nocheck - Jest ESM type inference issues
|
|
2
|
-
import { jest } from "@jest/globals";
|
|
3
|
-
import * as fs from "fs";
|
|
4
|
-
import * as path from "path";
|
|
5
|
-
import { fileURLToPath } from "url";
|
|
6
|
-
// AnalyticsService resolves the MCP version with
|
|
7
|
-
// `fileURLToPath(import.meta.url)` at module scope. Under ts-jest's old
|
|
8
|
-
// CommonJS transform that top-level import.meta failed to compile with TS1343,
|
|
9
|
-
// so this module — and therefore this test — could not exist. The native-ESM
|
|
10
|
-
// Jest config (SKYR-3799) is what makes it loadable.
|
|
11
|
-
// pushToolEvent lives in @skyramp/skyramp, which pulls the native dylib. Mock
|
|
12
|
-
// it so we can assert on the event payload without loading the binary. Under
|
|
13
|
-
// native ESM jest.mock is not hoisted — use unstable_mockModule + dynamic
|
|
14
|
-
// import of the subject below.
|
|
15
|
-
const pushToolEvent = jest.fn();
|
|
16
|
-
jest.unstable_mockModule("@skyramp/skyramp", () => ({ pushToolEvent }));
|
|
17
|
-
jest.unstable_mockModule("../utils/telemetry.js", () => ({
|
|
18
|
-
getEntryPoint: jest.fn(() => "mcp"),
|
|
19
|
-
getCIPlatform: jest.fn(() => undefined),
|
|
20
|
-
getRepositoryInfo: jest.fn(async () => ({})),
|
|
21
|
-
}));
|
|
22
|
-
jest.unstable_mockModule("../utils/logger.js", () => ({
|
|
23
|
-
logger: { info: jest.fn(), error: jest.fn(), debug: jest.fn(), warn: jest.fn() },
|
|
24
|
-
}));
|
|
25
|
-
const { AnalyticsService } = await import("./AnalyticsService.js");
|
|
26
|
-
const telemetry = await import("../utils/telemetry.js");
|
|
27
|
-
// The version the production code should resolve: package.json two levels up
|
|
28
|
-
// from this module's own location — the same lookup getMCPPackageVersion does
|
|
29
|
-
// via import.meta.url. Computing it the same way here proves the test harness
|
|
30
|
-
// itself supports import.meta now.
|
|
31
|
-
const expectedVersion = JSON.parse(fs.readFileSync(path.join(path.dirname(fileURLToPath(import.meta.url)), "../../package.json"), "utf8")).version;
|
|
32
|
-
describe("AnalyticsService — import.meta-based version resolution", () => {
|
|
33
|
-
beforeEach(() => {
|
|
34
|
-
jest.clearAllMocks();
|
|
35
|
-
telemetry.getEntryPoint.mockReturnValue("mcp");
|
|
36
|
-
telemetry.getCIPlatform.mockReturnValue(undefined);
|
|
37
|
-
telemetry.getRepositoryInfo.mockResolvedValue({});
|
|
38
|
-
});
|
|
39
|
-
it("stamps the package.json version (resolved via import.meta.url) into the event params", async () => {
|
|
40
|
-
const params = {};
|
|
41
|
-
await AnalyticsService.pushMCPToolEvent("skyramp_smoke_test_generation", undefined, params);
|
|
42
|
-
expect(pushToolEvent).toHaveBeenCalledTimes(1);
|
|
43
|
-
const [, toolName, , sentParams] = pushToolEvent.mock.calls[0];
|
|
44
|
-
expect(toolName).toBe("skyramp_smoke_test_generation");
|
|
45
|
-
expect(sentParams.mcpServerVersion).toBe(expectedVersion);
|
|
46
|
-
// Sanity: a real semver-ish string, not an empty/undefined fallback.
|
|
47
|
-
expect(sentParams.mcpServerVersion).toMatch(/^\d+\.\d+\.\d+/);
|
|
48
|
-
});
|
|
49
|
-
it("concatenates error text from an isError result into the event error message", async () => {
|
|
50
|
-
const result = {
|
|
51
|
-
isError: true,
|
|
52
|
-
content: [
|
|
53
|
-
{ type: "text", text: "first failure" },
|
|
54
|
-
{ type: "text", text: "second failure" },
|
|
55
|
-
],
|
|
56
|
-
};
|
|
57
|
-
await AnalyticsService.pushMCPToolEvent("skyramp_execute_test", result, {});
|
|
58
|
-
const [, , errorMessage] = pushToolEvent.mock.calls[0];
|
|
59
|
-
expect(errorMessage).toBe("first failure, second failure");
|
|
60
|
-
});
|
|
61
|
-
it("sends an empty error message for a successful result", async () => {
|
|
62
|
-
const result = { isError: false, content: [{ type: "text", text: "ok" }] };
|
|
63
|
-
await AnalyticsService.pushMCPToolEvent("skyramp_execute_test", result, {});
|
|
64
|
-
const [, , errorMessage] = pushToolEvent.mock.calls[0];
|
|
65
|
-
expect(errorMessage).toBe("");
|
|
66
|
-
});
|
|
67
|
-
it("includes ciPlatform in the params when running in CI", async () => {
|
|
68
|
-
telemetry.getCIPlatform.mockReturnValue("github_actions");
|
|
69
|
-
const params = {};
|
|
70
|
-
await AnalyticsService.pushMCPToolEvent("skyramp_init_workspace", undefined, params);
|
|
71
|
-
const [, , , sentParams] = pushToolEvent.mock.calls[0];
|
|
72
|
-
expect(sentParams.ciPlatform).toBe("github_actions");
|
|
73
|
-
});
|
|
74
|
-
it("merges repository info into the params", async () => {
|
|
75
|
-
telemetry.getRepositoryInfo.mockResolvedValue({ repoName: "letsramp/mcp", repoOwner: "letsramp" });
|
|
76
|
-
const params = { repositoryPath: "/repo" };
|
|
77
|
-
await AnalyticsService.pushMCPToolEvent("skyramp_analyze_changes", undefined, params);
|
|
78
|
-
expect(telemetry.getRepositoryInfo).toHaveBeenCalledWith("/repo");
|
|
79
|
-
const [, , , sentParams] = pushToolEvent.mock.calls[0];
|
|
80
|
-
expect(sentParams.repoName).toBe("letsramp/mcp");
|
|
81
|
-
});
|
|
82
|
-
it("swallows errors from pushToolEvent (telemetry must never break a tool call)", async () => {
|
|
83
|
-
pushToolEvent.mockRejectedValueOnce(new Error("network down"));
|
|
84
|
-
await expect(AnalyticsService.pushMCPToolEvent("skyramp_smoke_test_generation", undefined, {})).resolves.toBeUndefined();
|
|
85
|
-
});
|
|
86
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,162 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Integration test: verifies query params survive the full pipeline
|
|
3
|
-
* TS (ScenarioGenerationService) → JSON file → Go binary → generated test code
|
|
4
|
-
*
|
|
5
|
-
* Requires: @skyramp/skyramp native dylib (skips if not available)
|
|
6
|
-
*/
|
|
7
|
-
import { jest } from "@jest/globals";
|
|
8
|
-
import fs from "fs";
|
|
9
|
-
import path from "path";
|
|
10
|
-
import os from "os";
|
|
11
|
-
import { createRequire } from "module";
|
|
12
|
-
import { ScenarioGenerationService } from "./ScenarioGenerationService.js";
|
|
13
|
-
// Native dylib is a CommonJS addon — load it via createRequire under ESM.
|
|
14
|
-
const require = createRequire(import.meta.url);
|
|
15
|
-
let SkyrampClient;
|
|
16
|
-
try {
|
|
17
|
-
SkyrampClient = require("@skyramp/skyramp").SkyrampClient;
|
|
18
|
-
}
|
|
19
|
-
catch {
|
|
20
|
-
SkyrampClient = null;
|
|
21
|
-
}
|
|
22
|
-
// These tests require the native Go binary which is platform-specific.
|
|
23
|
-
// Skip in CI or when the binary isn't available for this OS/arch.
|
|
24
|
-
const isCI = Boolean(process.env.CI || process.env.GITHUB_ACTIONS);
|
|
25
|
-
const describeIfBinary = SkyrampClient && !isCI ? describe : describe.skip;
|
|
26
|
-
function buildTrace(overrides) {
|
|
27
|
-
const service = new ScenarioGenerationService();
|
|
28
|
-
return service.generateTraceRequestFromInput({
|
|
29
|
-
scenarioName: "integration-qp-test",
|
|
30
|
-
destination: "localhost",
|
|
31
|
-
baseURL: "http://localhost:8080",
|
|
32
|
-
method: "GET",
|
|
33
|
-
path: "/api/v1/items",
|
|
34
|
-
outputDir: os.tmpdir(),
|
|
35
|
-
authHeader: "",
|
|
36
|
-
authScheme: "",
|
|
37
|
-
...overrides,
|
|
38
|
-
});
|
|
39
|
-
}
|
|
40
|
-
describeIfBinary("QueryParams integration: TS → JSON → Go binary → generated code", () => {
|
|
41
|
-
let tmpDir;
|
|
42
|
-
let outputDir;
|
|
43
|
-
let client;
|
|
44
|
-
// Go dylib cold-starts can take a few seconds
|
|
45
|
-
jest.setTimeout(15000);
|
|
46
|
-
beforeAll(() => {
|
|
47
|
-
client = new SkyrampClient();
|
|
48
|
-
});
|
|
49
|
-
afterAll(() => {
|
|
50
|
-
client?.close?.();
|
|
51
|
-
});
|
|
52
|
-
beforeEach(() => {
|
|
53
|
-
tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "skyramp-qp-integration-"));
|
|
54
|
-
outputDir = path.join(tmpDir, "output");
|
|
55
|
-
fs.mkdirSync(outputDir);
|
|
56
|
-
});
|
|
57
|
-
afterEach(() => {
|
|
58
|
-
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
59
|
-
});
|
|
60
|
-
async function generateFromTrace(trace) {
|
|
61
|
-
const traceFile = path.join(tmpDir, "scenario.json");
|
|
62
|
-
fs.writeFileSync(traceFile, JSON.stringify([trace], null, 2));
|
|
63
|
-
await client.generateRestTest({
|
|
64
|
-
testType: "integration",
|
|
65
|
-
language: "python",
|
|
66
|
-
framework: "pytest",
|
|
67
|
-
outputDir,
|
|
68
|
-
force: true,
|
|
69
|
-
traceFilePath: traceFile,
|
|
70
|
-
});
|
|
71
|
-
const files = fs.readdirSync(outputDir).filter((f) => f.endsWith(".py"));
|
|
72
|
-
if (files.length === 0)
|
|
73
|
-
throw new Error("No generated test file found");
|
|
74
|
-
return fs.readFileSync(path.join(outputDir, files[0]), "utf8");
|
|
75
|
-
}
|
|
76
|
-
it("bracket-notation keys pass through to generated test code", async () => {
|
|
77
|
-
// Simulates: GET /api/v1/items?filter[status][_eq]=published&limit=25
|
|
78
|
-
const trace = buildTrace({
|
|
79
|
-
queryParams: '{"filter[status][_eq]":"published","limit":25}',
|
|
80
|
-
});
|
|
81
|
-
expect(trace).not.toBeNull();
|
|
82
|
-
expect(trace.QueryParams).toEqual({
|
|
83
|
-
"filter[status][_eq]": ["published"],
|
|
84
|
-
"limit": ["25"],
|
|
85
|
-
});
|
|
86
|
-
const code = await generateFromTrace(trace);
|
|
87
|
-
expect(code).toContain("filter[status][_eq]");
|
|
88
|
-
expect(code).toContain("published");
|
|
89
|
-
expect(code).toContain("limit");
|
|
90
|
-
expect(code).toContain("25");
|
|
91
|
-
});
|
|
92
|
-
it("simple flat params pass through to generated test code", async () => {
|
|
93
|
-
// Simulates: GET /api/v1/items?page=2&status=active
|
|
94
|
-
const trace = buildTrace({
|
|
95
|
-
queryParams: '{"page":2,"status":"active"}',
|
|
96
|
-
});
|
|
97
|
-
expect(trace).not.toBeNull();
|
|
98
|
-
expect(trace.QueryParams).toEqual({
|
|
99
|
-
page: ["2"],
|
|
100
|
-
status: ["active"],
|
|
101
|
-
});
|
|
102
|
-
const code = await generateFromTrace(trace);
|
|
103
|
-
expect(code).toContain("page");
|
|
104
|
-
expect(code).toContain("2");
|
|
105
|
-
expect(code).toContain("status");
|
|
106
|
-
expect(code).toContain("active");
|
|
107
|
-
});
|
|
108
|
-
it("JSON-encoded string value (Elasticsearch-style) passes through intact", async () => {
|
|
109
|
-
// Simulates: GET /search?source={"query":{"match":{"name":"bear"}}}
|
|
110
|
-
const trace = buildTrace({
|
|
111
|
-
queryParams: '{"source":"{\\"query\\":{\\"match\\":{\\"name\\":\\"bear\\"}}}"}',
|
|
112
|
-
});
|
|
113
|
-
expect(trace).not.toBeNull();
|
|
114
|
-
expect(trace.QueryParams).toEqual({
|
|
115
|
-
source: ['{"query":{"match":{"name":"bear"}}}'],
|
|
116
|
-
});
|
|
117
|
-
const code = await generateFromTrace(trace);
|
|
118
|
-
expect(code).toContain("source");
|
|
119
|
-
expect(code).toMatch(/query.*match.*name.*bear/);
|
|
120
|
-
});
|
|
121
|
-
it("array params are comma-joined into a single value in generated code", async () => {
|
|
122
|
-
// Simulates: GET /products?tags=sale,new
|
|
123
|
-
const trace = buildTrace({
|
|
124
|
-
queryParams: '{"tags":["sale","new"]}',
|
|
125
|
-
});
|
|
126
|
-
expect(trace).not.toBeNull();
|
|
127
|
-
expect(trace.QueryParams).toEqual({
|
|
128
|
-
tags: ["sale,new"],
|
|
129
|
-
});
|
|
130
|
-
const code = await generateFromTrace(trace);
|
|
131
|
-
expect(code).toContain("tags");
|
|
132
|
-
expect(code).toContain("sale,new");
|
|
133
|
-
});
|
|
134
|
-
it("null params are omitted — not present in generated code", async () => {
|
|
135
|
-
// null means "don't include this param" — same as axios/fetch omitting undefined
|
|
136
|
-
const trace = buildTrace({
|
|
137
|
-
queryParams: '{"limit":10,"cursor":null}',
|
|
138
|
-
});
|
|
139
|
-
expect(trace).not.toBeNull();
|
|
140
|
-
expect(trace.QueryParams).toEqual({
|
|
141
|
-
limit: ["10"],
|
|
142
|
-
});
|
|
143
|
-
const code = await generateFromTrace(trace);
|
|
144
|
-
expect(code).toContain("limit");
|
|
145
|
-
expect(code).toContain("10");
|
|
146
|
-
expect(code).not.toContain("cursor");
|
|
147
|
-
});
|
|
148
|
-
it("nested object fallback produces valid JSON string in generated code", async () => {
|
|
149
|
-
// LLM mistakenly sends nested object — service JSON-stringifies it
|
|
150
|
-
const trace = buildTrace({
|
|
151
|
-
queryParams: '{"filter":{"status":"active"}}',
|
|
152
|
-
});
|
|
153
|
-
expect(trace).not.toBeNull();
|
|
154
|
-
expect(trace.QueryParams).toEqual({
|
|
155
|
-
filter: ['{"status":"active"}'],
|
|
156
|
-
});
|
|
157
|
-
const code = await generateFromTrace(trace);
|
|
158
|
-
expect(code).toContain("filter");
|
|
159
|
-
expect(code).not.toContain("[object Object]");
|
|
160
|
-
expect(code).not.toContain("map[");
|
|
161
|
-
});
|
|
162
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,414 +0,0 @@
|
|
|
1
|
-
import { ScenarioGenerationService } from "./ScenarioGenerationService.js";
|
|
2
|
-
import { AUTH_PLACEHOLDER_TOKEN } from "../types/TestTypes.js";
|
|
3
|
-
const BASE_PARAMS = {
|
|
4
|
-
scenarioName: "test-scenario",
|
|
5
|
-
destination: "api.example.com",
|
|
6
|
-
method: "GET",
|
|
7
|
-
path: "/api/v1/items",
|
|
8
|
-
outputDir: "/tmp/skyramp-test",
|
|
9
|
-
baseURL: "http://localhost:8000",
|
|
10
|
-
};
|
|
11
|
-
function generateTrace(overrides = {}) {
|
|
12
|
-
const service = new ScenarioGenerationService();
|
|
13
|
-
return service["generateTraceRequestFromInput"]({
|
|
14
|
-
...BASE_PARAMS,
|
|
15
|
-
...overrides,
|
|
16
|
-
});
|
|
17
|
-
}
|
|
18
|
-
function getAuthHeaders(trace) {
|
|
19
|
-
const { "Content-Type": _, ...rest } = trace.RequestHeaders;
|
|
20
|
-
return rest;
|
|
21
|
-
}
|
|
22
|
-
describe("ScenarioGenerationService — auth header flavors", () => {
|
|
23
|
-
// 1. Public API — no auth (explicit empty string)
|
|
24
|
-
it("skips auth header when authHeader is empty string", () => {
|
|
25
|
-
const trace = generateTrace({ authHeader: "" });
|
|
26
|
-
expect(trace).not.toBeNull();
|
|
27
|
-
const auth = getAuthHeaders(trace);
|
|
28
|
-
expect(auth).toEqual({});
|
|
29
|
-
expect(trace.RequestHeaders["Authorization"]).toBeUndefined();
|
|
30
|
-
});
|
|
31
|
-
// 2. Public API — no auth params at all (undefined)
|
|
32
|
-
it("skips auth header when authHeader is undefined", () => {
|
|
33
|
-
const trace = generateTrace({ authHeader: undefined });
|
|
34
|
-
expect(trace).not.toBeNull();
|
|
35
|
-
const auth = getAuthHeaders(trace);
|
|
36
|
-
expect(auth).toEqual({});
|
|
37
|
-
});
|
|
38
|
-
// 3. Bearer token — Authorization with scheme embedded in trace for CLI compatibility
|
|
39
|
-
it("generates Authorization: Bearer <token> header in trace", () => {
|
|
40
|
-
const trace = generateTrace({
|
|
41
|
-
authHeader: "Authorization",
|
|
42
|
-
authScheme: "Bearer",
|
|
43
|
-
});
|
|
44
|
-
expect(trace).not.toBeNull();
|
|
45
|
-
expect(trace.RequestHeaders["Authorization"]).toEqual([
|
|
46
|
-
`Bearer ${AUTH_PLACEHOLDER_TOKEN}`,
|
|
47
|
-
]);
|
|
48
|
-
});
|
|
49
|
-
// 4. DRF Token — Authorization with Token scheme embedded in trace
|
|
50
|
-
it("generates Authorization: Token <token> header in trace", () => {
|
|
51
|
-
const trace = generateTrace({
|
|
52
|
-
authHeader: "Authorization",
|
|
53
|
-
authScheme: "Token",
|
|
54
|
-
});
|
|
55
|
-
expect(trace).not.toBeNull();
|
|
56
|
-
expect(trace.RequestHeaders["Authorization"]).toEqual([
|
|
57
|
-
`Token ${AUTH_PLACEHOLDER_TOKEN}`,
|
|
58
|
-
]);
|
|
59
|
-
});
|
|
60
|
-
// 5. Basic auth — Authorization with Basic scheme embedded in trace
|
|
61
|
-
it("generates Authorization: Basic <token> header in trace", () => {
|
|
62
|
-
const trace = generateTrace({
|
|
63
|
-
authHeader: "Authorization",
|
|
64
|
-
authScheme: "Basic",
|
|
65
|
-
});
|
|
66
|
-
expect(trace).not.toBeNull();
|
|
67
|
-
expect(trace.RequestHeaders["Authorization"]).toEqual([
|
|
68
|
-
`Basic ${AUTH_PLACEHOLDER_TOKEN}`,
|
|
69
|
-
]);
|
|
70
|
-
});
|
|
71
|
-
// 6. Raw token — Authorization with no scheme, placeholder only
|
|
72
|
-
it("generates Authorization with raw placeholder when authScheme is empty", () => {
|
|
73
|
-
const trace = generateTrace({
|
|
74
|
-
authHeader: "Authorization",
|
|
75
|
-
authScheme: "",
|
|
76
|
-
});
|
|
77
|
-
expect(trace).not.toBeNull();
|
|
78
|
-
expect(trace.RequestHeaders["Authorization"]).toEqual([
|
|
79
|
-
AUTH_PLACEHOLDER_TOKEN,
|
|
80
|
-
]);
|
|
81
|
-
});
|
|
82
|
-
// 7. X-API-Key header with explicit token
|
|
83
|
-
it("includes X-API-Key with explicit token in trace", () => {
|
|
84
|
-
const trace = generateTrace({
|
|
85
|
-
authHeader: "X-API-Key",
|
|
86
|
-
authToken: AUTH_PLACEHOLDER_TOKEN,
|
|
87
|
-
});
|
|
88
|
-
expect(trace).not.toBeNull();
|
|
89
|
-
expect(trace.RequestHeaders["X-API-Key"]).toEqual([
|
|
90
|
-
AUTH_PLACEHOLDER_TOKEN,
|
|
91
|
-
]);
|
|
92
|
-
expect(trace.RequestHeaders["Authorization"]).toBeUndefined();
|
|
93
|
-
});
|
|
94
|
-
// 8. Cookie-based auth (e.g. NextAuth)
|
|
95
|
-
it("includes Cookie with session token in trace", () => {
|
|
96
|
-
const trace = generateTrace({
|
|
97
|
-
authHeader: "Cookie",
|
|
98
|
-
authToken: `session=${AUTH_PLACEHOLDER_TOKEN}`,
|
|
99
|
-
});
|
|
100
|
-
expect(trace).not.toBeNull();
|
|
101
|
-
expect(trace.RequestHeaders["Cookie"]).toEqual([
|
|
102
|
-
`session=${AUTH_PLACEHOLDER_TOKEN}`,
|
|
103
|
-
]);
|
|
104
|
-
expect(trace.RequestHeaders["Authorization"]).toBeUndefined();
|
|
105
|
-
});
|
|
106
|
-
// 9. Custom header (e.g. internal microservice)
|
|
107
|
-
it("includes custom auth header with token in trace", () => {
|
|
108
|
-
const trace = generateTrace({
|
|
109
|
-
authHeader: "X-Internal-Service-Key",
|
|
110
|
-
authToken: AUTH_PLACEHOLDER_TOKEN,
|
|
111
|
-
});
|
|
112
|
-
expect(trace).not.toBeNull();
|
|
113
|
-
expect(trace.RequestHeaders["X-Internal-Service-Key"]).toEqual([
|
|
114
|
-
AUTH_PLACEHOLDER_TOKEN,
|
|
115
|
-
]);
|
|
116
|
-
expect(trace.RequestHeaders["Authorization"]).toBeUndefined();
|
|
117
|
-
});
|
|
118
|
-
// 10. OAuth — Authorization with OAuth scheme embedded in trace
|
|
119
|
-
it("generates Authorization: OAuth <token> header in trace", () => {
|
|
120
|
-
const trace = generateTrace({
|
|
121
|
-
authHeader: "Authorization",
|
|
122
|
-
authScheme: "OAuth",
|
|
123
|
-
});
|
|
124
|
-
expect(trace).not.toBeNull();
|
|
125
|
-
expect(trace.RequestHeaders["Authorization"]).toEqual([
|
|
126
|
-
`OAuth ${AUTH_PLACEHOLDER_TOKEN}`,
|
|
127
|
-
]);
|
|
128
|
-
});
|
|
129
|
-
// 11. Explicit token overrides scheme auto-generation
|
|
130
|
-
it("uses explicit authToken and ignores authScheme when both provided", () => {
|
|
131
|
-
const trace = generateTrace({
|
|
132
|
-
authHeader: "Authorization",
|
|
133
|
-
authScheme: "Bearer",
|
|
134
|
-
authToken: "my-hardcoded-token-123",
|
|
135
|
-
});
|
|
136
|
-
expect(trace).not.toBeNull();
|
|
137
|
-
expect(trace.RequestHeaders["Authorization"]).toEqual([
|
|
138
|
-
"my-hardcoded-token-123",
|
|
139
|
-
]);
|
|
140
|
-
});
|
|
141
|
-
// 12. Non-Authorization header without token — placeholder in trace (CLI parameterizes it)
|
|
142
|
-
it("includes X-API-Key with placeholder when authToken is missing", () => {
|
|
143
|
-
const trace = generateTrace({
|
|
144
|
-
authHeader: "X-API-Key",
|
|
145
|
-
});
|
|
146
|
-
expect(trace).not.toBeNull();
|
|
147
|
-
expect(trace.RequestHeaders["X-API-Key"]).toEqual([AUTH_PLACEHOLDER_TOKEN]);
|
|
148
|
-
});
|
|
149
|
-
// 16. Cookie header without explicit token — placeholder in trace (CLI parameterizes it)
|
|
150
|
-
it("includes Cookie with placeholder when authToken is missing", () => {
|
|
151
|
-
const trace = generateTrace({
|
|
152
|
-
authHeader: "Cookie",
|
|
153
|
-
});
|
|
154
|
-
expect(trace).not.toBeNull();
|
|
155
|
-
expect(trace.RequestHeaders["Cookie"]).toEqual([AUTH_PLACEHOLDER_TOKEN]);
|
|
156
|
-
});
|
|
157
|
-
// 17. Custom header without explicit token — placeholder in trace (CLI parameterizes it)
|
|
158
|
-
it("includes custom auth header with placeholder when authToken is missing", () => {
|
|
159
|
-
const trace = generateTrace({
|
|
160
|
-
authHeader: "X-Internal-Service-Key",
|
|
161
|
-
});
|
|
162
|
-
expect(trace).not.toBeNull();
|
|
163
|
-
expect(trace.RequestHeaders["X-Internal-Service-Key"]).toEqual([AUTH_PLACEHOLDER_TOKEN]);
|
|
164
|
-
});
|
|
165
|
-
// 13. Content-Type always present regardless of auth
|
|
166
|
-
it("always includes Content-Type regardless of auth config", () => {
|
|
167
|
-
const noAuth = generateTrace({ authHeader: "" });
|
|
168
|
-
const bearer = generateTrace({ authHeader: "Authorization", authScheme: "Bearer" });
|
|
169
|
-
const apiKey = generateTrace({ authHeader: "X-API-Key", authToken: "key123" });
|
|
170
|
-
expect(noAuth.RequestHeaders["Content-Type"]).toEqual(["application/json"]);
|
|
171
|
-
expect(bearer.RequestHeaders["Content-Type"]).toEqual(["application/json"]);
|
|
172
|
-
expect(apiKey.RequestHeaders["Content-Type"]).toEqual(["application/json"]);
|
|
173
|
-
});
|
|
174
|
-
// 14. Firebase AppCheck custom header
|
|
175
|
-
it("includes X-Firebase-AppCheck with token in trace", () => {
|
|
176
|
-
const trace = generateTrace({
|
|
177
|
-
authHeader: "X-Firebase-AppCheck",
|
|
178
|
-
authToken: AUTH_PLACEHOLDER_TOKEN,
|
|
179
|
-
});
|
|
180
|
-
expect(trace).not.toBeNull();
|
|
181
|
-
expect(trace.RequestHeaders["X-Firebase-AppCheck"]).toEqual([
|
|
182
|
-
AUTH_PLACEHOLDER_TOKEN,
|
|
183
|
-
]);
|
|
184
|
-
expect(trace.RequestHeaders["Authorization"]).toBeUndefined();
|
|
185
|
-
});
|
|
186
|
-
// 15. Proxy-Authorization header with explicit token
|
|
187
|
-
it("includes Proxy-Authorization with explicit token in trace", () => {
|
|
188
|
-
const trace = generateTrace({
|
|
189
|
-
authHeader: "Proxy-Authorization",
|
|
190
|
-
authToken: "Bearer proxy-token-abc",
|
|
191
|
-
});
|
|
192
|
-
expect(trace).not.toBeNull();
|
|
193
|
-
expect(trace.RequestHeaders["Proxy-Authorization"]).toEqual([
|
|
194
|
-
"Bearer proxy-token-abc",
|
|
195
|
-
]);
|
|
196
|
-
expect(trace.RequestHeaders["Authorization"]).toBeUndefined();
|
|
197
|
-
});
|
|
198
|
-
});
|
|
199
|
-
describe("ScenarioGenerationService — queryParams handling", () => {
|
|
200
|
-
// --- Primitive values (strings, numbers, booleans) ---
|
|
201
|
-
it("coerces string values into single-element arrays", () => {
|
|
202
|
-
const trace = generateTrace({ queryParams: '{"status":"active"}' });
|
|
203
|
-
expect(trace.QueryParams).toEqual({ status: ["active"] });
|
|
204
|
-
});
|
|
205
|
-
it("coerces numeric values to string", () => {
|
|
206
|
-
const trace = generateTrace({ queryParams: '{"limit":10,"offset":0}' });
|
|
207
|
-
expect(trace.QueryParams).toEqual({ limit: ["10"], offset: ["0"] });
|
|
208
|
-
});
|
|
209
|
-
it("coerces boolean values to string", () => {
|
|
210
|
-
const trace = generateTrace({ queryParams: '{"active":true,"deleted":false}' });
|
|
211
|
-
expect(trace.QueryParams).toEqual({ active: ["true"], deleted: ["false"] });
|
|
212
|
-
});
|
|
213
|
-
it("coerces empty string to single-element array", () => {
|
|
214
|
-
const trace = generateTrace({ queryParams: '{"q":""}' });
|
|
215
|
-
expect(trace.QueryParams).toEqual({ q: [""] });
|
|
216
|
-
});
|
|
217
|
-
it("passes bracket-notation keys through verbatim (Directus regression)", () => {
|
|
218
|
-
// Root cause of the original P0: filter[title][_neq] must reach the Go backend
|
|
219
|
-
// as the literal key string, not as a nested object or [object Object].
|
|
220
|
-
const trace = generateTrace({
|
|
221
|
-
queryParams: '{"filter[status][_eq]":"published","filter[date_created][_gte]":"2024-01-01","limit":25}',
|
|
222
|
-
});
|
|
223
|
-
expect(trace.QueryParams["filter[status][_eq]"]).toEqual(["published"]);
|
|
224
|
-
expect(trace.QueryParams["filter[date_created][_gte]"]).toEqual(["2024-01-01"]);
|
|
225
|
-
expect(trace.QueryParams["limit"]).toEqual(["25"]);
|
|
226
|
-
});
|
|
227
|
-
// --- Null omission ---
|
|
228
|
-
it("omits null values entirely (prevents ?status=null in URL)", () => {
|
|
229
|
-
// null means "omit this param" — same convention as axios/fetch omitting undefined params
|
|
230
|
-
const trace = generateTrace({ queryParams: '{"cursor":null,"limit":20}' });
|
|
231
|
-
expect(trace.QueryParams["cursor"]).toBeUndefined();
|
|
232
|
-
expect(trace.QueryParams["limit"]).toEqual(["20"]);
|
|
233
|
-
});
|
|
234
|
-
it("omits all-null object leaving only non-null keys", () => {
|
|
235
|
-
const trace = generateTrace({ queryParams: '{"a":null,"b":null,"c":"keep"}' });
|
|
236
|
-
expect(trace.QueryParams).toEqual({ c: ["keep"] });
|
|
237
|
-
});
|
|
238
|
-
// --- Array values: filter nulls, comma-join into single element ---
|
|
239
|
-
it("array of strings joins into single comma-separated value", () => {
|
|
240
|
-
const trace = generateTrace({ queryParams: '{"tags":["sale","new","featured"]}' });
|
|
241
|
-
expect(trace.QueryParams["tags"]).toEqual(["sale,new,featured"]);
|
|
242
|
-
});
|
|
243
|
-
it("array of mixed primitives coerces each to string then joins", () => {
|
|
244
|
-
const trace = generateTrace({ queryParams: '{"ids":[1,2,"three",true,0,false]}' });
|
|
245
|
-
expect(trace.QueryParams["ids"]).toEqual(["1,2,three,true,0,false"]);
|
|
246
|
-
});
|
|
247
|
-
it("array with null items filters nulls before joining", () => {
|
|
248
|
-
const trace = generateTrace({ queryParams: '{"tags":[null,"active",null,"pending"]}' });
|
|
249
|
-
expect(trace.QueryParams["tags"]).toEqual(["active,pending"]);
|
|
250
|
-
});
|
|
251
|
-
it("array with all-null items omits the key entirely", () => {
|
|
252
|
-
const trace = generateTrace({ queryParams: '{"tags":[null,null],"limit":5}' });
|
|
253
|
-
expect(trace.QueryParams["tags"]).toBeUndefined();
|
|
254
|
-
expect(trace.QueryParams["limit"]).toEqual(["5"]);
|
|
255
|
-
});
|
|
256
|
-
it("empty array omits the key (no items after filtering)", () => {
|
|
257
|
-
const trace = generateTrace({ queryParams: '{"ids":[],"limit":10}' });
|
|
258
|
-
expect(trace.QueryParams["ids"]).toBeUndefined();
|
|
259
|
-
expect(trace.QueryParams["limit"]).toEqual(["10"]);
|
|
260
|
-
});
|
|
261
|
-
it("array containing objects JSON-stringifies each then joins", () => {
|
|
262
|
-
const trace = generateTrace({
|
|
263
|
-
queryParams: '{"filters":[{"field":"status","value":"active"},{"field":"price","op":"gte","value":"10"}]}',
|
|
264
|
-
});
|
|
265
|
-
expect(trace.QueryParams["filters"]).toEqual([
|
|
266
|
-
'{"field":"status","value":"active"},{"field":"price","op":"gte","value":"10"}',
|
|
267
|
-
]);
|
|
268
|
-
});
|
|
269
|
-
it("array mixing objects, primitives, and nulls: filters nulls, stringifies objects, joins all", () => {
|
|
270
|
-
const trace = generateTrace({
|
|
271
|
-
queryParams: '{"mixed":[null,{"nested":true},"plain",42,null]}',
|
|
272
|
-
});
|
|
273
|
-
expect(trace.QueryParams["mixed"]).toEqual(['{"nested":true},plain,42']);
|
|
274
|
-
});
|
|
275
|
-
// --- Nested object fallback: JSON.stringify ---
|
|
276
|
-
it("nested object is JSON-stringified as fallback", () => {
|
|
277
|
-
const trace = generateTrace({
|
|
278
|
-
queryParams: '{"filter":{"status":"active","min_price":10}}',
|
|
279
|
-
});
|
|
280
|
-
expect(trace.QueryParams["filter"]).toEqual(['{"status":"active","min_price":10}']);
|
|
281
|
-
});
|
|
282
|
-
it("deeply nested object is fully JSON-stringified", () => {
|
|
283
|
-
const trace = generateTrace({
|
|
284
|
-
queryParams: '{"filter":{"title":{"_neq":"not-a-number"}}}',
|
|
285
|
-
});
|
|
286
|
-
expect(trace.QueryParams["filter"]).toEqual(['{"title":{"_neq":"not-a-number"}}']);
|
|
287
|
-
});
|
|
288
|
-
it("empty nested object is JSON-stringified to '{}'", () => {
|
|
289
|
-
const trace = generateTrace({ queryParams: '{"filter":{}}' });
|
|
290
|
-
expect(trace.QueryParams["filter"]).toEqual(["{}"]);
|
|
291
|
-
});
|
|
292
|
-
// --- JSON parsing and object validation ---
|
|
293
|
-
it("omitted queryParams produces empty QueryParams", () => {
|
|
294
|
-
const trace = generateTrace({});
|
|
295
|
-
expect(trace.QueryParams).toEqual({});
|
|
296
|
-
});
|
|
297
|
-
it("invalid JSON produces empty QueryParams without throwing", () => {
|
|
298
|
-
const trace = generateTrace({ queryParams: "not-valid-json{" });
|
|
299
|
-
expect(trace).not.toBeNull();
|
|
300
|
-
expect(trace.QueryParams).toEqual({});
|
|
301
|
-
});
|
|
302
|
-
it("JSON array (not object) produces empty QueryParams", () => {
|
|
303
|
-
const trace = generateTrace({ queryParams: '["limit","10"]' });
|
|
304
|
-
expect(trace.QueryParams).toEqual({});
|
|
305
|
-
});
|
|
306
|
-
it("JSON null produces empty QueryParams", () => {
|
|
307
|
-
const trace = generateTrace({ queryParams: "null" });
|
|
308
|
-
expect(trace.QueryParams).toEqual({});
|
|
309
|
-
});
|
|
310
|
-
it("JSON number produces empty QueryParams", () => {
|
|
311
|
-
const trace = generateTrace({ queryParams: "42" });
|
|
312
|
-
expect(trace.QueryParams).toEqual({});
|
|
313
|
-
});
|
|
314
|
-
it("JSON string produces empty QueryParams", () => {
|
|
315
|
-
const trace = generateTrace({ queryParams: '"just a string"' });
|
|
316
|
-
expect(trace.QueryParams).toEqual({});
|
|
317
|
-
});
|
|
318
|
-
it("empty object produces empty QueryParams", () => {
|
|
319
|
-
const trace = generateTrace({ queryParams: '{}' });
|
|
320
|
-
expect(trace.QueryParams).toEqual({});
|
|
321
|
-
});
|
|
322
|
-
// --- Security: PROTO_KEYS guard ---
|
|
323
|
-
it("skips __proto__ key to prevent prototype pollution via setter", () => {
|
|
324
|
-
const trace = generateTrace({
|
|
325
|
-
queryParams: '{"__proto__":{"polluted":true},"limit":10}',
|
|
326
|
-
});
|
|
327
|
-
expect(Object.hasOwn(trace.QueryParams, "__proto__")).toBe(false);
|
|
328
|
-
expect(Object.getPrototypeOf(trace.QueryParams)).toBe(Object.prototype);
|
|
329
|
-
expect(trace.QueryParams["limit"]).toEqual(["10"]);
|
|
330
|
-
});
|
|
331
|
-
it("skips constructor and prototype keys", () => {
|
|
332
|
-
const trace = generateTrace({
|
|
333
|
-
queryParams: '{"constructor":"pwned","prototype":"evil","valid":"yes"}',
|
|
334
|
-
});
|
|
335
|
-
expect(Object.hasOwn(trace.QueryParams, "constructor")).toBe(false);
|
|
336
|
-
expect(Object.hasOwn(trace.QueryParams, "prototype")).toBe(false);
|
|
337
|
-
expect(trace.QueryParams["valid"]).toEqual(["yes"]);
|
|
338
|
-
});
|
|
339
|
-
});
|
|
340
|
-
describe("ScenarioGenerationService — baseURL parsing", () => {
|
|
341
|
-
it("parses http baseURL correctly", () => {
|
|
342
|
-
const trace = generateTrace({
|
|
343
|
-
baseURL: "http://localhost:3000",
|
|
344
|
-
authHeader: "",
|
|
345
|
-
});
|
|
346
|
-
expect(trace).not.toBeNull();
|
|
347
|
-
expect(trace.Scheme).toBe("http");
|
|
348
|
-
expect(trace.Destination).toBe("localhost");
|
|
349
|
-
expect(trace.Port).toBe(3000);
|
|
350
|
-
});
|
|
351
|
-
it("parses https baseURL with default port", () => {
|
|
352
|
-
const trace = generateTrace({
|
|
353
|
-
baseURL: "https://api.example.com",
|
|
354
|
-
authHeader: "",
|
|
355
|
-
});
|
|
356
|
-
expect(trace).not.toBeNull();
|
|
357
|
-
expect(trace.Scheme).toBe("https");
|
|
358
|
-
expect(trace.Destination).toBe("api.example.com");
|
|
359
|
-
expect(trace.Port).toBe(443);
|
|
360
|
-
});
|
|
361
|
-
it("parses http baseURL with default port", () => {
|
|
362
|
-
const trace = generateTrace({
|
|
363
|
-
baseURL: "http://api.example.com",
|
|
364
|
-
authHeader: "",
|
|
365
|
-
});
|
|
366
|
-
expect(trace).not.toBeNull();
|
|
367
|
-
expect(trace.Scheme).toBe("http");
|
|
368
|
-
expect(trace.Port).toBe(80);
|
|
369
|
-
});
|
|
370
|
-
});
|
|
371
|
-
describe("ScenarioGenerationService — unique fields (SKYR-3882)", () => {
|
|
372
|
-
it("carries uniqueFields onto the trace request", () => {
|
|
373
|
-
const trace = generateTrace({ uniqueFields: ["name", "slug"] });
|
|
374
|
-
expect(trace).not.toBeNull();
|
|
375
|
-
expect(trace.UniqueFields).toEqual(["name", "slug"]);
|
|
376
|
-
});
|
|
377
|
-
it("omits UniqueFields when none are provided", () => {
|
|
378
|
-
const trace = generateTrace({});
|
|
379
|
-
expect(trace).not.toBeNull();
|
|
380
|
-
expect(trace.UniqueFields).toBeUndefined();
|
|
381
|
-
});
|
|
382
|
-
});
|
|
383
|
-
describe("ScenarioGenerationService — step timestamps (SKYR-3884)", () => {
|
|
384
|
-
it("stamps consecutive traces from one instance with strictly increasing timestamps", () => {
|
|
385
|
-
// All steps of a batch are generated within the same millisecond, so a bare
|
|
386
|
-
// new Date().toISOString() gives every step the identical timestamp. The Go
|
|
387
|
-
// codegen treats "earlier timestamp" as "prior step" when chaining FKs and
|
|
388
|
-
// path params from create responses — equal stamps break every chain. The
|
|
389
|
-
// service owns the invariant, so callers cannot forget to thread an index.
|
|
390
|
-
const service = new ScenarioGenerationService();
|
|
391
|
-
const traces = [0, 1, 2].map(() => service.generateTraceRequestFromInput(BASE_PARAMS));
|
|
392
|
-
for (let i = 1; i < traces.length; i++) {
|
|
393
|
-
expect(new Date(traces[i].Timestamp).getTime()).toBeGreaterThan(new Date(traces[i - 1].Timestamp).getTime());
|
|
394
|
-
}
|
|
395
|
-
});
|
|
396
|
-
it("stamps the first trace of a fresh instance at the current time", () => {
|
|
397
|
-
const before = Date.now();
|
|
398
|
-
const trace = generateTrace({});
|
|
399
|
-
const after = Date.now();
|
|
400
|
-
const ts = new Date(trace.Timestamp).getTime();
|
|
401
|
-
expect(ts).toBeGreaterThanOrEqual(before);
|
|
402
|
-
expect(ts).toBeLessThanOrEqual(after);
|
|
403
|
-
});
|
|
404
|
-
});
|
|
405
|
-
describe("ScenarioGenerationService — scenarioName embedding (SKYR-3884)", () => {
|
|
406
|
-
it("writes scenarioName into each trace request so the plan guard can identify the file", () => {
|
|
407
|
-
// readScenarioNameFromFile (generateIntegrationRestTool) reads
|
|
408
|
-
// parsed[0].scenarioName to match the file against the approved plan.
|
|
409
|
-
// Without this field every scenarioFile-mode generation is rejected
|
|
410
|
-
// fail-closed whenever a plan is active.
|
|
411
|
-
const trace = generateTrace({});
|
|
412
|
-
expect(trace.scenarioName).toBe("test-scenario");
|
|
413
|
-
});
|
|
414
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|