eigent/test/unit/electron/main/fileReader.test.ts

358 lines
10 KiB
TypeScript

import { describe, it, expect, vi, beforeEach, Mock } from "vitest";
// Mock modules with inline factories to avoid vitest hoisting issues.
vi.mock("electron", () => {
const dialogMocks = {
showOpenDialog: vi.fn(),
showSaveDialog: vi.fn(),
};
return { dialog: dialogMocks };
});
vi.mock("node:fs", () => {
const fsMocks = {
existsSync: vi.fn(),
readFileSync: vi.fn(),
writeFileSync: vi.fn(),
createReadStream: vi.fn(),
mkdirSync: vi.fn(),
};
return {
default: fsMocks,
existsSync: fsMocks.existsSync,
readFileSync: fsMocks.readFileSync,
writeFileSync: fsMocks.writeFileSync,
createReadStream: fsMocks.createReadStream,
mkdirSync: fsMocks.mkdirSync,
};
});
vi.mock("fs/promises", () => ({
readFile: vi.fn(),
writeFile: vi.fn(),
stat: vi.fn(),
rm: vi.fn(),
}));
import { dialog } from "electron";
import fs from "node:fs";
import * as fsp from "fs/promises";
import path from "node:path";
describe("File Operations and Utilities", () => {
beforeEach(() => {
vi.clearAllMocks();
});
describe("select-file IPC handler", () => {
it("should handle successful file selection", async () => {
const mockResult = {
canceled: false,
filePaths: ["/path/to/file1.txt", "/path/to/file2.pdf"],
};
(dialog.showOpenDialog as Mock).mockResolvedValue(mockResult);
const result = await dialog.showOpenDialog({} as any, {
properties: ["openFile", "multiSelections"],
});
expect(result.canceled).toBe(false);
expect(result.filePaths).toHaveLength(2);
expect(result.filePaths[0]).toContain(".txt");
expect(result.filePaths[1]).toContain(".pdf");
});
it("should handle cancelled file selection", async () => {
const mockResult = {
canceled: true,
filePaths: [],
};
(dialog.showOpenDialog as Mock).mockResolvedValue(mockResult);
const result = await dialog.showOpenDialog({} as any, {
properties: ["openFile", "multiSelections"],
});
expect(result.canceled).toBe(true);
expect(result.filePaths).toHaveLength(0);
});
it("should handle file selection with filters", async () => {
const options = {
properties: ["openFile"] as const,
filters: [
{ name: "Text Files", extensions: ["txt", "md"] },
{ name: "PDF Files", extensions: ["pdf"] },
{ name: "All Files", extensions: ["*"] },
],
};
expect(options.filters).toHaveLength(3);
expect(options.filters[0].extensions).toContain("txt");
expect(options.filters[1].extensions).toContain("pdf");
});
it("should process successful file selection result", () => {
const result = {
canceled: false,
filePaths: ["/path/to/selected/file.txt"],
};
if (!result.canceled && result.filePaths.length > 0) {
const firstFile = result.filePaths[0];
const fileName = path.basename(firstFile);
const fileExt = path.extname(firstFile);
expect(fileName).toBe("file.txt");
expect(fileExt).toBe(".txt");
}
});
});
describe("read-file IPC handler", () => {
it("should successfully read file content", async () => {
const mockContent = "This is the file content\nWith multiple lines";
(fsp.readFile as Mock).mockResolvedValue(mockContent);
const content = await fsp.readFile("/path/to/file.txt", "utf-8");
expect(content).toBe(mockContent);
expect(content).toContain("multiple lines");
});
it("should handle file read errors", async () => {
const error = new Error("ENOENT: no such file or directory");
(fsp.readFile as Mock).mockRejectedValue(error);
try {
await fsp.readFile("/nonexistent/file.txt", "utf-8");
} catch (e) {
expect(e).toBeInstanceOf(Error);
expect((e as Error).message).toContain("no such file or directory");
}
});
it("should handle different file encodings", async () => {
const mockContent = Buffer.from("Binary content");
(fsp.readFile as Mock).mockResolvedValue(mockContent);
const content = await fsp.readFile("/path/to/binary.bin");
expect(Buffer.isBuffer(content)).toBe(true);
});
it("should validate file path", () => {
const filePath = path.normalize("/path/to/file.txt");
const isAbsolute = path.isAbsolute(filePath);
const normalizedPath = path.normalize(filePath);
expect(isAbsolute).toBe(true);
expect(normalizedPath).toBe(filePath);
});
});
describe("reveal-in-folder IPC handler", () => {
it("should handle valid file path", () => {
const filePath = "/Users/test/Documents/file.txt";
const isValid = path.isAbsolute(filePath) && filePath.length > 0;
expect(isValid).toBe(true);
});
it("should handle invalid file path", () => {
const filePath = "";
const isValid = path.isAbsolute(filePath) && filePath.length > 0;
expect(isValid).toBe(false);
});
it("should normalize file path", () => {
const filePath = "/Users/test/../test/Documents/./file.txt";
const normalized = path.normalize(filePath);
expect(normalized).toBe(path.normalize("/Users/test/Documents/file.txt"));
});
it("should extract directory from file path", () => {
const filePath = "/Users/test/Documents/file.txt";
const directory = path.dirname(filePath);
expect(path.normalize(directory)).toBe(
path.normalize("/Users/test/Documents")
);
});
});
describe("File System Utilities", () => {
it("should check file existence", () => {
(fs.existsSync as Mock).mockReturnValue(true);
const exists = fs.existsSync("/path/to/file.txt");
expect(exists).toBe(true);
});
it("should handle non-existent files", () => {
(fs.existsSync as Mock).mockReturnValue(false);
const exists = fs.existsSync("/path/to/nonexistent.txt");
expect(exists).toBe(false);
});
it("should create directory path", () => {
const dirPath = "/path/to/new/directory";
const mockMkdirSync = vi.fn();
vi.mocked(fs).mkdirSync = mockMkdirSync;
fs.mkdirSync(dirPath, { recursive: true });
expect(mockMkdirSync).toHaveBeenCalledWith(dirPath, { recursive: true });
});
it("should handle path operations", () => {
const filePath = "/Users/test/Documents/file.txt";
const basename = path.basename(filePath);
const dirname = path.dirname(filePath);
const extname = path.extname(filePath);
const parsed = path.parse(filePath);
expect(basename).toBe("file.txt");
expect(path.normalize(dirname)).toBe(
path.normalize("/Users/test/Documents")
);
expect(extname).toBe(".txt");
expect(parsed.name).toBe("file");
expect(parsed.ext).toBe(".txt");
});
});
describe("File Validation", () => {
it("should validate file extension", () => {
const allowedExtensions = [".txt", ".md", ".json", ".pdf"];
const filePath = "/path/to/document.pdf";
const fileExt = path.extname(filePath);
const isAllowed = allowedExtensions.includes(fileExt);
expect(isAllowed).toBe(true);
});
it("should reject invalid file extension", () => {
const allowedExtensions = [".txt", ".md", ".json"];
const filePath = "/path/to/executable.exe";
const fileExt = path.extname(filePath);
const isAllowed = allowedExtensions.includes(fileExt);
expect(isAllowed).toBe(false);
});
it("should validate file size", () => {
const maxSize = 10 * 1024 * 1024; // 10MB
const mockStats = { size: 5 * 1024 * 1024 }; // 5MB
const isValidSize = mockStats.size <= maxSize;
expect(isValidSize).toBe(true);
});
it("should reject files that are too large", () => {
const maxSize = 10 * 1024 * 1024; // 10MB
const mockStats = { size: 20 * 1024 * 1024 }; // 20MB
const isValidSize = mockStats.size <= maxSize;
expect(isValidSize).toBe(false);
});
});
describe("File Content Processing", () => {
it("should process text file content", () => {
const content = "Line 1\nLine 2\nLine 3";
const lines = content.split("\n");
expect(lines).toHaveLength(3);
expect(lines[0]).toBe("Line 1");
expect(lines[2]).toBe("Line 3");
});
it("should handle empty file content", () => {
const content = "";
const lines = content.split("\n");
expect(lines).toHaveLength(1);
expect(lines[0]).toBe("");
});
it("should process CSV-like content", () => {
const content =
"name,age,email\nJohn,30,john@example.com\nJane,25,jane@example.com";
const lines = content.split("\n");
const headers = lines[0].split(",");
expect(headers).toEqual(["name", "age", "email"]);
expect(lines).toHaveLength(3);
});
it("should handle binary file detection", () => {
const textContent = "This is regular text content";
const binaryContent = Buffer.from([0x00, 0x01, 0x02, 0xff]);
const isText = typeof textContent === "string";
const isBinary = Buffer.isBuffer(binaryContent);
expect(isText).toBe(true);
expect(isBinary).toBe(true);
});
});
describe("File Stream Operations", () => {
it("should create readable stream", () => {
const mockCreateReadStream = vi.fn().mockReturnValue({
pipe: vi.fn(),
on: vi.fn(),
destroy: vi.fn(),
});
vi.mocked(fs).createReadStream = mockCreateReadStream;
const stream = fs.createReadStream("/path/to/file.txt");
expect(mockCreateReadStream).toHaveBeenCalledWith("/path/to/file.txt");
expect(stream.pipe).toBeDefined();
expect(stream.on).toBeDefined();
});
it("should handle stream errors", () => {
const mockStream = {
on: vi.fn((event, callback) => {
if (event === "error") {
setTimeout(() => callback(new Error("Stream error")), 0);
}
}),
destroy: vi.fn(),
};
let errorReceived = false;
mockStream.on("error", (error) => {
errorReceived = true;
expect(error.message).toBe("Stream error");
});
setTimeout(() => {
expect(errorReceived).toBe(true);
}, 10);
});
it("should cleanup stream resources", () => {
const mockStream = {
destroy: vi.fn(),
on: vi.fn(),
};
// Simulate cleanup
if (mockStream && typeof mockStream.destroy === "function") {
mockStream.destroy();
}
expect(mockStream.destroy).toHaveBeenCalled();
});
});
});