mirror of
https://gitea.toothfairyai.com/ToothFairyAI/tf_code.git
synced 2026-03-30 22:03:58 +00:00
395 lines
13 KiB
TypeScript
395 lines
13 KiB
TypeScript
import { describe, test, expect } from "bun:test"
|
|
import path from "path"
|
|
import fs from "fs/promises"
|
|
import { File } from "../../src/file"
|
|
import { Instance } from "../../src/project/instance"
|
|
import { Filesystem } from "../../src/util/filesystem"
|
|
import { tmpdir } from "../fixture/fixture"
|
|
|
|
describe("file/index Filesystem patterns", () => {
|
|
describe("File.read() - text content", () => {
|
|
test("reads text file via Filesystem.readText()", async () => {
|
|
await using tmp = await tmpdir()
|
|
const filepath = path.join(tmp.path, "test.txt")
|
|
await fs.writeFile(filepath, "Hello World", "utf-8")
|
|
|
|
await Instance.provide({
|
|
directory: tmp.path,
|
|
fn: async () => {
|
|
const result = await File.read("test.txt")
|
|
expect(result.type).toBe("text")
|
|
expect(result.content).toBe("Hello World")
|
|
},
|
|
})
|
|
})
|
|
|
|
test("reads with Filesystem.exists() check", async () => {
|
|
await using tmp = await tmpdir()
|
|
|
|
await Instance.provide({
|
|
directory: tmp.path,
|
|
fn: async () => {
|
|
// Non-existent file should return empty content
|
|
const result = await File.read("nonexistent.txt")
|
|
expect(result.type).toBe("text")
|
|
expect(result.content).toBe("")
|
|
},
|
|
})
|
|
})
|
|
|
|
test("trims whitespace from text content", async () => {
|
|
await using tmp = await tmpdir()
|
|
const filepath = path.join(tmp.path, "test.txt")
|
|
await fs.writeFile(filepath, " content with spaces \n\n", "utf-8")
|
|
|
|
await Instance.provide({
|
|
directory: tmp.path,
|
|
fn: async () => {
|
|
const result = await File.read("test.txt")
|
|
expect(result.content).toBe("content with spaces")
|
|
},
|
|
})
|
|
})
|
|
|
|
test("handles empty text file", async () => {
|
|
await using tmp = await tmpdir()
|
|
const filepath = path.join(tmp.path, "empty.txt")
|
|
await fs.writeFile(filepath, "", "utf-8")
|
|
|
|
await Instance.provide({
|
|
directory: tmp.path,
|
|
fn: async () => {
|
|
const result = await File.read("empty.txt")
|
|
expect(result.type).toBe("text")
|
|
expect(result.content).toBe("")
|
|
},
|
|
})
|
|
})
|
|
|
|
test("handles multi-line text files", async () => {
|
|
await using tmp = await tmpdir()
|
|
const filepath = path.join(tmp.path, "multiline.txt")
|
|
await fs.writeFile(filepath, "line1\nline2\nline3", "utf-8")
|
|
|
|
await Instance.provide({
|
|
directory: tmp.path,
|
|
fn: async () => {
|
|
const result = await File.read("multiline.txt")
|
|
expect(result.content).toBe("line1\nline2\nline3")
|
|
},
|
|
})
|
|
})
|
|
})
|
|
|
|
describe("File.read() - binary content", () => {
|
|
test("reads binary file via Filesystem.readArrayBuffer()", async () => {
|
|
await using tmp = await tmpdir()
|
|
const filepath = path.join(tmp.path, "image.png")
|
|
const binaryContent = Buffer.from([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a])
|
|
await fs.writeFile(filepath, binaryContent)
|
|
|
|
await Instance.provide({
|
|
directory: tmp.path,
|
|
fn: async () => {
|
|
const result = await File.read("image.png")
|
|
expect(result.type).toBe("text") // Images return as text with base64 encoding
|
|
expect(result.encoding).toBe("base64")
|
|
expect(result.mimeType).toBe("image/png")
|
|
expect(result.content).toBe(binaryContent.toString("base64"))
|
|
},
|
|
})
|
|
})
|
|
|
|
test("returns empty for binary non-image files", async () => {
|
|
await using tmp = await tmpdir()
|
|
const filepath = path.join(tmp.path, "binary.so")
|
|
await fs.writeFile(filepath, Buffer.from([0x7f, 0x45, 0x4c, 0x46]), "binary")
|
|
|
|
await Instance.provide({
|
|
directory: tmp.path,
|
|
fn: async () => {
|
|
const result = await File.read("binary.so")
|
|
expect(result.type).toBe("binary")
|
|
expect(result.content).toBe("")
|
|
},
|
|
})
|
|
})
|
|
})
|
|
|
|
describe("File.read() - Filesystem.mimeType()", () => {
|
|
test("detects MIME type via Filesystem.mimeType()", async () => {
|
|
await using tmp = await tmpdir()
|
|
const filepath = path.join(tmp.path, "test.json")
|
|
await fs.writeFile(filepath, '{"key": "value"}', "utf-8")
|
|
|
|
await Instance.provide({
|
|
directory: tmp.path,
|
|
fn: async () => {
|
|
expect(Filesystem.mimeType(filepath)).toContain("application/json")
|
|
|
|
const result = await File.read("test.json")
|
|
expect(result.type).toBe("text")
|
|
},
|
|
})
|
|
})
|
|
|
|
test("handles various image MIME types", async () => {
|
|
await using tmp = await tmpdir()
|
|
const testCases = [
|
|
{ ext: "jpg", mime: "image/jpeg" },
|
|
{ ext: "png", mime: "image/png" },
|
|
{ ext: "gif", mime: "image/gif" },
|
|
{ ext: "webp", mime: "image/webp" },
|
|
]
|
|
|
|
for (const { ext, mime } of testCases) {
|
|
const filepath = path.join(tmp.path, `test.${ext}`)
|
|
await fs.writeFile(filepath, Buffer.from([0x00, 0x00, 0x00, 0x00]), "binary")
|
|
|
|
await Instance.provide({
|
|
directory: tmp.path,
|
|
fn: async () => {
|
|
expect(Filesystem.mimeType(filepath)).toContain(mime)
|
|
},
|
|
})
|
|
}
|
|
})
|
|
})
|
|
|
|
describe("File.list() - Filesystem.exists() and readText()", () => {
|
|
test("reads .gitignore via Filesystem.exists() and readText()", async () => {
|
|
await using tmp = await tmpdir({ git: true })
|
|
|
|
await Instance.provide({
|
|
directory: tmp.path,
|
|
fn: async () => {
|
|
const gitignorePath = path.join(tmp.path, ".gitignore")
|
|
await fs.writeFile(gitignorePath, "node_modules\ndist\n", "utf-8")
|
|
|
|
// This is used internally in File.list()
|
|
expect(await Filesystem.exists(gitignorePath)).toBe(true)
|
|
|
|
const content = await Filesystem.readText(gitignorePath)
|
|
expect(content).toContain("node_modules")
|
|
},
|
|
})
|
|
})
|
|
|
|
test("reads .ignore file similarly", async () => {
|
|
await using tmp = await tmpdir({ git: true })
|
|
|
|
await Instance.provide({
|
|
directory: tmp.path,
|
|
fn: async () => {
|
|
const ignorePath = path.join(tmp.path, ".ignore")
|
|
await fs.writeFile(ignorePath, "*.log\n.env\n", "utf-8")
|
|
|
|
expect(await Filesystem.exists(ignorePath)).toBe(true)
|
|
expect(await Filesystem.readText(ignorePath)).toContain("*.log")
|
|
},
|
|
})
|
|
})
|
|
|
|
test("handles missing .gitignore gracefully", async () => {
|
|
await using tmp = await tmpdir({ git: true })
|
|
|
|
await Instance.provide({
|
|
directory: tmp.path,
|
|
fn: async () => {
|
|
const gitignorePath = path.join(tmp.path, ".gitignore")
|
|
expect(await Filesystem.exists(gitignorePath)).toBe(false)
|
|
|
|
// File.list() should still work
|
|
const nodes = await File.list()
|
|
expect(Array.isArray(nodes)).toBe(true)
|
|
},
|
|
})
|
|
})
|
|
})
|
|
|
|
describe("File.changed() - Filesystem.readText() for untracked files", () => {
|
|
test("reads untracked files via Filesystem.readText()", async () => {
|
|
await using tmp = await tmpdir({ git: true })
|
|
|
|
await Instance.provide({
|
|
directory: tmp.path,
|
|
fn: async () => {
|
|
const untrackedPath = path.join(tmp.path, "untracked.txt")
|
|
await fs.writeFile(untrackedPath, "new content\nwith multiple lines", "utf-8")
|
|
|
|
// This is how File.changed() reads untracked files
|
|
const content = await Filesystem.readText(untrackedPath)
|
|
const lines = content.split("\n").length
|
|
expect(lines).toBe(2)
|
|
},
|
|
})
|
|
})
|
|
})
|
|
|
|
describe("Error handling", () => {
|
|
test("handles errors gracefully in Filesystem.readText()", async () => {
|
|
await using tmp = await tmpdir()
|
|
const filepath = path.join(tmp.path, "readonly.txt")
|
|
await fs.writeFile(filepath, "content", "utf-8")
|
|
|
|
await Instance.provide({
|
|
directory: tmp.path,
|
|
fn: async () => {
|
|
const nonExistentPath = path.join(tmp.path, "does-not-exist.txt")
|
|
// Filesystem.readText() on non-existent file throws
|
|
await expect(Filesystem.readText(nonExistentPath)).rejects.toThrow()
|
|
|
|
// But File.read() handles this gracefully
|
|
const result = await File.read("does-not-exist.txt")
|
|
expect(result.content).toBe("")
|
|
},
|
|
})
|
|
})
|
|
|
|
test("handles errors in Filesystem.readArrayBuffer()", async () => {
|
|
await using tmp = await tmpdir()
|
|
|
|
await Instance.provide({
|
|
directory: tmp.path,
|
|
fn: async () => {
|
|
const nonExistentPath = path.join(tmp.path, "does-not-exist.bin")
|
|
const buffer = await Filesystem.readArrayBuffer(nonExistentPath).catch(() => new ArrayBuffer(0))
|
|
expect(buffer.byteLength).toBe(0)
|
|
},
|
|
})
|
|
})
|
|
|
|
test("returns empty array buffer on error for images", async () => {
|
|
await using tmp = await tmpdir()
|
|
const filepath = path.join(tmp.path, "broken.png")
|
|
// Don't create the file
|
|
|
|
await Instance.provide({
|
|
directory: tmp.path,
|
|
fn: async () => {
|
|
// File.read() handles missing images gracefully
|
|
const result = await File.read("broken.png")
|
|
expect(result.type).toBe("text")
|
|
expect(result.content).toBe("")
|
|
},
|
|
})
|
|
})
|
|
})
|
|
|
|
describe("shouldEncode() logic", () => {
|
|
test("treats .ts files as text", async () => {
|
|
await using tmp = await tmpdir()
|
|
const filepath = path.join(tmp.path, "test.ts")
|
|
await fs.writeFile(filepath, "export const value = 1", "utf-8")
|
|
|
|
await Instance.provide({
|
|
directory: tmp.path,
|
|
fn: async () => {
|
|
const result = await File.read("test.ts")
|
|
expect(result.type).toBe("text")
|
|
expect(result.content).toBe("export const value = 1")
|
|
},
|
|
})
|
|
})
|
|
|
|
test("treats .mts files as text", async () => {
|
|
await using tmp = await tmpdir()
|
|
const filepath = path.join(tmp.path, "test.mts")
|
|
await fs.writeFile(filepath, "export const value = 1", "utf-8")
|
|
|
|
await Instance.provide({
|
|
directory: tmp.path,
|
|
fn: async () => {
|
|
const result = await File.read("test.mts")
|
|
expect(result.type).toBe("text")
|
|
expect(result.content).toBe("export const value = 1")
|
|
},
|
|
})
|
|
})
|
|
|
|
test("treats .sh files as text", async () => {
|
|
await using tmp = await tmpdir()
|
|
const filepath = path.join(tmp.path, "test.sh")
|
|
await fs.writeFile(filepath, "#!/usr/bin/env bash\necho hello", "utf-8")
|
|
|
|
await Instance.provide({
|
|
directory: tmp.path,
|
|
fn: async () => {
|
|
const result = await File.read("test.sh")
|
|
expect(result.type).toBe("text")
|
|
expect(result.content).toBe("#!/usr/bin/env bash\necho hello")
|
|
},
|
|
})
|
|
})
|
|
|
|
test("treats Dockerfile as text", async () => {
|
|
await using tmp = await tmpdir()
|
|
const filepath = path.join(tmp.path, "Dockerfile")
|
|
await fs.writeFile(filepath, "FROM alpine:3.20", "utf-8")
|
|
|
|
await Instance.provide({
|
|
directory: tmp.path,
|
|
fn: async () => {
|
|
const result = await File.read("Dockerfile")
|
|
expect(result.type).toBe("text")
|
|
expect(result.content).toBe("FROM alpine:3.20")
|
|
},
|
|
})
|
|
})
|
|
|
|
test("returns encoding info for text files", async () => {
|
|
await using tmp = await tmpdir()
|
|
const filepath = path.join(tmp.path, "test.txt")
|
|
await fs.writeFile(filepath, "simple text", "utf-8")
|
|
|
|
await Instance.provide({
|
|
directory: tmp.path,
|
|
fn: async () => {
|
|
const result = await File.read("test.txt")
|
|
expect(result.encoding).toBeUndefined()
|
|
expect(result.type).toBe("text")
|
|
},
|
|
})
|
|
})
|
|
|
|
test("returns base64 encoding for images", async () => {
|
|
await using tmp = await tmpdir()
|
|
const filepath = path.join(tmp.path, "test.jpg")
|
|
await fs.writeFile(filepath, Buffer.from([0xff, 0xd8, 0xff, 0xe0]), "binary")
|
|
|
|
await Instance.provide({
|
|
directory: tmp.path,
|
|
fn: async () => {
|
|
const result = await File.read("test.jpg")
|
|
expect(result.encoding).toBe("base64")
|
|
expect(result.mimeType).toBe("image/jpeg")
|
|
},
|
|
})
|
|
})
|
|
})
|
|
|
|
describe("Path security", () => {
|
|
test("throws for paths outside project directory", async () => {
|
|
await using tmp = await tmpdir()
|
|
|
|
await Instance.provide({
|
|
directory: tmp.path,
|
|
fn: async () => {
|
|
await expect(File.read("../outside.txt")).rejects.toThrow("Access denied")
|
|
},
|
|
})
|
|
})
|
|
|
|
test("throws for paths outside project directory", async () => {
|
|
await using tmp = await tmpdir()
|
|
|
|
await Instance.provide({
|
|
directory: tmp.path,
|
|
fn: async () => {
|
|
await expect(File.read("../outside.txt")).rejects.toThrow("Access denied")
|
|
},
|
|
})
|
|
})
|
|
})
|
|
})
|