@dotsetlabs/bellwether 0.10.0
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/CHANGELOG.md +291 -0
- package/LICENSE +21 -0
- package/README.md +739 -0
- package/dist/auth/credentials.d.ts +64 -0
- package/dist/auth/credentials.js +218 -0
- package/dist/auth/index.d.ts +6 -0
- package/dist/auth/index.js +6 -0
- package/dist/auth/keychain.d.ts +64 -0
- package/dist/auth/keychain.js +268 -0
- package/dist/baseline/ab-testing.d.ts +80 -0
- package/dist/baseline/ab-testing.js +236 -0
- package/dist/baseline/ai-compatibility-scorer.d.ts +95 -0
- package/dist/baseline/ai-compatibility-scorer.js +606 -0
- package/dist/baseline/calibration.d.ts +77 -0
- package/dist/baseline/calibration.js +136 -0
- package/dist/baseline/category-matching.d.ts +85 -0
- package/dist/baseline/category-matching.js +289 -0
- package/dist/baseline/change-impact-analyzer.d.ts +98 -0
- package/dist/baseline/change-impact-analyzer.js +592 -0
- package/dist/baseline/comparator.d.ts +64 -0
- package/dist/baseline/comparator.js +916 -0
- package/dist/baseline/confidence.d.ts +55 -0
- package/dist/baseline/confidence.js +122 -0
- package/dist/baseline/converter.d.ts +61 -0
- package/dist/baseline/converter.js +585 -0
- package/dist/baseline/dependency-analyzer.d.ts +89 -0
- package/dist/baseline/dependency-analyzer.js +567 -0
- package/dist/baseline/deprecation-tracker.d.ts +133 -0
- package/dist/baseline/deprecation-tracker.js +322 -0
- package/dist/baseline/diff.d.ts +55 -0
- package/dist/baseline/diff.js +1584 -0
- package/dist/baseline/documentation-scorer.d.ts +205 -0
- package/dist/baseline/documentation-scorer.js +466 -0
- package/dist/baseline/embeddings.d.ts +118 -0
- package/dist/baseline/embeddings.js +251 -0
- package/dist/baseline/error-analyzer.d.ts +198 -0
- package/dist/baseline/error-analyzer.js +721 -0
- package/dist/baseline/evaluation/evaluator.d.ts +42 -0
- package/dist/baseline/evaluation/evaluator.js +323 -0
- package/dist/baseline/evaluation/expanded-dataset.d.ts +45 -0
- package/dist/baseline/evaluation/expanded-dataset.js +1164 -0
- package/dist/baseline/evaluation/golden-dataset.d.ts +58 -0
- package/dist/baseline/evaluation/golden-dataset.js +717 -0
- package/dist/baseline/evaluation/index.d.ts +15 -0
- package/dist/baseline/evaluation/index.js +15 -0
- package/dist/baseline/evaluation/types.d.ts +186 -0
- package/dist/baseline/evaluation/types.js +8 -0
- package/dist/baseline/external-dependency-detector.d.ts +181 -0
- package/dist/baseline/external-dependency-detector.js +524 -0
- package/dist/baseline/golden-output.d.ts +162 -0
- package/dist/baseline/golden-output.js +636 -0
- package/dist/baseline/health-scorer.d.ts +174 -0
- package/dist/baseline/health-scorer.js +451 -0
- package/dist/baseline/incremental-checker.d.ts +97 -0
- package/dist/baseline/incremental-checker.js +174 -0
- package/dist/baseline/index.d.ts +31 -0
- package/dist/baseline/index.js +42 -0
- package/dist/baseline/migration-generator.d.ts +137 -0
- package/dist/baseline/migration-generator.js +554 -0
- package/dist/baseline/migrations.d.ts +60 -0
- package/dist/baseline/migrations.js +197 -0
- package/dist/baseline/performance-tracker.d.ts +214 -0
- package/dist/baseline/performance-tracker.js +577 -0
- package/dist/baseline/pr-comment-generator.d.ts +117 -0
- package/dist/baseline/pr-comment-generator.js +546 -0
- package/dist/baseline/response-fingerprint.d.ts +127 -0
- package/dist/baseline/response-fingerprint.js +728 -0
- package/dist/baseline/response-schema-tracker.d.ts +129 -0
- package/dist/baseline/response-schema-tracker.js +420 -0
- package/dist/baseline/risk-scorer.d.ts +54 -0
- package/dist/baseline/risk-scorer.js +434 -0
- package/dist/baseline/saver.d.ts +89 -0
- package/dist/baseline/saver.js +554 -0
- package/dist/baseline/scenario-generator.d.ts +151 -0
- package/dist/baseline/scenario-generator.js +905 -0
- package/dist/baseline/schema-compare.d.ts +86 -0
- package/dist/baseline/schema-compare.js +557 -0
- package/dist/baseline/schema-evolution.d.ts +189 -0
- package/dist/baseline/schema-evolution.js +467 -0
- package/dist/baseline/semantic.d.ts +203 -0
- package/dist/baseline/semantic.js +908 -0
- package/dist/baseline/synonyms.d.ts +60 -0
- package/dist/baseline/synonyms.js +386 -0
- package/dist/baseline/telemetry.d.ts +165 -0
- package/dist/baseline/telemetry.js +294 -0
- package/dist/baseline/test-pruner.d.ts +120 -0
- package/dist/baseline/test-pruner.js +387 -0
- package/dist/baseline/types.d.ts +449 -0
- package/dist/baseline/types.js +5 -0
- package/dist/baseline/version.d.ts +138 -0
- package/dist/baseline/version.js +206 -0
- package/dist/cache/index.d.ts +5 -0
- package/dist/cache/index.js +5 -0
- package/dist/cache/response-cache.d.ts +151 -0
- package/dist/cache/response-cache.js +287 -0
- package/dist/ci/index.d.ts +60 -0
- package/dist/ci/index.js +342 -0
- package/dist/cli/commands/auth.d.ts +12 -0
- package/dist/cli/commands/auth.js +352 -0
- package/dist/cli/commands/badge.d.ts +3 -0
- package/dist/cli/commands/badge.js +74 -0
- package/dist/cli/commands/baseline-accept.d.ts +15 -0
- package/dist/cli/commands/baseline-accept.js +178 -0
- package/dist/cli/commands/baseline-migrate.d.ts +12 -0
- package/dist/cli/commands/baseline-migrate.js +164 -0
- package/dist/cli/commands/baseline.d.ts +14 -0
- package/dist/cli/commands/baseline.js +449 -0
- package/dist/cli/commands/beta.d.ts +10 -0
- package/dist/cli/commands/beta.js +231 -0
- package/dist/cli/commands/check.d.ts +11 -0
- package/dist/cli/commands/check.js +820 -0
- package/dist/cli/commands/cloud/badge.d.ts +3 -0
- package/dist/cli/commands/cloud/badge.js +74 -0
- package/dist/cli/commands/cloud/diff.d.ts +6 -0
- package/dist/cli/commands/cloud/diff.js +79 -0
- package/dist/cli/commands/cloud/history.d.ts +6 -0
- package/dist/cli/commands/cloud/history.js +102 -0
- package/dist/cli/commands/cloud/link.d.ts +9 -0
- package/dist/cli/commands/cloud/link.js +119 -0
- package/dist/cli/commands/cloud/login.d.ts +7 -0
- package/dist/cli/commands/cloud/login.js +499 -0
- package/dist/cli/commands/cloud/projects.d.ts +6 -0
- package/dist/cli/commands/cloud/projects.js +44 -0
- package/dist/cli/commands/cloud/shared.d.ts +7 -0
- package/dist/cli/commands/cloud/shared.js +42 -0
- package/dist/cli/commands/cloud/teams.d.ts +8 -0
- package/dist/cli/commands/cloud/teams.js +169 -0
- package/dist/cli/commands/cloud/upload.d.ts +8 -0
- package/dist/cli/commands/cloud/upload.js +181 -0
- package/dist/cli/commands/contract.d.ts +11 -0
- package/dist/cli/commands/contract.js +280 -0
- package/dist/cli/commands/discover.d.ts +3 -0
- package/dist/cli/commands/discover.js +82 -0
- package/dist/cli/commands/eval.d.ts +9 -0
- package/dist/cli/commands/eval.js +187 -0
- package/dist/cli/commands/explore.d.ts +11 -0
- package/dist/cli/commands/explore.js +437 -0
- package/dist/cli/commands/feedback.d.ts +9 -0
- package/dist/cli/commands/feedback.js +174 -0
- package/dist/cli/commands/golden.d.ts +12 -0
- package/dist/cli/commands/golden.js +407 -0
- package/dist/cli/commands/history.d.ts +10 -0
- package/dist/cli/commands/history.js +202 -0
- package/dist/cli/commands/init.d.ts +9 -0
- package/dist/cli/commands/init.js +219 -0
- package/dist/cli/commands/interview.d.ts +3 -0
- package/dist/cli/commands/interview.js +903 -0
- package/dist/cli/commands/link.d.ts +10 -0
- package/dist/cli/commands/link.js +169 -0
- package/dist/cli/commands/login.d.ts +7 -0
- package/dist/cli/commands/login.js +499 -0
- package/dist/cli/commands/preset.d.ts +33 -0
- package/dist/cli/commands/preset.js +297 -0
- package/dist/cli/commands/profile.d.ts +33 -0
- package/dist/cli/commands/profile.js +286 -0
- package/dist/cli/commands/registry.d.ts +11 -0
- package/dist/cli/commands/registry.js +146 -0
- package/dist/cli/commands/shared.d.ts +79 -0
- package/dist/cli/commands/shared.js +196 -0
- package/dist/cli/commands/teams.d.ts +8 -0
- package/dist/cli/commands/teams.js +169 -0
- package/dist/cli/commands/test.d.ts +9 -0
- package/dist/cli/commands/test.js +500 -0
- package/dist/cli/commands/upload.d.ts +8 -0
- package/dist/cli/commands/upload.js +223 -0
- package/dist/cli/commands/validate-config.d.ts +6 -0
- package/dist/cli/commands/validate-config.js +35 -0
- package/dist/cli/commands/verify.d.ts +11 -0
- package/dist/cli/commands/verify.js +283 -0
- package/dist/cli/commands/watch.d.ts +12 -0
- package/dist/cli/commands/watch.js +253 -0
- package/dist/cli/index.d.ts +3 -0
- package/dist/cli/index.js +178 -0
- package/dist/cli/interactive.d.ts +47 -0
- package/dist/cli/interactive.js +216 -0
- package/dist/cli/output/terminal-reporter.d.ts +19 -0
- package/dist/cli/output/terminal-reporter.js +104 -0
- package/dist/cli/output.d.ts +226 -0
- package/dist/cli/output.js +438 -0
- package/dist/cli/utils/env.d.ts +5 -0
- package/dist/cli/utils/env.js +14 -0
- package/dist/cli/utils/progress.d.ts +59 -0
- package/dist/cli/utils/progress.js +206 -0
- package/dist/cli/utils/server-context.d.ts +10 -0
- package/dist/cli/utils/server-context.js +36 -0
- package/dist/cloud/auth.d.ts +144 -0
- package/dist/cloud/auth.js +374 -0
- package/dist/cloud/client.d.ts +24 -0
- package/dist/cloud/client.js +65 -0
- package/dist/cloud/http-client.d.ts +38 -0
- package/dist/cloud/http-client.js +215 -0
- package/dist/cloud/index.d.ts +23 -0
- package/dist/cloud/index.js +25 -0
- package/dist/cloud/mock-client.d.ts +107 -0
- package/dist/cloud/mock-client.js +545 -0
- package/dist/cloud/types.d.ts +515 -0
- package/dist/cloud/types.js +15 -0
- package/dist/config/defaults.d.ts +160 -0
- package/dist/config/defaults.js +169 -0
- package/dist/config/loader.d.ts +24 -0
- package/dist/config/loader.js +122 -0
- package/dist/config/template.d.ts +42 -0
- package/dist/config/template.js +647 -0
- package/dist/config/validator.d.ts +2112 -0
- package/dist/config/validator.js +658 -0
- package/dist/constants/cloud.d.ts +107 -0
- package/dist/constants/cloud.js +110 -0
- package/dist/constants/core.d.ts +521 -0
- package/dist/constants/core.js +556 -0
- package/dist/constants/testing.d.ts +1283 -0
- package/dist/constants/testing.js +1568 -0
- package/dist/constants.d.ts +10 -0
- package/dist/constants.js +10 -0
- package/dist/contract/index.d.ts +6 -0
- package/dist/contract/index.js +5 -0
- package/dist/contract/validator.d.ts +177 -0
- package/dist/contract/validator.js +574 -0
- package/dist/cost/index.d.ts +6 -0
- package/dist/cost/index.js +5 -0
- package/dist/cost/tracker.d.ts +134 -0
- package/dist/cost/tracker.js +313 -0
- package/dist/discovery/discovery.d.ts +16 -0
- package/dist/discovery/discovery.js +173 -0
- package/dist/discovery/types.d.ts +51 -0
- package/dist/discovery/types.js +2 -0
- package/dist/docs/agents.d.ts +3 -0
- package/dist/docs/agents.js +995 -0
- package/dist/docs/contract.d.ts +51 -0
- package/dist/docs/contract.js +1681 -0
- package/dist/docs/generator.d.ts +4 -0
- package/dist/docs/generator.js +4 -0
- package/dist/docs/html-reporter.d.ts +9 -0
- package/dist/docs/html-reporter.js +757 -0
- package/dist/docs/index.d.ts +10 -0
- package/dist/docs/index.js +11 -0
- package/dist/docs/junit-reporter.d.ts +18 -0
- package/dist/docs/junit-reporter.js +210 -0
- package/dist/docs/report.d.ts +14 -0
- package/dist/docs/report.js +44 -0
- package/dist/docs/sarif-reporter.d.ts +19 -0
- package/dist/docs/sarif-reporter.js +335 -0
- package/dist/docs/shared.d.ts +35 -0
- package/dist/docs/shared.js +162 -0
- package/dist/docs/templates.d.ts +12 -0
- package/dist/docs/templates.js +76 -0
- package/dist/errors/index.d.ts +6 -0
- package/dist/errors/index.js +6 -0
- package/dist/errors/retry.d.ts +92 -0
- package/dist/errors/retry.js +323 -0
- package/dist/errors/types.d.ts +321 -0
- package/dist/errors/types.js +584 -0
- package/dist/index.d.ts +32 -0
- package/dist/index.js +32 -0
- package/dist/interview/dependency-resolver.d.ts +11 -0
- package/dist/interview/dependency-resolver.js +32 -0
- package/dist/interview/interviewer.d.ts +232 -0
- package/dist/interview/interviewer.js +1939 -0
- package/dist/interview/mock-response-generator.d.ts +7 -0
- package/dist/interview/mock-response-generator.js +102 -0
- package/dist/interview/orchestrator.d.ts +237 -0
- package/dist/interview/orchestrator.js +1296 -0
- package/dist/interview/rate-limiter.d.ts +15 -0
- package/dist/interview/rate-limiter.js +55 -0
- package/dist/interview/response-validator.d.ts +10 -0
- package/dist/interview/response-validator.js +132 -0
- package/dist/interview/schema-inferrer.d.ts +8 -0
- package/dist/interview/schema-inferrer.js +71 -0
- package/dist/interview/schema-test-generator.d.ts +71 -0
- package/dist/interview/schema-test-generator.js +834 -0
- package/dist/interview/smart-value-generator.d.ts +155 -0
- package/dist/interview/smart-value-generator.js +554 -0
- package/dist/interview/stateful-test-runner.d.ts +19 -0
- package/dist/interview/stateful-test-runner.js +106 -0
- package/dist/interview/types.d.ts +561 -0
- package/dist/interview/types.js +2 -0
- package/dist/llm/anthropic.d.ts +41 -0
- package/dist/llm/anthropic.js +355 -0
- package/dist/llm/client.d.ts +123 -0
- package/dist/llm/client.js +42 -0
- package/dist/llm/factory.d.ts +38 -0
- package/dist/llm/factory.js +145 -0
- package/dist/llm/fallback.d.ts +140 -0
- package/dist/llm/fallback.js +379 -0
- package/dist/llm/index.d.ts +18 -0
- package/dist/llm/index.js +15 -0
- package/dist/llm/ollama.d.ts +37 -0
- package/dist/llm/ollama.js +330 -0
- package/dist/llm/openai.d.ts +25 -0
- package/dist/llm/openai.js +320 -0
- package/dist/llm/token-budget.d.ts +161 -0
- package/dist/llm/token-budget.js +395 -0
- package/dist/logging/logger.d.ts +70 -0
- package/dist/logging/logger.js +130 -0
- package/dist/metrics/collector.d.ts +106 -0
- package/dist/metrics/collector.js +547 -0
- package/dist/metrics/index.d.ts +7 -0
- package/dist/metrics/index.js +7 -0
- package/dist/metrics/prometheus.d.ts +20 -0
- package/dist/metrics/prometheus.js +241 -0
- package/dist/metrics/types.d.ts +209 -0
- package/dist/metrics/types.js +5 -0
- package/dist/persona/builtins.d.ts +54 -0
- package/dist/persona/builtins.js +219 -0
- package/dist/persona/index.d.ts +8 -0
- package/dist/persona/index.js +8 -0
- package/dist/persona/loader.d.ts +30 -0
- package/dist/persona/loader.js +190 -0
- package/dist/persona/types.d.ts +144 -0
- package/dist/persona/types.js +5 -0
- package/dist/persona/validation.d.ts +94 -0
- package/dist/persona/validation.js +332 -0
- package/dist/prompts/index.d.ts +5 -0
- package/dist/prompts/index.js +5 -0
- package/dist/prompts/templates.d.ts +180 -0
- package/dist/prompts/templates.js +431 -0
- package/dist/registry/client.d.ts +49 -0
- package/dist/registry/client.js +191 -0
- package/dist/registry/index.d.ts +7 -0
- package/dist/registry/index.js +6 -0
- package/dist/registry/types.d.ts +140 -0
- package/dist/registry/types.js +6 -0
- package/dist/scenarios/evaluator.d.ts +43 -0
- package/dist/scenarios/evaluator.js +206 -0
- package/dist/scenarios/index.d.ts +10 -0
- package/dist/scenarios/index.js +9 -0
- package/dist/scenarios/loader.d.ts +20 -0
- package/dist/scenarios/loader.js +285 -0
- package/dist/scenarios/types.d.ts +153 -0
- package/dist/scenarios/types.js +8 -0
- package/dist/security/index.d.ts +17 -0
- package/dist/security/index.js +18 -0
- package/dist/security/payloads.d.ts +61 -0
- package/dist/security/payloads.js +268 -0
- package/dist/security/security-tester.d.ts +42 -0
- package/dist/security/security-tester.js +582 -0
- package/dist/security/types.d.ts +166 -0
- package/dist/security/types.js +8 -0
- package/dist/transport/base-transport.d.ts +59 -0
- package/dist/transport/base-transport.js +38 -0
- package/dist/transport/http-transport.d.ts +67 -0
- package/dist/transport/http-transport.js +238 -0
- package/dist/transport/mcp-client.d.ts +141 -0
- package/dist/transport/mcp-client.js +496 -0
- package/dist/transport/sse-transport.d.ts +88 -0
- package/dist/transport/sse-transport.js +316 -0
- package/dist/transport/stdio-transport.d.ts +43 -0
- package/dist/transport/stdio-transport.js +238 -0
- package/dist/transport/types.d.ts +125 -0
- package/dist/transport/types.js +16 -0
- package/dist/utils/concurrency.d.ts +123 -0
- package/dist/utils/concurrency.js +213 -0
- package/dist/utils/formatters.d.ts +16 -0
- package/dist/utils/formatters.js +37 -0
- package/dist/utils/index.d.ts +8 -0
- package/dist/utils/index.js +8 -0
- package/dist/utils/jsonpath.d.ts +87 -0
- package/dist/utils/jsonpath.js +326 -0
- package/dist/utils/markdown.d.ts +113 -0
- package/dist/utils/markdown.js +265 -0
- package/dist/utils/network.d.ts +14 -0
- package/dist/utils/network.js +17 -0
- package/dist/utils/sanitize.d.ts +92 -0
- package/dist/utils/sanitize.js +191 -0
- package/dist/utils/semantic.d.ts +194 -0
- package/dist/utils/semantic.js +1051 -0
- package/dist/utils/smart-truncate.d.ts +94 -0
- package/dist/utils/smart-truncate.js +361 -0
- package/dist/utils/timeout.d.ts +153 -0
- package/dist/utils/timeout.js +205 -0
- package/dist/utils/yaml-parser.d.ts +58 -0
- package/dist/utils/yaml-parser.js +86 -0
- package/dist/validation/index.d.ts +32 -0
- package/dist/validation/index.js +32 -0
- package/dist/validation/semantic-test-generator.d.ts +50 -0
- package/dist/validation/semantic-test-generator.js +176 -0
- package/dist/validation/semantic-types.d.ts +66 -0
- package/dist/validation/semantic-types.js +94 -0
- package/dist/validation/semantic-validator.d.ts +38 -0
- package/dist/validation/semantic-validator.js +340 -0
- package/dist/verification/index.d.ts +6 -0
- package/dist/verification/index.js +5 -0
- package/dist/verification/types.d.ts +133 -0
- package/dist/verification/types.js +5 -0
- package/dist/verification/verifier.d.ts +30 -0
- package/dist/verification/verifier.js +309 -0
- package/dist/version.d.ts +19 -0
- package/dist/version.js +48 -0
- package/dist/workflow/auto-generator.d.ts +27 -0
- package/dist/workflow/auto-generator.js +513 -0
- package/dist/workflow/discovery.d.ts +40 -0
- package/dist/workflow/discovery.js +195 -0
- package/dist/workflow/executor.d.ts +82 -0
- package/dist/workflow/executor.js +611 -0
- package/dist/workflow/index.d.ts +10 -0
- package/dist/workflow/index.js +10 -0
- package/dist/workflow/loader.d.ts +24 -0
- package/dist/workflow/loader.js +194 -0
- package/dist/workflow/state-tracker.d.ts +98 -0
- package/dist/workflow/state-tracker.js +424 -0
- package/dist/workflow/types.d.ts +337 -0
- package/dist/workflow/types.js +5 -0
- package/package.json +94 -0
- package/schemas/bellwether-check.schema.json +651 -0
|
@@ -0,0 +1,905 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Auto-generated test scenario generation from schema analysis.
|
|
3
|
+
*
|
|
4
|
+
* This module analyzes tool schemas and generates comprehensive test scenarios
|
|
5
|
+
* covering happy paths, edge cases, error handling, and security testing.
|
|
6
|
+
*/
|
|
7
|
+
import { SCENARIO_GENERATION, ORCHESTRATOR } from '../constants.js';
|
|
8
|
+
/**
|
|
9
|
+
* Extract properties from a JSON schema.
|
|
10
|
+
*/
|
|
11
|
+
function extractSchemaProperties(schema) {
|
|
12
|
+
const properties = [];
|
|
13
|
+
const schemaProps = schema.properties;
|
|
14
|
+
const required = schema.required || [];
|
|
15
|
+
if (!schemaProps) {
|
|
16
|
+
return properties;
|
|
17
|
+
}
|
|
18
|
+
for (const [name, prop] of Object.entries(schemaProps)) {
|
|
19
|
+
const property = {
|
|
20
|
+
name,
|
|
21
|
+
type: prop.type || 'unknown',
|
|
22
|
+
required: required.includes(name),
|
|
23
|
+
description: prop.description,
|
|
24
|
+
};
|
|
25
|
+
// Extract constraints
|
|
26
|
+
if (prop.enum)
|
|
27
|
+
property.enum = prop.enum;
|
|
28
|
+
if (typeof prop.minimum === 'number')
|
|
29
|
+
property.minimum = prop.minimum;
|
|
30
|
+
if (typeof prop.maximum === 'number')
|
|
31
|
+
property.maximum = prop.maximum;
|
|
32
|
+
if (typeof prop.minLength === 'number')
|
|
33
|
+
property.minLength = prop.minLength;
|
|
34
|
+
if (typeof prop.maxLength === 'number')
|
|
35
|
+
property.maxLength = prop.maxLength;
|
|
36
|
+
if (prop.pattern)
|
|
37
|
+
property.pattern = prop.pattern;
|
|
38
|
+
if (prop.format)
|
|
39
|
+
property.format = prop.format;
|
|
40
|
+
if (prop.default !== undefined)
|
|
41
|
+
property.default = prop.default;
|
|
42
|
+
// Handle array items
|
|
43
|
+
if (prop.type === 'array' && prop.items) {
|
|
44
|
+
const items = prop.items;
|
|
45
|
+
property.items = {
|
|
46
|
+
name: `${name}[]`,
|
|
47
|
+
type: items.type || 'unknown',
|
|
48
|
+
required: false,
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
properties.push(property);
|
|
52
|
+
}
|
|
53
|
+
return properties;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Generate a valid value for a schema property.
|
|
57
|
+
*/
|
|
58
|
+
function generateValidValue(prop) {
|
|
59
|
+
// Use default if available
|
|
60
|
+
if (prop.default !== undefined) {
|
|
61
|
+
return prop.default;
|
|
62
|
+
}
|
|
63
|
+
// Use first enum value if available
|
|
64
|
+
if (prop.enum && prop.enum.length > 0) {
|
|
65
|
+
return prop.enum[0];
|
|
66
|
+
}
|
|
67
|
+
// Generate based on type
|
|
68
|
+
switch (prop.type) {
|
|
69
|
+
case 'string':
|
|
70
|
+
if (prop.format === 'email')
|
|
71
|
+
return 'test@example.com';
|
|
72
|
+
if (prop.format === 'uri' || prop.format === 'url')
|
|
73
|
+
return 'https://example.com';
|
|
74
|
+
if (prop.format === 'date')
|
|
75
|
+
return '2025-01-15';
|
|
76
|
+
if (prop.format === 'date-time')
|
|
77
|
+
return '2025-01-15T10:30:00Z';
|
|
78
|
+
if (prop.format === 'uuid')
|
|
79
|
+
return '550e8400-e29b-41d4-a716-446655440000';
|
|
80
|
+
if (prop.pattern)
|
|
81
|
+
return generateFromPattern(prop.pattern);
|
|
82
|
+
if (prop.minLength)
|
|
83
|
+
return 'a'.repeat(prop.minLength);
|
|
84
|
+
return 'test-value';
|
|
85
|
+
case 'number':
|
|
86
|
+
case 'integer':
|
|
87
|
+
if (prop.minimum !== undefined && prop.maximum !== undefined) {
|
|
88
|
+
return Math.floor((prop.minimum + prop.maximum) / 2);
|
|
89
|
+
}
|
|
90
|
+
if (prop.minimum !== undefined)
|
|
91
|
+
return prop.minimum;
|
|
92
|
+
if (prop.maximum !== undefined)
|
|
93
|
+
return Math.min(prop.maximum, 100);
|
|
94
|
+
return prop.type === 'integer' ? 42 : 42.5;
|
|
95
|
+
case 'boolean':
|
|
96
|
+
return true;
|
|
97
|
+
case 'array':
|
|
98
|
+
if (prop.items) {
|
|
99
|
+
return [generateValidValue(prop.items)];
|
|
100
|
+
}
|
|
101
|
+
return [];
|
|
102
|
+
case 'object':
|
|
103
|
+
return {};
|
|
104
|
+
default:
|
|
105
|
+
return null;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Generate a simple value matching a regex pattern.
|
|
110
|
+
*/
|
|
111
|
+
function generateFromPattern(pattern) {
|
|
112
|
+
// Simple pattern matching for common cases
|
|
113
|
+
if (pattern.includes('[a-z]'))
|
|
114
|
+
return 'abc';
|
|
115
|
+
if (pattern.includes('[A-Z]'))
|
|
116
|
+
return 'ABC';
|
|
117
|
+
if (pattern.includes('[0-9]') || pattern.includes('\\d'))
|
|
118
|
+
return '123';
|
|
119
|
+
if (pattern.includes('@'))
|
|
120
|
+
return 'test@example.com';
|
|
121
|
+
return 'pattern-value';
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Generate an invalid value for a schema property.
|
|
125
|
+
*/
|
|
126
|
+
function generateInvalidValue(prop) {
|
|
127
|
+
switch (prop.type) {
|
|
128
|
+
case 'string':
|
|
129
|
+
return 12345; // Number instead of string
|
|
130
|
+
case 'number':
|
|
131
|
+
case 'integer':
|
|
132
|
+
return 'not-a-number';
|
|
133
|
+
case 'boolean':
|
|
134
|
+
return 'not-a-boolean';
|
|
135
|
+
case 'array':
|
|
136
|
+
return 'not-an-array';
|
|
137
|
+
case 'object':
|
|
138
|
+
return 'not-an-object';
|
|
139
|
+
default:
|
|
140
|
+
return Symbol('invalid');
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Generate boundary values for a property.
|
|
145
|
+
*/
|
|
146
|
+
function generateBoundaryValues(prop) {
|
|
147
|
+
const values = [];
|
|
148
|
+
switch (prop.type) {
|
|
149
|
+
case 'string':
|
|
150
|
+
if (prop.minLength !== undefined) {
|
|
151
|
+
// At minimum
|
|
152
|
+
values.push('a'.repeat(prop.minLength));
|
|
153
|
+
// Just below minimum (if > 0)
|
|
154
|
+
if (prop.minLength > 0) {
|
|
155
|
+
values.push('a'.repeat(prop.minLength - 1));
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
if (prop.maxLength !== undefined) {
|
|
159
|
+
// At maximum
|
|
160
|
+
values.push('a'.repeat(prop.maxLength));
|
|
161
|
+
// Just above maximum
|
|
162
|
+
values.push('a'.repeat(prop.maxLength + 1));
|
|
163
|
+
}
|
|
164
|
+
// Empty string
|
|
165
|
+
values.push('');
|
|
166
|
+
break;
|
|
167
|
+
case 'number':
|
|
168
|
+
case 'integer':
|
|
169
|
+
if (prop.minimum !== undefined) {
|
|
170
|
+
values.push(prop.minimum);
|
|
171
|
+
values.push(prop.minimum - 1);
|
|
172
|
+
}
|
|
173
|
+
if (prop.maximum !== undefined) {
|
|
174
|
+
values.push(prop.maximum);
|
|
175
|
+
values.push(prop.maximum + 1);
|
|
176
|
+
}
|
|
177
|
+
// Zero and negative
|
|
178
|
+
values.push(0);
|
|
179
|
+
values.push(-1);
|
|
180
|
+
// Very large number
|
|
181
|
+
values.push(Number.MAX_SAFE_INTEGER);
|
|
182
|
+
break;
|
|
183
|
+
case 'array':
|
|
184
|
+
values.push([]); // Empty array
|
|
185
|
+
values.push([generateValidValue(prop.items || { name: 'item', type: 'string', required: false })]); // Single item
|
|
186
|
+
break;
|
|
187
|
+
}
|
|
188
|
+
return values;
|
|
189
|
+
}
|
|
190
|
+
/**
|
|
191
|
+
* Generate a unique scenario ID using the provided counter state.
|
|
192
|
+
*/
|
|
193
|
+
function generateScenarioId(toolName, category, counter) {
|
|
194
|
+
return `${toolName}-${category}-${++counter.value}`;
|
|
195
|
+
}
|
|
196
|
+
/**
|
|
197
|
+
* Generate happy path scenarios for a tool.
|
|
198
|
+
*/
|
|
199
|
+
function generateHappyPathScenarios(tool, properties, maxScenarios, counter) {
|
|
200
|
+
const scenarios = [];
|
|
201
|
+
// Scenario 1: All required parameters with valid values
|
|
202
|
+
const requiredProps = properties.filter(p => p.required);
|
|
203
|
+
if (requiredProps.length > 0 || properties.length > 0) {
|
|
204
|
+
const input = {};
|
|
205
|
+
const targetProps = requiredProps.length > 0 ? requiredProps : properties.slice(0, 3);
|
|
206
|
+
for (const prop of targetProps) {
|
|
207
|
+
input[prop.name] = generateValidValue(prop);
|
|
208
|
+
}
|
|
209
|
+
scenarios.push({
|
|
210
|
+
id: generateScenarioId(tool.name, 'happy_path', counter),
|
|
211
|
+
toolName: tool.name,
|
|
212
|
+
category: 'happy_path',
|
|
213
|
+
description: `Basic usage with ${requiredProps.length > 0 ? 'required' : 'minimal'} parameters`,
|
|
214
|
+
input,
|
|
215
|
+
expectedBehavior: 'Tool should execute successfully and return valid response',
|
|
216
|
+
priority: 'critical',
|
|
217
|
+
tags: ['basic', 'smoke-test'],
|
|
218
|
+
rationale: 'Validates core functionality with minimal valid input',
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
// Scenario 2: All parameters with valid values
|
|
222
|
+
if (properties.length > requiredProps.length && scenarios.length < maxScenarios) {
|
|
223
|
+
const input = {};
|
|
224
|
+
for (const prop of properties) {
|
|
225
|
+
input[prop.name] = generateValidValue(prop);
|
|
226
|
+
}
|
|
227
|
+
scenarios.push({
|
|
228
|
+
id: generateScenarioId(tool.name, 'happy_path', counter),
|
|
229
|
+
toolName: tool.name,
|
|
230
|
+
category: 'happy_path',
|
|
231
|
+
description: 'Full usage with all parameters',
|
|
232
|
+
input,
|
|
233
|
+
expectedBehavior: 'Tool should execute successfully with all options',
|
|
234
|
+
priority: 'high',
|
|
235
|
+
tags: ['full', 'comprehensive'],
|
|
236
|
+
rationale: 'Validates all parameters work together correctly',
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
// Scenario 3-N: Test different enum values
|
|
240
|
+
for (const prop of properties) {
|
|
241
|
+
if (prop.enum && prop.enum.length > 1 && scenarios.length < maxScenarios) {
|
|
242
|
+
for (let i = 1; i < Math.min(prop.enum.length, ORCHESTRATOR.MAX_ENUM_TESTS + 1); i++) {
|
|
243
|
+
if (scenarios.length >= maxScenarios)
|
|
244
|
+
break;
|
|
245
|
+
const input = {};
|
|
246
|
+
// Set required params
|
|
247
|
+
for (const p of properties.filter(p => p.required && p.name !== prop.name)) {
|
|
248
|
+
input[p.name] = generateValidValue(p);
|
|
249
|
+
}
|
|
250
|
+
input[prop.name] = prop.enum[i];
|
|
251
|
+
scenarios.push({
|
|
252
|
+
id: generateScenarioId(tool.name, 'happy_path', counter),
|
|
253
|
+
toolName: tool.name,
|
|
254
|
+
category: 'happy_path',
|
|
255
|
+
description: `Test ${prop.name} with value: ${JSON.stringify(prop.enum[i])}`,
|
|
256
|
+
input,
|
|
257
|
+
expectedBehavior: `Tool should handle ${prop.name}=${JSON.stringify(prop.enum[i])} correctly`,
|
|
258
|
+
priority: 'medium',
|
|
259
|
+
tags: ['enum', prop.name],
|
|
260
|
+
rationale: `Validates enum value: ${JSON.stringify(prop.enum[i])}`,
|
|
261
|
+
targetParameter: prop.name,
|
|
262
|
+
});
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
// Scenario: Test with default values
|
|
267
|
+
const propsWithDefaults = properties.filter(p => p.default !== undefined);
|
|
268
|
+
if (propsWithDefaults.length > 0 && scenarios.length < maxScenarios) {
|
|
269
|
+
const input = {};
|
|
270
|
+
// Only set required params, let defaults apply
|
|
271
|
+
for (const prop of properties.filter(p => p.required)) {
|
|
272
|
+
input[prop.name] = generateValidValue(prop);
|
|
273
|
+
}
|
|
274
|
+
scenarios.push({
|
|
275
|
+
id: generateScenarioId(tool.name, 'happy_path', counter),
|
|
276
|
+
toolName: tool.name,
|
|
277
|
+
category: 'happy_path',
|
|
278
|
+
description: 'Test with default values applied',
|
|
279
|
+
input,
|
|
280
|
+
expectedBehavior: 'Tool should apply defaults correctly',
|
|
281
|
+
priority: 'medium',
|
|
282
|
+
tags: ['defaults'],
|
|
283
|
+
rationale: `Tests default value behavior for: ${propsWithDefaults.map(p => p.name).join(', ')}`,
|
|
284
|
+
});
|
|
285
|
+
}
|
|
286
|
+
return scenarios.slice(0, maxScenarios);
|
|
287
|
+
}
|
|
288
|
+
/**
|
|
289
|
+
* Generate edge case scenarios for a tool.
|
|
290
|
+
*/
|
|
291
|
+
function generateEdgeCaseScenarios(tool, properties, maxScenarios, counter) {
|
|
292
|
+
const scenarios = [];
|
|
293
|
+
for (const prop of properties) {
|
|
294
|
+
if (scenarios.length >= maxScenarios)
|
|
295
|
+
break;
|
|
296
|
+
const boundaryValues = generateBoundaryValues(prop);
|
|
297
|
+
for (const value of boundaryValues) {
|
|
298
|
+
if (scenarios.length >= maxScenarios)
|
|
299
|
+
break;
|
|
300
|
+
const input = {};
|
|
301
|
+
// Set required params
|
|
302
|
+
for (const p of properties.filter(p => p.required && p.name !== prop.name)) {
|
|
303
|
+
input[p.name] = generateValidValue(p);
|
|
304
|
+
}
|
|
305
|
+
input[prop.name] = value;
|
|
306
|
+
const isInvalid = isBoundaryValueInvalid(prop, value);
|
|
307
|
+
scenarios.push({
|
|
308
|
+
id: generateScenarioId(tool.name, 'edge_cases', counter),
|
|
309
|
+
toolName: tool.name,
|
|
310
|
+
category: 'edge_cases',
|
|
311
|
+
description: `Boundary test: ${prop.name} = ${formatValue(value)}`,
|
|
312
|
+
input,
|
|
313
|
+
expectedBehavior: isInvalid
|
|
314
|
+
? `Tool should reject or handle invalid ${prop.name} gracefully`
|
|
315
|
+
: `Tool should handle boundary value for ${prop.name}`,
|
|
316
|
+
priority: isInvalid ? 'high' : 'medium',
|
|
317
|
+
tags: ['boundary', prop.name, isInvalid ? 'invalid' : 'valid'],
|
|
318
|
+
rationale: `Tests ${prop.name} at boundary: ${formatValue(value)}`,
|
|
319
|
+
targetParameter: prop.name,
|
|
320
|
+
});
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
// Add empty object test
|
|
324
|
+
if (scenarios.length < maxScenarios) {
|
|
325
|
+
const requiredProps = properties.filter(p => p.required);
|
|
326
|
+
if (requiredProps.length === 0) {
|
|
327
|
+
scenarios.push({
|
|
328
|
+
id: generateScenarioId(tool.name, 'edge_cases', counter),
|
|
329
|
+
toolName: tool.name,
|
|
330
|
+
category: 'edge_cases',
|
|
331
|
+
description: 'Empty input (no parameters)',
|
|
332
|
+
input: {},
|
|
333
|
+
expectedBehavior: 'Tool should handle empty input gracefully',
|
|
334
|
+
priority: 'medium',
|
|
335
|
+
tags: ['empty', 'minimal'],
|
|
336
|
+
rationale: 'Tests behavior with no parameters provided',
|
|
337
|
+
});
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
// Add null/undefined tests for optional params
|
|
341
|
+
const optionalProps = properties.filter(p => !p.required);
|
|
342
|
+
for (const prop of optionalProps) {
|
|
343
|
+
if (scenarios.length >= maxScenarios)
|
|
344
|
+
break;
|
|
345
|
+
const input = {};
|
|
346
|
+
for (const p of properties.filter(p => p.required)) {
|
|
347
|
+
input[p.name] = generateValidValue(p);
|
|
348
|
+
}
|
|
349
|
+
input[prop.name] = null;
|
|
350
|
+
scenarios.push({
|
|
351
|
+
id: generateScenarioId(tool.name, 'edge_cases', counter),
|
|
352
|
+
toolName: tool.name,
|
|
353
|
+
category: 'edge_cases',
|
|
354
|
+
description: `Null value for optional: ${prop.name}`,
|
|
355
|
+
input,
|
|
356
|
+
expectedBehavior: `Tool should handle null ${prop.name} gracefully`,
|
|
357
|
+
priority: 'low',
|
|
358
|
+
tags: ['null', prop.name],
|
|
359
|
+
rationale: 'Tests null handling for optional parameter',
|
|
360
|
+
targetParameter: prop.name,
|
|
361
|
+
});
|
|
362
|
+
}
|
|
363
|
+
return scenarios.slice(0, maxScenarios);
|
|
364
|
+
}
|
|
365
|
+
/**
|
|
366
|
+
* Check if a boundary value is invalid according to the schema.
|
|
367
|
+
*/
|
|
368
|
+
function isBoundaryValueInvalid(prop, value) {
|
|
369
|
+
if (value === null || value === undefined)
|
|
370
|
+
return true;
|
|
371
|
+
switch (prop.type) {
|
|
372
|
+
case 'string':
|
|
373
|
+
if (typeof value !== 'string')
|
|
374
|
+
return true;
|
|
375
|
+
if (prop.minLength !== undefined && value.length < prop.minLength)
|
|
376
|
+
return true;
|
|
377
|
+
if (prop.maxLength !== undefined && value.length > prop.maxLength)
|
|
378
|
+
return true;
|
|
379
|
+
break;
|
|
380
|
+
case 'number':
|
|
381
|
+
case 'integer':
|
|
382
|
+
if (typeof value !== 'number')
|
|
383
|
+
return true;
|
|
384
|
+
if (prop.minimum !== undefined && value < prop.minimum)
|
|
385
|
+
return true;
|
|
386
|
+
if (prop.maximum !== undefined && value > prop.maximum)
|
|
387
|
+
return true;
|
|
388
|
+
break;
|
|
389
|
+
case 'array':
|
|
390
|
+
if (!Array.isArray(value))
|
|
391
|
+
return true;
|
|
392
|
+
break;
|
|
393
|
+
}
|
|
394
|
+
return false;
|
|
395
|
+
}
|
|
396
|
+
/**
|
|
397
|
+
* Format a value for display.
|
|
398
|
+
*/
|
|
399
|
+
function formatValue(value) {
|
|
400
|
+
if (value === null)
|
|
401
|
+
return 'null';
|
|
402
|
+
if (value === undefined)
|
|
403
|
+
return 'undefined';
|
|
404
|
+
if (typeof value === 'string') {
|
|
405
|
+
if (value.length > 30)
|
|
406
|
+
return `"${value.substring(0, 27)}..."`;
|
|
407
|
+
return `"${value}"`;
|
|
408
|
+
}
|
|
409
|
+
if (Array.isArray(value)) {
|
|
410
|
+
if (value.length === 0)
|
|
411
|
+
return '[]';
|
|
412
|
+
return `[${value.length} items]`;
|
|
413
|
+
}
|
|
414
|
+
return String(value);
|
|
415
|
+
}
|
|
416
|
+
/**
|
|
417
|
+
* Generate error case scenarios for a tool.
|
|
418
|
+
*/
|
|
419
|
+
function generateErrorCaseScenarios(tool, properties, maxScenarios, counter) {
|
|
420
|
+
const scenarios = [];
|
|
421
|
+
// Test missing required parameters
|
|
422
|
+
const requiredProps = properties.filter(p => p.required);
|
|
423
|
+
for (const prop of requiredProps) {
|
|
424
|
+
if (scenarios.length >= maxScenarios)
|
|
425
|
+
break;
|
|
426
|
+
const input = {};
|
|
427
|
+
// Set all required except this one
|
|
428
|
+
for (const p of requiredProps.filter(p => p.name !== prop.name)) {
|
|
429
|
+
input[p.name] = generateValidValue(p);
|
|
430
|
+
}
|
|
431
|
+
scenarios.push({
|
|
432
|
+
id: generateScenarioId(tool.name, 'error_handling', counter),
|
|
433
|
+
toolName: tool.name,
|
|
434
|
+
category: 'error_handling',
|
|
435
|
+
description: `Missing required parameter: ${prop.name}`,
|
|
436
|
+
input,
|
|
437
|
+
expectedBehavior: `Tool should return error for missing ${prop.name}`,
|
|
438
|
+
priority: 'critical',
|
|
439
|
+
tags: ['missing-required', prop.name],
|
|
440
|
+
rationale: 'Validates required parameter enforcement',
|
|
441
|
+
targetParameter: prop.name,
|
|
442
|
+
});
|
|
443
|
+
}
|
|
444
|
+
// Test invalid types
|
|
445
|
+
for (const prop of properties) {
|
|
446
|
+
if (scenarios.length >= maxScenarios)
|
|
447
|
+
break;
|
|
448
|
+
const input = {};
|
|
449
|
+
// Set required params
|
|
450
|
+
for (const p of properties.filter(p => p.required && p.name !== prop.name)) {
|
|
451
|
+
input[p.name] = generateValidValue(p);
|
|
452
|
+
}
|
|
453
|
+
input[prop.name] = generateInvalidValue(prop);
|
|
454
|
+
scenarios.push({
|
|
455
|
+
id: generateScenarioId(tool.name, 'error_handling', counter),
|
|
456
|
+
toolName: tool.name,
|
|
457
|
+
category: 'error_handling',
|
|
458
|
+
description: `Invalid type for ${prop.name}: expected ${prop.type}`,
|
|
459
|
+
input,
|
|
460
|
+
expectedBehavior: `Tool should reject invalid type for ${prop.name}`,
|
|
461
|
+
priority: 'high',
|
|
462
|
+
tags: ['invalid-type', prop.name],
|
|
463
|
+
rationale: `Tests type validation for ${prop.name} (expected ${prop.type})`,
|
|
464
|
+
targetParameter: prop.name,
|
|
465
|
+
});
|
|
466
|
+
}
|
|
467
|
+
// Test invalid enum values
|
|
468
|
+
for (const prop of properties.filter(p => p.enum)) {
|
|
469
|
+
if (scenarios.length >= maxScenarios)
|
|
470
|
+
break;
|
|
471
|
+
const input = {};
|
|
472
|
+
for (const p of properties.filter(p => p.required && p.name !== prop.name)) {
|
|
473
|
+
input[p.name] = generateValidValue(p);
|
|
474
|
+
}
|
|
475
|
+
input[prop.name] = 'invalid-enum-value-xyz';
|
|
476
|
+
scenarios.push({
|
|
477
|
+
id: generateScenarioId(tool.name, 'error_handling', counter),
|
|
478
|
+
toolName: tool.name,
|
|
479
|
+
category: 'error_handling',
|
|
480
|
+
description: `Invalid enum value for ${prop.name}`,
|
|
481
|
+
input,
|
|
482
|
+
expectedBehavior: `Tool should reject invalid enum value for ${prop.name}`,
|
|
483
|
+
priority: 'high',
|
|
484
|
+
tags: ['invalid-enum', prop.name],
|
|
485
|
+
rationale: `Tests enum validation for ${prop.name}`,
|
|
486
|
+
targetParameter: prop.name,
|
|
487
|
+
});
|
|
488
|
+
}
|
|
489
|
+
return scenarios.slice(0, maxScenarios);
|
|
490
|
+
}
|
|
491
|
+
/**
|
|
492
|
+
* Generate security test scenarios for a tool.
|
|
493
|
+
*/
|
|
494
|
+
function generateSecurityScenarios(tool, properties, maxScenarios, includePayloads, counter) {
|
|
495
|
+
const scenarios = [];
|
|
496
|
+
const stringProps = properties.filter(p => p.type === 'string');
|
|
497
|
+
if (!includePayloads || stringProps.length === 0) {
|
|
498
|
+
return scenarios;
|
|
499
|
+
}
|
|
500
|
+
// SQL Injection tests
|
|
501
|
+
for (const prop of stringProps) {
|
|
502
|
+
if (scenarios.length >= maxScenarios)
|
|
503
|
+
break;
|
|
504
|
+
const payload = SCENARIO_GENERATION.SQL_INJECTION_PAYLOADS[0];
|
|
505
|
+
const input = {};
|
|
506
|
+
for (const p of properties.filter(p => p.required && p.name !== prop.name)) {
|
|
507
|
+
input[p.name] = generateValidValue(p);
|
|
508
|
+
}
|
|
509
|
+
input[prop.name] = payload;
|
|
510
|
+
scenarios.push({
|
|
511
|
+
id: generateScenarioId(tool.name, 'security', counter),
|
|
512
|
+
toolName: tool.name,
|
|
513
|
+
category: 'security',
|
|
514
|
+
description: `SQL injection test on ${prop.name}`,
|
|
515
|
+
input,
|
|
516
|
+
expectedBehavior: 'Tool should sanitize input and not execute SQL',
|
|
517
|
+
priority: 'critical',
|
|
518
|
+
tags: ['sql-injection', 'security', prop.name],
|
|
519
|
+
rationale: 'Tests SQL injection vulnerability',
|
|
520
|
+
targetParameter: prop.name,
|
|
521
|
+
});
|
|
522
|
+
}
|
|
523
|
+
// XSS tests
|
|
524
|
+
for (const prop of stringProps) {
|
|
525
|
+
if (scenarios.length >= maxScenarios)
|
|
526
|
+
break;
|
|
527
|
+
const payload = SCENARIO_GENERATION.XSS_PAYLOADS[0];
|
|
528
|
+
const input = {};
|
|
529
|
+
for (const p of properties.filter(p => p.required && p.name !== prop.name)) {
|
|
530
|
+
input[p.name] = generateValidValue(p);
|
|
531
|
+
}
|
|
532
|
+
input[prop.name] = payload;
|
|
533
|
+
scenarios.push({
|
|
534
|
+
id: generateScenarioId(tool.name, 'security', counter),
|
|
535
|
+
toolName: tool.name,
|
|
536
|
+
category: 'security',
|
|
537
|
+
description: `XSS test on ${prop.name}`,
|
|
538
|
+
input,
|
|
539
|
+
expectedBehavior: 'Tool should sanitize or escape HTML/script content',
|
|
540
|
+
priority: 'critical',
|
|
541
|
+
tags: ['xss', 'security', prop.name],
|
|
542
|
+
rationale: 'Tests cross-site scripting vulnerability',
|
|
543
|
+
targetParameter: prop.name,
|
|
544
|
+
});
|
|
545
|
+
}
|
|
546
|
+
// Path traversal tests (for file-related parameters)
|
|
547
|
+
const fileProps = stringProps.filter(p => p.name.toLowerCase().includes('path') ||
|
|
548
|
+
p.name.toLowerCase().includes('file') ||
|
|
549
|
+
p.name.toLowerCase().includes('dir') ||
|
|
550
|
+
p.description?.toLowerCase().includes('path') ||
|
|
551
|
+
p.description?.toLowerCase().includes('file'));
|
|
552
|
+
for (const prop of fileProps) {
|
|
553
|
+
if (scenarios.length >= maxScenarios)
|
|
554
|
+
break;
|
|
555
|
+
const payload = SCENARIO_GENERATION.PATH_TRAVERSAL_PAYLOADS[0];
|
|
556
|
+
const input = {};
|
|
557
|
+
for (const p of properties.filter(p => p.required && p.name !== prop.name)) {
|
|
558
|
+
input[p.name] = generateValidValue(p);
|
|
559
|
+
}
|
|
560
|
+
input[prop.name] = payload;
|
|
561
|
+
scenarios.push({
|
|
562
|
+
id: generateScenarioId(tool.name, 'security', counter),
|
|
563
|
+
toolName: tool.name,
|
|
564
|
+
category: 'security',
|
|
565
|
+
description: `Path traversal test on ${prop.name}`,
|
|
566
|
+
input,
|
|
567
|
+
expectedBehavior: 'Tool should reject path traversal attempts',
|
|
568
|
+
priority: 'critical',
|
|
569
|
+
tags: ['path-traversal', 'security', prop.name],
|
|
570
|
+
rationale: 'Tests path traversal vulnerability',
|
|
571
|
+
targetParameter: prop.name,
|
|
572
|
+
});
|
|
573
|
+
}
|
|
574
|
+
// Command injection tests (for command-like parameters)
|
|
575
|
+
const cmdProps = stringProps.filter(p => p.name.toLowerCase().includes('command') ||
|
|
576
|
+
p.name.toLowerCase().includes('cmd') ||
|
|
577
|
+
p.name.toLowerCase().includes('exec') ||
|
|
578
|
+
p.name.toLowerCase().includes('shell'));
|
|
579
|
+
for (const prop of cmdProps) {
|
|
580
|
+
if (scenarios.length >= maxScenarios)
|
|
581
|
+
break;
|
|
582
|
+
const input = {};
|
|
583
|
+
for (const p of properties.filter(p => p.required && p.name !== prop.name)) {
|
|
584
|
+
input[p.name] = generateValidValue(p);
|
|
585
|
+
}
|
|
586
|
+
input[prop.name] = '; rm -rf /';
|
|
587
|
+
scenarios.push({
|
|
588
|
+
id: generateScenarioId(tool.name, 'security', counter),
|
|
589
|
+
toolName: tool.name,
|
|
590
|
+
category: 'security',
|
|
591
|
+
description: `Command injection test on ${prop.name}`,
|
|
592
|
+
input,
|
|
593
|
+
expectedBehavior: 'Tool should sanitize command input',
|
|
594
|
+
priority: 'critical',
|
|
595
|
+
tags: ['command-injection', 'security', prop.name],
|
|
596
|
+
rationale: 'Tests command injection vulnerability',
|
|
597
|
+
targetParameter: prop.name,
|
|
598
|
+
});
|
|
599
|
+
}
|
|
600
|
+
return scenarios.slice(0, maxScenarios);
|
|
601
|
+
}
|
|
602
|
+
/**
|
|
603
|
+
* Generate test scenarios for a single tool.
|
|
604
|
+
*/
|
|
605
|
+
export function generateToolScenarios(tool, config = {}) {
|
|
606
|
+
const { maxHappyPath = SCENARIO_GENERATION.MAX_HAPPY_PATH_SCENARIOS, maxEdgeCases = SCENARIO_GENERATION.MAX_EDGE_CASE_SCENARIOS, maxErrorCases = SCENARIO_GENERATION.MAX_ERROR_CASE_SCENARIOS, maxSecurityScenarios = SCENARIO_GENERATION.MAX_SECURITY_SCENARIOS, categories = SCENARIO_GENERATION.CATEGORIES, includeSecurityPayloads = true, } = config;
|
|
607
|
+
// Counter for generating unique scenario IDs within this tool
|
|
608
|
+
const counter = { value: 0 };
|
|
609
|
+
const properties = tool.inputSchema ? extractSchemaProperties(tool.inputSchema) : [];
|
|
610
|
+
const happyPath = categories.includes('happy_path')
|
|
611
|
+
? generateHappyPathScenarios(tool, properties, maxHappyPath, counter)
|
|
612
|
+
: [];
|
|
613
|
+
const edgeCases = categories.includes('edge_cases')
|
|
614
|
+
? generateEdgeCaseScenarios(tool, properties, maxEdgeCases, counter)
|
|
615
|
+
: [];
|
|
616
|
+
const errorCases = categories.includes('error_handling')
|
|
617
|
+
? generateErrorCaseScenarios(tool, properties, maxErrorCases, counter)
|
|
618
|
+
: [];
|
|
619
|
+
const securityTests = categories.includes('security')
|
|
620
|
+
? generateSecurityScenarios(tool, properties, maxSecurityScenarios, includeSecurityPayloads, counter)
|
|
621
|
+
: [];
|
|
622
|
+
// Calculate coverage
|
|
623
|
+
const totalScenarios = happyPath.length + edgeCases.length + errorCases.length + securityTests.length;
|
|
624
|
+
const coveredParams = new Set();
|
|
625
|
+
for (const scenario of [...happyPath, ...edgeCases, ...errorCases, ...securityTests]) {
|
|
626
|
+
if (scenario.targetParameter) {
|
|
627
|
+
coveredParams.add(scenario.targetParameter);
|
|
628
|
+
}
|
|
629
|
+
// Also count parameters in input
|
|
630
|
+
for (const param of Object.keys(scenario.input)) {
|
|
631
|
+
coveredParams.add(param);
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
const allParams = properties.map(p => p.name);
|
|
635
|
+
const uncovered = allParams.filter(p => !coveredParams.has(p));
|
|
636
|
+
// Estimate coverage based on parameters covered and scenario diversity
|
|
637
|
+
let coverageEstimate = 0;
|
|
638
|
+
if (allParams.length > 0) {
|
|
639
|
+
const paramCoverage = (coveredParams.size / allParams.length) * 100;
|
|
640
|
+
const categoryBonus = categories.length * 5; // Bonus for testing multiple categories
|
|
641
|
+
coverageEstimate = Math.min(100, Math.round(paramCoverage + categoryBonus));
|
|
642
|
+
}
|
|
643
|
+
else if (totalScenarios > 0) {
|
|
644
|
+
coverageEstimate = 50; // Basic coverage for tools without schema
|
|
645
|
+
}
|
|
646
|
+
return {
|
|
647
|
+
toolName: tool.name,
|
|
648
|
+
toolDescription: tool.description,
|
|
649
|
+
happyPath,
|
|
650
|
+
edgeCases,
|
|
651
|
+
errorCases,
|
|
652
|
+
securityTests,
|
|
653
|
+
coverageEstimate,
|
|
654
|
+
coveredParameters: Array.from(coveredParams),
|
|
655
|
+
uncoveredParameters: uncovered,
|
|
656
|
+
generatedAt: new Date(),
|
|
657
|
+
};
|
|
658
|
+
}
|
|
659
|
+
/**
|
|
660
|
+
* Generate test scenarios for all tools in a baseline.
|
|
661
|
+
*/
|
|
662
|
+
export function generateBaselineScenarios(baseline, config = {}) {
|
|
663
|
+
const { tools: targetTools, minCoverage = SCENARIO_GENERATION.DEFAULT_MIN_COVERAGE } = config;
|
|
664
|
+
const toolsToProcess = targetTools
|
|
665
|
+
? baseline.tools.filter(t => targetTools.includes(t.name))
|
|
666
|
+
: baseline.tools;
|
|
667
|
+
const scenarios = [];
|
|
668
|
+
let toolsWithScenarios = 0;
|
|
669
|
+
let toolsSkipped = 0;
|
|
670
|
+
const categoryTotals = {
|
|
671
|
+
happy_path: 0,
|
|
672
|
+
edge_cases: 0,
|
|
673
|
+
error_handling: 0,
|
|
674
|
+
security: 0,
|
|
675
|
+
};
|
|
676
|
+
const priorityTotals = {
|
|
677
|
+
critical: 0,
|
|
678
|
+
high: 0,
|
|
679
|
+
medium: 0,
|
|
680
|
+
low: 0,
|
|
681
|
+
};
|
|
682
|
+
for (const tool of toolsToProcess) {
|
|
683
|
+
// Skip tools without schema (can't generate meaningful scenarios)
|
|
684
|
+
if (!tool.inputSchema) {
|
|
685
|
+
toolsSkipped++;
|
|
686
|
+
continue;
|
|
687
|
+
}
|
|
688
|
+
const toolScenarios = generateToolScenarios(tool, config);
|
|
689
|
+
const totalForTool = toolScenarios.happyPath.length +
|
|
690
|
+
toolScenarios.edgeCases.length +
|
|
691
|
+
toolScenarios.errorCases.length +
|
|
692
|
+
toolScenarios.securityTests.length;
|
|
693
|
+
if (totalForTool > 0) {
|
|
694
|
+
toolsWithScenarios++;
|
|
695
|
+
scenarios.push(toolScenarios);
|
|
696
|
+
// Update category totals
|
|
697
|
+
categoryTotals.happy_path += toolScenarios.happyPath.length;
|
|
698
|
+
categoryTotals.edge_cases += toolScenarios.edgeCases.length;
|
|
699
|
+
categoryTotals.error_handling += toolScenarios.errorCases.length;
|
|
700
|
+
categoryTotals.security += toolScenarios.securityTests.length;
|
|
701
|
+
// Update priority totals
|
|
702
|
+
for (const s of [
|
|
703
|
+
...toolScenarios.happyPath,
|
|
704
|
+
...toolScenarios.edgeCases,
|
|
705
|
+
...toolScenarios.errorCases,
|
|
706
|
+
...toolScenarios.securityTests,
|
|
707
|
+
]) {
|
|
708
|
+
priorityTotals[s.priority]++;
|
|
709
|
+
}
|
|
710
|
+
}
|
|
711
|
+
else {
|
|
712
|
+
toolsSkipped++;
|
|
713
|
+
}
|
|
714
|
+
}
|
|
715
|
+
const totalScenarios = Object.values(categoryTotals).reduce((a, b) => a + b, 0);
|
|
716
|
+
const averageCoverage = scenarios.length > 0
|
|
717
|
+
? Math.round(scenarios.reduce((sum, s) => sum + s.coverageEstimate, 0) / scenarios.length)
|
|
718
|
+
: 0;
|
|
719
|
+
const lowCoverageTools = scenarios
|
|
720
|
+
.filter(s => s.coverageEstimate < minCoverage)
|
|
721
|
+
.map(s => s.toolName);
|
|
722
|
+
return {
|
|
723
|
+
scenarios,
|
|
724
|
+
summary: {
|
|
725
|
+
toolsProcessed: toolsToProcess.length,
|
|
726
|
+
toolsWithScenarios,
|
|
727
|
+
toolsSkipped,
|
|
728
|
+
totalScenarios,
|
|
729
|
+
scenariosByCategory: categoryTotals,
|
|
730
|
+
scenariosByPriority: priorityTotals,
|
|
731
|
+
averageCoverage,
|
|
732
|
+
lowCoverageTools,
|
|
733
|
+
generatedAt: new Date(),
|
|
734
|
+
},
|
|
735
|
+
};
|
|
736
|
+
}
|
|
737
|
+
/**
|
|
738
|
+
* Format scenarios as YAML for test execution.
|
|
739
|
+
*/
|
|
740
|
+
export function formatScenariosAsYaml(result) {
|
|
741
|
+
const lines = [
|
|
742
|
+
'# Auto-generated test scenarios',
|
|
743
|
+
`# Generated: ${result.summary.generatedAt.toISOString()}`,
|
|
744
|
+
`# Total scenarios: ${result.summary.totalScenarios}`,
|
|
745
|
+
'',
|
|
746
|
+
'scenarios:',
|
|
747
|
+
];
|
|
748
|
+
for (const toolScenarios of result.scenarios) {
|
|
749
|
+
lines.push(` # Tool: ${toolScenarios.toolName}`);
|
|
750
|
+
lines.push(` # Coverage: ${toolScenarios.coverageEstimate}%`);
|
|
751
|
+
const allScenarios = [
|
|
752
|
+
...toolScenarios.happyPath,
|
|
753
|
+
...toolScenarios.edgeCases,
|
|
754
|
+
...toolScenarios.errorCases,
|
|
755
|
+
...toolScenarios.securityTests,
|
|
756
|
+
];
|
|
757
|
+
for (const scenario of allScenarios) {
|
|
758
|
+
lines.push(` - id: ${scenario.id}`);
|
|
759
|
+
lines.push(` tool: ${scenario.toolName}`);
|
|
760
|
+
lines.push(` category: ${scenario.category}`);
|
|
761
|
+
lines.push(` description: "${scenario.description.replace(/"/g, '\\"')}"`);
|
|
762
|
+
lines.push(` priority: ${scenario.priority}`);
|
|
763
|
+
lines.push(` input:`);
|
|
764
|
+
for (const [key, value] of Object.entries(scenario.input)) {
|
|
765
|
+
const yamlValue = formatYamlValue(value);
|
|
766
|
+
lines.push(` ${key}: ${yamlValue}`);
|
|
767
|
+
}
|
|
768
|
+
lines.push(` expected: "${scenario.expectedBehavior.replace(/"/g, '\\"')}"`);
|
|
769
|
+
lines.push(` tags: [${scenario.tags.map(t => `"${t}"`).join(', ')}]`);
|
|
770
|
+
lines.push('');
|
|
771
|
+
}
|
|
772
|
+
}
|
|
773
|
+
return lines.join('\n');
|
|
774
|
+
}
|
|
775
|
+
/**
|
|
776
|
+
* Format a value for YAML output.
|
|
777
|
+
*/
|
|
778
|
+
function formatYamlValue(value) {
|
|
779
|
+
if (value === null)
|
|
780
|
+
return 'null';
|
|
781
|
+
if (value === undefined)
|
|
782
|
+
return 'null';
|
|
783
|
+
if (typeof value === 'string') {
|
|
784
|
+
// Escape special characters
|
|
785
|
+
if (value.includes('\n') || value.includes('"') || value.includes(':')) {
|
|
786
|
+
return `"${value.replace(/\\/g, '\\\\').replace(/"/g, '\\"').replace(/\n/g, '\\n')}"`;
|
|
787
|
+
}
|
|
788
|
+
return `"${value}"`;
|
|
789
|
+
}
|
|
790
|
+
if (typeof value === 'number' || typeof value === 'boolean') {
|
|
791
|
+
return String(value);
|
|
792
|
+
}
|
|
793
|
+
if (Array.isArray(value)) {
|
|
794
|
+
if (value.length === 0)
|
|
795
|
+
return '[]';
|
|
796
|
+
return `[${value.map(v => formatYamlValue(v)).join(', ')}]`;
|
|
797
|
+
}
|
|
798
|
+
if (typeof value === 'object') {
|
|
799
|
+
return JSON.stringify(value);
|
|
800
|
+
}
|
|
801
|
+
return String(value);
|
|
802
|
+
}
|
|
803
|
+
/**
|
|
804
|
+
* Format scenarios as a human-readable report.
|
|
805
|
+
*/
|
|
806
|
+
export function formatScenariosReport(result) {
|
|
807
|
+
const lines = [
|
|
808
|
+
'═══════════════════════════════════════════════════════════════',
|
|
809
|
+
' AUTO-GENERATED TEST SCENARIOS ',
|
|
810
|
+
'═══════════════════════════════════════════════════════════════',
|
|
811
|
+
'',
|
|
812
|
+
`Generated: ${result.summary.generatedAt.toISOString()}`,
|
|
813
|
+
'',
|
|
814
|
+
'── Summary ──────────────────────────────────────────────────────',
|
|
815
|
+
` Tools processed: ${result.summary.toolsProcessed}`,
|
|
816
|
+
` Tools with scenarios: ${result.summary.toolsWithScenarios}`,
|
|
817
|
+
` Tools skipped: ${result.summary.toolsSkipped}`,
|
|
818
|
+
` Total scenarios: ${result.summary.totalScenarios}`,
|
|
819
|
+
` Average coverage: ${result.summary.averageCoverage}%`,
|
|
820
|
+
'',
|
|
821
|
+
'── By Category ──────────────────────────────────────────────────',
|
|
822
|
+
` Happy Path: ${result.summary.scenariosByCategory.happy_path}`,
|
|
823
|
+
` Edge Cases: ${result.summary.scenariosByCategory.edge_cases}`,
|
|
824
|
+
` Error Handling: ${result.summary.scenariosByCategory.error_handling}`,
|
|
825
|
+
` Security: ${result.summary.scenariosByCategory.security}`,
|
|
826
|
+
'',
|
|
827
|
+
'── By Priority ──────────────────────────────────────────────────',
|
|
828
|
+
` Critical: ${result.summary.scenariosByPriority.critical}`,
|
|
829
|
+
` High: ${result.summary.scenariosByPriority.high}`,
|
|
830
|
+
` Medium: ${result.summary.scenariosByPriority.medium}`,
|
|
831
|
+
` Low: ${result.summary.scenariosByPriority.low}`,
|
|
832
|
+
];
|
|
833
|
+
if (result.summary.lowCoverageTools.length > 0) {
|
|
834
|
+
lines.push('');
|
|
835
|
+
lines.push('── Low Coverage Tools (need attention) ─────────────────────────');
|
|
836
|
+
for (const tool of result.summary.lowCoverageTools) {
|
|
837
|
+
const toolData = result.scenarios.find(s => s.toolName === tool);
|
|
838
|
+
lines.push(` • ${tool} (${toolData?.coverageEstimate ?? 0}%)`);
|
|
839
|
+
}
|
|
840
|
+
}
|
|
841
|
+
lines.push('');
|
|
842
|
+
lines.push('── Tool Details ─────────────────────────────────────────────────');
|
|
843
|
+
for (const toolScenarios of result.scenarios) {
|
|
844
|
+
const total = toolScenarios.happyPath.length +
|
|
845
|
+
toolScenarios.edgeCases.length +
|
|
846
|
+
toolScenarios.errorCases.length +
|
|
847
|
+
toolScenarios.securityTests.length;
|
|
848
|
+
lines.push('');
|
|
849
|
+
lines.push(` ${toolScenarios.toolName}`);
|
|
850
|
+
lines.push(` Scenarios: ${total} | Coverage: ${toolScenarios.coverageEstimate}%`);
|
|
851
|
+
lines.push(` HP: ${toolScenarios.happyPath.length} | EC: ${toolScenarios.edgeCases.length} | EH: ${toolScenarios.errorCases.length} | SEC: ${toolScenarios.securityTests.length}`);
|
|
852
|
+
if (toolScenarios.uncoveredParameters.length > 0) {
|
|
853
|
+
lines.push(` Uncovered: ${toolScenarios.uncoveredParameters.join(', ')}`);
|
|
854
|
+
}
|
|
855
|
+
}
|
|
856
|
+
lines.push('');
|
|
857
|
+
lines.push('═══════════════════════════════════════════════════════════════');
|
|
858
|
+
return lines.join('\n');
|
|
859
|
+
}
|
|
860
|
+
/**
|
|
861
|
+
* Get scenarios filtered by priority.
|
|
862
|
+
*/
|
|
863
|
+
export function getScenariosByPriority(result, priority) {
|
|
864
|
+
const allScenarios = [];
|
|
865
|
+
for (const toolScenarios of result.scenarios) {
|
|
866
|
+
allScenarios.push(...toolScenarios.happyPath.filter(s => s.priority === priority), ...toolScenarios.edgeCases.filter(s => s.priority === priority), ...toolScenarios.errorCases.filter(s => s.priority === priority), ...toolScenarios.securityTests.filter(s => s.priority === priority));
|
|
867
|
+
}
|
|
868
|
+
return allScenarios;
|
|
869
|
+
}
|
|
870
|
+
/**
|
|
871
|
+
* Get scenarios filtered by category.
|
|
872
|
+
*/
|
|
873
|
+
export function getScenariosByCategory(result, category) {
|
|
874
|
+
const allScenarios = [];
|
|
875
|
+
for (const toolScenarios of result.scenarios) {
|
|
876
|
+
switch (category) {
|
|
877
|
+
case 'happy_path':
|
|
878
|
+
allScenarios.push(...toolScenarios.happyPath);
|
|
879
|
+
break;
|
|
880
|
+
case 'edge_cases':
|
|
881
|
+
allScenarios.push(...toolScenarios.edgeCases);
|
|
882
|
+
break;
|
|
883
|
+
case 'error_handling':
|
|
884
|
+
allScenarios.push(...toolScenarios.errorCases);
|
|
885
|
+
break;
|
|
886
|
+
case 'security':
|
|
887
|
+
allScenarios.push(...toolScenarios.securityTests);
|
|
888
|
+
break;
|
|
889
|
+
}
|
|
890
|
+
}
|
|
891
|
+
return allScenarios;
|
|
892
|
+
}
|
|
893
|
+
/**
|
|
894
|
+
* Get critical scenarios for smoke testing.
|
|
895
|
+
*/
|
|
896
|
+
export function getCriticalScenarios(result) {
|
|
897
|
+
return getScenariosByPriority(result, 'critical');
|
|
898
|
+
}
|
|
899
|
+
/**
|
|
900
|
+
* Get security-focused scenarios.
|
|
901
|
+
*/
|
|
902
|
+
export function getSecurityScenarios(result) {
|
|
903
|
+
return getScenariosByCategory(result, 'security');
|
|
904
|
+
}
|
|
905
|
+
//# sourceMappingURL=scenario-generator.js.map
|