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
|
- import path from 'path';
- import cliState from '../../../src/cliState';
- import { importModule } from '../../../src/esm';
- import logger from '../../../src/logger';
- import { validateStrategies, loadStrategy } from '../../../src/redteam/strategies';
- import type { RedteamStrategyObject, TestCaseWithPlugin } from '../../../src/types';
- jest.mock('../../../src/cliState');
- jest.mock('../../../src/esm', () => ({
- importModule: jest.fn(),
- }));
- describe('validateStrategies', () => {
- beforeEach(() => {
- jest.resetAllMocks();
- });
- it('should validate valid strategies', async () => {
- const validStrategies: RedteamStrategyObject[] = [
- { id: 'basic' },
- { id: 'base64' },
- { id: 'video' },
- { id: 'morse' },
- { id: 'piglatin' },
- ];
- await expect(validateStrategies(validStrategies)).resolves.toBeUndefined();
- });
- it('should validate basic strategy with enabled config', async () => {
- const strategies: RedteamStrategyObject[] = [{ id: 'basic', config: { enabled: true } }];
- await expect(validateStrategies(strategies)).resolves.toBeUndefined();
- });
- it('should throw error for invalid basic strategy config', async () => {
- const strategies: RedteamStrategyObject[] = [
- { id: 'basic', config: { enabled: 'not-a-boolean' as any } },
- ];
- await expect(validateStrategies(strategies)).rejects.toThrow(
- 'Basic strategy enabled config must be a boolean',
- );
- });
- it('should skip validation for file:// strategies', async () => {
- const strategies: RedteamStrategyObject[] = [{ id: 'file://custom.js' }];
- await expect(validateStrategies(strategies)).resolves.toBeUndefined();
- });
- it('should exit for invalid strategies', async () => {
- const mockExit = jest.spyOn(process, 'exit').mockImplementation(() => undefined as never);
- const invalidStrategies: RedteamStrategyObject[] = [{ id: 'invalid-strategy' }];
- await validateStrategies(invalidStrategies);
- expect(logger.error).toHaveBeenCalledWith(expect.stringContaining('Invalid strategy(s)'));
- expect(mockExit).toHaveBeenCalledWith(1);
- });
- });
- describe('loadStrategy', () => {
- beforeEach(() => {
- jest.resetAllMocks();
- });
- it('should load predefined strategy', async () => {
- const strategy = await loadStrategy('basic');
- expect(strategy).toBeDefined();
- expect(strategy.id).toBe('basic');
- });
- it('should load video strategy', async () => {
- const strategy = await loadStrategy('video');
- expect(strategy).toBeDefined();
- expect(strategy.id).toBe('video');
- expect(typeof strategy.action).toBe('function');
- });
- it('should call video strategy action with correct parameters', async () => {
- const strategy = await loadStrategy('video');
- const testCases: TestCaseWithPlugin[] = [
- { vars: { test: 'value' }, metadata: { pluginId: 'test' } },
- ];
- const injectVar = 'inject';
- const config = {};
- await strategy.action(testCases, injectVar, config);
- expect(logger.debug).toHaveBeenCalledWith(expect.stringContaining('Adding video encoding'));
- expect(logger.debug).toHaveBeenCalledWith(expect.stringContaining('Added'));
- });
- it('should load morse strategy', async () => {
- const strategy = await loadStrategy('morse');
- expect(strategy).toBeDefined();
- expect(strategy.id).toBe('morse');
- });
- it('should load piglatin strategy', async () => {
- const strategy = await loadStrategy('piglatin');
- expect(strategy).toBeDefined();
- expect(strategy.id).toBe('piglatin');
- });
- it('should throw error for non-existent strategy', async () => {
- await expect(loadStrategy('non-existent')).rejects.toThrow('Strategy not found: non-existent');
- });
- it('should load custom file strategy', async () => {
- const customStrategy = {
- id: 'custom',
- action: jest.fn(),
- };
- jest.mocked(importModule).mockResolvedValue(customStrategy);
- (cliState as any).basePath = '/test/path';
- const strategy = await loadStrategy('file://custom.js');
- expect(strategy).toEqual(customStrategy);
- });
- it('should throw error for non-js custom file', async () => {
- await expect(loadStrategy('file://custom.txt')).rejects.toThrow(
- 'Custom strategy file must be a JavaScript file',
- );
- });
- it('should throw error for invalid custom strategy', async () => {
- jest.mocked(importModule).mockResolvedValue({});
- await expect(loadStrategy('file://invalid.js')).rejects.toThrow(
- "Custom strategy in invalid.js must export an object with 'key' and 'action' properties",
- );
- });
- it('should use absolute path for custom strategy', async () => {
- const customStrategy = {
- id: 'custom',
- action: jest.fn(),
- };
- jest.mocked(importModule).mockResolvedValue(customStrategy);
- await loadStrategy('file:///absolute/path/custom.js');
- expect(importModule).toHaveBeenCalledWith('/absolute/path/custom.js');
- });
- it('should use relative path from basePath for custom strategy', async () => {
- const customStrategy = {
- id: 'custom',
- action: jest.fn(),
- };
- jest.mocked(importModule).mockResolvedValue(customStrategy);
- (cliState as any).basePath = '/base/path';
- await loadStrategy('file://relative/custom.js');
- expect(importModule).toHaveBeenCalledWith(path.join('/base/path', 'relative/custom.js'));
- });
- });
|