0d3cb498a3
CI / Shell Format Check (push) Has been cancelled
CI / Check Ruby (3.4) (push) Has been cancelled
CI / CI Config (push) Has been cancelled
CI / Test on Node ${{ matrix.node }} and ${{ matrix.os }}${{ matrix.shard && format(' (shard {0}/3)', matrix.shard) || '' }} (push) Has been cancelled
CI / Build on Node ${{ matrix.node }} (push) Has been cancelled
CI / Style Check (push) Has been cancelled
CI / Generate Assets (push) Has been cancelled
CI / Check Python (3.14) (push) Has been cancelled
CI / Check Python (3.9) (push) Has been cancelled
CI / Build Docs (push) Has been cancelled
CI / Code Scan Action (push) Has been cancelled
CI / Site tests (push) Has been cancelled
CI / webui tests (push) Has been cancelled
CI / Run Integration Tests (push) Has been cancelled
CI / Run Smoke Tests (push) Has been cancelled
CI / Go Tests (push) Has been cancelled
CI / Share Test (push) Has been cancelled
CI / Redteam (Production API) (push) Has been cancelled
CI / Redteam (Staging API) (push) Has been cancelled
CI / GitHub Actions Lint (push) Has been cancelled
CI / Check Ruby (3.0) (push) Has been cancelled
release-please / release-please (push) Has been cancelled
release-please / build (push) Has been cancelled
release-please / publish-npm (push) Has been cancelled
release-please / publish-npm-backfill (push) Has been cancelled
release-please / docker (push) Has been cancelled
release-please / publish-code-scan-action (push) Has been cancelled
release-please / attest-code-scan-action (push) Has been cancelled
Deploy local.promptfoo.app / Deploy to Cloudflare Pages (push) Has been cancelled
Test and Publish Multi-arch Docker Image / test (push) Has been cancelled
Test and Publish Multi-arch Docker Image / build-docker-and-push-digests (map[digest-suffix:linux-amd64 platform:linux/amd64 runner:ubuntu-latest]) (push) Has been cancelled
Test and Publish Multi-arch Docker Image / build-docker-and-push-digests (map[digest-suffix:linux-arm64 platform:linux/arm64 runner:ubuntu-24.04-arm]) (push) Has been cancelled
Test and Publish Multi-arch Docker Image / merge-docker-digests (push) Has been cancelled
Test and Publish Multi-arch Docker Image / Attest Multi-arch Image (push) Has been cancelled
Validate Renovate Config / Validate Renovate Configuration (push) Has been cancelled
353 lines
11 KiB
TypeScript
353 lines
11 KiB
TypeScript
import { describe, expect, it } from 'vitest';
|
|
import { Severity } from '../../src/redteam/constants';
|
|
import { StrategyConfigSchema } from '../../src/redteam/types';
|
|
import { createMockProvider } from '../factories/provider';
|
|
|
|
import type {
|
|
BaseRedteamMetadata,
|
|
PluginActionParams,
|
|
RedteamCliGenerateOptions,
|
|
RedteamFileConfig,
|
|
RedteamGenerateOptions,
|
|
RedteamPlugin,
|
|
RedteamPluginObject,
|
|
RedteamRunOptions,
|
|
RedteamStrategyObject,
|
|
SavedRedteamConfig,
|
|
SynthesizeOptions,
|
|
} from '../../src/redteam/types';
|
|
|
|
describe('redteam types', () => {
|
|
it('should create valid RedteamPluginObject', () => {
|
|
const pluginObj: RedteamPluginObject = {
|
|
id: 'test-plugin',
|
|
config: {
|
|
examples: ['example1', 'example2'],
|
|
graderExamples: [
|
|
{
|
|
output: 'test output',
|
|
pass: true,
|
|
score: 1,
|
|
reason: 'test reason',
|
|
},
|
|
],
|
|
severity: Severity.Low,
|
|
},
|
|
severity: Severity.Medium,
|
|
numTests: 5,
|
|
};
|
|
|
|
expect(pluginObj.id).toBe('test-plugin');
|
|
expect(pluginObj.severity).toBe(Severity.Medium);
|
|
expect(pluginObj.numTests).toBe(5);
|
|
});
|
|
|
|
it('should create valid RedteamPlugin string or object', () => {
|
|
const pluginStr: RedteamPlugin = 'test-plugin';
|
|
const pluginObj: RedteamPlugin = {
|
|
id: 'test-plugin',
|
|
severity: Severity.Low,
|
|
};
|
|
|
|
expect(typeof pluginStr).toBe('string');
|
|
expect(typeof pluginObj).toBe('object');
|
|
});
|
|
|
|
it('should create valid RedteamStrategyObject', () => {
|
|
const strategyObj: RedteamStrategyObject = {
|
|
id: 'test-strategy',
|
|
config: {
|
|
enabled: true,
|
|
plugins: ['plugin1', 'plugin2'],
|
|
customField: 'value',
|
|
},
|
|
};
|
|
|
|
expect(strategyObj.id).toBe('test-strategy');
|
|
expect(strategyObj.config?.enabled).toBe(true);
|
|
expect(strategyObj.config?.plugins).toEqual(['plugin1', 'plugin2']);
|
|
});
|
|
|
|
it('should create valid PluginActionParams', () => {
|
|
const provider = createMockProvider({ response: { output: 'test' } });
|
|
|
|
const params: PluginActionParams = {
|
|
provider,
|
|
purpose: 'test purpose',
|
|
injectVar: 'test_var',
|
|
n: 5,
|
|
delayMs: 1000,
|
|
config: {
|
|
examples: ['example'],
|
|
},
|
|
};
|
|
|
|
expect(params.provider.id()).toBe('test-provider');
|
|
expect(params.purpose).toBe('test purpose');
|
|
expect(params.n).toBe(5);
|
|
});
|
|
|
|
it('should create valid RedteamCliGenerateOptions', () => {
|
|
const options: RedteamCliGenerateOptions = {
|
|
cache: true,
|
|
write: true,
|
|
defaultConfig: {},
|
|
plugins: [
|
|
{
|
|
id: 'test-plugin',
|
|
numTests: 5,
|
|
},
|
|
],
|
|
strategies: ['test-strategy'],
|
|
injectVar: 'test_var',
|
|
maxConcurrency: 5,
|
|
};
|
|
|
|
expect(options.cache).toBe(true);
|
|
expect(options.write).toBe(true);
|
|
expect(options.plugins?.length).toBe(1);
|
|
});
|
|
|
|
it('should create valid RedteamFileConfig', () => {
|
|
const pluginKey = 'agentic' as keyof NonNullable<RedteamFileConfig['severity']>;
|
|
const config: RedteamFileConfig = {
|
|
entities: ['entity1', 'entity2'],
|
|
severity: {
|
|
[pluginKey]: Severity.High,
|
|
} as Record<string, Severity>,
|
|
language: 'en',
|
|
numTests: 10,
|
|
};
|
|
|
|
expect(config.entities).toEqual(['entity1', 'entity2']);
|
|
expect(config.severity?.[pluginKey]).toBe(Severity.High);
|
|
});
|
|
|
|
it('should create valid SynthesizeOptions', () => {
|
|
const options: SynthesizeOptions = {
|
|
language: 'en',
|
|
numTests: 5,
|
|
plugins: [
|
|
{
|
|
id: 'test-plugin',
|
|
numTests: 3,
|
|
},
|
|
],
|
|
prompts: ['prompt1', 'prompt2'],
|
|
strategies: [
|
|
{
|
|
id: 'test-strategy',
|
|
},
|
|
],
|
|
targetIds: ['label1', 'label2'],
|
|
maxConcurrency: 3,
|
|
};
|
|
|
|
expect(options.language).toBe('en');
|
|
expect(options.numTests).toBe(5);
|
|
expect(options.plugins).toHaveLength(1);
|
|
});
|
|
|
|
it('should create valid RedteamRunOptions', () => {
|
|
const options: RedteamRunOptions = {
|
|
id: 'test-run',
|
|
target: 'test-target',
|
|
cache: true,
|
|
maxConcurrency: 5,
|
|
delay: 1000,
|
|
remote: false,
|
|
progressBar: true,
|
|
};
|
|
|
|
expect(options.id).toBe('test-run');
|
|
expect(options.target).toBe('test-target');
|
|
expect(options.maxConcurrency).toBe(5);
|
|
});
|
|
|
|
it('should create RedteamRunOptions with description for custom scan names', () => {
|
|
const options: RedteamRunOptions = {
|
|
id: 'test-run',
|
|
config: 'config-uuid',
|
|
target: 'target-uuid',
|
|
description: 'My Custom Scan Name',
|
|
maxConcurrency: 5,
|
|
};
|
|
|
|
expect(options.description).toBe('My Custom Scan Name');
|
|
expect(options.config).toBe('config-uuid');
|
|
expect(options.target).toBe('target-uuid');
|
|
});
|
|
|
|
it('should create valid SavedRedteamConfig', () => {
|
|
const config: SavedRedteamConfig = {
|
|
description: 'Test config',
|
|
prompts: ['prompt1', 'prompt2'],
|
|
target: {
|
|
id: 'test-target',
|
|
},
|
|
plugins: ['plugin1', { id: 'plugin2' }],
|
|
strategies: ['strategy1'],
|
|
entities: ['entity1'],
|
|
maxConcurrency: 5,
|
|
applicationDefinition: {
|
|
purpose: 'test purpose',
|
|
features: 'key app features',
|
|
hasAccessTo: 'allowed data and systems',
|
|
doesNotHaveAccessTo: 'restricted data',
|
|
userTypes: 'admin, regular users',
|
|
securityRequirements: 'security controls',
|
|
exampleIdentifiers: 'sample IDs',
|
|
industry: 'healthcare',
|
|
sensitiveDataTypes: 'PHI, PII',
|
|
criticalActions: 'data deletion',
|
|
forbiddenTopics: 'restricted topics',
|
|
competitors: 'competitor list',
|
|
systemPrompt: 'test prompt',
|
|
redteamUser: 'test user',
|
|
accessToData: 'accessible data',
|
|
forbiddenData: 'restricted data',
|
|
accessToActions: 'allowed actions',
|
|
forbiddenActions: 'restricted actions',
|
|
connectedSystems: 'integrated systems',
|
|
},
|
|
};
|
|
|
|
expect(config.description).toBe('Test config');
|
|
expect(config.prompts).toHaveLength(2);
|
|
expect(config.plugins).toHaveLength(2);
|
|
expect(config.maxConcurrency).toBe(5);
|
|
expect(config.applicationDefinition.features).toBe('key app features');
|
|
expect(config.applicationDefinition.hasAccessTo).toBe('allowed data and systems');
|
|
expect(config.applicationDefinition.doesNotHaveAccessTo).toBe('restricted data');
|
|
expect(config.applicationDefinition.userTypes).toBe('admin, regular users');
|
|
expect(config.applicationDefinition.securityRequirements).toBe('security controls');
|
|
expect(config.applicationDefinition.exampleIdentifiers).toBe('sample IDs');
|
|
expect(config.applicationDefinition.industry).toBe('healthcare');
|
|
expect(config.applicationDefinition.sensitiveDataTypes).toBe('PHI, PII');
|
|
expect(config.applicationDefinition.criticalActions).toBe('data deletion');
|
|
expect(config.applicationDefinition.forbiddenTopics).toBe('restricted topics');
|
|
expect(config.applicationDefinition.competitors).toBe('competitor list');
|
|
});
|
|
|
|
it('should create valid BaseRedteamMetadata', () => {
|
|
const metadata: BaseRedteamMetadata = {
|
|
redteamFinalPrompt: 'final prompt',
|
|
messages: [{ role: 'user', content: 'test' }],
|
|
stopReason: 'complete',
|
|
redteamHistory: [{ prompt: 'test prompt', output: 'test output' }],
|
|
};
|
|
|
|
expect(metadata.redteamFinalPrompt).toBe('final prompt');
|
|
expect(metadata.messages).toHaveLength(1);
|
|
expect(metadata.stopReason).toBe('complete');
|
|
});
|
|
|
|
it('should create valid RedteamGenerateOptions', () => {
|
|
const emptyOptions: RedteamGenerateOptions = {};
|
|
const partialOptions: RedteamGenerateOptions = {
|
|
cache: true,
|
|
plugins: [
|
|
{
|
|
id: 'test-plugin',
|
|
numTests: 5,
|
|
},
|
|
],
|
|
maxConcurrency: 3,
|
|
injectVar: 'test_var',
|
|
language: 'en',
|
|
purpose: 'test purpose',
|
|
remote: false,
|
|
sharing: true,
|
|
testGenerationInstructions: 'test instructions',
|
|
};
|
|
|
|
expect(emptyOptions).toBeDefined();
|
|
expect(partialOptions.cache).toBe(true);
|
|
expect(partialOptions.plugins?.length).toBe(1);
|
|
expect(partialOptions.maxConcurrency).toBe(3);
|
|
expect(partialOptions.language).toBe('en');
|
|
expect(partialOptions.remote).toBe(false);
|
|
expect(partialOptions.sharing).toBe(true);
|
|
expect(partialOptions.testGenerationInstructions).toBe('test instructions');
|
|
});
|
|
|
|
describe('StrategyConfigSchema numTests validation', () => {
|
|
it('should accept valid positive numTests', () => {
|
|
const result = StrategyConfigSchema.safeParse({ numTests: 5 });
|
|
expect(result.success).toBe(true);
|
|
if (result.success) {
|
|
expect(result.data.numTests).toBe(5);
|
|
}
|
|
});
|
|
|
|
it('should accept numTests: 0', () => {
|
|
const result = StrategyConfigSchema.safeParse({ numTests: 0 });
|
|
expect(result.success).toBe(true);
|
|
if (result.success) {
|
|
expect(result.data.numTests).toBe(0);
|
|
}
|
|
});
|
|
|
|
it('should accept missing numTests (undefined)', () => {
|
|
const result = StrategyConfigSchema.safeParse({});
|
|
expect(result.success).toBe(true);
|
|
if (result.success) {
|
|
expect(result.data.numTests).toBeUndefined();
|
|
}
|
|
});
|
|
|
|
it('should reject negative numTests', () => {
|
|
const result = StrategyConfigSchema.safeParse({ numTests: -1 });
|
|
expect(result.success).toBe(false);
|
|
});
|
|
|
|
it('should reject non-integer numTests', () => {
|
|
const result = StrategyConfigSchema.safeParse({ numTests: 1.5 });
|
|
expect(result.success).toBe(false);
|
|
});
|
|
|
|
it('should reject string numTests', () => {
|
|
const result = StrategyConfigSchema.safeParse({ numTests: '5' });
|
|
expect(result.success).toBe(false);
|
|
});
|
|
|
|
it('should accept numTests with other strategy config fields', () => {
|
|
const result = StrategyConfigSchema.safeParse({
|
|
enabled: true,
|
|
plugins: ['plugin1', 'plugin2'],
|
|
numTests: 10,
|
|
customField: 'value',
|
|
});
|
|
expect(result.success).toBe(true);
|
|
if (result.success) {
|
|
expect(result.data.numTests).toBe(10);
|
|
expect(result.data.enabled).toBe(true);
|
|
expect(result.data.plugins).toEqual(['plugin1', 'plugin2']);
|
|
}
|
|
});
|
|
|
|
it('should reject Infinity numTests', () => {
|
|
const result = StrategyConfigSchema.safeParse({ numTests: Infinity });
|
|
expect(result.success).toBe(false);
|
|
});
|
|
|
|
it('should reject -Infinity numTests', () => {
|
|
const result = StrategyConfigSchema.safeParse({ numTests: -Infinity });
|
|
expect(result.success).toBe(false);
|
|
});
|
|
|
|
it('should reject NaN numTests', () => {
|
|
const result = StrategyConfigSchema.safeParse({ numTests: NaN });
|
|
expect(result.success).toBe(false);
|
|
});
|
|
|
|
it('should accept large but finite numTests', () => {
|
|
const result = StrategyConfigSchema.safeParse({ numTests: 10000 });
|
|
expect(result.success).toBe(true);
|
|
if (result.success) {
|
|
expect(result.data.numTests).toBe(10000);
|
|
}
|
|
});
|
|
});
|
|
});
|