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
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
|
- import { Command } from 'commander';
- import logger from '../../../src/logger';
- import { redteamRunCommand } from '../../../src/redteam/commands/run';
- import { doRedteamRun } from '../../../src/redteam/shared';
- import { getConfigFromCloud } from '../../../src/util/cloud';
- jest.mock('../../../src/cliState', () => ({
- remote: false,
- }));
- jest.mock('../../../src/telemetry', () => ({
- record: jest.fn(),
- }));
- jest.mock('../../../src/util', () => ({
- setupEnv: jest.fn(),
- }));
- jest.mock('../../../src/redteam/shared', () => ({
- doRedteamRun: jest.fn().mockResolvedValue({}),
- }));
- jest.mock('../../../src/util/cloud', () => ({
- getConfigFromCloud: jest.fn(),
- }));
- describe('redteamRunCommand', () => {
- let program: Command;
- let originalExitCode: number | undefined;
- beforeEach(() => {
- jest.resetAllMocks();
- program = new Command();
- redteamRunCommand(program);
- // Store original exitCode to restore later
- originalExitCode = process.exitCode as number | undefined;
- process.exitCode = 0;
- });
- afterEach(() => {
- // Restore original exitCode
- process.exitCode = originalExitCode;
- });
- it('should use target option to set cloud target when UUID is provided', async () => {
- // Mock the getConfigFromCloud function
- const mockConfig = {
- prompts: ['Test prompt'],
- vars: {},
- providers: [{ id: 'test-provider' }],
- targets: [
- {
- id: 'test-provider',
- },
- ],
- };
- jest.mocked(getConfigFromCloud).mockResolvedValue(mockConfig);
- // UUID format for config and target
- const configUUID = '12345678-1234-1234-1234-123456789012';
- const targetUUID = '87654321-4321-4321-4321-210987654321';
- // Find the run command
- const runCommand = program.commands.find((cmd) => cmd.name() === 'run');
- expect(runCommand).toBeDefined();
- // Execute the command with the target option
- await runCommand!.parseAsync(['node', 'test', '--config', configUUID, '--target', targetUUID]);
- // Verify doRedteamRun was called with the right parameters
- expect(doRedteamRun).toHaveBeenCalledWith(
- expect.objectContaining({
- liveRedteamConfig: mockConfig,
- config: undefined,
- loadedFromCloud: true,
- target: targetUUID,
- }),
- );
- });
- it('should not support target argument with a local config file', async () => {
- // Find the run command
- const runCommand = program.commands.find((cmd) => cmd.name() === 'run');
- expect(runCommand).toBeDefined();
- const configPath = 'path/to/config.yaml';
- const targetUUID = '87654321-4321-4321-4321-210987654321';
- // Execute the command with the target option but a path config
- await runCommand!.parseAsync(['node', 'test', '--config', configPath, '--target', targetUUID]);
- // Should log error message
- expect(logger.error).toHaveBeenCalledWith(
- `Target ID (-t) can only be used when -c is used. To use a cloud target inside of a config set the id of the target to promptfoo://provider/${targetUUID}. `,
- );
- // Should set exit code to 1
- expect(process.exitCode).toBe(1);
- // getConfigFromCloud should not be called
- expect(getConfigFromCloud).not.toHaveBeenCalled();
- // doRedteamRun should not be called
- expect(doRedteamRun).not.toHaveBeenCalled();
- });
- it('should not support target argument when no cloud config file is provided', async () => {
- // Find the run command
- const runCommand = program.commands.find((cmd) => cmd.name() === 'run');
- expect(runCommand).toBeDefined();
- const targetUUID = '87654321-4321-4321-4321-210987654321';
- // Execute the command with the target option but a path config
- await runCommand!.parseAsync(['node', 'test', '--target', targetUUID]);
- // Should log error message
- expect(logger.error).toHaveBeenCalledWith(
- `Target ID (-t) can only be used when -c is used. To use a cloud target inside of a config set the id of the target to promptfoo://provider/${targetUUID}. `,
- );
- // Should set exit code to 1
- expect(process.exitCode).toBe(1);
- // getConfigFromCloud should not be called
- expect(getConfigFromCloud).not.toHaveBeenCalled();
- // doRedteamRun should not be called
- expect(doRedteamRun).not.toHaveBeenCalled();
- });
- it('should throw error when target is not a UUID', async () => {
- // UUID format for config but not for target
- const configUUID = '12345678-1234-1234-1234-123456789012';
- const invalidTarget = 'not-a-uuid';
- // Find the run command
- const runCommand = program.commands.find((cmd) => cmd.name() === 'run');
- expect(runCommand).toBeDefined();
- // Execute the command with the target option and expect it to throw
- await expect(
- runCommand!.parseAsync(['node', 'test', '--config', configUUID, '--target', invalidTarget]),
- ).rejects.toThrow('Invalid target ID, it must be a valid UUID');
- // Verify getConfigFromCloud was not called
- expect(getConfigFromCloud).not.toHaveBeenCalled();
- });
- it('should handle backwards compatibility with empty targets and a valid target UUID', async () => {
- // Mock the getConfigFromCloud function to return config without targets
- const mockConfig = {
- prompts: ['Test prompt'],
- vars: {},
- providers: [{ id: 'test-provider' }],
- targets: [], // Empty targets
- };
- jest.mocked(getConfigFromCloud).mockResolvedValue(mockConfig);
- // UUID format for config and target
- const configUUID = '12345678-1234-1234-1234-123456789012';
- const targetUUID = '87654321-4321-4321-4321-210987654321';
- // Find the run command
- const runCommand = program.commands.find((cmd) => cmd.name() === 'run');
- expect(runCommand).toBeDefined();
- // Execute the command with the target option
- await runCommand!.parseAsync(['node', 'test', '--config', configUUID, '--target', targetUUID]);
- // Verify that a target was added to the config with the CLOUD_PROVIDER_PREFIX
- expect(mockConfig.targets).toEqual([
- {
- id: `promptfoo://provider/${targetUUID}`,
- config: {},
- },
- ]);
- // Verify doRedteamRun was called with the updated config
- expect(doRedteamRun).toHaveBeenCalledWith(
- expect.objectContaining({
- liveRedteamConfig: mockConfig,
- config: undefined,
- loadedFromCloud: true,
- target: targetUUID,
- }),
- );
- });
- });
|