1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
|
- import { Command } from 'commander';
- import { doValidate, validateCommand } from '../../src/commands/validate';
- import logger from '../../src/logger';
- import { resolveConfigs } from '../../src/util/config/load';
- import type { UnifiedConfig } from '../../src/types';
- jest.mock('../../src/logger');
- jest.mock('../../src/util/config/load');
- jest.mock('../../src/telemetry', () => ({
- record: jest.fn(),
- send: jest.fn(),
- }));
- describe('Validate Command Exit Codes', () => {
- let program: Command;
- const defaultConfig = {} as UnifiedConfig;
- const defaultConfigPath = 'config.yaml';
- beforeEach(() => {
- program = new Command();
- jest.clearAllMocks();
- // Reset exit code before each test
- process.exitCode = 0;
- });
- describe('Success scenarios - should set exit code 0', () => {
- it('should set exit code 0 when configuration is valid', async () => {
- // Mock successful config resolution and validation
- const mockValidConfig = {
- prompts: ['test prompt'],
- providers: ['test-provider'],
- tests: [{ vars: { test: 'value' } }],
- };
- const mockValidTestSuite = {
- prompts: [{ raw: 'test prompt', label: 'test' }],
- providers: [{ id: () => 'test-provider' }],
- tests: [{ vars: { test: 'value' } }],
- };
- jest.mocked(resolveConfigs).mockResolvedValue({
- config: mockValidConfig as any,
- testSuite: mockValidTestSuite as any,
- basePath: '/test',
- });
- await doValidate({ config: ['test-config.yaml'] }, defaultConfig, defaultConfigPath);
- expect(logger.info).toHaveBeenCalledWith(expect.stringContaining('Configuration is valid'));
- expect(process.exitCode).toBe(0);
- });
- it('should set exit code 0 when validating with default config path', async () => {
- const mockValidConfig = {
- prompts: ['test prompt'],
- providers: ['test-provider'],
- };
- const mockValidTestSuite = {
- prompts: [{ raw: 'test prompt', label: 'test' }],
- providers: [{ id: () => 'test-provider' }],
- };
- jest.mocked(resolveConfigs).mockResolvedValue({
- config: mockValidConfig as any,
- testSuite: mockValidTestSuite as any,
- basePath: '/test',
- });
- await doValidate({}, defaultConfig, defaultConfigPath);
- expect(logger.info).toHaveBeenCalledWith(expect.stringContaining('Configuration is valid'));
- expect(process.exitCode).toBe(0);
- });
- });
- describe('Failure scenarios - should set exit code 1', () => {
- it('should set exit code 1 when configuration validation fails', async () => {
- // Mock invalid config that fails schema validation
- const mockInvalidConfig = {
- // Missing required fields to trigger validation error
- invalidField: 'invalid value',
- };
- const mockValidTestSuite = {
- prompts: [{ raw: 'test prompt', label: 'test' }],
- providers: [{ id: () => 'test-provider' }],
- };
- jest.mocked(resolveConfigs).mockResolvedValue({
- config: mockInvalidConfig as any,
- testSuite: mockValidTestSuite as any,
- basePath: '/test',
- });
- await doValidate({ config: ['invalid-config.yaml'] }, defaultConfig, defaultConfigPath);
- expect(logger.error).toHaveBeenCalledWith(
- expect.stringContaining('Configuration validation error'),
- );
- expect(process.exitCode).toBe(1);
- });
- it('should set exit code 1 when test suite validation fails', async () => {
- // Mock valid config but invalid test suite
- const mockValidConfig = {
- prompts: ['test prompt'],
- providers: ['test-provider'],
- };
- const mockInvalidTestSuite = {
- // Invalid test suite structure to trigger validation error
- prompts: 'invalid prompts format', // Should be an array
- providers: [{ id: () => 'test-provider' }],
- };
- jest.mocked(resolveConfigs).mockResolvedValue({
- config: mockValidConfig as any,
- testSuite: mockInvalidTestSuite as any,
- basePath: '/test',
- });
- await doValidate({ config: ['test-config.yaml'] }, defaultConfig, defaultConfigPath);
- expect(logger.error).toHaveBeenCalledWith(
- expect.stringContaining('Test suite validation error'),
- );
- expect(process.exitCode).toBe(1);
- });
- it('should set exit code 1 when config resolution throws an error', async () => {
- // Mock resolveConfigs to throw an error
- jest.mocked(resolveConfigs).mockRejectedValue(new Error('Failed to load configuration'));
- await doValidate({ config: ['non-existent-config.yaml'] }, defaultConfig, defaultConfigPath);
- expect(logger.error).toHaveBeenCalledWith(
- expect.stringContaining('Failed to validate configuration: Failed to load configuration'),
- );
- expect(process.exitCode).toBe(1);
- });
- });
- describe('Command registration', () => {
- it('should register validate command correctly', () => {
- validateCommand(program, defaultConfig, defaultConfigPath);
- const validateCmd = program.commands.find((cmd) => cmd.name() === 'validate');
- expect(validateCmd).toBeDefined();
- expect(validateCmd?.name()).toBe('validate');
- expect(validateCmd?.description()).toBe('Validate a promptfoo configuration file');
- // Check that the config option is registered
- const configOption = validateCmd?.options.find((opt) => opt.long === '--config');
- expect(configOption).toBeDefined();
- });
- });
- });
|