mirror of
https://github.com/elder-plinius/P4RS3LT0NGV3.git
synced 2026-07-12 15:36:32 +02:00
1287 lines
46 KiB
JavaScript
1287 lines
46 KiB
JavaScript
#!/usr/bin/env node
|
||
|
||
/**
|
||
* Comprehensive Universal Test Suite
|
||
*
|
||
* For each transformer:
|
||
* 1. Encode simple string ("hello world")
|
||
* 2. Encode complex string ("Hello World. <3 🌞")
|
||
* 3. Pass encoded output to universal decoder
|
||
* 4. Verify decoder correctly identifies the encoding method
|
||
* 5. Verify decoded output matches original (accounting for limitations)
|
||
*/
|
||
|
||
const fs = require('fs');
|
||
const path = require('path');
|
||
const vm = require('vm');
|
||
|
||
// Get project root directory (parent of tests directory)
|
||
const projectRoot = path.resolve(__dirname, '..');
|
||
|
||
const transforms = require(path.join(projectRoot, 'src/transformers/loader-node.js'));
|
||
const transformOptionsCode = fs.readFileSync(path.join(projectRoot, 'js/core/transformOptions.js'), 'utf8');
|
||
const decoderCode = fs.readFileSync(path.join(projectRoot, 'js/core/decoder.js'), 'utf8');
|
||
const emojiWordMapCode = fs.readFileSync(path.join(projectRoot, 'src/emojiWordMap.js'), 'utf8');
|
||
const emojiUtilsCode = fs.readFileSync(path.join(projectRoot, 'js/utils/emoji.js'), 'utf8');
|
||
|
||
const mockSteganography = {
|
||
decodeEmoji: (text) => null,
|
||
decodeInvisible: (text) => null
|
||
};
|
||
|
||
const sandbox = {
|
||
window: {
|
||
transforms: transforms,
|
||
steganography: mockSteganography,
|
||
emojiLibrary: {},
|
||
emojiKeywords: {}
|
||
},
|
||
console: console,
|
||
TextEncoder: TextEncoder,
|
||
TextDecoder: TextDecoder,
|
||
btoa: (str) => Buffer.from(str, 'binary').toString('base64'),
|
||
atob: (str) => Buffer.from(str, 'base64').toString('binary'),
|
||
Intl: Intl
|
||
};
|
||
|
||
vm.createContext(sandbox);
|
||
vm.runInContext(emojiUtilsCode, sandbox);
|
||
vm.runInContext(emojiWordMapCode, sandbox);
|
||
vm.runInContext(transformOptionsCode, sandbox);
|
||
vm.runInContext(decoderCode, sandbox);
|
||
|
||
const universalDecode = sandbox.universalDecode;
|
||
|
||
// Test strings
|
||
const testStrings = {
|
||
simple: 'hello world',
|
||
complex: 'Hello World. <3 🌞',
|
||
edgeCase: 'this is the vest sukkess we\'ve ever had! hello world. <#3 😊'
|
||
};
|
||
|
||
// Helper function to normalize strings based on transformer limitations
|
||
function normalizeForComparison(text, transformations = {}) {
|
||
let normalized = text;
|
||
|
||
if (transformations.lowercase) {
|
||
normalized = normalized.toLowerCase();
|
||
}
|
||
|
||
if (transformations.uppercase) {
|
||
normalized = normalized.toUpperCase();
|
||
}
|
||
|
||
if (transformations.stripEmoji) {
|
||
normalized = normalized.replace(/[\u{1F300}-\u{1F9FF}\u{2600}-\u{27BF}\u{1F1E6}-\u{1F1FF}\u{2300}-\u{23FF}\u{2B50}\u{1F004}]/gu, '');
|
||
}
|
||
|
||
if (transformations.stripPunctuation) {
|
||
normalized = normalized.replace(/[.,!?;:'"]/g, '');
|
||
}
|
||
|
||
if (transformations.stripSpecialChars) {
|
||
normalized = normalized.replace(/[^a-zA-Z0-9\s]/g, '');
|
||
}
|
||
|
||
if (transformations.stripWhitespace) {
|
||
normalized = normalized.replace(/\s+/g, '');
|
||
}
|
||
|
||
if (transformations.stripNonLetters) {
|
||
normalized = normalized.replace(/[^a-zA-Z\s]/g, '');
|
||
}
|
||
|
||
if (transformations.collapseWhitespace) {
|
||
normalized = normalized.replace(/\s+/g, ' ').trim();
|
||
}
|
||
|
||
return normalized;
|
||
}
|
||
|
||
// Transformers with known limitations
|
||
const limitations = {
|
||
// These transformers intentionally don't preserve everything
|
||
'morse': {
|
||
issues: 'Lowercases, strips emoji, converts some punctuation to text',
|
||
acceptPartial: true,
|
||
normalize: { lowercase: true, stripEmoji: true, collapseWhitespace: true },
|
||
emojiOnly: true // Only has limitations with emoji, works fine for simple text
|
||
},
|
||
'braille': {
|
||
issues: 'Lowercases, may not encode all special characters',
|
||
acceptPartial: true,
|
||
normalize: { lowercase: true, stripEmoji: true }
|
||
},
|
||
'nato': {
|
||
issues: 'Lowercases, may not encode symbols/emoji',
|
||
acceptPartial: true,
|
||
normalize: { lowercase: true, stripEmoji: true, stripSpecialChars: true, collapseWhitespace: true }
|
||
},
|
||
'rail_fence': {
|
||
issues: 'Rearranges characters, hard to detect uniquely',
|
||
acceptPartial: true
|
||
},
|
||
'quenya': {
|
||
issues: 'Fantasy language with vowel/consonant pairs',
|
||
acceptPartial: true
|
||
},
|
||
'hieroglyphics': {
|
||
issues: 'Lossy encoding, case sensitive',
|
||
acceptPartial: true
|
||
},
|
||
'a1z26': {
|
||
issues: 'Only encodes letters A-Z, strips everything else including spaces, lowercases',
|
||
normalize: { lowercase: true, stripNonLetters: true, stripWhitespace: true }
|
||
},
|
||
'semaphore': {
|
||
issues: 'Limited character set (uppercase letters only), uses emoji arrows',
|
||
normalize: { uppercase: true, stripNonLetters: true, stripWhitespace: true },
|
||
acceptPartial: true
|
||
},
|
||
'tap_code': {
|
||
issues: 'Limited character set, lowercases, uses dots',
|
||
normalize: { lowercase: true, stripNonLetters: true, stripPunctuation: true },
|
||
acceptPartial: true
|
||
},
|
||
'html': {
|
||
issues: 'Only encodes special characters, rest unchanged',
|
||
acceptPartial: true
|
||
},
|
||
'ubbi_dubbi': {
|
||
issues: 'May not preserve all special characters',
|
||
acceptPartial: true
|
||
},
|
||
'rovarspraket': {
|
||
issues: 'Swedish language game, may not preserve everything',
|
||
acceptPartial: true
|
||
},
|
||
'baconian': {
|
||
issues: 'Only encodes A-Z',
|
||
acceptPartial: true,
|
||
minMatch: 'hello'
|
||
},
|
||
'alternating_case': {
|
||
issues: 'Generic case formatting, hard to detect uniquely (looks like Base64)',
|
||
acceptPartial: true,
|
||
normalize: (t) => t.toLowerCase()
|
||
},
|
||
|
||
// === CIPHERS (Added during BaseTransformer conversion) ===
|
||
'atbash': {
|
||
issues: 'Simple substitution cipher, hard to detect uniquely',
|
||
acceptPartial: true
|
||
},
|
||
'caesar': {
|
||
issues: 'Simple substitution cipher, hard to detect uniquely',
|
||
acceptPartial: true
|
||
},
|
||
'affine': {
|
||
issues: 'Only encodes A-Z, preserves case',
|
||
acceptPartial: true,
|
||
caseInsensitive: true
|
||
},
|
||
'vigenere': {
|
||
issues: 'Only encodes A-Z, preserves case',
|
||
acceptPartial: true
|
||
},
|
||
'rot13': {
|
||
issues: 'Letter substitution, hard to detect uniquely',
|
||
acceptPartial: true
|
||
},
|
||
'rot18': {
|
||
issues: 'Letter and number substitution, hard to detect uniquely',
|
||
acceptPartial: true
|
||
},
|
||
'rot47': {
|
||
issues: 'ASCII rotation, hard to detect uniquely',
|
||
acceptPartial: true
|
||
},
|
||
'rot5': {
|
||
issues: 'Number rotation only, hard to detect uniquely',
|
||
acceptPartial: true
|
||
},
|
||
|
||
// === TEXT FORMATTING ===
|
||
'disemvowel': {
|
||
issues: 'Removes vowels, reverse is ambiguous',
|
||
acceptPartial: true
|
||
},
|
||
'leetspeak': {
|
||
issues: 'One-way transformation, reverse is ambiguous',
|
||
acceptPartial: true
|
||
},
|
||
'qwerty_shift': {
|
||
issues: 'May not encode all characters',
|
||
acceptPartial: true
|
||
},
|
||
'pigLatin': {
|
||
issues: 'Ambiguous rules for "way" endings',
|
||
acceptPartial: true
|
||
},
|
||
'kebab_case': {
|
||
issues: 'Lowercases, removes punctuation (splits on apostrophes), removes special characters',
|
||
normalize: { lowercase: true, stripPunctuation: true, stripSpecialChars: true, stripEmoji: true, collapseWhitespace: true },
|
||
acceptPartial: true // Apostrophes within words cause ambiguity
|
||
},
|
||
'snake_case': {
|
||
issues: 'Lowercases, removes punctuation (splits on apostrophes), removes special characters',
|
||
normalize: { lowercase: true, stripPunctuation: true, stripSpecialChars: true, stripEmoji: true, collapseWhitespace: true },
|
||
acceptPartial: true // Apostrophes within words cause ambiguity
|
||
},
|
||
'camel_case': {
|
||
issues: 'Lowercases, removes punctuation, loses word boundaries (especially for numbers)',
|
||
normalize: { lowercase: true, stripPunctuation: true, stripSpecialChars: true, stripEmoji: true, collapseWhitespace: true },
|
||
acceptPartial: true // Word boundaries and apostrophes cause ambiguity
|
||
},
|
||
|
||
// === FANTASY SCRIPTS (Case-insensitive) ===
|
||
'tengwar': {
|
||
issues: 'Case-insensitive mapping, hard to distinguish from Elder Futhark',
|
||
acceptPartial: true,
|
||
caseInsensitive: true
|
||
},
|
||
'klingon': {
|
||
issues: 'Case-insensitive mapping, mixed case letters',
|
||
acceptPartial: true,
|
||
caseInsensitive: true
|
||
},
|
||
'aurebesh': {
|
||
issues: 'Uppercase only, removes whitespace',
|
||
normalize: { uppercase: true, stripWhitespace: true },
|
||
acceptPartial: true
|
||
},
|
||
'standard_galactic': {
|
||
issues: 'Standard Galactic Alphabet; uppercase letters only, multi-char glyphs for P/T/X/Y',
|
||
acceptPartial: true,
|
||
normalize: { uppercase: true }
|
||
},
|
||
'dovahzul': {
|
||
issues: 'Case-insensitive mapping with vowel expansion',
|
||
caseInsensitive: true
|
||
},
|
||
'ogham': {
|
||
issues: 'Ancient script, uppercase only',
|
||
normalize: { uppercase: true },
|
||
acceptPartial: true
|
||
},
|
||
'elder_futhark': {
|
||
issues: 'Runic alphabet, case-insensitive',
|
||
caseInsensitive: true
|
||
},
|
||
'small_caps': {
|
||
issues: 'Lowercases',
|
||
caseInsensitive: true
|
||
},
|
||
'hiragana': {
|
||
issues: 'Syllabic script, may not preserve everything',
|
||
acceptPartial: true
|
||
},
|
||
'katakana': {
|
||
issues: 'Syllabic script, may not preserve everything',
|
||
acceptPartial: true
|
||
},
|
||
'cyrillic_stylized': {
|
||
issues: 'Mixed script, partial character replacement',
|
||
acceptPartial: true
|
||
},
|
||
|
||
// === SYMBOLS ===
|
||
'chemical': {
|
||
issues: 'Lowercases all text (case-insensitive)',
|
||
caseInsensitive: true
|
||
},
|
||
'regional_indicator': {
|
||
issues: 'Only encodes A-Z as flag emojis',
|
||
acceptPartial: true
|
||
},
|
||
'emoji_speak': {
|
||
issues: 'Limited vocabulary, word-based, random selection means decoded words may differ from original (synonyms)',
|
||
acceptPartial: true // Decodes to synonyms, not exact original words
|
||
},
|
||
'roman_numerals': {
|
||
issues: 'Only converts numbers, may have limits',
|
||
acceptPartial: true
|
||
},
|
||
|
||
// === UNICODE STYLES ===
|
||
'zalgo': {
|
||
issues: 'Adds combining marks, may not decode perfectly',
|
||
acceptPartial: true
|
||
},
|
||
'mirror': {
|
||
issues: 'Reverses text, hard to distinguish from reverse transform',
|
||
acceptPartial: true
|
||
},
|
||
'reverse': {
|
||
issues: 'Reverses text, hard to distinguish from mirror transform',
|
||
acceptPartial: true
|
||
},
|
||
'reverse_words': {
|
||
issues: 'Reverses word order, may be confused with other transforms',
|
||
acceptPartial: true
|
||
},
|
||
'upside_down': {
|
||
issues: 'Uses Unicode lookalikes, may be confused with ciphers',
|
||
acceptPartial: true
|
||
},
|
||
'vaporwave': {
|
||
issues: 'Fullwidth + spaces, may be confused with other styles',
|
||
acceptPartial: true
|
||
},
|
||
'fraktur': {
|
||
issues: 'Gothic script with special chars, may confuse detector',
|
||
acceptPartial: true
|
||
},
|
||
'subscript': {
|
||
issues: 'Limited character set, some chars use special Unicode',
|
||
acceptPartial: true
|
||
},
|
||
'superscript': {
|
||
issues: 'Limited character set, some chars use special Unicode',
|
||
acceptPartial: true
|
||
},
|
||
'regional_indicator': {
|
||
issues: 'Flag emojis for letters, special Unicode handling',
|
||
acceptPartial: true
|
||
},
|
||
|
||
// === BASE ENCODINGS ===
|
||
'ebcdic': {
|
||
issues: 'EBCDIC is uppercase-only, converts lowercase to uppercase',
|
||
normalize: { uppercase: true },
|
||
acceptPartial: true
|
||
},
|
||
'ascii85': {
|
||
issues: 'May have issues with certain emoji at end of string',
|
||
acceptPartial: true,
|
||
complexOnly: true
|
||
},
|
||
'base62': {
|
||
issues: 'Hard to distinguish from other base encodings',
|
||
acceptPartial: true
|
||
},
|
||
'base64url': {
|
||
issues: 'Very similar to Base64, hard to distinguish',
|
||
acceptPartial: true
|
||
},
|
||
'base45': {
|
||
issues: 'May be confused with other encodings',
|
||
acceptPartial: true
|
||
},
|
||
|
||
// === SPECIAL ===
|
||
'brainfuck': {
|
||
issues: 'Esoteric language, encoding is not bijective',
|
||
acceptPartial: true
|
||
},
|
||
'invisible_text': {
|
||
issues: 'Uses private use area, may have decoding issues',
|
||
acceptPartial: true
|
||
},
|
||
|
||
// === NEW CIPHERS (Added in latest update) ===
|
||
'playfair': {
|
||
issues: 'Requires key, only encodes A-Z (J=I), pads with X',
|
||
acceptPartial: true,
|
||
normalize: { stripNonLetters: true, stripWhitespace: true }
|
||
},
|
||
'pigpen': {
|
||
issues: 'Only encodes A-Z, uses geometric symbols, returns uppercase on decode',
|
||
acceptPartial: true,
|
||
normalize: { uppercase: true }
|
||
},
|
||
'porta': {
|
||
issues: 'Requires key, only encodes A-Z',
|
||
acceptPartial: true
|
||
},
|
||
'homophonic': {
|
||
issues: 'Uses random selection, same input produces different output',
|
||
acceptPartial: true,
|
||
normalize: { stripWhitespace: true }
|
||
},
|
||
'hill': {
|
||
issues: 'Requires key matrix, only encodes A-Z, pads with X',
|
||
acceptPartial: true,
|
||
normalize: { stripNonLetters: true, stripWhitespace: true }
|
||
},
|
||
'beaufort': {
|
||
issues: 'Requires key, only encodes A-Z',
|
||
acceptPartial: true
|
||
},
|
||
'columnar_transposition': {
|
||
issues: 'Requires key, transposition cipher',
|
||
acceptPartial: true
|
||
},
|
||
'xor': {
|
||
issues: 'Requires key, outputs hex, may be detected as Hexadecimal',
|
||
acceptPartial: true
|
||
},
|
||
'louchebem': {
|
||
issues: 'French slang transformation, reverse may not be perfect',
|
||
acceptPartial: true
|
||
},
|
||
'uppercase_lowercase': {
|
||
issues: 'Toggle case, may be confused with other ciphers in detection',
|
||
acceptPartial: true
|
||
},
|
||
|
||
// === NEW ENCODINGS ===
|
||
'base91': {
|
||
issues: 'May be confused with other base encodings',
|
||
acceptPartial: true
|
||
},
|
||
'quoted_printable': {
|
||
issues: 'Email encoding, adds soft line breaks, may have whitespace issues',
|
||
acceptPartial: true,
|
||
normalize: { collapseWhitespace: true }
|
||
},
|
||
'bcd': {
|
||
issues: 'Binary Coded Decimal encoding of character codes, complex reverse logic',
|
||
acceptPartial: true
|
||
},
|
||
'base36': {
|
||
issues: 'May be confused with Base32 in detection',
|
||
acceptPartial: true
|
||
},
|
||
|
||
// === NEW FORMAT TRANSFORMS ===
|
||
'boustrophedon': {
|
||
issues: 'Only works on multi-line text, single-line produces no encoding',
|
||
acceptPartial: true
|
||
},
|
||
'latin_gibberish': {
|
||
issues: 'Adds "us" after consonants, may not preserve everything',
|
||
acceptPartial: true
|
||
},
|
||
'syllables_separator': {
|
||
issues: 'Adds separators, reverse removes them but original separators lost',
|
||
acceptPartial: true
|
||
},
|
||
'toggle_case': {
|
||
issues: 'Self-inverse, hard to detect uniquely',
|
||
acceptPartial: true
|
||
},
|
||
|
||
// === NEWEST CIPHERS ===
|
||
'adfgx': {
|
||
issues: 'Requires key, only encodes A-Z, removes spaces and punctuation',
|
||
acceptPartial: true,
|
||
normalize: { stripNonLetters: true, stripWhitespace: true }
|
||
},
|
||
'adfgvx': {
|
||
issues: 'Only encodes A-Z and 0-9, removes spaces and punctuation',
|
||
acceptPartial: true,
|
||
normalize: { stripNonLetters: true, stripWhitespace: true, lowercase: true }
|
||
},
|
||
'amsco': {
|
||
issues: 'Only encodes A-Z, removes spaces and punctuation',
|
||
acceptPartial: true,
|
||
normalize: { stripNonLetters: true, stripWhitespace: true, lowercase: true }
|
||
},
|
||
'book_cipher': {
|
||
issues: 'Encodes words as indices; words must appear in reference book text',
|
||
acceptPartial: true,
|
||
normalize: { collapseWhitespace: true, lowercase: true }
|
||
},
|
||
'double_transposition': {
|
||
issues: 'Only encodes A-Z, removes spaces and punctuation',
|
||
acceptPartial: true,
|
||
normalize: { stripNonLetters: true, stripWhitespace: true, lowercase: true }
|
||
},
|
||
'dtmf': {
|
||
issues: 'Only encodes telephone digits 0-9, * and #',
|
||
acceptPartial: true,
|
||
normalize: { stripNonLetters: true, stripWhitespace: true, stripSpecialChars: true }
|
||
},
|
||
'fractionated_morse': {
|
||
issues: 'Only encodes A-Z; optional word/letter separators affect output shape',
|
||
acceptPartial: true,
|
||
normalize: { stripNonLetters: true, stripWhitespace: true, collapseWhitespace: true }
|
||
},
|
||
'phone_keypad': {
|
||
issues: 'Maps letters to dial digits; decode is lossy (one letter per key)',
|
||
acceptPartial: true,
|
||
normalize: { stripNonLetters: true, stripWhitespace: true, uppercase: true }
|
||
},
|
||
'route_cipher': {
|
||
issues: 'Only encodes A-Z, removes spaces and punctuation',
|
||
acceptPartial: true,
|
||
normalize: { stripNonLetters: true, stripWhitespace: true, lowercase: true }
|
||
},
|
||
't9': {
|
||
issues: 'Multi-tap encoding; preserves letters only',
|
||
acceptPartial: true,
|
||
normalize: { stripNonLetters: true, stripWhitespace: true, collapseWhitespace: true, lowercase: true }
|
||
},
|
||
'keyword_shift': {
|
||
issues: 'Only encodes A-Z, preserves case',
|
||
acceptPartial: true
|
||
},
|
||
'trithemius': {
|
||
issues: 'Only encodes A-Z, preserves case',
|
||
acceptPartial: true
|
||
},
|
||
'vernam': {
|
||
issues: 'Only encodes A-Z, preserves case',
|
||
acceptPartial: true
|
||
},
|
||
'multiplicative_cipher': {
|
||
issues: 'Only encodes A-Z, preserves case',
|
||
acceptPartial: true
|
||
},
|
||
'monoalphabetic': {
|
||
issues: 'Only encodes A-Z, preserves case',
|
||
acceptPartial: true
|
||
},
|
||
'polybius': {
|
||
issues: 'Only encodes A-Z, removes spaces and punctuation',
|
||
acceptPartial: true,
|
||
normalize: { stripNonLetters: true, stripWhitespace: true }
|
||
},
|
||
'bifid': {
|
||
issues: 'Only encodes A-Z, removes spaces and punctuation',
|
||
acceptPartial: true,
|
||
normalize: { stripNonLetters: true, stripWhitespace: true }
|
||
},
|
||
'trifid': {
|
||
issues: 'Only encodes A-Z, removes spaces and punctuation',
|
||
acceptPartial: true,
|
||
normalize: { stripNonLetters: true, stripWhitespace: true }
|
||
},
|
||
'scytale': {
|
||
issues: 'Only encodes A-Z, removes spaces and punctuation',
|
||
acceptPartial: true,
|
||
normalize: { stripNonLetters: true, stripWhitespace: true }
|
||
},
|
||
'nihilist': {
|
||
issues: 'Only encodes A-Z, removes spaces and punctuation',
|
||
acceptPartial: true,
|
||
normalize: { stripNonLetters: true, stripWhitespace: true }
|
||
},
|
||
'four_square': {
|
||
issues: 'Only encodes A-Z, removes spaces and punctuation',
|
||
acceptPartial: true,
|
||
normalize: { stripNonLetters: true, stripWhitespace: true }
|
||
},
|
||
'two_square': {
|
||
issues: 'Only encodes A-Z, removes spaces and punctuation',
|
||
acceptPartial: true,
|
||
normalize: { stripNonLetters: true, stripWhitespace: true }
|
||
},
|
||
'gronsfeld': {
|
||
issues: 'Requires numeric key, only encodes A-Z',
|
||
acceptPartial: true
|
||
},
|
||
'autokey': {
|
||
issues: 'Requires key, only encodes A-Z',
|
||
acceptPartial: true
|
||
},
|
||
|
||
// === NEWEST ENCODINGS ===
|
||
'base122': {
|
||
issues: 'May have issues with emoji and special Unicode characters',
|
||
acceptPartial: true
|
||
},
|
||
'z85': {
|
||
issues: 'May have issues with emoji and special Unicode characters',
|
||
acceptPartial: true
|
||
},
|
||
'yenc': {
|
||
issues: 'Binary encoding, may be confused with other encodings',
|
||
acceptPartial: true
|
||
},
|
||
'unicode_points': {
|
||
issues: 'Encodes as U+XXXX format, works perfectly',
|
||
acceptPartial: false
|
||
},
|
||
'emoji_encoding': {
|
||
issues: 'Multiformats Base256Emoji; optional multibase 🚀 prefix via transform options',
|
||
acceptPartial: false
|
||
},
|
||
|
||
// === NEWEST TECHNICAL ===
|
||
'icao': {
|
||
issues: 'Only encodes A-Z, removes spaces and punctuation',
|
||
acceptPartial: true,
|
||
normalize: { stripNonLetters: true, stripWhitespace: true }
|
||
},
|
||
'itu': {
|
||
issues: 'Only encodes A-Z, removes spaces and punctuation',
|
||
acceptPartial: true,
|
||
normalize: { stripNonLetters: true, stripWhitespace: true }
|
||
},
|
||
'maritime_flags': {
|
||
issues: 'Only encodes A-Z as flag emojis',
|
||
acceptPartial: true,
|
||
normalize: { stripNonLetters: true, stripWhitespace: true }
|
||
},
|
||
'baudot': {
|
||
issues: 'Uppercase only, limited character set',
|
||
acceptPartial: true,
|
||
normalize: { uppercase: true }
|
||
},
|
||
|
||
// === NEWEST FORMAT ===
|
||
'whitespace_steganography': {
|
||
issues: 'Encodes in whitespace patterns, works perfectly',
|
||
acceptPartial: false
|
||
},
|
||
'zerowidth_steganography': {
|
||
issues: 'Encodes using zero-width characters, works perfectly',
|
||
acceptPartial: false
|
||
},
|
||
'bitwise_not': {
|
||
issues: 'Inverts bits, produces non-printable characters',
|
||
acceptPartial: true
|
||
},
|
||
'word_wrap': {
|
||
issues: 'Only works on multi-line text, single-line produces no encoding',
|
||
acceptPartial: true
|
||
},
|
||
'text_justify': {
|
||
issues: 'Only works on multi-line text, single-line produces no encoding',
|
||
acceptPartial: true
|
||
},
|
||
'indent': {
|
||
issues: 'Adds indentation, reverse removes it',
|
||
acceptPartial: true
|
||
},
|
||
'line_numbers': {
|
||
issues: 'Adds line numbers, reverse removes them',
|
||
acceptPartial: true
|
||
},
|
||
|
||
// === NEWEST UNICODE ===
|
||
'squared': {
|
||
issues: 'Uppercase only',
|
||
acceptPartial: true,
|
||
normalize: { uppercase: true }
|
||
},
|
||
'negative_squared': {
|
||
issues: 'Uppercase only',
|
||
acceptPartial: true,
|
||
normalize: { uppercase: true }
|
||
},
|
||
'circled': {
|
||
issues: 'Uppercase only',
|
||
acceptPartial: true,
|
||
normalize: { uppercase: true }
|
||
},
|
||
'parenthesized': {
|
||
issues: 'Uppercase only',
|
||
acceptPartial: true,
|
||
normalize: { uppercase: true }
|
||
},
|
||
'mirror_digits': {
|
||
issues: 'Only affects digits, self-inverse, hard to detect',
|
||
acceptPartial: true
|
||
},
|
||
|
||
// === NEWEST VISUAL/UNICODE EFFECTS ===
|
||
'bold': {
|
||
issues: 'Mathematical Bold Unicode, works perfectly',
|
||
acceptPartial: false
|
||
},
|
||
'italic': {
|
||
issues: 'Mathematical Italic Unicode, works perfectly',
|
||
acceptPartial: false
|
||
},
|
||
'bold_italic': {
|
||
issues: 'Mathematical Bold Italic Unicode, works perfectly',
|
||
acceptPartial: false
|
||
},
|
||
'wide_spacing': {
|
||
issues: 'Adds spaces between characters, reverse removes them',
|
||
acceptPartial: true
|
||
},
|
||
'dotted_underline': {
|
||
issues: 'Adds combining characters, reverse removes them',
|
||
acceptPartial: true
|
||
},
|
||
'dashed_underline': {
|
||
issues: 'Adds combining characters, reverse removes them',
|
||
acceptPartial: true
|
||
},
|
||
'wavy_underline': {
|
||
issues: 'Adds combining characters, reverse removes them',
|
||
acceptPartial: true
|
||
},
|
||
'overline': {
|
||
issues: 'Adds combining characters, reverse removes them',
|
||
acceptPartial: true
|
||
},
|
||
|
||
// === CONCEALMENT / STEGO ===
|
||
'null_cipher': {
|
||
issues: 'Encodes letters only at a fixed word position; cover text is generated',
|
||
acceptPartial: true,
|
||
normalize: { stripNonLetters: false, collapseWhitespace: true }
|
||
},
|
||
'acrostic': {
|
||
issues: 'Encodes letters only; line mode produces multi-line cover text',
|
||
acceptPartial: true,
|
||
normalize: { stripNonLetters: true }
|
||
},
|
||
'cardan_grille': {
|
||
issues: 'Encodes alphanumeric into a fixed-size grille grid',
|
||
acceptPartial: true,
|
||
normalize: { stripNonLetters: true, lowercase: true }
|
||
},
|
||
'leading_zeros': {
|
||
issues: 'Only affects numeric tokens in pad/strip modes',
|
||
acceptPartial: true
|
||
},
|
||
'group_letters': {
|
||
issues: 'Letters-only mode strips non-letters before grouping',
|
||
acceptPartial: true,
|
||
normalize: { stripNonLetters: true, lowercase: true }
|
||
},
|
||
|
||
// === MUSIC & ELECTRONICS (LLM testing encodings) ===
|
||
'seven_segment': {
|
||
issues: 'Only 0-9, A-F, dash and space have segment masks; other chars become blank segments',
|
||
acceptPartial: true,
|
||
normalize: { stripNonLetters: true, stripWhitespace: true, uppercase: true }
|
||
},
|
||
'decabit': {
|
||
issues: 'Each character becomes a 10-pulse +/- pattern',
|
||
acceptPartial: true
|
||
},
|
||
'manchester_code': {
|
||
issues: 'Binary line coding of UTF-8 bytes',
|
||
acceptPartial: true
|
||
},
|
||
'acere_cipher': {
|
||
issues: 'French solfège with optional note durations; lossy in solfège-only or A–M/N–Z modes',
|
||
acceptPartial: true,
|
||
normalize: { stripNonLetters: true, stripWhitespace: true, lowercase: true }
|
||
},
|
||
|
||
// === BATCH A: Word games & concealment ===
|
||
'word_letter_add': {
|
||
issues: 'Inserts character per word; decode removes if same char/position',
|
||
acceptPartial: true,
|
||
normalize: { collapseWhitespace: true }
|
||
},
|
||
'word_letter_remove': {
|
||
issues: 'One-way letter removal per word',
|
||
nonReversible: true
|
||
},
|
||
'word_letter_change': {
|
||
issues: 'One-way letter replacement per word',
|
||
nonReversible: true
|
||
},
|
||
'shuffled_letters': {
|
||
issues: 'Seeded shuffle; decoder may not detect without seed option',
|
||
acceptPartial: true
|
||
},
|
||
'trevanion': {
|
||
issues: 'Steganography — extract letters after punctuation marks; embed builds cover text',
|
||
acceptPartial: true,
|
||
normalize: { stripNonLetters: true, lowercase: true }
|
||
},
|
||
'friderici_windows': {
|
||
issues: 'J→I, V→U; 4-pane window symbols; pane map from Cryptographia (1685)',
|
||
acceptPartial: true,
|
||
normalize: { stripNonLetters: true, uppercase: true }
|
||
},
|
||
'homoglyph': {
|
||
issues: 'Partial Cyrillic homoglyph map; not all letters substituted',
|
||
acceptPartial: true
|
||
},
|
||
'navajo_code': {
|
||
issues: 'Word code for A-Z; strips non-letters on decode',
|
||
acceptPartial: true,
|
||
normalize: { stripNonLetters: true, uppercase: true, collapseWhitespace: true }
|
||
},
|
||
|
||
// === BATCH B: Encodings ===
|
||
'bibi_binary': {
|
||
issues: 'UTF-8 bytes via Bibi-binary hex syllables',
|
||
acceptPartial: true
|
||
},
|
||
'shadoks': {
|
||
issues: 'UTF-8 bytes via Shadoks base-4 words',
|
||
acceptPartial: true
|
||
},
|
||
'periodic_table': {
|
||
issues: 'Letters only A-Z to element symbols',
|
||
acceptPartial: true,
|
||
normalize: { stripNonLetters: true, uppercase: true }
|
||
},
|
||
'codons': {
|
||
issues: 'Letters only A-Z to DNA triplets; ambiguous reverse for duplicate codons',
|
||
acceptPartial: true,
|
||
normalize: { stripNonLetters: true, uppercase: true }
|
||
},
|
||
'dominos_in_digits': {
|
||
issues: 'Digits only; non-digits stripped on encode',
|
||
acceptPartial: true,
|
||
normalize: { stripNonLetters: true, stripWhitespace: true }
|
||
},
|
||
|
||
// === BATCH C: Symbol alphabets (generated) ===
|
||
'eye_of_horus': {
|
||
issues: 'A1Z26 cycles 7 Wedjat glyphs; lossy reverse for duplicate mappings',
|
||
acceptPartial: true,
|
||
caseInsensitive: true
|
||
},
|
||
'babylonian_numerals': {
|
||
issues: 'A1Z26 cuneiform numerals; letters only',
|
||
caseInsensitive: true
|
||
},
|
||
'egyptian_numerals': {
|
||
issues: 'A1Z26 hieroglyph numerals; letters only',
|
||
caseInsensitive: true
|
||
},
|
||
'mayan_numerals': {
|
||
issues: 'A1Z26 Mayan numerals; letters only',
|
||
caseInsensitive: true
|
||
},
|
||
'younger_futhark': {
|
||
issues: '16 runes mapped to 26 Latin letters; lossy reverse',
|
||
acceptPartial: true,
|
||
caseInsensitive: true
|
||
}
|
||
};
|
||
|
||
// Track results
|
||
let totalTests = 0;
|
||
let passedTests = 0;
|
||
let failedTests = 0;
|
||
let skippedTests = 0;
|
||
|
||
const failures = [];
|
||
|
||
console.log('🧪 Comprehensive Universal Encoder/Decoder Test Suite\n');
|
||
console.log('=' .repeat(80));
|
||
console.log('\nTest Strategy:');
|
||
console.log('1. For each transformer, encode simple & complex strings');
|
||
console.log('2. Pass encoded output to universal decoder');
|
||
console.log('3. Verify decoder identifies correct method');
|
||
console.log('4. Verify decoded output matches original (with known limitations)');
|
||
console.log('\n' + '='.repeat(80));
|
||
|
||
// Discover all transforms
|
||
const transformNames = Object.keys(transforms).sort();
|
||
|
||
console.log(`\nFound ${transformNames.length} transformers to test\n`);
|
||
console.log('='.repeat(80));
|
||
|
||
for (const transformName of transformNames) {
|
||
const transform = transforms[transformName];
|
||
|
||
// Skip transforms without reverse function
|
||
if (!transform.reverse) {
|
||
console.log(`\n⏭️ ${transformName}: Skipped (no reverse function)`);
|
||
skippedTests += 2; // Would have tested both simple and complex
|
||
continue;
|
||
}
|
||
|
||
console.log(`\n📝 Testing: ${transform.name || transformName}`);
|
||
console.log('-'.repeat(80));
|
||
|
||
let limitation = limitations[transformName];
|
||
if (!limitation && transform.category === 'symbol') {
|
||
limitation = {
|
||
issues: 'Symbol map decodes to uppercase letters',
|
||
caseInsensitive: true
|
||
};
|
||
}
|
||
if (limitation) {
|
||
console.log(`⚠️ Known limitation: ${limitation.issues}`);
|
||
}
|
||
|
||
// Test both strings
|
||
for (const [testType, testString] of Object.entries(testStrings)) {
|
||
totalTests++;
|
||
|
||
// Skip simple test if complex-only limitation
|
||
if (testType === 'simple' && limitation?.complexOnly) {
|
||
console.log(` [${testType}] Skipped (complex-only limitation)`);
|
||
skippedTests++;
|
||
totalTests--;
|
||
continue;
|
||
}
|
||
|
||
// For emoji-only limitations, only apply them to tests with emoji
|
||
const hasEmoji = /[\u{1F300}-\u{1F9FF}\u{2600}-\u{27BF}\u{1F1E6}-\u{1F1FF}]/u.test(testString);
|
||
const currentLimitation = (limitation?.emojiOnly && !hasEmoji) ? null : limitation;
|
||
|
||
try {
|
||
// Step 1: Encode
|
||
const encoded = transform.func(testString);
|
||
|
||
if (!encoded || encoded === testString) {
|
||
console.log(` [${testType}] ⏭️ No encoding produced`);
|
||
skippedTests++;
|
||
continue;
|
||
}
|
||
|
||
// Step 2: Decode with universal decoder
|
||
const decoderResult = universalDecode(encoded);
|
||
|
||
if (!decoderResult) {
|
||
// Some transformers are non-reversible and that's expected
|
||
if (currentLimitation?.nonReversible) {
|
||
console.log(` [${testType}] ⚠️ Non-reversible: Decoder returned null (expected)`);
|
||
passedTests++;
|
||
continue;
|
||
}
|
||
|
||
console.log(` [${testType}] ❌ Decoder returned null`);
|
||
console.log(` Input: "${testString}"`);
|
||
console.log(` Encoded: "${encoded.substring(0, 60)}..."`);
|
||
failedTests++;
|
||
failures.push({
|
||
transform: transformName,
|
||
testType,
|
||
issue: 'Decoder returned null',
|
||
input: testString,
|
||
encoded: encoded.substring(0, 100)
|
||
});
|
||
continue;
|
||
}
|
||
|
||
const { text: decoded, method: detectedMethod, alternatives = [] } = decoderResult;
|
||
|
||
// Step 3: Check if correct decoding is in primary or alternatives
|
||
const expectedMethod = transform.name || transformName;
|
||
|
||
// Build list of all possible decodings to check
|
||
const allDecodings = [
|
||
{ text: decoded, method: detectedMethod, isPrimary: true },
|
||
...alternatives.map(alt => ({ ...alt, isPrimary: false }))
|
||
];
|
||
|
||
// Helper to check if method name matches (flexible matching)
|
||
const methodNameMatches = (detected, expected) => {
|
||
return detected === expected ||
|
||
detected.toLowerCase() === expected.toLowerCase() ||
|
||
detected.replace(/\s/g, '') === expected.replace(/\s/g, '');
|
||
};
|
||
|
||
// Find the first decoding that matches our expected method
|
||
const correctDecoding = allDecodings.find(d => methodNameMatches(d.method, expectedMethod));
|
||
|
||
// Special handling for non-reversible transforms
|
||
if (!correctDecoding && currentLimitation?.nonReversible) {
|
||
// For non-reversible transforms, it's okay if decoder returns null or finds alternatives
|
||
// The transform itself works (encoding), just can't decode
|
||
console.log(` [${testType}] ⚠️ Non-reversible transform: Encoding works, decoding not supported (expected)`);
|
||
} else if (!correctDecoding) {
|
||
// If we didn't find it in the expected method, log it
|
||
const alternativeNames = allDecodings.map(d => d.method).join(', ');
|
||
console.log(` [${testType}] ⚠️ Method mismatch: expected "${expectedMethod}", got "${detectedMethod}"${alternatives.length > 0 ? ` (alternatives: ${alternatives.map(a => a.method).join(', ')})` : ''}`);
|
||
}
|
||
|
||
// Use the correct decoding if found, otherwise fall back to primary
|
||
const decodingToCheck = correctDecoding || allDecodings[0];
|
||
const actualDecoded = decodingToCheck ? decodingToCheck.text : '';
|
||
const isFromAlternative = correctDecoding && !correctDecoding.isPrimary;
|
||
|
||
// For non-reversible transforms, skip content matching if decoder found alternatives
|
||
if (!correctDecoding && currentLimitation?.nonReversible && allDecodings.length > 0) {
|
||
// Encoding works, decoding not supported - this is expected
|
||
console.log(` [${testType}] ⚠️ Non-reversible: Encoding verified, decoding not supported (expected)`);
|
||
passedTests++;
|
||
continue;
|
||
}
|
||
|
||
// Step 4: Verify decoded content
|
||
let contentMatches = actualDecoded === testString;
|
||
let normalizedMatches = false;
|
||
let caseInsensitiveMatches = false;
|
||
|
||
// Check if there's a normalization rule
|
||
if (!contentMatches && currentLimitation) {
|
||
// Apply normalization if specified
|
||
if (currentLimitation.normalize) {
|
||
const normalizedExpected = normalizeForComparison(testString, currentLimitation.normalize);
|
||
const normalizedDecoded = normalizeForComparison(actualDecoded, currentLimitation.normalize);
|
||
normalizedMatches = normalizedExpected === normalizedDecoded;
|
||
}
|
||
|
||
// Check case-insensitive match
|
||
if (!normalizedMatches && currentLimitation.caseInsensitive) {
|
||
caseInsensitiveMatches = actualDecoded.toLowerCase() === testString.toLowerCase();
|
||
}
|
||
}
|
||
|
||
const altIndicator = isFromAlternative ? ' (from alternative)' : '';
|
||
|
||
if (contentMatches) {
|
||
console.log(` [${testType}] ✅ Perfect: "${testString}" → [encoded] → "${actualDecoded}"${altIndicator}`);
|
||
passedTests++;
|
||
} else if (normalizedMatches) {
|
||
console.log(` [${testType}] ✅ Match (with expected transformations): "${testString}" → "${actualDecoded}"${altIndicator}`);
|
||
passedTests++;
|
||
} else if (caseInsensitiveMatches) {
|
||
console.log(` [${testType}] ✅ Match (case-insensitive): "${testString}" → "${actualDecoded}"${altIndicator}`);
|
||
passedTests++;
|
||
} else if (currentLimitation?.acceptPartial) {
|
||
// For acceptPartial, we're lenient - just check that decoding returned something
|
||
// and it's not completely empty or broken
|
||
const hasReasonableContent = actualDecoded && actualDecoded.length > 0 &&
|
||
actualDecoded.length >= Math.min(5, testString.length * 0.3) &&
|
||
actualDecoded !== '[Invalid input]' &&
|
||
actualDecoded !== 'undefined';
|
||
|
||
if (hasReasonableContent) {
|
||
console.log(` [${testType}] ⚠️ Partial: Expected limitations in decoded content${altIndicator}`);
|
||
console.log(` Original: "${testString}"`);
|
||
console.log(` Decoded: "${actualDecoded}"`);
|
||
passedTests++;
|
||
} else {
|
||
console.log(` [${testType}] ❌ Content mismatch (beyond expected limitations)`);
|
||
console.log(` Expected: "${testString}"`);
|
||
console.log(` Decoded: "${actualDecoded}"`);
|
||
failedTests++;
|
||
failures.push({
|
||
transform: transformName,
|
||
testType,
|
||
issue: 'Content mismatch',
|
||
expected: testString,
|
||
decoded: actualDecoded
|
||
});
|
||
}
|
||
} else {
|
||
console.log(` [${testType}] ❌ Content mismatch`);
|
||
console.log(` Expected: "${testString}"`);
|
||
console.log(` Decoded: "${actualDecoded}"`);
|
||
console.log(` Method detected: ${decodingToCheck.method}`);
|
||
failedTests++;
|
||
failures.push({
|
||
transform: transformName,
|
||
testType,
|
||
issue: 'Content mismatch',
|
||
expected: testString,
|
||
decoded: actualDecoded,
|
||
method: decodingToCheck.method
|
||
});
|
||
}
|
||
|
||
} catch (error) {
|
||
console.log(` [${testType}] ❌ Error: ${error.message}`);
|
||
failedTests++;
|
||
failures.push({
|
||
transform: transformName,
|
||
testType,
|
||
issue: `Error: ${error.message}`,
|
||
input: testString
|
||
});
|
||
}
|
||
}
|
||
}
|
||
|
||
// ============================================================================
|
||
// ADDITIONAL UNIVERSAL DECODER TESTS
|
||
// ============================================================================
|
||
|
||
console.log('\n' + '='.repeat(80));
|
||
console.log('\n🔍 Additional Universal Decoder Tests\n');
|
||
console.log('='.repeat(80));
|
||
|
||
// Test 1: Negative tests - plain text should not trigger false positives
|
||
console.log('\n📝 Testing: Negative Cases (False Positive Prevention)');
|
||
console.log('-'.repeat(80));
|
||
|
||
const negativeTestCases = [
|
||
{ name: 'Plain English', text: 'The quick brown fox jumps over the lazy dog.' },
|
||
{ name: 'Normal sentence', text: 'This is a normal sentence with punctuation!' },
|
||
{ name: 'Mixed case', text: 'Hello World This Is Normal Text' },
|
||
{ name: 'Numbers and text', text: 'I have 42 apples and 3 oranges.' },
|
||
{ name: 'Short text', text: 'Hi there' },
|
||
{ name: 'Long text', text: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.' }
|
||
];
|
||
|
||
let negativeTestsPassed = 0;
|
||
let negativeTestsFailed = 0;
|
||
|
||
for (const testCase of negativeTestCases) {
|
||
totalTests++;
|
||
try {
|
||
const result = universalDecode(testCase.text);
|
||
|
||
// For plain text, we expect either:
|
||
// 1. null (no detection) - ideal
|
||
// 2. Or if something is detected, it should be low priority and the decoded text should be very different
|
||
if (!result) {
|
||
// Perfect - no false positive
|
||
negativeTestsPassed++;
|
||
console.log(` ✅ "${testCase.name}": No false positive (decoder returned null)`);
|
||
} else {
|
||
// Check if the decoded text is significantly different (not a false positive)
|
||
const decoded = result.text;
|
||
const similarity = calculateSimilarity(testCase.text, decoded);
|
||
|
||
// If decoded text is very similar to original, it's likely a false positive
|
||
if (similarity > 0.8 && result.method !== 'Reverse Text' && result.method !== 'Mirror') {
|
||
negativeTestsFailed++;
|
||
console.log(` ❌ "${testCase.name}": False positive detected`);
|
||
console.log(` Original: "${testCase.text}"`);
|
||
console.log(` Detected as: ${result.method}`);
|
||
console.log(` Decoded: "${decoded}"`);
|
||
console.log(` Similarity: ${(similarity * 100).toFixed(1)}%`);
|
||
} else {
|
||
// Acceptable - decoder found something but it's clearly different
|
||
negativeTestsPassed++;
|
||
console.log(` ⚠️ "${testCase.name}": Detected as "${result.method}" but decoded text is different (similarity: ${(similarity * 100).toFixed(1)}%)`);
|
||
}
|
||
}
|
||
} catch (error) {
|
||
negativeTestsFailed++;
|
||
console.log(` ❌ "${testCase.name}": Error - ${error.message}`);
|
||
}
|
||
}
|
||
|
||
// Test 2: Edge cases
|
||
console.log('\n📝 Testing: Edge Cases');
|
||
console.log('-'.repeat(80));
|
||
|
||
const edgeCases = [
|
||
{ name: 'Empty string', text: '' },
|
||
{ name: 'Single character', text: 'a' },
|
||
{ name: 'Single space', text: ' ' },
|
||
{ name: 'Only punctuation', text: '!!!???' },
|
||
{ name: 'Only numbers', text: '123456789' },
|
||
{ name: 'Very long string', text: 'a'.repeat(1000) },
|
||
{ name: 'Unicode only', text: '🌞🌞🌞' },
|
||
{ name: 'Mixed unicode', text: 'Hello 🌞 World 😊' }
|
||
];
|
||
|
||
let edgeTestsPassed = 0;
|
||
let edgeTestsFailed = 0;
|
||
|
||
for (const testCase of edgeCases) {
|
||
totalTests++;
|
||
try {
|
||
const result = universalDecode(testCase.text);
|
||
|
||
// Edge cases should either return null or handle gracefully
|
||
if (!result) {
|
||
edgeTestsPassed++;
|
||
console.log(` ✅ "${testCase.name}": Handled gracefully (returned null)`);
|
||
} else {
|
||
// If it returns something, that's okay too - just log it
|
||
edgeTestsPassed++;
|
||
console.log(` ⚠️ "${testCase.name}": Detected as "${result.method}"`);
|
||
}
|
||
} catch (error) {
|
||
edgeTestsFailed++;
|
||
console.log(` ❌ "${testCase.name}": Error - ${error.message}`);
|
||
}
|
||
}
|
||
|
||
// Test 3: Priority testing - high priority transforms should be detected first
|
||
console.log('\n📝 Testing: Priority Detection');
|
||
console.log('-'.repeat(80));
|
||
|
||
// Create test cases where multiple transforms could match
|
||
const priorityTestCases = [
|
||
{
|
||
name: 'Base64 (high priority) vs other base encodings',
|
||
encoded: 'SGVsbG8gV29ybGQ=', // "Hello World" in Base64
|
||
expectedHighPriority: ['Base64'],
|
||
expectedLowerPriority: ['Base32', 'Base36', 'Base58']
|
||
}
|
||
];
|
||
|
||
let priorityTestsPassed = 0;
|
||
let priorityTestsFailed = 0;
|
||
|
||
for (const testCase of priorityTestCases) {
|
||
totalTests++;
|
||
try {
|
||
const result = universalDecode(testCase.encoded);
|
||
|
||
if (!result) {
|
||
priorityTestsFailed++;
|
||
console.log(` ❌ "${testCase.name}": No detection`);
|
||
continue;
|
||
}
|
||
|
||
// Check if high priority method is detected first
|
||
const primaryMethod = result.method;
|
||
const isHighPriority = testCase.expectedHighPriority.some(method =>
|
||
primaryMethod.includes(method) || method.includes(primaryMethod)
|
||
);
|
||
|
||
if (isHighPriority) {
|
||
priorityTestsPassed++;
|
||
console.log(` ✅ "${testCase.name}": High priority method "${primaryMethod}" detected first`);
|
||
} else {
|
||
// Check if it's in alternatives
|
||
const inAlternatives = result.alternatives?.some(alt =>
|
||
testCase.expectedHighPriority.some(method =>
|
||
alt.method.includes(method) || method.includes(alt.method)
|
||
)
|
||
);
|
||
|
||
if (inAlternatives) {
|
||
priorityTestsPassed++;
|
||
console.log(` ⚠️ "${testCase.name}": High priority method found in alternatives (primary: "${primaryMethod}")`);
|
||
} else {
|
||
priorityTestsFailed++;
|
||
console.log(` ❌ "${testCase.name}": High priority method not detected (got: "${primaryMethod}")`);
|
||
}
|
||
}
|
||
} catch (error) {
|
||
priorityTestsFailed++;
|
||
console.log(` ❌ "${testCase.name}": Error - ${error.message}`);
|
||
}
|
||
}
|
||
|
||
// Helper function to calculate string similarity
|
||
function calculateSimilarity(str1, str2) {
|
||
if (str1 === str2) return 1;
|
||
if (!str1 || !str2) return 0;
|
||
|
||
const longer = str1.length > str2.length ? str1 : str2;
|
||
const shorter = str1.length > str2.length ? str2 : str1;
|
||
|
||
if (longer.length === 0) return 1;
|
||
|
||
// Simple similarity based on common characters
|
||
let matches = 0;
|
||
const minLen = Math.min(str1.length, str2.length);
|
||
for (let i = 0; i < minLen; i++) {
|
||
if (str1[i] === str2[i]) matches++;
|
||
}
|
||
|
||
return matches / longer.length;
|
||
}
|
||
|
||
// Update test counts
|
||
passedTests += negativeTestsPassed + edgeTestsPassed + priorityTestsPassed;
|
||
failedTests += negativeTestsFailed + edgeTestsFailed + priorityTestsFailed;
|
||
|
||
// Summary
|
||
console.log('\n' + '='.repeat(80));
|
||
console.log('\n📊 Test Summary:\n');
|
||
console.log(`✅ Passed: ${passedTests} tests`);
|
||
console.log(`❌ Failed: ${failedTests} tests`);
|
||
console.log(`⏭️ Skipped: ${skippedTests} tests`);
|
||
console.log(`📝 Total: ${totalTests} tests\n`);
|
||
|
||
if (failures.length > 0) {
|
||
console.log('Failed Tests Details:\n');
|
||
failures.forEach((failure, index) => {
|
||
console.log(`${index + 1}. ${failure.transform} (${failure.testType}):`);
|
||
console.log(` ${failure.issue}`);
|
||
if (failure.expected) console.log(` Expected: "${failure.expected}"`);
|
||
if (failure.decoded) console.log(` Decoded: "${failure.decoded}"`);
|
||
if (failure.detectedMethod) console.log(` Detected as: ${failure.detectedMethod}`);
|
||
console.log();
|
||
});
|
||
}
|
||
|
||
if (failedTests === 0) {
|
||
console.log('✨ All tests passed!\n');
|
||
process.exit(0);
|
||
} else {
|
||
console.log(`❌ ${failedTests} test(s) failed!\n`);
|
||
process.exit(1);
|
||
}
|
||
|