@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,834 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Schema-based test generator for deterministic testing in check mode.
|
|
3
|
+
*
|
|
4
|
+
* Generates comprehensive test cases from JSON Schema without requiring LLM.
|
|
5
|
+
* This module is the core of the enhanced testing capability, producing
|
|
6
|
+
* 8-12 tests per tool covering boundaries, types, enums, and error handling.
|
|
7
|
+
*/
|
|
8
|
+
import { SCHEMA_TESTING, SEMANTIC_VALIDATION, OUTCOME_ASSESSMENT } from '../constants.js';
|
|
9
|
+
import { generateSemanticTests } from '../validation/semantic-test-generator.js';
|
|
10
|
+
// ==================== Helper Functions ====================
|
|
11
|
+
/**
|
|
12
|
+
* Get the primary type from a schema property.
|
|
13
|
+
*/
|
|
14
|
+
function getPrimaryType(schema) {
|
|
15
|
+
if (Array.isArray(schema.type)) {
|
|
16
|
+
// Return first non-null type
|
|
17
|
+
return schema.type.find((t) => t !== 'null') ?? schema.type[0];
|
|
18
|
+
}
|
|
19
|
+
return schema.type;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Generate a smart default value for a property based on its type and constraints.
|
|
23
|
+
*/
|
|
24
|
+
function generateDefaultValue(propName, prop) {
|
|
25
|
+
const type = getPrimaryType(prop);
|
|
26
|
+
// Use schema example if available
|
|
27
|
+
if (prop.examples && prop.examples.length > 0) {
|
|
28
|
+
return prop.examples[0];
|
|
29
|
+
}
|
|
30
|
+
// Use schema default if available
|
|
31
|
+
if (prop.default !== undefined) {
|
|
32
|
+
return prop.default;
|
|
33
|
+
}
|
|
34
|
+
// Use first enum value if available
|
|
35
|
+
if (prop.enum && prop.enum.length > 0) {
|
|
36
|
+
return prop.enum[0];
|
|
37
|
+
}
|
|
38
|
+
// Use const value if available
|
|
39
|
+
if (prop.const !== undefined) {
|
|
40
|
+
return prop.const;
|
|
41
|
+
}
|
|
42
|
+
// Generate based on type
|
|
43
|
+
switch (type) {
|
|
44
|
+
case 'string':
|
|
45
|
+
return generateSmartStringValue(propName, prop);
|
|
46
|
+
case 'number':
|
|
47
|
+
case 'integer':
|
|
48
|
+
return generateSmartNumberValue(prop);
|
|
49
|
+
case 'boolean':
|
|
50
|
+
return true;
|
|
51
|
+
case 'array':
|
|
52
|
+
return [];
|
|
53
|
+
case 'object':
|
|
54
|
+
return {};
|
|
55
|
+
default:
|
|
56
|
+
return 'test';
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Pattern matchers for detecting date/time formats in descriptions.
|
|
61
|
+
* Each pattern maps to a format string and example value.
|
|
62
|
+
*/
|
|
63
|
+
const DATE_FORMAT_PATTERNS = [
|
|
64
|
+
// ISO 8601 date patterns
|
|
65
|
+
{ pattern: /YYYY-MM-DD|ISO\s*8601\s*date|date.*format.*YYYY/i, value: '2024-01-15', formatName: 'ISO 8601 date' },
|
|
66
|
+
{ pattern: /YYYY-MM|year-month|month.*format/i, value: '2024-01', formatName: 'year-month' },
|
|
67
|
+
{ pattern: /ISO\s*8601\s*(datetime|timestamp)|datetime.*format|timestamp.*ISO/i, value: '2024-01-15T14:30:00Z', formatName: 'ISO 8601 datetime' },
|
|
68
|
+
// Unix timestamp patterns
|
|
69
|
+
{ pattern: /unix\s*timestamp|epoch\s*time|seconds\s*since/i, value: '1705330200', formatName: 'Unix timestamp' },
|
|
70
|
+
{ pattern: /milliseconds?\s*(since|timestamp)|ms\s*timestamp/i, value: '1705330200000', formatName: 'Unix timestamp (ms)' },
|
|
71
|
+
// Time patterns
|
|
72
|
+
{ pattern: /HH:MM:SS|time.*format.*HH|24.hour.*time/i, value: '14:30:00', formatName: '24-hour time' },
|
|
73
|
+
{ pattern: /HH:MM|hour.*minute/i, value: '14:30', formatName: 'hour:minute' },
|
|
74
|
+
// Other date formats
|
|
75
|
+
{ pattern: /MM\/DD\/YYYY|US\s*date/i, value: '01/15/2024', formatName: 'US date' },
|
|
76
|
+
{ pattern: /DD\/MM\/YYYY|European\s*date/i, value: '15/01/2024', formatName: 'European date' },
|
|
77
|
+
];
|
|
78
|
+
/**
|
|
79
|
+
* Pattern matchers for detecting other semantic types in descriptions.
|
|
80
|
+
*/
|
|
81
|
+
const SEMANTIC_FORMAT_PATTERNS = [
|
|
82
|
+
// Currency/money patterns
|
|
83
|
+
{ pattern: /currency.*amount|dollar.*amount|price/i, value: '99.99', formatName: 'currency' },
|
|
84
|
+
{ pattern: /percentage|percent/i, value: '50', formatName: 'percentage' },
|
|
85
|
+
// Phone patterns
|
|
86
|
+
{ pattern: /phone.*number|telephone/i, value: '+1-555-123-4567', formatName: 'phone' },
|
|
87
|
+
// UUID patterns
|
|
88
|
+
{ pattern: /UUID|unique.*identifier/i, value: '550e8400-e29b-41d4-a716-446655440000', formatName: 'UUID' },
|
|
89
|
+
// IP address patterns
|
|
90
|
+
{ pattern: /IP.*address|IPv4/i, value: '192.168.1.100', formatName: 'IP address' },
|
|
91
|
+
// JSON patterns
|
|
92
|
+
{ pattern: /JSON\s*string|stringify|serialized/i, value: '{"key": "value"}', formatName: 'JSON' },
|
|
93
|
+
// Base64 patterns
|
|
94
|
+
{ pattern: /base64|encoded/i, value: 'dGVzdA==', formatName: 'base64' },
|
|
95
|
+
];
|
|
96
|
+
/**
|
|
97
|
+
* Generate a contextually appropriate string value based on property name,
|
|
98
|
+
* constraints, and description.
|
|
99
|
+
*
|
|
100
|
+
* This function implements smart test value generation by:
|
|
101
|
+
* 1. Parsing schema descriptions for format hints (e.g., "YYYY-MM-DD")
|
|
102
|
+
* 2. Checking schema format field
|
|
103
|
+
* 3. Inferring from property name patterns
|
|
104
|
+
*/
|
|
105
|
+
function generateSmartStringValue(propName, prop) {
|
|
106
|
+
const lowerName = propName.toLowerCase();
|
|
107
|
+
const description = (prop.description ?? '').toLowerCase();
|
|
108
|
+
// Priority 1: Check description for explicit date/time format hints
|
|
109
|
+
// This is the most reliable indicator since the schema author specified it
|
|
110
|
+
for (const { pattern, value } of DATE_FORMAT_PATTERNS) {
|
|
111
|
+
if (pattern.test(prop.description ?? '') || pattern.test(description)) {
|
|
112
|
+
return value;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
// Priority 2: Check description for other semantic format hints
|
|
116
|
+
for (const { pattern, value } of SEMANTIC_FORMAT_PATTERNS) {
|
|
117
|
+
if (pattern.test(prop.description ?? '') || pattern.test(description)) {
|
|
118
|
+
return value;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
// Priority 3: Check schema format field (JSON Schema standard)
|
|
122
|
+
if (prop.format === 'date') {
|
|
123
|
+
return '2024-01-15';
|
|
124
|
+
}
|
|
125
|
+
if (prop.format === 'date-time') {
|
|
126
|
+
return '2024-01-15T14:30:00Z';
|
|
127
|
+
}
|
|
128
|
+
if (prop.format === 'email') {
|
|
129
|
+
return 'test@example.com';
|
|
130
|
+
}
|
|
131
|
+
if (prop.format === 'uri' || prop.format === 'url') {
|
|
132
|
+
return 'https://example.com';
|
|
133
|
+
}
|
|
134
|
+
if (prop.format === 'uuid') {
|
|
135
|
+
return '550e8400-e29b-41d4-a716-446655440000';
|
|
136
|
+
}
|
|
137
|
+
if (prop.format === 'ipv4') {
|
|
138
|
+
return '192.168.1.100';
|
|
139
|
+
}
|
|
140
|
+
if (prop.format === 'time') {
|
|
141
|
+
return '14:30:00';
|
|
142
|
+
}
|
|
143
|
+
// Priority 4: Infer from property name patterns
|
|
144
|
+
if (lowerName.includes('date') || description.includes('date')) {
|
|
145
|
+
return '2024-01-15';
|
|
146
|
+
}
|
|
147
|
+
if (lowerName.includes('time') || description.includes('time')) {
|
|
148
|
+
return '14:30:00';
|
|
149
|
+
}
|
|
150
|
+
if (lowerName.includes('email') || description.includes('email')) {
|
|
151
|
+
return 'test@example.com';
|
|
152
|
+
}
|
|
153
|
+
if (lowerName.includes('url') || lowerName.includes('uri') ||
|
|
154
|
+
description.includes('url') || description.includes('uri')) {
|
|
155
|
+
return 'https://example.com';
|
|
156
|
+
}
|
|
157
|
+
if (lowerName.includes('path') || lowerName.includes('directory') ||
|
|
158
|
+
lowerName.includes('dir') || description.includes('path')) {
|
|
159
|
+
return '/tmp/test';
|
|
160
|
+
}
|
|
161
|
+
if (lowerName.includes('id') || description.includes('identifier')) {
|
|
162
|
+
return 'test-id-123';
|
|
163
|
+
}
|
|
164
|
+
if (lowerName.includes('name')) {
|
|
165
|
+
return 'test-name';
|
|
166
|
+
}
|
|
167
|
+
if (lowerName.includes('query') || lowerName.includes('search')) {
|
|
168
|
+
return 'test query';
|
|
169
|
+
}
|
|
170
|
+
if (lowerName.includes('token')) {
|
|
171
|
+
return 'test-token-abc123';
|
|
172
|
+
}
|
|
173
|
+
if (lowerName.includes('account') || description.includes('account')) {
|
|
174
|
+
return 'test-account-123';
|
|
175
|
+
}
|
|
176
|
+
if (lowerName.includes('amount') || description.includes('amount')) {
|
|
177
|
+
return '100.00';
|
|
178
|
+
}
|
|
179
|
+
if (lowerName.includes('category') || description.includes('category')) {
|
|
180
|
+
return 'test-category';
|
|
181
|
+
}
|
|
182
|
+
// Default fallback
|
|
183
|
+
return 'test';
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* Generate a contextually appropriate number value based on constraints.
|
|
187
|
+
*/
|
|
188
|
+
function generateSmartNumberValue(prop) {
|
|
189
|
+
const min = prop.minimum ?? 0;
|
|
190
|
+
const max = prop.maximum ?? 100;
|
|
191
|
+
// Use midpoint between min and max if both are specified
|
|
192
|
+
if (prop.minimum !== undefined && prop.maximum !== undefined) {
|
|
193
|
+
return Math.floor((min + max) / 2);
|
|
194
|
+
}
|
|
195
|
+
// Use minimum + 1 if only minimum is specified
|
|
196
|
+
if (prop.minimum !== undefined) {
|
|
197
|
+
return min + 1;
|
|
198
|
+
}
|
|
199
|
+
// Use reasonable defaults
|
|
200
|
+
return 1;
|
|
201
|
+
}
|
|
202
|
+
/**
|
|
203
|
+
* Determine expected outcome for a test based on its category and description.
|
|
204
|
+
* Uses OUTCOME_ASSESSMENT constants to classify tests.
|
|
205
|
+
*
|
|
206
|
+
* @param category - Test category
|
|
207
|
+
* @param description - Test description
|
|
208
|
+
* @returns Expected outcome: 'success', 'error', or 'either'
|
|
209
|
+
*/
|
|
210
|
+
export function determineExpectedOutcome(category, description) {
|
|
211
|
+
// Check if category expects error
|
|
212
|
+
if (OUTCOME_ASSESSMENT.EXPECTS_ERROR_CATEGORIES.includes(category)) {
|
|
213
|
+
return 'error';
|
|
214
|
+
}
|
|
215
|
+
// Check if category expects success
|
|
216
|
+
if (OUTCOME_ASSESSMENT.EXPECTS_SUCCESS_CATEGORIES.includes(category)) {
|
|
217
|
+
return 'success';
|
|
218
|
+
}
|
|
219
|
+
// Check description patterns for error expectation
|
|
220
|
+
for (const pattern of OUTCOME_ASSESSMENT.EXPECTS_ERROR_PATTERNS) {
|
|
221
|
+
if (pattern.test(description)) {
|
|
222
|
+
return 'error';
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
// Default to 'either' for edge cases
|
|
226
|
+
return 'either';
|
|
227
|
+
}
|
|
228
|
+
/**
|
|
229
|
+
* Add a question to the list, avoiding duplicates.
|
|
230
|
+
*/
|
|
231
|
+
function addQuestion(questions, question) {
|
|
232
|
+
// Check for duplicates based on args
|
|
233
|
+
const argsJson = JSON.stringify(question.args);
|
|
234
|
+
const isDuplicate = questions.some((q) => JSON.stringify(q.args) === argsJson);
|
|
235
|
+
if (!isDuplicate) {
|
|
236
|
+
questions.push(question);
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
/**
|
|
240
|
+
* Build base args with required parameters populated.
|
|
241
|
+
*/
|
|
242
|
+
function buildBaseArgs(properties, requiredParams) {
|
|
243
|
+
const args = {};
|
|
244
|
+
for (const param of requiredParams) {
|
|
245
|
+
const prop = properties[param];
|
|
246
|
+
if (prop) {
|
|
247
|
+
args[param] = generateDefaultValue(param, prop);
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
return args;
|
|
251
|
+
}
|
|
252
|
+
// ==================== Test Generators ====================
|
|
253
|
+
/**
|
|
254
|
+
* Generate happy path tests.
|
|
255
|
+
* Tests the tool with valid, expected inputs.
|
|
256
|
+
* All happy path tests expect success - errors indicate tool problems.
|
|
257
|
+
*/
|
|
258
|
+
function generateHappyPathTests(properties, requiredParams) {
|
|
259
|
+
const questions = [];
|
|
260
|
+
const { CATEGORY_DESCRIPTIONS } = SCHEMA_TESTING;
|
|
261
|
+
// Test 1: Empty args (if no required params)
|
|
262
|
+
if (requiredParams.length === 0) {
|
|
263
|
+
addQuestion(questions, {
|
|
264
|
+
description: `${CATEGORY_DESCRIPTIONS.HAPPY_PATH}: empty arguments`,
|
|
265
|
+
category: 'happy_path',
|
|
266
|
+
args: {},
|
|
267
|
+
expectedOutcome: 'success',
|
|
268
|
+
});
|
|
269
|
+
}
|
|
270
|
+
// Test 2: Minimal required args with smart defaults
|
|
271
|
+
if (requiredParams.length > 0) {
|
|
272
|
+
const minimalArgs = buildBaseArgs(properties, requiredParams);
|
|
273
|
+
addQuestion(questions, {
|
|
274
|
+
description: `${CATEGORY_DESCRIPTIONS.HAPPY_PATH}: minimal required arguments`,
|
|
275
|
+
category: 'happy_path',
|
|
276
|
+
args: minimalArgs,
|
|
277
|
+
expectedOutcome: 'success',
|
|
278
|
+
});
|
|
279
|
+
}
|
|
280
|
+
// Test 3: All parameters with defaults (if there are optional params)
|
|
281
|
+
const optionalParams = Object.keys(properties).filter((p) => !requiredParams.includes(p));
|
|
282
|
+
if (optionalParams.length > 0 && questions.length < SCHEMA_TESTING.MAX_TESTS_PER_CATEGORY) {
|
|
283
|
+
const fullArgs = buildBaseArgs(properties, requiredParams);
|
|
284
|
+
for (const param of optionalParams.slice(0, 3)) {
|
|
285
|
+
const prop = properties[param];
|
|
286
|
+
if (prop) {
|
|
287
|
+
fullArgs[param] = generateDefaultValue(param, prop);
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
addQuestion(questions, {
|
|
291
|
+
description: `${CATEGORY_DESCRIPTIONS.HAPPY_PATH}: with optional parameters`,
|
|
292
|
+
category: 'happy_path',
|
|
293
|
+
args: fullArgs,
|
|
294
|
+
expectedOutcome: 'success',
|
|
295
|
+
});
|
|
296
|
+
}
|
|
297
|
+
return questions.slice(0, SCHEMA_TESTING.MAX_TESTS_PER_CATEGORY);
|
|
298
|
+
}
|
|
299
|
+
/**
|
|
300
|
+
* Generate boundary value tests.
|
|
301
|
+
* Tests edge cases like empty strings, zero, large numbers.
|
|
302
|
+
* Boundary tests use 'either' outcome - they may succeed or fail depending on tool implementation.
|
|
303
|
+
*/
|
|
304
|
+
function generateBoundaryTests(properties, requiredParams) {
|
|
305
|
+
const questions = [];
|
|
306
|
+
const { BOUNDARY_VALUES, CATEGORY_DESCRIPTIONS } = SCHEMA_TESTING;
|
|
307
|
+
for (const [propName, prop] of Object.entries(properties)) {
|
|
308
|
+
if (questions.length >= SCHEMA_TESTING.MAX_TESTS_PER_CATEGORY)
|
|
309
|
+
break;
|
|
310
|
+
const type = getPrimaryType(prop);
|
|
311
|
+
const baseArgs = buildBaseArgs(properties, requiredParams);
|
|
312
|
+
switch (type) {
|
|
313
|
+
case 'string': {
|
|
314
|
+
// Test empty string
|
|
315
|
+
if (prop.minLength === undefined || prop.minLength === 0) {
|
|
316
|
+
addQuestion(questions, {
|
|
317
|
+
description: `${CATEGORY_DESCRIPTIONS.BOUNDARY}: empty string for "${propName}"`,
|
|
318
|
+
category: 'edge_case',
|
|
319
|
+
args: { ...baseArgs, [propName]: BOUNDARY_VALUES.EMPTY_STRING },
|
|
320
|
+
expectedOutcome: 'either',
|
|
321
|
+
});
|
|
322
|
+
}
|
|
323
|
+
// Test long string if no maxLength
|
|
324
|
+
if (prop.maxLength === undefined && questions.length < SCHEMA_TESTING.MAX_TESTS_PER_CATEGORY) {
|
|
325
|
+
const longString = 'x'.repeat(BOUNDARY_VALUES.LONG_STRING_LENGTH);
|
|
326
|
+
addQuestion(questions, {
|
|
327
|
+
description: `${CATEGORY_DESCRIPTIONS.BOUNDARY}: long string for "${propName}"`,
|
|
328
|
+
category: 'edge_case',
|
|
329
|
+
args: { ...baseArgs, [propName]: longString },
|
|
330
|
+
expectedOutcome: 'either',
|
|
331
|
+
});
|
|
332
|
+
}
|
|
333
|
+
break;
|
|
334
|
+
}
|
|
335
|
+
case 'number':
|
|
336
|
+
case 'integer': {
|
|
337
|
+
// Test zero
|
|
338
|
+
if (prop.minimum === undefined || prop.minimum <= 0) {
|
|
339
|
+
addQuestion(questions, {
|
|
340
|
+
description: `${CATEGORY_DESCRIPTIONS.BOUNDARY}: zero for "${propName}"`,
|
|
341
|
+
category: 'edge_case',
|
|
342
|
+
args: { ...baseArgs, [propName]: BOUNDARY_VALUES.ZERO },
|
|
343
|
+
expectedOutcome: 'either',
|
|
344
|
+
});
|
|
345
|
+
}
|
|
346
|
+
// Test negative (if allowed or not specified)
|
|
347
|
+
if ((prop.minimum === undefined || prop.minimum < 0) &&
|
|
348
|
+
questions.length < SCHEMA_TESTING.MAX_TESTS_PER_CATEGORY) {
|
|
349
|
+
addQuestion(questions, {
|
|
350
|
+
description: `${CATEGORY_DESCRIPTIONS.BOUNDARY}: negative value for "${propName}"`,
|
|
351
|
+
category: 'edge_case',
|
|
352
|
+
args: { ...baseArgs, [propName]: BOUNDARY_VALUES.NEGATIVE_ONE },
|
|
353
|
+
expectedOutcome: 'either',
|
|
354
|
+
});
|
|
355
|
+
}
|
|
356
|
+
// Test large number
|
|
357
|
+
if (prop.maximum === undefined &&
|
|
358
|
+
questions.length < SCHEMA_TESTING.MAX_TESTS_PER_CATEGORY) {
|
|
359
|
+
addQuestion(questions, {
|
|
360
|
+
description: `${CATEGORY_DESCRIPTIONS.BOUNDARY}: large value for "${propName}"`,
|
|
361
|
+
category: 'edge_case',
|
|
362
|
+
args: { ...baseArgs, [propName]: BOUNDARY_VALUES.LARGE_POSITIVE },
|
|
363
|
+
expectedOutcome: 'either',
|
|
364
|
+
});
|
|
365
|
+
}
|
|
366
|
+
break;
|
|
367
|
+
}
|
|
368
|
+
case 'array': {
|
|
369
|
+
// Test empty array
|
|
370
|
+
addQuestion(questions, {
|
|
371
|
+
description: `${CATEGORY_DESCRIPTIONS.ARRAY_HANDLING}: empty array for "${propName}"`,
|
|
372
|
+
category: 'edge_case',
|
|
373
|
+
args: { ...baseArgs, [propName]: [] },
|
|
374
|
+
expectedOutcome: 'either',
|
|
375
|
+
});
|
|
376
|
+
break;
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
return questions.slice(0, SCHEMA_TESTING.MAX_TESTS_PER_CATEGORY);
|
|
381
|
+
}
|
|
382
|
+
/**
|
|
383
|
+
* Generate type coercion tests.
|
|
384
|
+
* Tests what happens when wrong types are passed.
|
|
385
|
+
*/
|
|
386
|
+
function generateTypeCoercionTests(properties, requiredParams) {
|
|
387
|
+
const questions = [];
|
|
388
|
+
const { TYPE_COERCION, CATEGORY_DESCRIPTIONS } = SCHEMA_TESTING;
|
|
389
|
+
for (const [propName, prop] of Object.entries(properties)) {
|
|
390
|
+
if (questions.length >= SCHEMA_TESTING.MAX_TESTS_PER_CATEGORY)
|
|
391
|
+
break;
|
|
392
|
+
const type = getPrimaryType(prop);
|
|
393
|
+
const baseArgs = buildBaseArgs(properties, requiredParams);
|
|
394
|
+
switch (type) {
|
|
395
|
+
case 'number':
|
|
396
|
+
case 'integer':
|
|
397
|
+
// Pass string instead of number - tool should reject
|
|
398
|
+
addQuestion(questions, {
|
|
399
|
+
description: `${CATEGORY_DESCRIPTIONS.TYPE_COERCION}: string for number "${propName}"`,
|
|
400
|
+
category: 'error_handling',
|
|
401
|
+
args: { ...baseArgs, [propName]: TYPE_COERCION.NUMERIC_STRING },
|
|
402
|
+
expectedOutcome: 'error',
|
|
403
|
+
});
|
|
404
|
+
break;
|
|
405
|
+
case 'boolean':
|
|
406
|
+
// Pass string instead of boolean - tool should reject
|
|
407
|
+
addQuestion(questions, {
|
|
408
|
+
description: `${CATEGORY_DESCRIPTIONS.TYPE_COERCION}: string for boolean "${propName}"`,
|
|
409
|
+
category: 'error_handling',
|
|
410
|
+
args: { ...baseArgs, [propName]: TYPE_COERCION.TRUE_STRING },
|
|
411
|
+
expectedOutcome: 'error',
|
|
412
|
+
});
|
|
413
|
+
break;
|
|
414
|
+
case 'string':
|
|
415
|
+
// Pass number instead of string - tool should reject
|
|
416
|
+
if (questions.length < SCHEMA_TESTING.MAX_TESTS_PER_CATEGORY) {
|
|
417
|
+
addQuestion(questions, {
|
|
418
|
+
description: `${CATEGORY_DESCRIPTIONS.TYPE_COERCION}: number for string "${propName}"`,
|
|
419
|
+
category: 'error_handling',
|
|
420
|
+
args: { ...baseArgs, [propName]: 12345 },
|
|
421
|
+
expectedOutcome: 'error',
|
|
422
|
+
});
|
|
423
|
+
}
|
|
424
|
+
break;
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
return questions.slice(0, SCHEMA_TESTING.MAX_TESTS_PER_CATEGORY);
|
|
428
|
+
}
|
|
429
|
+
/**
|
|
430
|
+
* Generate enum validation tests.
|
|
431
|
+
* Tests that invalid enum values are properly rejected.
|
|
432
|
+
* All enum tests expect error - tool should reject invalid values.
|
|
433
|
+
*/
|
|
434
|
+
function generateEnumTests(properties, requiredParams) {
|
|
435
|
+
const questions = [];
|
|
436
|
+
const { INVALID_ENUM_VALUES, CATEGORY_DESCRIPTIONS } = SCHEMA_TESTING;
|
|
437
|
+
for (const [propName, prop] of Object.entries(properties)) {
|
|
438
|
+
if (questions.length >= SCHEMA_TESTING.MAX_TESTS_PER_CATEGORY)
|
|
439
|
+
break;
|
|
440
|
+
if (prop.enum && prop.enum.length > 0) {
|
|
441
|
+
const baseArgs = buildBaseArgs(properties, requiredParams);
|
|
442
|
+
addQuestion(questions, {
|
|
443
|
+
description: `${CATEGORY_DESCRIPTIONS.ENUM_VIOLATION}: invalid enum for "${propName}"`,
|
|
444
|
+
category: 'error_handling',
|
|
445
|
+
args: { ...baseArgs, [propName]: INVALID_ENUM_VALUES[0] },
|
|
446
|
+
expectedOutcome: 'error',
|
|
447
|
+
});
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
return questions;
|
|
451
|
+
}
|
|
452
|
+
/**
|
|
453
|
+
* Generate array handling tests.
|
|
454
|
+
* Tests arrays with different sizes.
|
|
455
|
+
* Array tests use 'either' outcome - they test edge cases that may or may not be accepted.
|
|
456
|
+
*/
|
|
457
|
+
function generateArrayTests(properties, requiredParams) {
|
|
458
|
+
const questions = [];
|
|
459
|
+
const { ARRAY_TESTS, CATEGORY_DESCRIPTIONS } = SCHEMA_TESTING;
|
|
460
|
+
for (const [propName, prop] of Object.entries(properties)) {
|
|
461
|
+
if (questions.length >= SCHEMA_TESTING.MAX_TESTS_PER_CATEGORY)
|
|
462
|
+
break;
|
|
463
|
+
const type = getPrimaryType(prop);
|
|
464
|
+
if (type === 'array' && prop.items) {
|
|
465
|
+
const baseArgs = buildBaseArgs(properties, requiredParams);
|
|
466
|
+
const itemType = getPrimaryType(prop.items);
|
|
467
|
+
// Generate sample items
|
|
468
|
+
let sampleItem = 'item';
|
|
469
|
+
if (itemType === 'number' || itemType === 'integer') {
|
|
470
|
+
sampleItem = 1;
|
|
471
|
+
}
|
|
472
|
+
else if (itemType === 'boolean') {
|
|
473
|
+
sampleItem = true;
|
|
474
|
+
}
|
|
475
|
+
else if (itemType === 'object') {
|
|
476
|
+
sampleItem = {};
|
|
477
|
+
}
|
|
478
|
+
// Test with single item
|
|
479
|
+
addQuestion(questions, {
|
|
480
|
+
description: `${CATEGORY_DESCRIPTIONS.ARRAY_HANDLING}: single item for "${propName}"`,
|
|
481
|
+
category: 'edge_case',
|
|
482
|
+
args: { ...baseArgs, [propName]: [sampleItem] },
|
|
483
|
+
expectedOutcome: 'either',
|
|
484
|
+
});
|
|
485
|
+
// Test with many items
|
|
486
|
+
if (questions.length < SCHEMA_TESTING.MAX_TESTS_PER_CATEGORY) {
|
|
487
|
+
const manyItems = Array(ARRAY_TESTS.MANY_ITEMS_COUNT).fill(sampleItem);
|
|
488
|
+
addQuestion(questions, {
|
|
489
|
+
description: `${CATEGORY_DESCRIPTIONS.ARRAY_HANDLING}: many items for "${propName}"`,
|
|
490
|
+
category: 'edge_case',
|
|
491
|
+
args: { ...baseArgs, [propName]: manyItems },
|
|
492
|
+
expectedOutcome: 'either',
|
|
493
|
+
});
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
return questions;
|
|
498
|
+
}
|
|
499
|
+
/**
|
|
500
|
+
* Generate null/undefined handling tests.
|
|
501
|
+
* Tests how the tool handles null and undefined values.
|
|
502
|
+
* Null tests use 'either' outcome - tool may or may not accept null for optional params.
|
|
503
|
+
*/
|
|
504
|
+
function generateNullabilityTests(properties, requiredParams) {
|
|
505
|
+
const questions = [];
|
|
506
|
+
const { CATEGORY_DESCRIPTIONS } = SCHEMA_TESTING;
|
|
507
|
+
// Test null for optional parameters
|
|
508
|
+
const optionalParams = Object.keys(properties).filter((p) => !requiredParams.includes(p));
|
|
509
|
+
for (const propName of optionalParams.slice(0, 2)) {
|
|
510
|
+
if (questions.length >= SCHEMA_TESTING.MAX_TESTS_PER_CATEGORY)
|
|
511
|
+
break;
|
|
512
|
+
const baseArgs = buildBaseArgs(properties, requiredParams);
|
|
513
|
+
addQuestion(questions, {
|
|
514
|
+
description: `${CATEGORY_DESCRIPTIONS.NULL_HANDLING}: null for optional "${propName}"`,
|
|
515
|
+
category: 'edge_case',
|
|
516
|
+
args: { ...baseArgs, [propName]: null },
|
|
517
|
+
expectedOutcome: 'either',
|
|
518
|
+
});
|
|
519
|
+
}
|
|
520
|
+
return questions;
|
|
521
|
+
}
|
|
522
|
+
/**
|
|
523
|
+
* Generate error handling tests.
|
|
524
|
+
* Tests that required parameters are properly validated.
|
|
525
|
+
* All error handling tests expect error - tool should reject missing required params.
|
|
526
|
+
*/
|
|
527
|
+
function generateErrorHandlingTests(properties, requiredParams) {
|
|
528
|
+
const questions = [];
|
|
529
|
+
const { CATEGORY_DESCRIPTIONS } = SCHEMA_TESTING;
|
|
530
|
+
// Test missing all required params
|
|
531
|
+
if (requiredParams.length > 0) {
|
|
532
|
+
addQuestion(questions, {
|
|
533
|
+
description: `${CATEGORY_DESCRIPTIONS.MISSING_REQUIRED}: missing all required arguments`,
|
|
534
|
+
category: 'error_handling',
|
|
535
|
+
args: {},
|
|
536
|
+
expectedOutcome: 'error',
|
|
537
|
+
});
|
|
538
|
+
}
|
|
539
|
+
// Test missing each required param individually
|
|
540
|
+
for (const param of requiredParams.slice(0, 2)) {
|
|
541
|
+
if (questions.length >= SCHEMA_TESTING.MAX_TESTS_PER_CATEGORY)
|
|
542
|
+
break;
|
|
543
|
+
const partialArgs = buildBaseArgs(properties, requiredParams);
|
|
544
|
+
delete partialArgs[param];
|
|
545
|
+
addQuestion(questions, {
|
|
546
|
+
description: `${CATEGORY_DESCRIPTIONS.MISSING_REQUIRED}: missing "${param}"`,
|
|
547
|
+
category: 'error_handling',
|
|
548
|
+
args: partialArgs,
|
|
549
|
+
expectedOutcome: 'error',
|
|
550
|
+
});
|
|
551
|
+
}
|
|
552
|
+
return questions;
|
|
553
|
+
}
|
|
554
|
+
// ==================== Varied Tests for Simple Tools ====================
|
|
555
|
+
/**
|
|
556
|
+
* Generate varied tests for tools with few/no parameters.
|
|
557
|
+
* Instead of repeating the same test, this generates meaningful variations
|
|
558
|
+
* to improve statistical confidence without redundant test data.
|
|
559
|
+
* All varied tests are happy_path with expectedOutcome: 'success'.
|
|
560
|
+
*/
|
|
561
|
+
function generateVariedTestsForSimpleTools(properties, requiredParams, count, existingQuestions) {
|
|
562
|
+
const questions = [];
|
|
563
|
+
const { CATEGORY_DESCRIPTIONS } = SCHEMA_TESTING;
|
|
564
|
+
// Get existing arg signatures to avoid duplicates
|
|
565
|
+
const existingArgSignatures = new Set(existingQuestions.map(q => JSON.stringify(q.args)));
|
|
566
|
+
// Variation strategies for simple/no-param tools
|
|
567
|
+
const variationStrategies = [];
|
|
568
|
+
// Strategy 1: Different timing contexts (useful for stateful tools)
|
|
569
|
+
variationStrategies.push(() => ({
|
|
570
|
+
description: `${CATEGORY_DESCRIPTIONS.HAPPY_PATH}: sequential call verification`,
|
|
571
|
+
category: 'happy_path',
|
|
572
|
+
args: buildBaseArgs(properties, requiredParams),
|
|
573
|
+
expectedOutcome: 'success',
|
|
574
|
+
}));
|
|
575
|
+
// Strategy 2: Rapid succession test (for rate limiting / caching behavior)
|
|
576
|
+
variationStrategies.push(() => ({
|
|
577
|
+
description: `${CATEGORY_DESCRIPTIONS.HAPPY_PATH}: rapid succession call`,
|
|
578
|
+
category: 'happy_path',
|
|
579
|
+
args: buildBaseArgs(properties, requiredParams),
|
|
580
|
+
expectedOutcome: 'success',
|
|
581
|
+
}));
|
|
582
|
+
// Strategy 3: Idempotency verification
|
|
583
|
+
variationStrategies.push(() => ({
|
|
584
|
+
description: `${CATEGORY_DESCRIPTIONS.HAPPY_PATH}: idempotency verification`,
|
|
585
|
+
category: 'happy_path',
|
|
586
|
+
args: buildBaseArgs(properties, requiredParams),
|
|
587
|
+
expectedOutcome: 'success',
|
|
588
|
+
}));
|
|
589
|
+
// Strategy 4: If there are any string params, try different valid values
|
|
590
|
+
// Start with variant 1 and 2 since variant 0 is typically the same as base value
|
|
591
|
+
const stringParams = Object.entries(properties).filter(([, prop]) => getPrimaryType(prop) === 'string');
|
|
592
|
+
for (const [paramName, prop] of stringParams) {
|
|
593
|
+
// Variant with different valid string (start at 1 to avoid duplicate with base)
|
|
594
|
+
variationStrategies.push(() => {
|
|
595
|
+
const args = buildBaseArgs(properties, requiredParams);
|
|
596
|
+
args[paramName] = generateAlternativeStringValue(paramName, prop, 1);
|
|
597
|
+
return {
|
|
598
|
+
description: `${CATEGORY_DESCRIPTIONS.HAPPY_PATH}: alternative value for "${paramName}"`,
|
|
599
|
+
category: 'happy_path',
|
|
600
|
+
args,
|
|
601
|
+
expectedOutcome: 'success',
|
|
602
|
+
};
|
|
603
|
+
});
|
|
604
|
+
variationStrategies.push(() => {
|
|
605
|
+
const args = buildBaseArgs(properties, requiredParams);
|
|
606
|
+
args[paramName] = generateAlternativeStringValue(paramName, prop, 2);
|
|
607
|
+
return {
|
|
608
|
+
description: `${CATEGORY_DESCRIPTIONS.HAPPY_PATH}: second alternative for "${paramName}"`,
|
|
609
|
+
category: 'happy_path',
|
|
610
|
+
args,
|
|
611
|
+
expectedOutcome: 'success',
|
|
612
|
+
};
|
|
613
|
+
});
|
|
614
|
+
}
|
|
615
|
+
// Strategy 5: If there are number params, try different valid values
|
|
616
|
+
const numberParams = Object.entries(properties).filter(([, prop]) => {
|
|
617
|
+
const type = getPrimaryType(prop);
|
|
618
|
+
return type === 'number' || type === 'integer';
|
|
619
|
+
});
|
|
620
|
+
for (const [paramName, prop] of numberParams) {
|
|
621
|
+
variationStrategies.push(() => {
|
|
622
|
+
const args = buildBaseArgs(properties, requiredParams);
|
|
623
|
+
args[paramName] = generateAlternativeNumberValue(prop, 0);
|
|
624
|
+
return {
|
|
625
|
+
description: `${CATEGORY_DESCRIPTIONS.HAPPY_PATH}: alternative value for "${paramName}"`,
|
|
626
|
+
category: 'happy_path',
|
|
627
|
+
args,
|
|
628
|
+
expectedOutcome: 'success',
|
|
629
|
+
};
|
|
630
|
+
});
|
|
631
|
+
variationStrategies.push(() => {
|
|
632
|
+
const args = buildBaseArgs(properties, requiredParams);
|
|
633
|
+
args[paramName] = generateAlternativeNumberValue(prop, 1);
|
|
634
|
+
return {
|
|
635
|
+
description: `${CATEGORY_DESCRIPTIONS.HAPPY_PATH}: second alternative for "${paramName}"`,
|
|
636
|
+
category: 'happy_path',
|
|
637
|
+
args,
|
|
638
|
+
expectedOutcome: 'success',
|
|
639
|
+
};
|
|
640
|
+
});
|
|
641
|
+
}
|
|
642
|
+
// Strategy 6: Boolean params - test both values
|
|
643
|
+
const booleanParams = Object.entries(properties).filter(([, prop]) => getPrimaryType(prop) === 'boolean');
|
|
644
|
+
for (const [paramName] of booleanParams) {
|
|
645
|
+
variationStrategies.push(() => {
|
|
646
|
+
const args = buildBaseArgs(properties, requiredParams);
|
|
647
|
+
args[paramName] = true;
|
|
648
|
+
return {
|
|
649
|
+
description: `${CATEGORY_DESCRIPTIONS.HAPPY_PATH}: "${paramName}" = true`,
|
|
650
|
+
category: 'happy_path',
|
|
651
|
+
args,
|
|
652
|
+
expectedOutcome: 'success',
|
|
653
|
+
};
|
|
654
|
+
});
|
|
655
|
+
variationStrategies.push(() => {
|
|
656
|
+
const args = buildBaseArgs(properties, requiredParams);
|
|
657
|
+
args[paramName] = false;
|
|
658
|
+
return {
|
|
659
|
+
description: `${CATEGORY_DESCRIPTIONS.HAPPY_PATH}: "${paramName}" = false`,
|
|
660
|
+
category: 'happy_path',
|
|
661
|
+
args,
|
|
662
|
+
expectedOutcome: 'success',
|
|
663
|
+
};
|
|
664
|
+
});
|
|
665
|
+
}
|
|
666
|
+
// Strategy 7: Consistency checks (same args, different run)
|
|
667
|
+
for (let i = 0; i < 3; i++) {
|
|
668
|
+
variationStrategies.push(() => ({
|
|
669
|
+
description: `${CATEGORY_DESCRIPTIONS.HAPPY_PATH}: consistency check run ${i + 1}`,
|
|
670
|
+
category: 'happy_path',
|
|
671
|
+
args: buildBaseArgs(properties, requiredParams),
|
|
672
|
+
expectedOutcome: 'success',
|
|
673
|
+
}));
|
|
674
|
+
}
|
|
675
|
+
// Apply strategies until we have enough tests
|
|
676
|
+
let strategyIndex = 0;
|
|
677
|
+
while (questions.length < count && strategyIndex < variationStrategies.length) {
|
|
678
|
+
const question = variationStrategies[strategyIndex]();
|
|
679
|
+
if (question) {
|
|
680
|
+
const argSignature = JSON.stringify(question.args);
|
|
681
|
+
// Only add if not a duplicate (different description is still unique)
|
|
682
|
+
const descSignature = `${question.description}:${argSignature}`;
|
|
683
|
+
if (!existingArgSignatures.has(descSignature)) {
|
|
684
|
+
questions.push(question);
|
|
685
|
+
existingArgSignatures.add(descSignature);
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
strategyIndex++;
|
|
689
|
+
}
|
|
690
|
+
// If we still need more, add numbered consistency checks
|
|
691
|
+
let consistencyRun = 4;
|
|
692
|
+
while (questions.length < count) {
|
|
693
|
+
questions.push({
|
|
694
|
+
description: `${CATEGORY_DESCRIPTIONS.HAPPY_PATH}: consistency check run ${consistencyRun}`,
|
|
695
|
+
category: 'happy_path',
|
|
696
|
+
args: buildBaseArgs(properties, requiredParams),
|
|
697
|
+
expectedOutcome: 'success',
|
|
698
|
+
});
|
|
699
|
+
consistencyRun++;
|
|
700
|
+
}
|
|
701
|
+
return questions;
|
|
702
|
+
}
|
|
703
|
+
/**
|
|
704
|
+
* Generate an alternative string value for variation.
|
|
705
|
+
*/
|
|
706
|
+
function generateAlternativeStringValue(propName, prop, variant) {
|
|
707
|
+
const lowerName = propName.toLowerCase();
|
|
708
|
+
// Generate different valid values based on type
|
|
709
|
+
if (lowerName.includes('date')) {
|
|
710
|
+
const dates = ['2024-01-15', '2024-06-30', '2024-12-31'];
|
|
711
|
+
return dates[variant % dates.length];
|
|
712
|
+
}
|
|
713
|
+
if (lowerName.includes('id')) {
|
|
714
|
+
const ids = ['test-id-123', 'test-id-456', 'test-id-789'];
|
|
715
|
+
return ids[variant % ids.length];
|
|
716
|
+
}
|
|
717
|
+
if (lowerName.includes('name')) {
|
|
718
|
+
const names = ['test-name', 'sample-name', 'example-name'];
|
|
719
|
+
return names[variant % names.length];
|
|
720
|
+
}
|
|
721
|
+
if (lowerName.includes('query') || lowerName.includes('search')) {
|
|
722
|
+
const queries = ['test query', 'sample search', 'example term'];
|
|
723
|
+
return queries[variant % queries.length];
|
|
724
|
+
}
|
|
725
|
+
// Check if enum, use different values
|
|
726
|
+
if (prop.enum && prop.enum.length > 1) {
|
|
727
|
+
return String(prop.enum[(variant + 1) % prop.enum.length]);
|
|
728
|
+
}
|
|
729
|
+
// Default alternatives
|
|
730
|
+
const defaults = ['test', 'sample', 'example'];
|
|
731
|
+
return defaults[variant % defaults.length];
|
|
732
|
+
}
|
|
733
|
+
/**
|
|
734
|
+
* Generate an alternative number value for variation.
|
|
735
|
+
*/
|
|
736
|
+
function generateAlternativeNumberValue(prop, variant) {
|
|
737
|
+
const min = prop.minimum ?? 0;
|
|
738
|
+
const max = prop.maximum ?? 100;
|
|
739
|
+
// Generate different valid values within the range
|
|
740
|
+
const range = max - min;
|
|
741
|
+
const values = [
|
|
742
|
+
min + Math.floor(range * 0.25),
|
|
743
|
+
min + Math.floor(range * 0.5),
|
|
744
|
+
min + Math.floor(range * 0.75),
|
|
745
|
+
];
|
|
746
|
+
return values[variant % values.length];
|
|
747
|
+
}
|
|
748
|
+
// ==================== Main Export ====================
|
|
749
|
+
/**
|
|
750
|
+
* Generate comprehensive schema-based test cases for a tool.
|
|
751
|
+
*
|
|
752
|
+
* This function analyzes the tool's JSON Schema and generates deterministic
|
|
753
|
+
* test cases covering:
|
|
754
|
+
* - Happy path (valid inputs)
|
|
755
|
+
* - Boundary values (empty strings, zero, large numbers)
|
|
756
|
+
* - Type coercion (wrong types)
|
|
757
|
+
* - Enum validation (invalid enum values)
|
|
758
|
+
* - Array handling (empty, single, many items)
|
|
759
|
+
* - Null/undefined handling
|
|
760
|
+
* - Error handling (missing required params)
|
|
761
|
+
* - Semantic validation (date formats, emails, URLs, etc.)
|
|
762
|
+
*
|
|
763
|
+
* @param tool - The MCP tool to generate tests for
|
|
764
|
+
* @param options - Configuration options
|
|
765
|
+
* @returns Array of interview questions (test cases)
|
|
766
|
+
*/
|
|
767
|
+
export function generateSchemaTests(tool, options = {}) {
|
|
768
|
+
const result = generateSchemaTestsWithInferences(tool, options);
|
|
769
|
+
return result.questions;
|
|
770
|
+
}
|
|
771
|
+
/**
|
|
772
|
+
* Generate schema-based tests with semantic inference information.
|
|
773
|
+
*
|
|
774
|
+
* This variant returns both the test questions and semantic type inferences,
|
|
775
|
+
* useful when you need to track inferred types for documentation.
|
|
776
|
+
*
|
|
777
|
+
* @param tool - The MCP tool to generate tests for
|
|
778
|
+
* @param options - Configuration options
|
|
779
|
+
* @returns Test questions and semantic inferences
|
|
780
|
+
*/
|
|
781
|
+
export function generateSchemaTestsWithInferences(tool, options = {}) {
|
|
782
|
+
const questions = [];
|
|
783
|
+
const schema = tool.inputSchema;
|
|
784
|
+
const maxTests = options.maxTestsPerTool ?? SCHEMA_TESTING.MAX_TESTS_PER_TOOL;
|
|
785
|
+
const properties = schema?.properties ?? {};
|
|
786
|
+
const requiredParams = (schema?.required ?? []);
|
|
787
|
+
// 1. Happy Path Tests (always included)
|
|
788
|
+
questions.push(...generateHappyPathTests(properties, requiredParams));
|
|
789
|
+
// 2. Boundary Value Tests
|
|
790
|
+
questions.push(...generateBoundaryTests(properties, requiredParams));
|
|
791
|
+
// 3. Type Coercion Tests (unless skipping error tests)
|
|
792
|
+
if (!options.skipErrorTests) {
|
|
793
|
+
questions.push(...generateTypeCoercionTests(properties, requiredParams));
|
|
794
|
+
}
|
|
795
|
+
// 4. Enum Validation Tests (unless skipping error tests)
|
|
796
|
+
if (!options.skipErrorTests) {
|
|
797
|
+
questions.push(...generateEnumTests(properties, requiredParams));
|
|
798
|
+
}
|
|
799
|
+
// 5. Array Handling Tests
|
|
800
|
+
questions.push(...generateArrayTests(properties, requiredParams));
|
|
801
|
+
// 6. Nullability Tests
|
|
802
|
+
questions.push(...generateNullabilityTests(properties, requiredParams));
|
|
803
|
+
// 7. Error Handling Tests (unless skipped)
|
|
804
|
+
if (!options.skipErrorTests) {
|
|
805
|
+
questions.push(...generateErrorHandlingTests(properties, requiredParams));
|
|
806
|
+
}
|
|
807
|
+
// 8. Semantic Validation Tests (unless skipped)
|
|
808
|
+
let semanticInferences = [];
|
|
809
|
+
if (!options.skipSemanticTests) {
|
|
810
|
+
const semanticResult = generateSemanticTests(tool, {
|
|
811
|
+
minConfidence: SEMANTIC_VALIDATION.MIN_CONFIDENCE_THRESHOLD,
|
|
812
|
+
maxInvalidValuesPerParam: SEMANTIC_VALIDATION.MAX_INVALID_VALUES_PER_PARAM,
|
|
813
|
+
});
|
|
814
|
+
// Limit semantic tests to prevent explosion
|
|
815
|
+
const semanticTestsToAdd = semanticResult.tests.slice(0, SEMANTIC_VALIDATION.MAX_SEMANTIC_TESTS_PER_TOOL);
|
|
816
|
+
questions.push(...semanticTestsToAdd);
|
|
817
|
+
semanticInferences = semanticResult.inferences;
|
|
818
|
+
}
|
|
819
|
+
// Enforce minimum tests for statistical confidence
|
|
820
|
+
// For simple tools with few/no parameters, generate varied tests
|
|
821
|
+
// Use the greater of MIN_TESTS_PER_TOOL and maxTests (target confidence samples)
|
|
822
|
+
const minTests = Math.max(SCHEMA_TESTING.MIN_TESTS_PER_TOOL, maxTests);
|
|
823
|
+
if (questions.length < minTests && questions.length > 0) {
|
|
824
|
+
const existingCount = questions.length;
|
|
825
|
+
const additionalTests = generateVariedTestsForSimpleTools(properties, requiredParams, minTests - existingCount, questions);
|
|
826
|
+
questions.push(...additionalTests);
|
|
827
|
+
}
|
|
828
|
+
// Limit total tests
|
|
829
|
+
return {
|
|
830
|
+
questions: questions.slice(0, maxTests),
|
|
831
|
+
semanticInferences,
|
|
832
|
+
};
|
|
833
|
+
}
|
|
834
|
+
//# sourceMappingURL=schema-test-generator.js.map
|