write-multiple-files.test.ts
1 import { describe, it, expect, beforeEach, afterEach } from "@jest/globals"; 2 import * as fs from "fs/promises"; 3 import * as path from "path"; 4 import { handleWriteTool } from "../tools/write-tools.js"; 5 import { setAllowedDirectories } from "../utils/lib.js"; 6 7 const TEST_DIR = path.join(process.cwd(), "test-temp-dir"); 8 9 describe("write_multiple_files tool", () => { 10 beforeEach(async () => { 11 // Create test directory 12 await fs.mkdir(TEST_DIR, { recursive: true }); 13 setAllowedDirectories([TEST_DIR]); 14 }); 15 16 afterEach(async () => { 17 // Clean up test directory 18 try { 19 await fs.rm(TEST_DIR, { recursive: true, force: true }); 20 } catch (error) { 21 // Ignore cleanup errors 22 } 23 }); 24 25 it("should write multiple files successfully", async () => { 26 const files = [ 27 { 28 path: path.join(TEST_DIR, "file1.txt"), 29 content: "Hello World 1", 30 }, 31 { 32 path: path.join(TEST_DIR, "file2.txt"), 33 content: "Hello World 2", 34 }, 35 { 36 path: path.join(TEST_DIR, "file3.txt"), 37 content: "Hello World 3", 38 }, 39 ]; 40 41 const result = await handleWriteTool("write_multiple_files", { files }); 42 43 expect(result).toBeDefined(); 44 expect(result.content).toBeDefined(); 45 expect(result.content[0].type).toBe("text"); 46 47 const resultText = result.content[0].text; 48 expect(resultText).toContain("Wrote 3 of 3 files:"); 49 expect(resultText).toContain("✓"); 50 expect(resultText).toContain("All files written successfully."); 51 52 // Verify files were actually written 53 for (const file of files) { 54 const content = await fs.readFile(file.path, "utf-8"); 55 expect(content).toBe(file.content); 56 } 57 }); 58 59 it("should handle partial failures", async () => { 60 // Create a directory to block file creation 61 const blockingDir = path.join(TEST_DIR, "blocking-file.txt"); 62 await fs.mkdir(blockingDir); 63 64 const files = [ 65 { 66 path: path.join(TEST_DIR, "good-file.txt"), 67 content: "This should work", 68 }, 69 { 70 path: blockingDir, // This will fail because it's a directory 71 content: "This should fail", 72 }, 73 ]; 74 75 const result = await handleWriteTool("write_multiple_files", { files }); 76 77 expect(result).toBeDefined(); 78 expect(result.content).toBeDefined(); 79 expect(result.content[0].type).toBe("text"); 80 81 const resultText = result.content[0].text; 82 expect(resultText).toContain("Wrote 1 of 2 files:"); 83 expect(resultText).toContain("✓"); 84 expect(resultText).toContain("✗"); 85 expect(resultText).toContain("1 files succeeded, 1 failed."); 86 }); 87 88 it("should reject invalid file paths", async () => { 89 const files = [ 90 { 91 path: "/invalid/path/file.txt", // Invalid path 92 content: "This should fail", 93 }, 94 { 95 path: path.join(TEST_DIR, "valid-file.txt"), 96 content: "This should also fail because first path is invalid", 97 }, 98 ]; 99 100 await expect( 101 handleWriteTool("write_multiple_files", { files }), 102 ).rejects.toThrow("Invalid file paths"); 103 }); 104 105 it("should reject too many files", async () => { 106 const files = Array.from({ length: 51 }, (_, i) => ({ 107 path: path.join(TEST_DIR, `file${i}.txt`), 108 content: `Content ${i}`, 109 })); 110 111 await expect( 112 handleWriteTool("write_multiple_files", { files }), 113 ).rejects.toThrow("Maximum 50 files per operation"); 114 }); 115 116 it("should reject empty file array", async () => { 117 const files: any[] = []; 118 119 await expect( 120 handleWriteTool("write_multiple_files", { files }), 121 ).rejects.toThrow("At least one file must be provided"); 122 }); 123 124 it("should handle HTML to PDF conversion", async () => { 125 const htmlContent = ` 126 <html> 127 <body> 128 <h1>HTML to PDF Test</h1> 129 <p>This is <strong>bold</strong> text.</p> 130 </body> 131 </html> 132 `; 133 134 const files = [ 135 { 136 path: path.join(TEST_DIR, "html-batch.pdf"), 137 content: htmlContent, 138 }, 139 ]; 140 141 const result = await handleWriteTool("write_multiple_files", { files }); 142 143 expect(result.content[0].type).toBe("text"); 144 const resultText = result.content[0].text; 145 expect(resultText).toContain("Wrote 1 of 1 files:"); 146 expect(resultText).toContain("html-batch.pdf"); 147 148 const stats = await fs.stat(path.join(TEST_DIR, "html-batch.pdf")); 149 expect(stats.isFile()).toBe(true); 150 expect(stats.size).toBeGreaterThan(0); 151 }, 15000); 152 153 it("should handle HTML to DOCX conversion", async () => { 154 const htmlContent = ` 155 <html> 156 <body> 157 <h1>HTML to DOCX Test</h1> 158 <p>This is <em>italic</em> text.</p> 159 </body> 160 </html> 161 `; 162 163 const files = [ 164 { 165 path: path.join(TEST_DIR, "html-batch.docx"), 166 content: htmlContent, 167 }, 168 ]; 169 170 const result = await handleWriteTool("write_multiple_files", { files }); 171 172 expect(result.content[0].type).toBe("text"); 173 const resultText = result.content[0].text; 174 expect(resultText).toContain("Wrote 1 of 1 files:"); 175 expect(resultText).toContain("html-batch.docx"); 176 177 const stats = await fs.stat(path.join(TEST_DIR, "html-batch.docx")); 178 expect(stats.isFile()).toBe(true); 179 expect(stats.size).toBeGreaterThan(0); 180 }, 15000); 181 182 it("should handle mixed HTML and plain text documents", async () => { 183 const files = [ 184 { 185 path: path.join(TEST_DIR, "html-doc.pdf"), 186 content: "<html><body><h1>HTML PDF</h1></body></html>", 187 }, 188 { 189 path: path.join(TEST_DIR, "plain-doc.pdf"), 190 content: "Plain text PDF content", 191 }, 192 { 193 path: path.join(TEST_DIR, "html-doc.docx"), 194 content: "<html><body><h1>HTML DOCX</h1></body></html>", 195 }, 196 { 197 path: path.join(TEST_DIR, "plain-doc.docx"), 198 content: "Plain text DOCX content", 199 }, 200 ]; 201 202 const result = await handleWriteTool("write_multiple_files", { files }); 203 204 expect(result.content[0].type).toBe("text"); 205 const resultText = result.content[0].text; 206 expect(resultText).toContain("Wrote 4 of 4 files:"); 207 expect(resultText).toContain("html-doc.pdf"); 208 expect(resultText).toContain("plain-doc.pdf"); 209 expect(resultText).toContain("html-doc.docx"); 210 expect(resultText).toContain("plain-doc.docx"); 211 212 // Verify all files exist 213 for (const file of files) { 214 const stats = await fs.stat(file.path); 215 expect(stats.isFile()).toBe(true); 216 } 217 }, 20000); 218 });