scanner.spec.ts•5.8 kB
// npx vitest services/code-index/processors/__tests__/scanner.spec.ts
import { vi, describe, it, expect, beforeEach } from "vitest"
import { DirectoryScanner } from "../scanner"
import { stat } from "fs/promises"
vi.mock("fs/promises", () => ({
default: {
readFile: vi.fn(),
writeFile: vi.fn(),
mkdir: vi.fn(),
access: vi.fn(),
rename: vi.fn(),
constants: {},
},
stat: vi.fn(),
}))
// Create a simple mock for vscode since we can't access the real one
vi.mock("vscode", () => ({
workspace: {
workspaceFolders: [
{
uri: {
fsPath: "/mock/workspace",
},
},
],
getWorkspaceFolder: vi.fn().mockReturnValue({
uri: {
fsPath: "/mock/workspace",
},
}),
fs: {
readFile: vi.fn().mockResolvedValue(Buffer.from("test content")),
},
},
Uri: {
file: vi.fn().mockImplementation((path) => path),
},
window: {
activeTextEditor: {
document: {
uri: {
fsPath: "/mock/workspace",
},
},
},
},
}))
vi.mock("../../../../core/ignore/RooIgnoreController")
vi.mock("ignore")
// Override the Jest-based mock with a vitest-compatible version
vi.mock("../../../glob/list-files", () => ({
listFiles: vi.fn(),
}))
describe("DirectoryScanner", () => {
let scanner: DirectoryScanner
let mockEmbedder: any
let mockVectorStore: any
let mockCodeParser: any
let mockCacheManager: any
let mockIgnoreInstance: any
let mockStats: any
beforeEach(async () => {
mockEmbedder = {
createEmbeddings: vi.fn().mockResolvedValue({ embeddings: [[0.1, 0.2, 0.3]] }),
embedderInfo: { name: "mock-embedder", dimensions: 384 },
}
mockVectorStore = {
upsertPoints: vi.fn().mockResolvedValue(undefined),
deletePointsByFilePath: vi.fn().mockResolvedValue(undefined),
deletePointsByMultipleFilePaths: vi.fn().mockResolvedValue(undefined),
initialize: vi.fn().mockResolvedValue(true),
search: vi.fn().mockResolvedValue([]),
clearCollection: vi.fn().mockResolvedValue(undefined),
deleteCollection: vi.fn().mockResolvedValue(undefined),
collectionExists: vi.fn().mockResolvedValue(true),
}
mockCodeParser = {
parseFile: vi.fn().mockResolvedValue([]),
}
mockCacheManager = {
getHash: vi.fn().mockReturnValue(undefined),
getAllHashes: vi.fn().mockReturnValue({}),
updateHash: vi.fn().mockResolvedValue(undefined),
deleteHash: vi.fn().mockResolvedValue(undefined),
initialize: vi.fn().mockResolvedValue(undefined),
clearCacheFile: vi.fn().mockResolvedValue(undefined),
}
mockIgnoreInstance = {
ignores: vi.fn().mockReturnValue(false),
}
scanner = new DirectoryScanner(
mockEmbedder,
mockVectorStore,
mockCodeParser,
mockCacheManager,
mockIgnoreInstance,
)
// Mock default implementations - create proper Stats object
mockStats = {
size: 1024,
isFile: () => true,
isDirectory: () => false,
isBlockDevice: () => false,
isCharacterDevice: () => false,
isSymbolicLink: () => false,
isFIFO: () => false,
isSocket: () => false,
dev: 0,
ino: 0,
mode: 0,
nlink: 0,
uid: 0,
gid: 0,
rdev: 0,
blksize: 0,
blocks: 0,
atimeMs: 0,
mtimeMs: 0,
ctimeMs: 0,
birthtimeMs: 0,
atime: new Date(),
mtime: new Date(),
ctime: new Date(),
birthtime: new Date(),
atimeNs: BigInt(0),
mtimeNs: BigInt(0),
ctimeNs: BigInt(0),
birthtimeNs: BigInt(0),
}
vi.mocked(stat).mockResolvedValue(mockStats)
// Get and mock the listFiles function
const { listFiles } = await import("../../../glob/list-files")
vi.mocked(listFiles).mockResolvedValue([["test/file1.js", "test/file2.js"], false])
})
describe("scanDirectory", () => {
it("should skip files larger than MAX_FILE_SIZE_BYTES", async () => {
const { listFiles } = await import("../../../glob/list-files")
vi.mocked(listFiles).mockResolvedValue([["test/file1.js"], false])
// Create large file mock stats
const largeFileStats = {
...mockStats,
size: 2 * 1024 * 1024, // 2MB > 1MB limit
}
vi.mocked(stat).mockResolvedValueOnce(largeFileStats)
const result = await scanner.scanDirectory("/test")
expect(result.stats.skipped).toBe(1)
expect(mockCodeParser.parseFile).not.toHaveBeenCalled()
})
it("should parse changed files and return code blocks", async () => {
const { listFiles } = await import("../../../glob/list-files")
vi.mocked(listFiles).mockResolvedValue([["test/file1.js"], false])
const mockBlocks: any[] = [
{
file_path: "test/file1.js",
content: "test content",
start_line: 1,
end_line: 5,
identifier: "test",
type: "function",
fileHash: "hash",
segmentHash: "segment-hash",
},
]
;(mockCodeParser.parseFile as any).mockResolvedValue(mockBlocks)
const result = await scanner.scanDirectory("/test")
expect(result.codeBlocks).toEqual(mockBlocks)
expect(result.stats.processed).toBe(1)
})
it("should process embeddings for new/changed files", async () => {
const mockBlocks: any[] = [
{
file_path: "test/file1.js",
content: "test content",
start_line: 1,
end_line: 5,
identifier: "test",
type: "function",
fileHash: "hash",
segmentHash: "segment-hash",
},
]
;(mockCodeParser.parseFile as any).mockResolvedValue(mockBlocks)
await scanner.scanDirectory("/test")
expect(mockEmbedder.createEmbeddings).toHaveBeenCalled()
expect(mockVectorStore.upsertPoints).toHaveBeenCalled()
})
it("should delete points for removed files", async () => {
;(mockCacheManager.getAllHashes as any).mockReturnValue({ "old/file.js": "old-hash" })
await scanner.scanDirectory("/test")
expect(mockVectorStore.deletePointsByFilePath).toHaveBeenCalledWith("old/file.js")
expect(mockCacheManager.deleteHash).toHaveBeenCalledWith("old/file.js")
})
})
})