|
|
@@ -0,0 +1,296 @@
|
|
|
+import { describe, it, expect, vi, beforeEach, afterEach } from "vitest"
|
|
|
+import * as vscode from "vscode"
|
|
|
+import * as fs from "fs/promises"
|
|
|
+import * as path from "path"
|
|
|
+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()
|
|
|
+ })
|
|
|
+ })
|
|
|
+})
|