155 lines
4.8 KiB
TypeScript
155 lines
4.8 KiB
TypeScript
// src/tools/file/write.test.ts
|
|
// Copyright (C) 2025 DTP Technologies, LLC
|
|
// All Rights Reserved
|
|
|
|
import { describe, it, expect, beforeEach, afterEach } from "vitest";
|
|
import { FileWriteTool } from "./write.js";
|
|
import fs from "node:fs/promises";
|
|
import path from "node:path";
|
|
import { PROJECT_ROOT } from "../../config/env.js";
|
|
|
|
const tool = new FileWriteTool();
|
|
|
|
describe("FileWriteTool", () => {
|
|
const testDir = path.join(PROJECT_ROOT, "test-temp-write");
|
|
const testFile = path.join(testDir, "test.txt");
|
|
|
|
beforeEach(async () => {
|
|
await fs.mkdir(testDir, { recursive: true });
|
|
});
|
|
|
|
afterEach(async () => {
|
|
await fs.rm(testDir, { recursive: true, force: true });
|
|
});
|
|
|
|
it("should have correct tool definition", () => {
|
|
expect(tool.definition.function.name).toBe("file_write");
|
|
expect(tool.definition.function.description).toContain(
|
|
"Create a new file or overwrite",
|
|
);
|
|
const params = tool.definition.function.parameters as any;
|
|
expect(params.required).toEqual(["path", "content"]);
|
|
});
|
|
|
|
it("should reject empty path", async () => {
|
|
const result = await tool.execute({} as any, { content: "test" });
|
|
expect(result).toContain("error");
|
|
expect(result).toContain("MISSING_PARAMETER");
|
|
});
|
|
|
|
it("should reject undefined content", async () => {
|
|
const result = await tool.execute({} as any, { path: "test.txt" });
|
|
expect(result).toContain("error");
|
|
expect(result).toContain("MISSING_PARAMETER");
|
|
});
|
|
|
|
it("should reject path traversal attempts", async () => {
|
|
const result = await tool.execute({} as any, {
|
|
path: "../../../etc/passwd",
|
|
content: "test",
|
|
});
|
|
expect(result).toContain("error");
|
|
expect(result).toContain("SECURITY_VIOLATION");
|
|
});
|
|
|
|
it("should create new file and return plain text response", async () => {
|
|
const result = await tool.execute({} as any, {
|
|
path: "test-temp-write/test.txt",
|
|
content: "Hello, World!",
|
|
});
|
|
|
|
expect(result).not.toContain('"success"');
|
|
expect(result).toContain("PATH:");
|
|
expect(result).toContain("FILE OPERATION: write");
|
|
expect(result).toContain("CREATED: true");
|
|
expect(result).toContain("BYTES WRITTEN:");
|
|
expect(result).toContain("---");
|
|
expect(result).toContain("File written:");
|
|
});
|
|
|
|
it("should overwrite existing file and return plain text response", async () => {
|
|
// Create file first
|
|
await fs.writeFile(testFile, "original content");
|
|
|
|
const result = await tool.execute({} as any, {
|
|
path: "test-temp-write/test.txt",
|
|
content: "new content",
|
|
});
|
|
|
|
expect(result).not.toContain('"success"');
|
|
expect(result).toContain("PATH:");
|
|
expect(result).toContain("FILE OPERATION: write");
|
|
expect(result).toContain("CREATED: false");
|
|
expect(result).toContain("BYTES WRITTEN:");
|
|
expect(result).toContain("---");
|
|
});
|
|
|
|
it("should create parent directories automatically", async () => {
|
|
const nestedPath = "test-temp-write/nested/deep/file.txt";
|
|
const result = await tool.execute({} as any, {
|
|
path: nestedPath,
|
|
content: "nested content",
|
|
});
|
|
|
|
expect(result).toContain("File written:");
|
|
const exists = await fs
|
|
.access(path.join(PROJECT_ROOT, nestedPath))
|
|
.then(() => true)
|
|
.catch(() => false);
|
|
expect(exists).toBe(true);
|
|
});
|
|
|
|
it("should return correct byte count", async () => {
|
|
const content = "Hello, World!";
|
|
const result = await tool.execute({} as any, {
|
|
path: "test-temp-write/bytecount.txt",
|
|
content: content,
|
|
});
|
|
|
|
const expectedBytes = Buffer.byteLength(content, "utf-8");
|
|
expect(result).toContain(`BYTES WRITTEN: ${expectedBytes}`);
|
|
});
|
|
|
|
it("should handle multiline content", async () => {
|
|
const content = "Line 1\nLine 2\nLine 3";
|
|
const result = await tool.execute({} as any, {
|
|
path: "test-temp-write/multiline.txt",
|
|
content: content,
|
|
});
|
|
|
|
expect(result).toContain("File written:");
|
|
const written = await fs.readFile(
|
|
path.join(PROJECT_ROOT, "test-temp-write/multiline.txt"),
|
|
"utf-8",
|
|
);
|
|
expect(written).toBe(content);
|
|
});
|
|
|
|
it("should handle empty content", async () => {
|
|
const result = await tool.execute({} as any, {
|
|
path: "test-temp-write/empty.txt",
|
|
content: "",
|
|
});
|
|
|
|
expect(result).toContain("BYTES WRITTEN: 0");
|
|
const written = await fs.readFile(
|
|
path.join(PROJECT_ROOT, "test-temp-write/empty.txt"),
|
|
"utf-8",
|
|
);
|
|
expect(written).toBe("");
|
|
});
|
|
|
|
it("should not return JSON format on success", async () => {
|
|
const result = await tool.execute({} as any, {
|
|
path: "test-temp-write/nojson.txt",
|
|
content: "test",
|
|
});
|
|
|
|
// Should not contain JSON structure
|
|
expect(result).not.toMatch(/^\s*\{/);
|
|
expect(result).not.toContain('"success":');
|
|
expect(result).not.toContain('"data":');
|
|
expect(result).not.toContain('"message":');
|
|
});
|
|
});
|