/ src / tests / write-multiple-files.test.ts
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  });