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
254 lines
9.3 KiB
TypeScript
254 lines
9.3 KiB
TypeScript
import { describe, expect, it } from 'vitest';
|
|
import {
|
|
addOtherEncodings,
|
|
EncodingType,
|
|
toCamelCase,
|
|
toEmojiEncoding,
|
|
toMorseCode,
|
|
toPigLatin,
|
|
} from '../../../src/redteam/strategies/otherEncodings';
|
|
|
|
import type { TestCase } from '../../../src/types/index';
|
|
|
|
describe('other encodings strategy', () => {
|
|
const testCases: TestCase[] = [
|
|
{
|
|
vars: {
|
|
prompt: 'Hello World! 123',
|
|
expected: 'normal value',
|
|
},
|
|
assert: [
|
|
{
|
|
type: 'equals',
|
|
value: 'expected value',
|
|
metric: 'original-metric',
|
|
},
|
|
],
|
|
},
|
|
];
|
|
|
|
describe('Morse code', () => {
|
|
it('should convert text to Morse code', () => {
|
|
const result = addOtherEncodings(testCases, 'prompt', EncodingType.MORSE);
|
|
expect(result[0].vars!.prompt).toBe(
|
|
'.... . .-.. .-.. --- / .-- --- .-. .-.. -.. -.-.-- / .---- ..--- ...--',
|
|
);
|
|
expect(result[0].assert?.[0].metric).toBe('original-metric/Morse');
|
|
|
|
// Check that other vars are not affected
|
|
expect(result[0].vars!.expected).toBe('normal value');
|
|
|
|
// Check that metadata and assertion are updated correctly
|
|
expect(result[0].metadata?.strategyId).toBe('morse');
|
|
expect(result[0].metadata?.encodingType).toBe(EncodingType.MORSE);
|
|
expect(result[0].metadata?.originalText).toBe('Hello World! 123');
|
|
});
|
|
|
|
it('should handle empty string', () => {
|
|
const emptyCase: TestCase[] = [
|
|
{
|
|
vars: { prompt: '' },
|
|
assert: [{ type: 'equals', value: '', metric: 'test' }],
|
|
},
|
|
];
|
|
const result = addOtherEncodings(emptyCase, 'prompt', EncodingType.MORSE);
|
|
expect(result[0].vars!.prompt).toBe('');
|
|
});
|
|
|
|
it('should handle special characters not in morse map', () => {
|
|
const specialCase: TestCase[] = [
|
|
{
|
|
vars: { prompt: 'Hello % ^ #' },
|
|
assert: [{ type: 'equals', value: '', metric: 'test' }],
|
|
},
|
|
];
|
|
const result = addOtherEncodings(specialCase, 'prompt', EncodingType.MORSE);
|
|
expect(result[0].vars!.prompt).toBe('.... . .-.. .-.. --- / % / ^ / #');
|
|
});
|
|
});
|
|
|
|
describe('Pig Latin', () => {
|
|
it('should convert text to Pig Latin', () => {
|
|
const result = addOtherEncodings(testCases, 'prompt', EncodingType.PIG_LATIN);
|
|
expect(result[0].vars!.prompt).toBe('elloHay orldWay! 123');
|
|
expect(result[0].assert?.[0].metric).toBe('original-metric/PigLatin');
|
|
|
|
// Check that other vars are not affected
|
|
expect(result[0].vars!.expected).toBe('normal value');
|
|
|
|
// Check that metadata and assertion are updated correctly
|
|
expect(result[0].metadata?.strategyId).toBe('piglatin');
|
|
expect(result[0].metadata?.encodingType).toBe(EncodingType.PIG_LATIN);
|
|
expect(result[0].metadata?.originalText).toBe('Hello World! 123');
|
|
});
|
|
|
|
it('should handle words with no vowels', () => {
|
|
const noVowelCase: TestCase[] = [
|
|
{
|
|
vars: { prompt: 'cry shy' },
|
|
assert: [{ type: 'equals', value: '', metric: 'test' }],
|
|
},
|
|
];
|
|
const result = addOtherEncodings(noVowelCase, 'prompt', EncodingType.PIG_LATIN);
|
|
expect(result[0].vars!.prompt).toBe('cryay shyay');
|
|
});
|
|
|
|
it('should handle words starting with numbers', () => {
|
|
const numericCase: TestCase[] = [
|
|
{
|
|
vars: { prompt: '123 hello 456world' },
|
|
assert: [{ type: 'equals', value: '', metric: 'test' }],
|
|
},
|
|
];
|
|
const result = addOtherEncodings(numericCase, 'prompt', EncodingType.PIG_LATIN);
|
|
expect(result[0].vars!.prompt).toBe('123 ellohay 456world');
|
|
});
|
|
|
|
it('should handle words with multiple punctuation', () => {
|
|
const punctuationCase: TestCase[] = [
|
|
{
|
|
vars: { prompt: 'hello!? world...' },
|
|
assert: [{ type: 'equals', value: '', metric: 'test' }],
|
|
},
|
|
];
|
|
const result = addOtherEncodings(punctuationCase, 'prompt', EncodingType.PIG_LATIN);
|
|
expect(result[0].vars!.prompt).toBe('ellohay!? orldway...');
|
|
});
|
|
|
|
it('should handle empty string', () => {
|
|
const emptyCase: TestCase[] = [
|
|
{
|
|
vars: { prompt: '' },
|
|
assert: [{ type: 'equals', value: '', metric: 'test' }],
|
|
},
|
|
];
|
|
const result = addOtherEncodings(emptyCase, 'prompt', EncodingType.PIG_LATIN);
|
|
expect(result[0].vars!.prompt).toBe('');
|
|
});
|
|
|
|
it('should handle words starting with vowels', () => {
|
|
const vowelCase: TestCase[] = [
|
|
{
|
|
vars: { prompt: 'eat apple ice' },
|
|
assert: [{ type: 'equals', value: '', metric: 'test' }],
|
|
},
|
|
];
|
|
const result = addOtherEncodings(vowelCase, 'prompt', EncodingType.PIG_LATIN);
|
|
expect(result[0].vars!.prompt).toBe('eatway appleway iceway');
|
|
});
|
|
});
|
|
|
|
describe('camelCase', () => {
|
|
it('should convert text to camelCase', () => {
|
|
const result = addOtherEncodings(testCases, 'prompt', EncodingType.CAMEL_CASE);
|
|
expect(result[0].vars!.prompt).toBe('helloWorld!123');
|
|
expect(result[0].assert?.[0].metric).toBe('original-metric/CamelCase');
|
|
|
|
expect(result[0].metadata?.strategyId).toBe('camelcase');
|
|
expect(result[0].metadata?.encodingType).toBe(EncodingType.CAMEL_CASE);
|
|
expect(result[0].metadata?.originalText).toBe('Hello World! 123');
|
|
});
|
|
});
|
|
|
|
describe('emoji encoding', () => {
|
|
it('should encode text using variation selectors', () => {
|
|
const result = addOtherEncodings(testCases, 'prompt', EncodingType.EMOJI);
|
|
const encoded = result[0].vars!.prompt as string;
|
|
const chars = Array.from(encoded);
|
|
expect(chars[0]).toBe('😊');
|
|
for (const ch of chars.slice(1)) {
|
|
const code = ch.codePointAt(0)!;
|
|
const valid = (code >= 0xfe00 && code <= 0xfe0f) || (code >= 0xe0100 && code <= 0xe01ef);
|
|
expect(valid).toBe(true);
|
|
}
|
|
// decode and verify round trip
|
|
const decodedBytes: number[] = [];
|
|
for (const ch of chars.slice(1)) {
|
|
const code = ch.codePointAt(0)!;
|
|
if (code >= 0xfe00 && code <= 0xfe0f) {
|
|
decodedBytes.push(code - 0xfe00);
|
|
} else if (code >= 0xe0100 && code <= 0xe01ef) {
|
|
decodedBytes.push(code - 0xe0100 + 16);
|
|
}
|
|
}
|
|
const decoded = Buffer.from(decodedBytes).toString('utf8');
|
|
expect(decoded).toBe(testCases[0].vars!.prompt);
|
|
|
|
expect(result[0].metadata?.strategyId).toBe('emoji');
|
|
expect(result[0].metadata?.encodingType).toBe(EncodingType.EMOJI);
|
|
});
|
|
});
|
|
|
|
describe('encoding type handling', () => {
|
|
it('should use Morse code as default encoding', () => {
|
|
const result = addOtherEncodings(testCases, 'prompt');
|
|
expect(result[0].vars!.prompt).toBe(
|
|
'.... . .-.. .-.. --- / .-- --- .-. .-.. -.. -.-.-- / .---- ..--- ...--',
|
|
);
|
|
expect(result[0].metadata?.encodingType).toBe(EncodingType.MORSE);
|
|
expect(result[0].metadata?.originalText).toBe('Hello World! 123');
|
|
});
|
|
|
|
it('should preserve other test case properties', () => {
|
|
const result = addOtherEncodings(testCases, 'prompt', EncodingType.MORSE);
|
|
expect(result[0].vars!.expected).toBe('normal value');
|
|
expect(result[0].metadata?.strategyId).toBe('morse');
|
|
expect(result[0].metadata?.originalText).toBe('Hello World! 123');
|
|
});
|
|
|
|
it('should handle invalid encoding type by defaulting to Morse', () => {
|
|
const result = addOtherEncodings(testCases, 'prompt', 'invalid' as EncodingType);
|
|
expect(result[0].vars!.prompt).toBe(
|
|
'.... . .-.. .-.. --- / .-- --- .-. .-.. -.. -.-.-- / .---- ..--- ...--',
|
|
);
|
|
expect(result[0].metadata?.originalText).toBe('Hello World! 123');
|
|
});
|
|
});
|
|
|
|
describe('direct encoding functions', () => {
|
|
it('should convert to morse code directly', () => {
|
|
expect(toMorseCode('SOS')).toBe('... --- ...');
|
|
expect(toMorseCode('hello@world.com')).toBe(
|
|
'.... . .-.. .-.. --- .--.-. .-- --- .-. .-.. -.. .-.-.- -.-. --- --',
|
|
);
|
|
});
|
|
|
|
it('should convert to pig latin directly', () => {
|
|
expect(toPigLatin('eat')).toBe('eatway');
|
|
expect(toPigLatin('pig')).toBe('igpay');
|
|
expect(toPigLatin('latin')).toBe('atinlay');
|
|
expect(toPigLatin('')).toBe('');
|
|
expect(toPigLatin('123')).toBe('123');
|
|
});
|
|
|
|
it('should convert to camelCase directly', () => {
|
|
expect(toCamelCase('hello world')).toBe('helloWorld');
|
|
expect(toCamelCase('Hello-World!')).toBe('hello-World!');
|
|
});
|
|
|
|
it('should convert to emoji encoding directly', () => {
|
|
const encoded = toEmojiEncoding('abc');
|
|
const chars = Array.from(encoded);
|
|
expect(chars[0]).toBe('😊');
|
|
const bytes: number[] = [];
|
|
for (const ch of chars.slice(1)) {
|
|
const code = ch.codePointAt(0)!;
|
|
if (code >= 0xfe00 && code <= 0xfe0f) {
|
|
bytes.push(code - 0xfe00);
|
|
} else if (code >= 0xe0100 && code <= 0xe01ef) {
|
|
bytes.push(code - 0xe0100 + 16);
|
|
}
|
|
}
|
|
expect(Buffer.from(bytes).toString('utf8')).toBe('abc');
|
|
});
|
|
|
|
it('should handle leading, trailing, and multiple spaces in toCamelCase', () => {
|
|
expect(toCamelCase(' hello world')).toBe('helloWorld');
|
|
expect(toCamelCase('hello world ')).toBe('helloWorld');
|
|
expect(toCamelCase(' hello world ')).toBe('helloWorld');
|
|
expect(toCamelCase('hello world')).toBe('helloWorld');
|
|
expect(toCamelCase(' multiple spaces here ')).toBe('multipleSpacesHere');
|
|
});
|
|
});
|
|
});
|