β vibesdk
worker/agents/output-formats/diff-formats/udiff-comprehensive.test.ts
14.6 KB
Download file
import { describe, it, expect } from 'vitest';
import { applyDiff } from './udiff';
describe('Unified Diff - Comprehensive LLM Resilience Tests', () => {
describe('Malformed diff headers', () => {
it('should handle missing --- header', () => {
const original = 'line1\nline2\nline3';
const diff = `+++ b/file.txt
@@ -1,3 +1,3 @@
line1
-line2
+modified
line3`;
// The diff parser is resilient and can still apply the diff
const result = applyDiff(original, diff);
expect(result).toBe('line1\nmodified\nline3');
});
it('should handle missing +++ header', () => {
const original = 'line1\nline2\nline3';
const diff = `--- a/file.txt
@@ -1,3 +1,3 @@
line1
-line2
+modified
line3`;
// The diff parser is resilient and can still apply the diff
const result = applyDiff(original, diff);
expect(result).toBe('line1\nmodified\nline3');
});
it('should handle malformed @@ header', () => {
const original = 'line1\nline2\nline3';
const diff = `--- a/file.txt
+++ b/file.txt
@@ garbage @@
line1
-line2
+modified
line3`;
// The diff parser is resilient and can still apply the diff
const result = applyDiff(original, diff);
expect(result).toBe('line1\nmodified\nline3');
});
it('should handle @@ header with wrong numbers', () => {
const original = 'line1\nline2\nline3';
const diff = `--- a/file.txt
+++ b/file.txt
@@ -1,5 +1,3 @@
line1
-line2
+modified
line3`;
// Should either handle gracefully or throw consistently
try {
const result = applyDiff(original, diff);
// If it doesn't throw, check result is reasonable
expect(result).toContain('line1');
} catch (e) {
// If it throws, that's also acceptable
expect(e).toBeDefined();
}
});
});
describe('LLM-specific formatting issues', () => {
it('should handle extra spaces after diff markers', () => {
const original = 'line1\nline2\nline3';
const diff = `--- a/file.txt
+++ b/file.txt
@@ -1,3 +1,3 @@
line1
- line2
+ modified
line3`;
const result = applyDiff(original, diff);
// Note: The implementation currently strips trailing spaces from the prefix
expect(result).toBe('line1\n modified\nline3');
});
it('should handle tabs mixed with spaces', () => {
const original = 'function test() {\n return true;\n}';
const diff = `--- a/file.js
+++ b/file.js
@@ -1,3 +1,3 @@
function test() {
- return true;
+ return false;
}`;
const result = applyDiff(original, diff);
expect(result).toBe('function test() {\n\treturn false;\n}');
});
it('should handle missing newline at end of file', () => {
const original = 'line1\nline2\nline3';
const diff = `--- a/file.txt
+++ b/file.txt
@@ -1,3 +1,3 @@
line1
line2
-line3
+line3 modified
\\ No newline at end of file`;
const result = applyDiff(original, diff);
expect(result).toBe('line1\nline2\nline3 modified');
});
it('should handle Windows CRLF in diff', () => {
const original = 'line1\r\nline2\r\nline3';
const diff = `--- a/file.txt
+++ b/file.txt
@@ -1,3 +1,3 @@
line1\r
-line2\r
+modified\r
line3`;
const result = applyDiff(original, diff);
expect(result).toBe('line1\r\nmodified\r\nline3');
});
it('should handle Mac CR line endings', () => {
const original = 'line1\rline2\rline3';
const diff = `--- a/file.txt
+++ b/file.txt
@@ -1,3 +1,3 @@
line1
-line2
+modified
line3`;
const result = applyDiff(original, diff);
expect(result.split(/\r|\n/).filter(l => l)).toContain('modified');
});
});
describe('Edge cases with line numbers', () => {
it('should handle @@ -0,0 +1,3 @@ (adding to empty file)', () => {
const original = '';
const diff = `--- a/file.txt
+++ b/file.txt
@@ -0,0 +1,3 @@
+line1
+line2
+line3`;
const result = applyDiff(original, diff);
// Note: Implementation adds an extra newline when starting from empty file
expect(result).toBe('\nline1\nline2\nline3');
});
it('should handle @@ -1,3 +0,0 @@ (deleting entire file)', () => {
const original = 'line1\nline2\nline3';
const diff = `--- a/file.txt
+++ b/file.txt
@@ -1,3 +0,0 @@
-line1
-line2
-line3`;
// Should throw an error for safety when trying to delete entire file
expect(() => applyDiff(original, diff)).toThrow('Hunk #1 would delete entire file content - aborting for safety');
});
it('should handle multiple hunks with context overlap', () => {
const original = 'a\nb\nc\nd\ne\nf\ng\nh\ni\nj';
const diff = `--- a/file.txt
+++ b/file.txt
@@ -2,4 +2,4 @@ a
b
c
-d
+D
e
@@ -6,4 +6,4 @@ e
f
g
-h
+H
i`;
const result = applyDiff(original, diff);
expect(result).toBe('a\nb\nc\nD\ne\nf\ng\nH\ni\nj');
});
it('should handle hunks that are out of order', () => {
const original = 'line1\nline2\nline3\nline4\nline5';
const diff = `--- a/file.txt
+++ b/file.txt
@@ -4,2 +4,2 @@
line4
-line5
+line5 modified
@@ -1,2 +1,2 @@
-line1
+line1 modified
line2`;
// Should either apply correctly or throw
try {
const result = applyDiff(original, diff);
expect(result).toContain('modified');
} catch (e) {
expect(e).toBeDefined();
}
});
});
describe('Malicious or confusing content', () => {
it('should handle diff markers in content', () => {
const original = 'normal line\n--- this looks like a header\n+++ but its not\nnormal end';
const diff = `--- a/file.txt
+++ b/file.txt
@@ -1,4 +1,4 @@
normal line
--- this looks like a header
+++ but its not
-normal end
+modified end`;
const result = applyDiff(original, diff);
expect(result).toBe('normal line\n--- this looks like a header\n+++ but its not\nmodified end');
});
it('should handle @@ in content', () => {
const original = 'line1\n@@ fake header @@\nline3';
const diff = `--- a/file.txt
+++ b/file.txt
@@ -1,3 +1,3 @@
line1
-@@ fake header @@
+@@ real content @@
line3`;
// Fixed: regex now only matches @@ at line start, so content with @@ works correctly
const result = applyDiff(original, diff);
expect(result).toBe('line1\n@@ real content @@\nline3');
});
it('should handle backslash escapes', () => {
const original = 'line with \\ backslash\nand \\\\ double';
const diff = `--- a/file.txt
+++ b/file.txt
@@ -1,2 +1,2 @@
-line with \\ backslash
+line with \\\\ more backslash
and \\\\ double`;
const result = applyDiff(original, diff);
expect(result).toBe('line with \\\\ more backslash\nand \\\\ double');
});
});
describe('Unicode and encoding issues', () => {
it('should handle emoji in diff', () => {
const original = 'Hello π World\nHow are you?';
const diff = `--- a/file.txt
+++ b/file.txt
@@ -1,2 +1,2 @@
-Hello π World
+Hello π World
How are you?`;
const result = applyDiff(original, diff);
expect(result).toBe('Hello π World\nHow are you?');
});
it('should handle multi-byte unicode', () => {
const original = 'ζ₯ζ¬θͺ\nδΈζ\nνκ΅μ΄';
const diff = `--- a/file.txt
+++ b/file.txt
@@ -1,3 +1,3 @@
ζ₯ζ¬θͺ
-δΈζ
+δΈζε符
νκ΅μ΄`;
const result = applyDiff(original, diff);
expect(result).toBe('ζ₯ζ¬θͺ\nδΈζε符\nνκ΅μ΄');
});
it('should handle zero-width characters', () => {
const original = 'visible\u200Btext'; // zero-width space
const diff = `--- a/file.txt
+++ b/file.txt
@@ -1 +1 @@
-visible\u200Btext
+visible\u200B\u200Btext`; // two zero-width spaces
const result = applyDiff(original, diff);
expect(result).toBe('visible\u200B\u200Btext');
});
});
describe('Performance and stress tests', () => {
it('should handle very long lines', () => {
const longLine = 'a'.repeat(10000);
const original = `short\n${longLine}\nshort`;
const diff = `--- a/file.txt
+++ b/file.txt
@@ -1,3 +1,3 @@
short
-${longLine}
+${'b'.repeat(10000)}
short`;
// Security check prevents lines over 10000 characters
expect(() => applyDiff(original, diff)).toThrow('Diff contains excessively long lines');
});
it('should handle many small hunks', () => {
const lines = Array.from({ length: 100 }, (_, i) => `line${i}`);
const original = lines.join('\n');
// Create diff that changes every 10th line
let diff = `--- a/file.txt\n+++ b/file.txt\n`;
for (let i = 0; i < 100; i += 10) {
diff += `@@ -${i + 1},3 +${i + 1},3 @@\n`;
if (i > 0) diff += ` line${i - 1}\n`;
diff += `-line${i}\n`;
diff += `+line${i} modified\n`;
if (i < 99) diff += ` line${i + 1}\n`;
}
const result = applyDiff(original, diff);
expect(result.split('\n')[0]).toBe('line0 modified');
expect(result.split('\n')[10]).toBe('line10 modified');
});
it('should handle file with no trailing newline to adding one', () => {
const original = 'line1\nline2\nline3';
const diff = `--- a/file.txt
+++ b/file.txt
@@ -1,3 +1,4 @@
line1
line2
-line3
\\ No newline at end of file
+line3
+`;
const result = applyDiff(original, diff);
expect(result).toBe('line1\nline2\nline3\n');
});
});
describe('Real-world LLM mistakes', () => {
it('should handle extra context lines from confused LLM', () => {
const original = 'a\nb\nc\nd\ne';
const diff = `--- a/file.txt
+++ b/file.txt
@@ -2,3 +2,3 @@ a
b
-c
+C modified
d
e
f`; // LLM added extra 'f' that doesn't exist
try {
const result = applyDiff(original, diff);
expect(result).toContain('C modified');
} catch (e) {
// Also acceptable to throw on invalid diff
expect(e).toBeDefined();
}
});
it('should handle missing context lines', () => {
const original = 'a\nb\nc\nd\ne';
const diff = `--- a/file.txt
+++ b/file.txt
@@ -2,3 +2,3 @@
-c
+C modified
d`; // Missing 'b' context
try {
const result = applyDiff(original, diff);
// If it works, check reasonable output
expect(result.split('\n')).toContain('C modified');
} catch (e) {
expect(e).toBeDefined();
}
});
it('should handle LLM adding line numbers in content', () => {
const original = 'function test() {\n return true;\n}';
const diff = `--- a/file.js
+++ b/file.js
@@ -1,3 +1,3 @@
function test() {
- return true;
+ return false; // Line 2: changed return value
}`;
const result = applyDiff(original, diff);
expect(result).toBe('function test() {\n return false; // Line 2: changed return value\n}');
});
it('should handle LLM word-wrapping long lines', () => {
const original = 'short line\nvery long line that goes on and on and on and should not be wrapped\nshort line';
const diff = `--- a/file.txt
+++ b/file.txt
@@ -1,3 +1,4 @@
short line
-very long line that goes on and on and on and should not be wrapped
+very long line that goes on and on
+and on and should not be wrapped
short line`;
const result = applyDiff(original, diff);
expect(result).toBe('short line\nvery long line that goes on and on\nand on and should not be wrapped\nshort line');
});
it('should handle LLM mixing spaces and tabs', () => {
const original = '{\n "indent": "spaces"\n}';
const diff = `--- a/file.json
+++ b/file.json
@@ -1,3 +1,3 @@
{
- "indent": "spaces"
+ "indent": "tabs"
}`;
const result = applyDiff(original, diff);
expect(result).toBe('{\n\t"indent": "tabs"\n}');
});
});
describe('Binary and special content', () => {
it('should handle null bytes in content', () => {
const original = 'before\0null\0after';
const diff = `--- a/file.txt
+++ b/file.txt
@@ -1 +1 @@
-before\0null\0after
+before\0NULL\0after`;
const result = applyDiff(original, diff);
expect(result).toBe('before\0NULL\0after');
});
it('should handle various line ending combinations', () => {
const original = 'unix\nwindows\r\nmac\rmixed\n\rweird';
const diff = `--- a/file.txt
+++ b/file.txt
@@ -1,5 +1,5 @@
unix
windows
mac
-mixed
+MIXED
weird`;
const result = applyDiff(original, diff);
expect(result).toContain('MIXED');
});
it('should handle control characters', () => {
const original = 'normal\x1B[31mred\x1B[0m\nnext';
const diff = `--- a/file.txt
+++ b/file.txt
@@ -1,2 +1,2 @@
-normal\x1B[31mred\x1B[0m
+normal\x1B[32mgreen\x1B[0m
next`;
const result = applyDiff(original, diff);
expect(result).toBe('normal\x1B[32mgreen\x1B[0m\nnext');
});
});
describe('Fuzzing-like tests', () => {
it('should handle random garbage after headers', () => {
const original = 'content';
const diff = `--- a/file.txt blah blah
+++ b/file.txt more garbage !@#$%
@@ -1 +1 @@ trailing stuff here too
-content
+modified`;
try {
const result = applyDiff(original, diff);
expect(result).toBe('modified');
} catch (e) {
// Also OK to reject garbage
expect(e).toBeDefined();
}
});
it('should handle incomplete diffs', () => {
const original = 'line1\nline2\nline3';
const diff = `--- a/file.txt
+++ b/file.txt
@@ -1,3 +1,3 @@
line1
-line2`;
// Diff is cut off mid-hunk
// The resilient parser applies what it can (LLM-friendly behavior)
const result = applyDiff(original, diff);
expect(result).toBe('line1\nline3'); // Applies the deletion
});
it('should handle diffs with only additions', () => {
const original = 'line1\nline2';
const diff = `--- a/file.txt
+++ b/file.txt
@@ -1,2 +1,5 @@
line1
+added1
+added2
+added3
line2`;
const result = applyDiff(original, diff);
expect(result).toBe('line1\nadded1\nadded2\nadded3\nline2');
});
it('should handle diffs with only deletions', () => {
const original = 'line1\ndelete1\ndelete2\nline2';
const diff = `--- a/file.txt
+++ b/file.txt
@@ -1,4 +1,2 @@
line1
-delete1
-delete2
line2`;
const result = applyDiff(original, diff);
expect(result).toBe('line1\nline2');
});
});
});