Roo-Code/src/utils/__tests__/migrateSettings.spec.ts
2025-08-28 11:18:45 -07:00

294 lines
9.5 KiB
TypeScript

import * as vscode from "vscode"
import { migrateSettings } from "../migrateSettings"
// Mock vscode module
vi.mock("vscode", () => ({
window: {
showInformationMessage: vi.fn(),
createOutputChannel: vi.fn(() => ({
appendLine: vi.fn(),
})),
},
commands: {
executeCommand: vi.fn(),
},
}))
// Mock fs module
vi.mock("fs/promises")
// Mock fs utils
vi.mock("../fs", () => ({
fileExistsAtPath: vi.fn(),
}))
// Mock yaml module
vi.mock("yaml", () => ({
parse: vi.fn((content) => JSON.parse(content)),
stringify: vi.fn((obj) => JSON.stringify(obj, null, 2)),
}))
describe("migrateSettings", () => {
let mockContext: any
let mockOutputChannel: any
let mockGlobalState: Map<string, any>
beforeEach(() => {
// Reset all mocks
vi.clearAllMocks()
// Create a mock global state
mockGlobalState = new Map()
// Create mock output channel
mockOutputChannel = {
appendLine: vi.fn(),
}
// Create mock context
mockContext = {
globalState: {
get: vi.fn((key: string) => mockGlobalState.get(key)),
update: vi.fn(async (key: string, value: any) => {
mockGlobalState.set(key, value)
}),
},
globalStorageUri: {
fsPath: "/mock/storage/path",
},
}
})
afterEach(() => {
vi.restoreAllMocks()
})
describe("default commands migration", () => {
it("should only run migration once", async () => {
// Set up initial state with old default commands
const initialCommands = ["npm install", "npm test", "tsc", "git log"]
mockGlobalState.set("allowedCommands", initialCommands)
// Mock file system
const { fileExistsAtPath } = await import("../fs")
vi.mocked(fileExistsAtPath).mockResolvedValue(false)
// Run migration first time
await migrateSettings(mockContext, mockOutputChannel)
// Check that old default commands were removed
expect(mockGlobalState.get("allowedCommands")).toEqual(["git log"])
// Check that migration was marked as complete
expect(mockContext.globalState.update).toHaveBeenCalledWith("defaultCommandsMigrationCompleted", true)
// Reset mocks but keep the migration flag
mockGlobalState.set("defaultCommandsMigrationCompleted", true)
mockGlobalState.set("allowedCommands", ["npm install", "npm test"])
vi.mocked(mockContext.globalState.update).mockClear()
// Run migration again
await migrateSettings(mockContext, mockOutputChannel)
// Verify commands were NOT modified the second time
expect(mockGlobalState.get("allowedCommands")).toEqual(["npm install", "npm test"])
expect(mockContext.globalState.update).not.toHaveBeenCalled()
})
it("should remove npm install, npm test, and tsc from allowed commands", async () => {
// Set up initial state with old default commands
const initialCommands = ["git log", "npm install", "npm test", "tsc", "git diff", "echo hello"]
mockGlobalState.set("allowedCommands", initialCommands)
// Mock file system to indicate no settings directory exists
const { fileExistsAtPath } = await import("../fs")
vi.mocked(fileExistsAtPath).mockResolvedValue(false)
// Run migration
await migrateSettings(mockContext, mockOutputChannel)
// Check that old default commands were removed
const updatedCommands = mockGlobalState.get("allowedCommands")
expect(updatedCommands).toEqual(["git log", "git diff", "echo hello"])
// Verify the update was called
expect(mockContext.globalState.update).toHaveBeenCalledWith("allowedCommands", [
"git log",
"git diff",
"echo hello",
])
// Verify migration was marked as complete
expect(mockContext.globalState.update).toHaveBeenCalledWith("defaultCommandsMigrationCompleted", true)
// No notification should be shown
expect(vscode.window.showInformationMessage).not.toHaveBeenCalled()
})
it("should not remove commands with arguments (only exact matches)", async () => {
// Set up initial state with commands that have arguments
const initialCommands = [
"npm install express",
"npm test --coverage",
"tsc --watch",
"npm list",
"npm view",
"yarn list",
"git status",
]
mockGlobalState.set("allowedCommands", initialCommands)
// Mock file system
const { fileExistsAtPath } = await import("../fs")
vi.mocked(fileExistsAtPath).mockResolvedValue(false)
// Run migration
await migrateSettings(mockContext, mockOutputChannel)
// Check that commands with arguments were NOT removed (only exact matches are removed)
const updatedCommands = mockGlobalState.get("allowedCommands")
expect(updatedCommands).toEqual([
"npm install express",
"npm test --coverage",
"tsc --watch",
"npm list",
"npm view",
"yarn list",
"git status",
])
// Migration should still be marked as complete
expect(mockContext.globalState.update).toHaveBeenCalledWith("defaultCommandsMigrationCompleted", true)
// No notification should be shown
expect(vscode.window.showInformationMessage).not.toHaveBeenCalled()
})
it("should handle case-insensitive matching", async () => {
// Set up initial state with mixed case commands
const initialCommands = ["NPM INSTALL", "Npm Test", "TSC", "git log"]
mockGlobalState.set("allowedCommands", initialCommands)
// Mock file system
const { fileExistsAtPath } = await import("../fs")
vi.mocked(fileExistsAtPath).mockResolvedValue(false)
// Run migration
await migrateSettings(mockContext, mockOutputChannel)
// Check that unsafe commands were removed regardless of case
const updatedCommands = mockGlobalState.get("allowedCommands")
expect(updatedCommands).toEqual(["git log"])
})
it("should not modify commands if no old defaults are present", async () => {
// Set up initial state with only safe commands
const initialCommands = ["git log", "git diff", "ls -la", "echo hello"]
mockGlobalState.set("allowedCommands", initialCommands)
// Mock file system
const { fileExistsAtPath } = await import("../fs")
vi.mocked(fileExistsAtPath).mockResolvedValue(false)
// Run migration
await migrateSettings(mockContext, mockOutputChannel)
// Check that commands remain unchanged
const updatedCommands = mockGlobalState.get("allowedCommands")
expect(updatedCommands).toEqual(initialCommands)
// Verify no notification was shown
expect(vscode.window.showInformationMessage).not.toHaveBeenCalled()
// Verify migration was still marked as complete
expect(mockContext.globalState.update).toHaveBeenCalledWith("defaultCommandsMigrationCompleted", true)
})
it("should handle missing or invalid allowedCommands gracefully", async () => {
// Test with no allowedCommands set
const { fileExistsAtPath } = await import("../fs")
vi.mocked(fileExistsAtPath).mockResolvedValue(false)
await migrateSettings(mockContext, mockOutputChannel)
// Should still mark migration as complete
expect(mockContext.globalState.update).toHaveBeenCalledWith("defaultCommandsMigrationCompleted", true)
// Verify appropriate log messages
expect(mockOutputChannel.appendLine).toHaveBeenCalledWith(
expect.stringContaining("marking migration as complete"),
)
})
it("should handle non-array allowedCommands gracefully", async () => {
// Test with non-array value
mockGlobalState.set("allowedCommands", "not an array")
const { fileExistsAtPath } = await import("../fs")
vi.mocked(fileExistsAtPath).mockResolvedValue(false)
await migrateSettings(mockContext, mockOutputChannel)
// Should still mark migration as complete
expect(mockContext.globalState.update).toHaveBeenCalledWith("defaultCommandsMigrationCompleted", true)
// Verify appropriate log messages
expect(mockOutputChannel.appendLine).toHaveBeenCalledWith(
expect.stringContaining("marking migration as complete"),
)
})
it("should handle errors gracefully", async () => {
// Set up state
mockGlobalState.set("allowedCommands", ["npm install"])
// Make update throw an error
mockContext.globalState.update = vi.fn().mockRejectedValue(new Error("Update failed"))
// Mock file system
const { fileExistsAtPath } = await import("../fs")
vi.mocked(fileExistsAtPath).mockResolvedValue(false)
// Run migration - should not throw
await expect(migrateSettings(mockContext, mockOutputChannel)).resolves.toBeUndefined()
// Verify error was logged
expect(mockOutputChannel.appendLine).toHaveBeenCalledWith(
expect.stringContaining("[Default Commands Migration] Error"),
)
})
it("should only remove exact matches, not commands with arguments", async () => {
// Set up initial state with exact matches and commands with arguments
const initialCommands = [
"npm install", // exact match - should be removed
"npm install --save-dev typescript", // has arguments - should NOT be removed
"npm test", // exact match - should be removed
"npm test --coverage", // has arguments - should NOT be removed
"tsc", // exact match - should be removed
"tsc --noEmit", // has arguments - should NOT be removed
"git log --oneline",
]
mockGlobalState.set("allowedCommands", initialCommands)
// Mock file system
const { fileExistsAtPath } = await import("../fs")
vi.mocked(fileExistsAtPath).mockResolvedValue(false)
// Run migration
await migrateSettings(mockContext, mockOutputChannel)
// Check that only exact matches were removed
const updatedCommands = mockGlobalState.get("allowedCommands")
expect(updatedCommands).toEqual([
"npm install --save-dev typescript",
"npm test --coverage",
"tsc --noEmit",
"git log --oneline",
])
// No notification should be shown
expect(vscode.window.showInformationMessage).not.toHaveBeenCalled()
})
})
})