Advertisement

← 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');
    });
  });
});