Skip to content
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
159 changes: 159 additions & 0 deletions src/utils/__tests__/historyMigration.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest"
import { migrateTasksToWorkspaceStructure, isMigrationNeeded } from "../historyMigration"

// Mock dependencies
vi.mock("../fs", () => ({
fileExistsAtPath: vi.fn(),
}))

vi.mock("../workspaceHash", () => ({
getWorkspaceHashFromPath: vi.fn().mockReturnValue("mockhash123"),
getShortWorkspaceHash: vi.fn().mockReturnValue("mockhash123"),
}))

vi.mock("../safeWriteJson", () => ({
safeWriteJson: vi.fn(),
}))

vi.mock("fs/promises", () => ({
readdir: vi.fn(),
mkdir: vi.fn(),
copyFile: vi.fn(),
rm: vi.fn(),
readFile: vi.fn(),
stat: vi.fn(),
}))

const { fileExistsAtPath } = await import("../fs")
const fs = await import("fs/promises")

const mockFileExistsAtPath = vi.mocked(fileExistsAtPath)
const mockFs = vi.mocked(fs)

describe("historyMigration", () => {
beforeEach(() => {
vi.clearAllMocks()
})

describe("isMigrationNeeded", () => {
it("should return false when old tasks directory does not exist", async () => {
mockFileExistsAtPath.mockResolvedValue(false)

const result = await isMigrationNeeded("/test/storage")

expect(result).toBe(false)
expect(mockFileExistsAtPath).toHaveBeenCalledWith("/test/storage/tasks")
})

it("should return false when old tasks directory exists but is empty", async () => {
mockFileExistsAtPath.mockResolvedValue(true)
mockFs.readdir.mockResolvedValue([])

const result = await isMigrationNeeded("/test/storage")

expect(result).toBe(false)
})

it("should return true when old tasks directory has task directories", async () => {
mockFileExistsAtPath.mockResolvedValue(true)
mockFs.readdir.mockResolvedValue([
{ name: "task-1", isDirectory: () => true },
{ name: "task-2", isDirectory: () => true },
{ name: "file.txt", isDirectory: () => false },
] as any)

const result = await isMigrationNeeded("/test/storage")

expect(result).toBe(true)
})

it("should return false when readdir fails", async () => {
mockFileExistsAtPath.mockResolvedValue(true)
mockFs.readdir.mockRejectedValue(new Error("Permission denied"))

const result = await isMigrationNeeded("/test/storage")

expect(result).toBe(false)
})
})

describe("migrateTasksToWorkspaceStructure", () => {
const mockLog = vi.fn()

beforeEach(() => {
mockLog.mockClear()
})

it("should return early when no tasks directory exists", async () => {
mockFileExistsAtPath.mockResolvedValue(false)

const result = await migrateTasksToWorkspaceStructure("/test/storage", mockLog)

expect(result).toEqual({
migratedTasks: 0,
skippedTasks: 0,
errors: [],
})
expect(mockLog).toHaveBeenCalledWith("No existing tasks directory found, migration not needed")
})

it("should handle empty tasks directory", async () => {
mockFileExistsAtPath.mockResolvedValue(true)
mockFs.readdir.mockResolvedValue([])

const result = await migrateTasksToWorkspaceStructure("/test/storage", mockLog)

expect(result).toEqual({
migratedTasks: 0,
skippedTasks: 0,
errors: [],
})
expect(mockLog).toHaveBeenCalledWith("Found 0 task directories to migrate")
})

it("should handle migration errors gracefully", async () => {
mockFileExistsAtPath
.mockResolvedValueOnce(true) // tasks directory exists
.mockResolvedValueOnce(false) // task directory doesn't exist (causes error)

mockFs.readdir.mockResolvedValue([{ name: "task-1", isDirectory: () => true }] as any)

const result = await migrateTasksToWorkspaceStructure("/test/storage", mockLog)

expect(result.migratedTasks).toBe(0)
expect(result.skippedTasks).toBe(1)
expect(result.errors).toHaveLength(1)
expect(result.errors[0]).toContain("Failed to migrate task task-1")
})

it("should handle top-level migration errors", async () => {
mockFileExistsAtPath.mockRejectedValue(new Error("File system error"))

const result = await migrateTasksToWorkspaceStructure("/test/storage", mockLog)

expect(result.migratedTasks).toBe(0)
expect(result.skippedTasks).toBe(0)
expect(result.errors).toHaveLength(1)
expect(result.errors[0]).toContain("Migration failed")
})

it("should log migration progress", async () => {
mockFileExistsAtPath.mockResolvedValue(true)
mockFs.readdir.mockResolvedValue([
{ name: "task-1", isDirectory: () => true },
{ name: "task-2", isDirectory: () => true },
] as any)

// Mock the migration to fail for both tasks to test error handling
mockFileExistsAtPath
.mockResolvedValueOnce(true) // tasks directory exists
.mockResolvedValueOnce(false) // task-1 directory doesn't exist
.mockResolvedValueOnce(false) // task-2 directory doesn't exist

const result = await migrateTasksToWorkspaceStructure("/test/storage", mockLog)

expect(mockLog).toHaveBeenCalledWith("Found 2 task directories to migrate")
expect(mockLog).toHaveBeenCalledWith(expect.stringContaining("Migration completed"))
})
})
})
147 changes: 147 additions & 0 deletions src/utils/__tests__/workspaceHash.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
import { describe, it, expect, vi, beforeEach } from "vitest"
import * as vscode from "vscode"
import { getWorkspaceHash, getWorkspaceHashFromPath, getShortWorkspaceHash } from "../workspaceHash"

// Mock vscode module
vi.mock("vscode", () => ({
workspace: {
workspaceFolders: undefined,
},
Uri: {
file: vi.fn(),
},
}))

describe("workspaceHash", () => {
beforeEach(() => {
vi.clearAllMocks()
})

describe("getWorkspaceHash", () => {
it("should return null when no workspace folders are available", () => {
// @ts-ignore
vscode.workspace.workspaceFolders = undefined

const result = getWorkspaceHash()

expect(result).toBeNull()
})

it("should return null when workspace folders array is empty", () => {
// @ts-ignore
vscode.workspace.workspaceFolders = []

const result = getWorkspaceHash()

expect(result).toBeNull()
})

it("should return a hash when workspace folder is available", () => {
const mockUri = {
toString: () => "file:///Users/test/project",
}

// @ts-ignore
vscode.workspace.workspaceFolders = [{ uri: mockUri }]

const result = getWorkspaceHash()

expect(result).toBeTruthy()
expect(typeof result).toBe("string")
expect(result).toHaveLength(40) // SHA1 hash length
})

it("should return consistent hash for same workspace URI", () => {
const mockUri = {
toString: () => "file:///Users/test/project",
}

// @ts-ignore
vscode.workspace.workspaceFolders = [{ uri: mockUri }]

const result1 = getWorkspaceHash()
const result2 = getWorkspaceHash()

expect(result1).toBe(result2)
})

it("should return different hashes for different workspace URIs", () => {
const mockUri1 = {
toString: () => "file:///Users/test/project1",
}
const mockUri2 = {
toString: () => "file:///Users/test/project2",
}

// @ts-ignore
vscode.workspace.workspaceFolders = [{ uri: mockUri1 }]
const result1 = getWorkspaceHash()

// @ts-ignore
vscode.workspace.workspaceFolders = [{ uri: mockUri2 }]
const result2 = getWorkspaceHash()

expect(result1).not.toBe(result2)
})
})

describe("getWorkspaceHashFromPath", () => {
it("should return a hash for a given workspace path", () => {
const mockUri = {
toString: () => "file:///Users/test/project",
}

vi.mocked(vscode.Uri.file).mockReturnValue(mockUri as any)

const result = getWorkspaceHashFromPath("/Users/test/project")

expect(result).toBeTruthy()
expect(typeof result).toBe("string")
expect(result).toHaveLength(40) // SHA1 hash length
expect(vscode.Uri.file).toHaveBeenCalledWith("/Users/test/project")
})

it("should return consistent hash for same path", () => {
const mockUri = {
toString: () => "file:///Users/test/project",
}

vi.mocked(vscode.Uri.file).mockReturnValue(mockUri as any)

const result1 = getWorkspaceHashFromPath("/Users/test/project")
const result2 = getWorkspaceHashFromPath("/Users/test/project")

expect(result1).toBe(result2)
})

it("should return different hashes for different paths", () => {
vi.mocked(vscode.Uri.file)
.mockReturnValueOnce({ toString: () => "file:///Users/test/project1" } as any)
.mockReturnValueOnce({ toString: () => "file:///Users/test/project2" } as any)

const result1 = getWorkspaceHashFromPath("/Users/test/project1")
const result2 = getWorkspaceHashFromPath("/Users/test/project2")

expect(result1).not.toBe(result2)
})
})

describe("getShortWorkspaceHash", () => {
it("should return first 16 characters of the hash", () => {
const fullHash = "abcdef1234567890abcdef1234567890abcdef12"

const result = getShortWorkspaceHash(fullHash)

expect(result).toBe("abcdef1234567890")
expect(result).toHaveLength(16)
})

it("should handle hashes shorter than 16 characters", () => {
const shortHash = "abc123"

const result = getShortWorkspaceHash(shortHash)

expect(result).toBe("abc123")
})
})
})
Loading
Loading