@dotsetlabs/bellwether 2.1.2 → 2.1.3
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 +18 -0
- package/README.md +2 -2
- package/dist/baseline/golden-output.d.ts +0 -4
- package/dist/baseline/golden-output.js +2 -47
- package/dist/cli/commands/baseline-accept.js +14 -45
- package/dist/cli/commands/baseline.js +23 -78
- package/dist/cli/commands/check-formatters.d.ts +10 -0
- package/dist/cli/commands/check-formatters.js +160 -0
- package/dist/cli/commands/check.js +33 -241
- package/dist/cli/commands/contract.js +1 -13
- package/dist/cli/commands/explore.js +19 -66
- package/dist/cli/commands/watch.js +2 -3
- package/dist/cli/output.d.ts +0 -42
- package/dist/cli/output.js +73 -110
- package/dist/cli/utils/config-loader.d.ts +6 -0
- package/dist/cli/utils/config-loader.js +19 -0
- package/dist/cli/utils/error-hints.d.ts +9 -0
- package/dist/cli/utils/error-hints.js +128 -0
- package/dist/cli/utils/headers.js +2 -25
- package/dist/cli/utils/path-resolution.d.ts +10 -0
- package/dist/cli/utils/path-resolution.js +27 -0
- package/dist/cli/utils/report-loader.d.ts +9 -0
- package/dist/cli/utils/report-loader.js +31 -0
- package/dist/cli/utils/server-runtime.d.ts +16 -0
- package/dist/cli/utils/server-runtime.js +31 -0
- package/dist/config/defaults.d.ts +0 -1
- package/dist/config/defaults.js +0 -1
- package/dist/constants/core.d.ts +0 -42
- package/dist/constants/core.js +0 -50
- package/dist/contract/validator.js +2 -47
- package/dist/interview/question-category.d.ts +5 -0
- package/dist/interview/question-category.js +2 -0
- package/dist/interview/question-types.d.ts +80 -0
- package/dist/interview/question-types.js +2 -0
- package/dist/interview/schema-test-generator.d.ts +3 -29
- package/dist/interview/schema-test-generator.js +11 -286
- package/dist/interview/test-fixtures.d.ts +19 -0
- package/dist/interview/test-fixtures.js +2 -0
- package/dist/interview/types.d.ts +5 -80
- package/dist/persona/types.d.ts +3 -5
- package/dist/scenarios/types.d.ts +1 -1
- package/dist/transport/auth-errors.d.ts +15 -0
- package/dist/transport/auth-errors.js +22 -0
- package/dist/transport/http-transport.js +7 -9
- package/dist/transport/mcp-client.d.ts +0 -4
- package/dist/transport/mcp-client.js +13 -37
- package/dist/transport/sse-transport.d.ts +0 -1
- package/dist/transport/sse-transport.js +13 -28
- package/dist/utils/content-type.d.ts +14 -0
- package/dist/utils/content-type.js +37 -0
- package/dist/utils/http-headers.d.ts +9 -0
- package/dist/utils/http-headers.js +34 -0
- package/dist/utils/smart-truncate.js +2 -23
- package/package.json +2 -2
package/dist/constants/core.d.ts
CHANGED
|
@@ -63,8 +63,6 @@ export declare const TRANSPORT_ERRORS: {
|
|
|
63
63
|
readonly MAX_ERRORS_TO_COLLECT: 50;
|
|
64
64
|
/** Error messages that indicate a likely server bug (vs environment issue) */
|
|
65
65
|
readonly SERVER_BUG_INDICATORS: readonly RegExp[];
|
|
66
|
-
/** Error messages that indicate environment/config issues */
|
|
67
|
-
readonly ENVIRONMENT_ISSUE_INDICATORS: readonly RegExp[];
|
|
68
66
|
};
|
|
69
67
|
/**
|
|
70
68
|
* Interview process configuration defaults.
|
|
@@ -508,46 +506,6 @@ export declare const CLI_SECURITY: {
|
|
|
508
506
|
/**
|
|
509
507
|
* External service URLs.
|
|
510
508
|
*/
|
|
511
|
-
export declare const TOKEN_ESTIMATION: {
|
|
512
|
-
/** Average input tokens per question */
|
|
513
|
-
readonly AVG_INPUT_PER_QUESTION: 500;
|
|
514
|
-
/** Average output tokens per question */
|
|
515
|
-
readonly AVG_OUTPUT_PER_QUESTION: 300;
|
|
516
|
-
/** Schema overhead tokens per tool */
|
|
517
|
-
readonly SCHEMA_OVERHEAD_PER_TOOL: 200;
|
|
518
|
-
/** Character to token ratio (approximate) */
|
|
519
|
-
readonly CHARS_PER_TOKEN: 4;
|
|
520
|
-
/** Word adjustment factor */
|
|
521
|
-
readonly WORD_ADJUSTMENT: 0.3;
|
|
522
|
-
/** Role/message overhead tokens */
|
|
523
|
-
readonly MESSAGE_OVERHEAD_TOKENS: 4;
|
|
524
|
-
/** Default context window when model unknown */
|
|
525
|
-
readonly DEFAULT_CONTEXT_WINDOW: 16000;
|
|
526
|
-
};
|
|
527
|
-
/**
|
|
528
|
-
* Default token budget limits.
|
|
529
|
-
*/
|
|
530
|
-
export declare const TOKEN_BUDGET: {
|
|
531
|
-
/** Maximum total tokens for an interview */
|
|
532
|
-
readonly MAX_TOTAL_TOKENS: 1000000;
|
|
533
|
-
/** Maximum input tokens per request */
|
|
534
|
-
readonly MAX_INPUT_PER_REQUEST: 100000;
|
|
535
|
-
/** Maximum output tokens per request */
|
|
536
|
-
readonly MAX_OUTPUT_PER_REQUEST: 8000;
|
|
537
|
-
/** Reserved tokens for output in context */
|
|
538
|
-
readonly OUTPUT_RESERVE: 4000;
|
|
539
|
-
};
|
|
540
|
-
/**
|
|
541
|
-
* Metrics collection configuration.
|
|
542
|
-
*/
|
|
543
|
-
export declare const METRICS_CONFIG: {
|
|
544
|
-
/** Maximum entries in metrics store */
|
|
545
|
-
readonly MAX_ENTRIES: 10000;
|
|
546
|
-
/** Latency histogram buckets (ms) */
|
|
547
|
-
readonly LATENCY_BUCKETS: readonly [10, 25, 50, 100, 250, 500, 1000, 2500, 5000, 10000];
|
|
548
|
-
/** Metric name prefix */
|
|
549
|
-
readonly PREFIX: "bellwether_";
|
|
550
|
-
};
|
|
551
509
|
/**
|
|
552
510
|
* Default model configurations per provider.
|
|
553
511
|
* Uses budget-friendly, non-reasoning models by default for cost efficiency.
|
package/dist/constants/core.js
CHANGED
|
@@ -112,14 +112,6 @@ export const TRANSPORT_ERRORS = {
|
|
|
112
112
|
/protocol.*error/i,
|
|
113
113
|
/invalid.*response/i,
|
|
114
114
|
],
|
|
115
|
-
/** Error messages that indicate environment/config issues */
|
|
116
|
-
ENVIRONMENT_ISSUE_INDICATORS: [
|
|
117
|
-
/ENOENT/i,
|
|
118
|
-
/not found/i,
|
|
119
|
-
/permission denied/i,
|
|
120
|
-
/EACCES/i,
|
|
121
|
-
/cannot find module/i,
|
|
122
|
-
],
|
|
123
115
|
};
|
|
124
116
|
// ==================== Interview Configuration ====================
|
|
125
117
|
/**
|
|
@@ -589,48 +581,6 @@ export const CLI_SECURITY = {
|
|
|
589
581
|
/**
|
|
590
582
|
* External service URLs.
|
|
591
583
|
*/
|
|
592
|
-
export const TOKEN_ESTIMATION = {
|
|
593
|
-
/** Average input tokens per question */
|
|
594
|
-
AVG_INPUT_PER_QUESTION: 500,
|
|
595
|
-
/** Average output tokens per question */
|
|
596
|
-
AVG_OUTPUT_PER_QUESTION: 300,
|
|
597
|
-
/** Schema overhead tokens per tool */
|
|
598
|
-
SCHEMA_OVERHEAD_PER_TOOL: 200,
|
|
599
|
-
/** Character to token ratio (approximate) */
|
|
600
|
-
CHARS_PER_TOKEN: 4,
|
|
601
|
-
/** Word adjustment factor */
|
|
602
|
-
WORD_ADJUSTMENT: 0.3,
|
|
603
|
-
/** Role/message overhead tokens */
|
|
604
|
-
MESSAGE_OVERHEAD_TOKENS: 4,
|
|
605
|
-
/** Default context window when model unknown */
|
|
606
|
-
DEFAULT_CONTEXT_WINDOW: 16000,
|
|
607
|
-
};
|
|
608
|
-
// ==================== Token Budget ====================
|
|
609
|
-
/**
|
|
610
|
-
* Default token budget limits.
|
|
611
|
-
*/
|
|
612
|
-
export const TOKEN_BUDGET = {
|
|
613
|
-
/** Maximum total tokens for an interview */
|
|
614
|
-
MAX_TOTAL_TOKENS: 1000000,
|
|
615
|
-
/** Maximum input tokens per request */
|
|
616
|
-
MAX_INPUT_PER_REQUEST: 100000,
|
|
617
|
-
/** Maximum output tokens per request */
|
|
618
|
-
MAX_OUTPUT_PER_REQUEST: 8000,
|
|
619
|
-
/** Reserved tokens for output in context */
|
|
620
|
-
OUTPUT_RESERVE: 4000,
|
|
621
|
-
};
|
|
622
|
-
// ==================== Metrics ====================
|
|
623
|
-
/**
|
|
624
|
-
* Metrics collection configuration.
|
|
625
|
-
*/
|
|
626
|
-
export const METRICS_CONFIG = {
|
|
627
|
-
/** Maximum entries in metrics store */
|
|
628
|
-
MAX_ENTRIES: 10000,
|
|
629
|
-
/** Latency histogram buckets (ms) */
|
|
630
|
-
LATENCY_BUCKETS: [10, 25, 50, 100, 250, 500, 1000, 2500, 5000, 10000],
|
|
631
|
-
/** Metric name prefix */
|
|
632
|
-
PREFIX: 'bellwether_',
|
|
633
|
-
};
|
|
634
584
|
// ==================== LLM Models ====================
|
|
635
585
|
/**
|
|
636
586
|
* Default model configurations per provider.
|
|
@@ -8,6 +8,8 @@
|
|
|
8
8
|
import { readFileSync, existsSync } from 'fs';
|
|
9
9
|
import * as yaml from 'yaml';
|
|
10
10
|
import { CONTRACT_TESTING } from '../constants.js';
|
|
11
|
+
import { getValueAtPath } from '../utils/jsonpath.js';
|
|
12
|
+
import { detectContentType } from '../utils/content-type.js';
|
|
11
13
|
/**
|
|
12
14
|
* Load a contract from a file.
|
|
13
15
|
*/
|
|
@@ -398,33 +400,6 @@ function validateOutput(toolName, result, outputContract) {
|
|
|
398
400
|
}
|
|
399
401
|
return violations;
|
|
400
402
|
}
|
|
401
|
-
/**
|
|
402
|
-
* Get value at a JSONPath-like path.
|
|
403
|
-
* Supports simple paths like $.field.nested[0].value
|
|
404
|
-
*/
|
|
405
|
-
function getValueAtPath(obj, path) {
|
|
406
|
-
// Remove leading $. if present
|
|
407
|
-
const cleanPath = path.replace(/^\$\.?/, '');
|
|
408
|
-
if (!cleanPath)
|
|
409
|
-
return obj;
|
|
410
|
-
const segments = cleanPath.split(/\.|\[|\]/).filter(Boolean);
|
|
411
|
-
let current = obj;
|
|
412
|
-
for (const segment of segments) {
|
|
413
|
-
if (current === null || current === undefined)
|
|
414
|
-
return undefined;
|
|
415
|
-
if (typeof current !== 'object')
|
|
416
|
-
return undefined;
|
|
417
|
-
// Handle array index
|
|
418
|
-
const index = parseInt(segment, 10);
|
|
419
|
-
if (!isNaN(index) && Array.isArray(current)) {
|
|
420
|
-
current = current[index];
|
|
421
|
-
}
|
|
422
|
-
else {
|
|
423
|
-
current = current[segment];
|
|
424
|
-
}
|
|
425
|
-
}
|
|
426
|
-
return current;
|
|
427
|
-
}
|
|
428
403
|
/**
|
|
429
404
|
* Get the type name of a value.
|
|
430
405
|
*/
|
|
@@ -435,26 +410,6 @@ function getValueType(value) {
|
|
|
435
410
|
return 'array';
|
|
436
411
|
return typeof value;
|
|
437
412
|
}
|
|
438
|
-
/**
|
|
439
|
-
* Detect content type from raw output.
|
|
440
|
-
*/
|
|
441
|
-
function detectContentType(raw) {
|
|
442
|
-
const trimmed = raw.trim();
|
|
443
|
-
if ((trimmed.startsWith('{') && trimmed.endsWith('}')) ||
|
|
444
|
-
(trimmed.startsWith('[') && trimmed.endsWith(']'))) {
|
|
445
|
-
try {
|
|
446
|
-
JSON.parse(trimmed);
|
|
447
|
-
return 'json';
|
|
448
|
-
}
|
|
449
|
-
catch {
|
|
450
|
-
// Not valid JSON
|
|
451
|
-
}
|
|
452
|
-
}
|
|
453
|
-
if (/^#|^\*{1,3}[^*]|\[.*\]\(.*\)|^```/.test(trimmed)) {
|
|
454
|
-
return 'markdown';
|
|
455
|
-
}
|
|
456
|
-
return 'text';
|
|
457
|
-
}
|
|
458
413
|
/**
|
|
459
414
|
* Truncate a string for display.
|
|
460
415
|
*/
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import type { QuestionCategory } from './question-category.js';
|
|
2
|
+
/**
|
|
3
|
+
* Expected outcome for a test question.
|
|
4
|
+
* - 'success': Test expects the tool to execute successfully
|
|
5
|
+
* - 'error': Test expects the tool to reject/fail (validation test)
|
|
6
|
+
* - 'either': Test outcome is acceptable either way
|
|
7
|
+
*/
|
|
8
|
+
export type ExpectedOutcome = 'success' | 'error' | 'either';
|
|
9
|
+
/**
|
|
10
|
+
* A question to ask about a tool's behavior.
|
|
11
|
+
*/
|
|
12
|
+
export interface InterviewQuestion {
|
|
13
|
+
/** Description of what this question tests */
|
|
14
|
+
description: string;
|
|
15
|
+
/** Category of question */
|
|
16
|
+
category: QuestionCategory;
|
|
17
|
+
/** Arguments to pass to the tool */
|
|
18
|
+
args: Record<string, unknown>;
|
|
19
|
+
/**
|
|
20
|
+
* Expected outcome of this test.
|
|
21
|
+
* Used to determine if the tool behaved correctly.
|
|
22
|
+
* - 'success': Expects successful execution (happy path)
|
|
23
|
+
* - 'error': Expects rejection/error (validation test)
|
|
24
|
+
* - 'either': Either outcome is acceptable
|
|
25
|
+
*/
|
|
26
|
+
expectedOutcome?: ExpectedOutcome;
|
|
27
|
+
/** Semantic validation metadata (for tests generated from semantic type inference) */
|
|
28
|
+
metadata?: {
|
|
29
|
+
/** The inferred semantic type being tested */
|
|
30
|
+
semanticType?: string;
|
|
31
|
+
/** Expected behavior: 'reject' for invalid values, 'accept' for valid */
|
|
32
|
+
expectedBehavior?: 'reject' | 'accept';
|
|
33
|
+
/** Confidence level of the semantic type inference (0-1) */
|
|
34
|
+
confidence?: number;
|
|
35
|
+
/** Stateful testing metadata */
|
|
36
|
+
stateful?: {
|
|
37
|
+
/** Keys injected from prior tool outputs */
|
|
38
|
+
usedKeys?: string[];
|
|
39
|
+
/** Keys captured from this response */
|
|
40
|
+
providedKeys?: string[];
|
|
41
|
+
};
|
|
42
|
+
/**
|
|
43
|
+
* Whether this tool uses operation-based dispatch pattern.
|
|
44
|
+
* Tools with this pattern have different required args per operation.
|
|
45
|
+
*/
|
|
46
|
+
operationBased?: boolean;
|
|
47
|
+
/** The parameter name that selects the operation (e.g., "operation", "action") */
|
|
48
|
+
operationParam?: string;
|
|
49
|
+
/** The parameter name that holds operation-specific args (e.g., "args", "params") */
|
|
50
|
+
argsParam?: string;
|
|
51
|
+
/**
|
|
52
|
+
* Whether this tool requires prior state (session, chain, etc.).
|
|
53
|
+
* These tools need an active session before they can work.
|
|
54
|
+
*/
|
|
55
|
+
selfStateful?: boolean;
|
|
56
|
+
/** Reason for self-stateful detection */
|
|
57
|
+
selfStatefulReason?: string;
|
|
58
|
+
/**
|
|
59
|
+
* Whether this tool has complex array schemas requiring structured data.
|
|
60
|
+
* Simple test data generation often fails for these tools.
|
|
61
|
+
*/
|
|
62
|
+
hasComplexArrays?: boolean;
|
|
63
|
+
/** Array parameters with complex item schemas */
|
|
64
|
+
complexArrayParams?: string[];
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Assessment of whether a tool interaction outcome matched expectations.
|
|
69
|
+
*/
|
|
70
|
+
export interface OutcomeAssessment {
|
|
71
|
+
/** What outcome was expected */
|
|
72
|
+
expected: ExpectedOutcome;
|
|
73
|
+
/** What actually happened */
|
|
74
|
+
actual: 'success' | 'error';
|
|
75
|
+
/** Whether the tool behaved correctly (matches expectation) */
|
|
76
|
+
correct: boolean;
|
|
77
|
+
/** True if this was a validation test that correctly rejected invalid input */
|
|
78
|
+
isValidationSuccess?: boolean;
|
|
79
|
+
}
|
|
80
|
+
//# sourceMappingURL=question-types.d.ts.map
|
|
@@ -6,27 +6,10 @@
|
|
|
6
6
|
* 8-12 tests per tool covering boundaries, types, enums, and error handling.
|
|
7
7
|
*/
|
|
8
8
|
import type { MCPTool } from '../transport/types.js';
|
|
9
|
-
import type { InterviewQuestion
|
|
10
|
-
import type { QuestionCategory } from '../persona/types.js';
|
|
9
|
+
import type { InterviewQuestion } from './types.js';
|
|
11
10
|
import type { SemanticInference } from '../validation/semantic-types.js';
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
*/
|
|
15
|
-
export interface TestFixturePattern {
|
|
16
|
-
/** Regex pattern to match parameter names */
|
|
17
|
-
match: string;
|
|
18
|
-
/** Value to use for matching parameters */
|
|
19
|
-
value: unknown;
|
|
20
|
-
}
|
|
21
|
-
/**
|
|
22
|
-
* Test fixtures configuration for customizing test values.
|
|
23
|
-
*/
|
|
24
|
-
export interface TestFixturesConfig {
|
|
25
|
-
/** Custom values for specific parameter names (exact match) */
|
|
26
|
-
parameterValues?: Record<string, unknown>;
|
|
27
|
-
/** Custom values for parameters matching regex patterns */
|
|
28
|
-
patterns?: TestFixturePattern[];
|
|
29
|
-
}
|
|
11
|
+
import type { TestFixturesConfig } from './test-fixtures.js';
|
|
12
|
+
export type { TestFixturePattern, TestFixturesConfig } from './test-fixtures.js';
|
|
30
13
|
/**
|
|
31
14
|
* Options for test generation.
|
|
32
15
|
*/
|
|
@@ -49,15 +32,6 @@ export interface SchemaTestGeneratorResult {
|
|
|
49
32
|
/** Semantic type inferences for parameters */
|
|
50
33
|
semanticInferences: SemanticInference[];
|
|
51
34
|
}
|
|
52
|
-
/**
|
|
53
|
-
* Determine expected outcome for a test based on its category and description.
|
|
54
|
-
* Uses OUTCOME_ASSESSMENT constants to classify tests.
|
|
55
|
-
*
|
|
56
|
-
* @param category - Test category
|
|
57
|
-
* @param description - Test description
|
|
58
|
-
* @returns Expected outcome: 'success', 'error', or 'either'
|
|
59
|
-
*/
|
|
60
|
-
export declare function determineExpectedOutcome(category: QuestionCategory, description: string): ExpectedOutcome;
|
|
61
35
|
/**
|
|
62
36
|
* Generate comprehensive schema-based test cases for a tool.
|
|
63
37
|
*
|
|
@@ -5,9 +5,10 @@
|
|
|
5
5
|
* This module is the core of the enhanced testing capability, producing
|
|
6
6
|
* 8-12 tests per tool covering boundaries, types, enums, and error handling.
|
|
7
7
|
*/
|
|
8
|
-
import { SCHEMA_TESTING, SEMANTIC_VALIDATION
|
|
9
|
-
import {
|
|
8
|
+
import { SCHEMA_TESTING, SEMANTIC_VALIDATION } from '../constants.js';
|
|
9
|
+
import { OPERATION_BASED_DETECTION, SELF_STATEFUL_DETECTION, COMPLEX_SCHEMA_DETECTION, } from '../constants/testing.js';
|
|
10
10
|
import { generateSemanticTests } from '../validation/semantic-test-generator.js';
|
|
11
|
+
import { generateSmartStringValue as generateSmartStringValueResult, generateSmartNumberValue as generateSmartNumberValueResult, } from './smart-value-generator.js';
|
|
11
12
|
// ==================== Helper Functions ====================
|
|
12
13
|
/**
|
|
13
14
|
* Get the primary type from a schema property.
|
|
@@ -107,39 +108,12 @@ function mergeAllOfSchemas(schemas) {
|
|
|
107
108
|
* Used for nested array items where we don't have a property name.
|
|
108
109
|
*/
|
|
109
110
|
function generateSmartStringValueForSchema(schema) {
|
|
110
|
-
const
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
if (pattern.test(schema.description ?? '') || pattern.test(description)) {
|
|
114
|
-
return value;
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
for (const { pattern, value } of SEMANTIC_FORMAT_PATTERNS) {
|
|
118
|
-
if (pattern.test(schema.description ?? '') || pattern.test(description)) {
|
|
119
|
-
return value;
|
|
120
|
-
}
|
|
111
|
+
const generated = generateSmartStringValueResult('', schema).value;
|
|
112
|
+
if (typeof generated === 'string' && generated.length > 0) {
|
|
113
|
+
return generated;
|
|
121
114
|
}
|
|
122
|
-
// Check schema format field
|
|
123
|
-
if (schema.format === 'date')
|
|
124
|
-
return '2024-01-15';
|
|
125
|
-
if (schema.format === 'date-time')
|
|
126
|
-
return '2024-01-15T14:30:00Z';
|
|
127
|
-
if (schema.format === 'email')
|
|
128
|
-
return 'test@example.com';
|
|
129
|
-
if (schema.format === 'uri' || schema.format === 'url')
|
|
130
|
-
return 'https://example.com';
|
|
131
|
-
if (schema.format === 'uuid')
|
|
132
|
-
return '550e8400-e29b-41d4-a716-446655440000';
|
|
133
|
-
if (schema.format === 'ipv4')
|
|
134
|
-
return '192.168.1.100';
|
|
135
|
-
if (schema.format === 'time')
|
|
136
|
-
return '14:30:00';
|
|
137
|
-
// Respect minLength constraint
|
|
138
115
|
const minLength = schema.minLength ?? 0;
|
|
139
|
-
|
|
140
|
-
return 'test'.padEnd(minLength, 'x');
|
|
141
|
-
}
|
|
142
|
-
return 'test';
|
|
116
|
+
return minLength > 4 ? 'test'.padEnd(minLength, 'x') : 'test';
|
|
143
117
|
}
|
|
144
118
|
/**
|
|
145
119
|
* Generate a minimal array that satisfies minItems constraint.
|
|
@@ -373,266 +347,17 @@ function generateDefaultValue(propName, prop, fixtures) {
|
|
|
373
347
|
return 'test';
|
|
374
348
|
}
|
|
375
349
|
}
|
|
376
|
-
/**
|
|
377
|
-
* Pattern matchers for detecting date/time formats in descriptions.
|
|
378
|
-
* Each pattern maps to a format string and example value.
|
|
379
|
-
*/
|
|
380
|
-
const DATE_FORMAT_PATTERNS = [
|
|
381
|
-
// ISO 8601 date patterns
|
|
382
|
-
{
|
|
383
|
-
pattern: /YYYY-MM-DD|ISO\s*8601\s*date|date.*format.*YYYY/i,
|
|
384
|
-
value: '2024-01-15',
|
|
385
|
-
formatName: 'ISO 8601 date',
|
|
386
|
-
},
|
|
387
|
-
{ pattern: /YYYY-MM|year-month|month.*format/i, value: '2024-01', formatName: 'year-month' },
|
|
388
|
-
{
|
|
389
|
-
pattern: /ISO\s*8601\s*(datetime|timestamp)|datetime.*format|timestamp.*ISO/i,
|
|
390
|
-
value: '2024-01-15T14:30:00Z',
|
|
391
|
-
formatName: 'ISO 8601 datetime',
|
|
392
|
-
},
|
|
393
|
-
// Unix timestamp patterns
|
|
394
|
-
{
|
|
395
|
-
pattern: /unix\s*timestamp|epoch\s*time|seconds\s*since/i,
|
|
396
|
-
value: '1705330200',
|
|
397
|
-
formatName: 'Unix timestamp',
|
|
398
|
-
},
|
|
399
|
-
{
|
|
400
|
-
pattern: /milliseconds?\s*(since|timestamp)|ms\s*timestamp/i,
|
|
401
|
-
value: '1705330200000',
|
|
402
|
-
formatName: 'Unix timestamp (ms)',
|
|
403
|
-
},
|
|
404
|
-
// Time patterns
|
|
405
|
-
{
|
|
406
|
-
pattern: /HH:MM:SS|time.*format.*HH|24.hour.*time/i,
|
|
407
|
-
value: '14:30:00',
|
|
408
|
-
formatName: '24-hour time',
|
|
409
|
-
},
|
|
410
|
-
{ pattern: /HH:MM|hour.*minute/i, value: '14:30', formatName: 'hour:minute' },
|
|
411
|
-
// Other date formats
|
|
412
|
-
{ pattern: /MM\/DD\/YYYY|US\s*date/i, value: '01/15/2024', formatName: 'US date' },
|
|
413
|
-
{ pattern: /DD\/MM\/YYYY|European\s*date/i, value: '15/01/2024', formatName: 'European date' },
|
|
414
|
-
];
|
|
415
|
-
/**
|
|
416
|
-
* Pattern matchers for detecting other semantic types in descriptions.
|
|
417
|
-
*/
|
|
418
|
-
const SEMANTIC_FORMAT_PATTERNS = [
|
|
419
|
-
// Currency/money patterns
|
|
420
|
-
{ pattern: /currency.*amount|dollar.*amount|price/i, value: '99.99', formatName: 'currency' },
|
|
421
|
-
{ pattern: /percentage|percent/i, value: '50', formatName: 'percentage' },
|
|
422
|
-
// Phone patterns
|
|
423
|
-
{ pattern: /phone.*number|telephone/i, value: '+1-555-123-4567', formatName: 'phone' },
|
|
424
|
-
// UUID patterns
|
|
425
|
-
{
|
|
426
|
-
pattern: /UUID|unique.*identifier/i,
|
|
427
|
-
value: '550e8400-e29b-41d4-a716-446655440000',
|
|
428
|
-
formatName: 'UUID',
|
|
429
|
-
},
|
|
430
|
-
// IP address patterns
|
|
431
|
-
{ pattern: /IP.*address|IPv4/i, value: '192.168.1.100', formatName: 'IP address' },
|
|
432
|
-
// JSON patterns
|
|
433
|
-
{ pattern: /JSON\s*string|stringify|serialized/i, value: '{"key": "value"}', formatName: 'JSON' },
|
|
434
|
-
// Base64 patterns
|
|
435
|
-
{ pattern: /base64|encoded/i, value: 'dGVzdA==', formatName: 'base64' },
|
|
436
|
-
];
|
|
437
|
-
/**
|
|
438
|
-
* Generate a contextually appropriate string value based on property name,
|
|
439
|
-
* constraints, and description.
|
|
440
|
-
*
|
|
441
|
-
* This function implements smart test value generation by:
|
|
442
|
-
* 1. Parsing schema descriptions for format hints (e.g., "YYYY-MM-DD")
|
|
443
|
-
* 2. Checking schema format field
|
|
444
|
-
* 3. Inferring from property name patterns
|
|
445
|
-
*/
|
|
446
350
|
function generateSmartStringValue(propName, prop) {
|
|
447
|
-
const
|
|
448
|
-
|
|
449
|
-
// Priority 1: Check description for explicit date/time format hints
|
|
450
|
-
// This is the most reliable indicator since the schema author specified it
|
|
451
|
-
for (const { pattern, value } of DATE_FORMAT_PATTERNS) {
|
|
452
|
-
if (pattern.test(prop.description ?? '') || pattern.test(description)) {
|
|
453
|
-
return value;
|
|
454
|
-
}
|
|
455
|
-
}
|
|
456
|
-
// Priority 2: Check description for other semantic format hints
|
|
457
|
-
for (const { pattern, value } of SEMANTIC_FORMAT_PATTERNS) {
|
|
458
|
-
if (pattern.test(prop.description ?? '') || pattern.test(description)) {
|
|
459
|
-
return value;
|
|
460
|
-
}
|
|
461
|
-
}
|
|
462
|
-
// Priority 3: Check schema format field (JSON Schema standard)
|
|
463
|
-
if (prop.format === 'date') {
|
|
464
|
-
return '2024-01-15';
|
|
465
|
-
}
|
|
466
|
-
if (prop.format === 'date-time') {
|
|
467
|
-
return '2024-01-15T14:30:00Z';
|
|
468
|
-
}
|
|
469
|
-
if (prop.format === 'email') {
|
|
470
|
-
return 'test@example.com';
|
|
471
|
-
}
|
|
472
|
-
if (prop.format === 'uri' || prop.format === 'url') {
|
|
473
|
-
return 'https://example.com';
|
|
474
|
-
}
|
|
475
|
-
if (prop.format === 'uuid') {
|
|
476
|
-
return '550e8400-e29b-41d4-a716-446655440000';
|
|
477
|
-
}
|
|
478
|
-
if (prop.format === 'ipv4') {
|
|
479
|
-
return '192.168.1.100';
|
|
480
|
-
}
|
|
481
|
-
if (prop.format === 'time') {
|
|
482
|
-
return '14:30:00';
|
|
483
|
-
}
|
|
484
|
-
// Priority 4: Infer from property name patterns
|
|
485
|
-
if (lowerName.includes('date') || description.includes('date')) {
|
|
486
|
-
return '2024-01-15';
|
|
487
|
-
}
|
|
488
|
-
if (lowerName.includes('time') || description.includes('time')) {
|
|
489
|
-
return '14:30:00';
|
|
490
|
-
}
|
|
491
|
-
if (lowerName.includes('email') || description.includes('email')) {
|
|
492
|
-
return 'test@example.com';
|
|
493
|
-
}
|
|
494
|
-
if (lowerName.includes('url') ||
|
|
495
|
-
lowerName.includes('uri') ||
|
|
496
|
-
description.includes('url') ||
|
|
497
|
-
description.includes('uri')) {
|
|
498
|
-
return 'https://example.com';
|
|
499
|
-
}
|
|
500
|
-
if (lowerName.includes('path') ||
|
|
501
|
-
lowerName.includes('directory') ||
|
|
502
|
-
lowerName.includes('dir') ||
|
|
503
|
-
description.includes('path')) {
|
|
504
|
-
return '/tmp/test';
|
|
505
|
-
}
|
|
506
|
-
if (lowerName.includes('id') || description.includes('identifier')) {
|
|
507
|
-
return 'test-id-123';
|
|
508
|
-
}
|
|
509
|
-
if (lowerName.includes('name')) {
|
|
510
|
-
return 'test-name';
|
|
511
|
-
}
|
|
512
|
-
if (lowerName.includes('query') || lowerName.includes('search')) {
|
|
513
|
-
return 'test query';
|
|
514
|
-
}
|
|
515
|
-
if (lowerName.includes('token')) {
|
|
516
|
-
return 'test-token-abc123';
|
|
517
|
-
}
|
|
518
|
-
if (lowerName.includes('account') || description.includes('account')) {
|
|
519
|
-
return 'test-account-123';
|
|
520
|
-
}
|
|
521
|
-
if (lowerName.includes('amount') || description.includes('amount')) {
|
|
522
|
-
return '100.00';
|
|
523
|
-
}
|
|
524
|
-
if (lowerName.includes('category') || description.includes('category')) {
|
|
525
|
-
return 'test-category';
|
|
526
|
-
}
|
|
527
|
-
// Default fallback
|
|
528
|
-
return 'test';
|
|
351
|
+
const generated = generateSmartStringValueResult(propName, prop).value;
|
|
352
|
+
return typeof generated === 'string' ? generated : 'test';
|
|
529
353
|
}
|
|
530
354
|
/**
|
|
531
355
|
* Generate a contextually appropriate number value based on constraints and property name.
|
|
532
356
|
* Detects coordinates (latitude/longitude) and pagination parameters.
|
|
533
357
|
*/
|
|
534
358
|
function generateSmartNumberValue(prop, propName) {
|
|
535
|
-
const
|
|
536
|
-
|
|
537
|
-
const description = (prop.description ?? '').toLowerCase();
|
|
538
|
-
// Check for latitude patterns
|
|
539
|
-
for (const pattern of COORDINATES.LATITUDE_PATTERNS) {
|
|
540
|
-
if (pattern.test(propName ?? '') || pattern.test(description)) {
|
|
541
|
-
// Ensure value is within valid latitude range
|
|
542
|
-
const value = COORDINATES.DEFAULTS.latitude;
|
|
543
|
-
if (prop.minimum !== undefined && value < prop.minimum) {
|
|
544
|
-
return prop.minimum;
|
|
545
|
-
}
|
|
546
|
-
if (prop.maximum !== undefined && value > prop.maximum) {
|
|
547
|
-
return prop.maximum;
|
|
548
|
-
}
|
|
549
|
-
return value;
|
|
550
|
-
}
|
|
551
|
-
}
|
|
552
|
-
// Check for longitude patterns
|
|
553
|
-
for (const pattern of COORDINATES.LONGITUDE_PATTERNS) {
|
|
554
|
-
if (pattern.test(propName ?? '') || pattern.test(description)) {
|
|
555
|
-
// Ensure value is within valid longitude range
|
|
556
|
-
const value = COORDINATES.DEFAULTS.longitude;
|
|
557
|
-
if (prop.minimum !== undefined && value < prop.minimum) {
|
|
558
|
-
return prop.minimum;
|
|
559
|
-
}
|
|
560
|
-
if (prop.maximum !== undefined && value > prop.maximum) {
|
|
561
|
-
return prop.maximum;
|
|
562
|
-
}
|
|
563
|
-
return value;
|
|
564
|
-
}
|
|
565
|
-
}
|
|
566
|
-
// Check for pagination patterns
|
|
567
|
-
for (const pattern of PAGINATION.LIMIT_PATTERNS) {
|
|
568
|
-
if (pattern.test(propName ?? '')) {
|
|
569
|
-
const value = PAGINATION.DEFAULTS.limit;
|
|
570
|
-
if (prop.minimum !== undefined && value < prop.minimum) {
|
|
571
|
-
return prop.minimum;
|
|
572
|
-
}
|
|
573
|
-
if (prop.maximum !== undefined && value > prop.maximum) {
|
|
574
|
-
return prop.maximum;
|
|
575
|
-
}
|
|
576
|
-
return value;
|
|
577
|
-
}
|
|
578
|
-
}
|
|
579
|
-
for (const pattern of PAGINATION.OFFSET_PATTERNS) {
|
|
580
|
-
if (pattern.test(propName ?? '')) {
|
|
581
|
-
// Distinguish between offset/skip (start at 0) and page (start at 1)
|
|
582
|
-
const lowerPropName = (propName ?? '').toLowerCase();
|
|
583
|
-
if (lowerPropName === 'page' || lowerPropName.includes('page')) {
|
|
584
|
-
return PAGINATION.DEFAULTS.page;
|
|
585
|
-
}
|
|
586
|
-
return PAGINATION.DEFAULTS.offset;
|
|
587
|
-
}
|
|
588
|
-
}
|
|
589
|
-
// Check for year detection in name/description
|
|
590
|
-
if (lowerName.includes('year') || description.includes('year')) {
|
|
591
|
-
return 2024;
|
|
592
|
-
}
|
|
593
|
-
// Check for percentage
|
|
594
|
-
if (lowerName.includes('percent') || description.includes('percent')) {
|
|
595
|
-
return 50;
|
|
596
|
-
}
|
|
597
|
-
// Standard value generation based on constraints
|
|
598
|
-
const min = prop.minimum ?? 0;
|
|
599
|
-
const max = prop.maximum ?? 100;
|
|
600
|
-
// Use midpoint between min and max if both are specified
|
|
601
|
-
if (prop.minimum !== undefined && prop.maximum !== undefined) {
|
|
602
|
-
return Math.floor((min + max) / 2);
|
|
603
|
-
}
|
|
604
|
-
// Use minimum + 1 if only minimum is specified
|
|
605
|
-
if (prop.minimum !== undefined) {
|
|
606
|
-
return min + 1;
|
|
607
|
-
}
|
|
608
|
-
// Use reasonable defaults
|
|
609
|
-
return 1;
|
|
610
|
-
}
|
|
611
|
-
/**
|
|
612
|
-
* Determine expected outcome for a test based on its category and description.
|
|
613
|
-
* Uses OUTCOME_ASSESSMENT constants to classify tests.
|
|
614
|
-
*
|
|
615
|
-
* @param category - Test category
|
|
616
|
-
* @param description - Test description
|
|
617
|
-
* @returns Expected outcome: 'success', 'error', or 'either'
|
|
618
|
-
*/
|
|
619
|
-
export function determineExpectedOutcome(category, description) {
|
|
620
|
-
// Check if category expects error
|
|
621
|
-
if (OUTCOME_ASSESSMENT.EXPECTS_ERROR_CATEGORIES.includes(category)) {
|
|
622
|
-
return 'error';
|
|
623
|
-
}
|
|
624
|
-
// Check if category expects success
|
|
625
|
-
if (OUTCOME_ASSESSMENT.EXPECTS_SUCCESS_CATEGORIES.includes(category)) {
|
|
626
|
-
return 'success';
|
|
627
|
-
}
|
|
628
|
-
// Check description patterns for error expectation
|
|
629
|
-
for (const pattern of OUTCOME_ASSESSMENT.EXPECTS_ERROR_PATTERNS) {
|
|
630
|
-
if (pattern.test(description)) {
|
|
631
|
-
return 'error';
|
|
632
|
-
}
|
|
633
|
-
}
|
|
634
|
-
// Default to 'either' for edge cases
|
|
635
|
-
return 'either';
|
|
359
|
+
const generated = generateSmartNumberValueResult(prop, propName).value;
|
|
360
|
+
return typeof generated === 'number' ? generated : 1;
|
|
636
361
|
}
|
|
637
362
|
/**
|
|
638
363
|
* Add a question to the list, avoiding duplicates.
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Test fixture pattern for matching parameter names.
|
|
3
|
+
*/
|
|
4
|
+
export interface TestFixturePattern {
|
|
5
|
+
/** Regex pattern to match parameter names */
|
|
6
|
+
match: string;
|
|
7
|
+
/** Value to use for matching parameters */
|
|
8
|
+
value: unknown;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Test fixtures configuration for customizing generated test values.
|
|
12
|
+
*/
|
|
13
|
+
export interface TestFixturesConfig {
|
|
14
|
+
/** Custom values for specific parameter names (exact match) */
|
|
15
|
+
parameterValues?: Record<string, unknown>;
|
|
16
|
+
/** Custom values for parameters matching regex patterns */
|
|
17
|
+
patterns?: TestFixturePattern[];
|
|
18
|
+
}
|
|
19
|
+
//# sourceMappingURL=test-fixtures.d.ts.map
|