Roo-Code/src/__tests__/extension.spec.ts
daniel-lxs 523abdb25d fix: resolve merge conflict, use startBackgroundRetentionPurge() for cleaner code
- Resolved merge conflict in extension.ts, keeping extracted function approach
- startBackgroundRetentionPurge() encapsulates all purge logic in task-history-retention.ts
- Added getStorageBasePath mock to task-storage-size tests (addresses roomote review)
2026-01-26 12:19:49 -07:00

402 lines
11 KiB
TypeScript

// npx vitest run __tests__/extension.spec.ts
import type * as vscode from "vscode"
import type { AuthState } from "@roo-code/types"
vi.mock("vscode", () => ({
window: {
createOutputChannel: vi.fn().mockReturnValue({
appendLine: vi.fn(),
}),
registerWebviewViewProvider: vi.fn(),
registerUriHandler: vi.fn(),
tabGroups: {
onDidChangeTabs: vi.fn(),
},
onDidChangeActiveTextEditor: vi.fn(),
},
workspace: {
registerTextDocumentContentProvider: vi.fn(),
getConfiguration: vi.fn().mockReturnValue({
get: vi.fn().mockReturnValue([]),
}),
createFileSystemWatcher: vi.fn().mockReturnValue({
onDidCreate: vi.fn(),
onDidChange: vi.fn(),
onDidDelete: vi.fn(),
dispose: vi.fn(),
}),
onDidChangeWorkspaceFolders: vi.fn(),
},
languages: {
registerCodeActionsProvider: vi.fn(),
},
commands: {
executeCommand: vi.fn(),
},
env: {
language: "en",
},
ExtensionMode: {
Production: 1,
},
}))
vi.mock("@dotenvx/dotenvx", () => ({
config: vi.fn(),
}))
const mockBridgeOrchestratorDisconnect = vi.fn().mockResolvedValue(undefined)
const mockCloudServiceInstance = {
off: vi.fn(),
on: vi.fn(),
getUserInfo: vi.fn().mockReturnValue(null),
isTaskSyncEnabled: vi.fn().mockReturnValue(false),
authService: {
getSessionToken: vi.fn().mockReturnValue("test-session-token"),
},
}
vi.mock("@roo-code/cloud", () => ({
CloudService: {
createInstance: vi.fn(),
hasInstance: vi.fn().mockReturnValue(true),
get instance() {
return mockCloudServiceInstance
},
},
BridgeOrchestrator: {
disconnect: mockBridgeOrchestratorDisconnect,
},
getRooCodeApiUrl: vi.fn().mockReturnValue("https://app.roocode.com"),
}))
vi.mock("@roo-code/telemetry", () => ({
TelemetryService: {
createInstance: vi.fn().mockReturnValue({
register: vi.fn(),
setProvider: vi.fn(),
shutdown: vi.fn(),
}),
get instance() {
return {
register: vi.fn(),
setProvider: vi.fn(),
shutdown: vi.fn(),
}
},
},
PostHogTelemetryClient: vi.fn(),
}))
vi.mock("../utils/outputChannelLogger", () => ({
createOutputChannelLogger: vi.fn().mockReturnValue(vi.fn()),
createDualLogger: vi.fn().mockReturnValue(vi.fn()),
}))
vi.mock("../shared/package", () => ({
Package: {
name: "test-extension",
outputChannel: "Test Output",
version: "1.0.0",
},
}))
vi.mock("../shared/language", () => ({
formatLanguage: vi.fn().mockReturnValue("en"),
}))
vi.mock("../core/config/ContextProxy", () => ({
ContextProxy: {
getInstance: vi.fn().mockResolvedValue({
getValue: vi.fn(),
setValue: vi.fn(),
getValues: vi.fn().mockReturnValue({}),
getProviderSettings: vi.fn().mockReturnValue({}),
// Needed by retention purge on activation
globalStorageUri: { fsPath: "/tmp/roo-retention-test" },
}),
},
}))
vi.mock("../integrations/editor/DiffViewProvider", () => ({
DIFF_VIEW_URI_SCHEME: "test-diff-scheme",
}))
vi.mock("../integrations/terminal/TerminalRegistry", () => ({
TerminalRegistry: {
initialize: vi.fn(),
cleanup: vi.fn(),
},
}))
vi.mock("../services/mcp/McpServerManager", () => ({
McpServerManager: {
cleanup: vi.fn().mockResolvedValue(undefined),
getInstance: vi.fn().mockResolvedValue(null),
unregisterProvider: vi.fn(),
},
}))
vi.mock("../services/code-index/manager", () => ({
CodeIndexManager: {
getInstance: vi.fn().mockReturnValue(null),
},
}))
vi.mock("../services/mdm/MdmService", () => ({
MdmService: {
createInstance: vi.fn().mockResolvedValue(null),
},
}))
vi.mock("../utils/migrateSettings", () => ({
migrateSettings: vi.fn().mockResolvedValue(undefined),
}))
vi.mock("../utils/autoImportSettings", () => ({
autoImportSettings: vi.fn().mockResolvedValue(undefined),
}))
// Avoid filesystem access during activation by stubbing background purge
vi.mock("../utils/task-history-retention", () => ({
startBackgroundRetentionPurge: vi.fn(),
}))
// Ensure storage base path resolves to provided path to avoid touching VS Code config
vi.mock("../utils/storage", () => ({
getStorageBasePath: (p: string) => Promise.resolve(p),
}))
vi.mock("../extension/api", () => ({
API: vi.fn().mockImplementation(() => ({})),
}))
vi.mock("../activate", () => ({
handleUri: vi.fn(),
registerCommands: vi.fn(),
registerCodeActions: vi.fn(),
registerTerminalActions: vi.fn(),
CodeActionProvider: vi.fn().mockImplementation(() => ({
providedCodeActionKinds: [],
})),
}))
vi.mock("../i18n", () => ({
initializeI18n: vi.fn(),
t: vi.fn((key) => key),
}))
// Mock ClineProvider - remoteControlEnabled must call BridgeOrchestrator.disconnect for the test
vi.mock("../core/webview/ClineProvider", async () => {
const { BridgeOrchestrator } = await import("@roo-code/cloud")
const mockInstance = {
resolveWebviewView: vi.fn(),
postMessageToWebview: vi.fn(),
postStateToWebview: vi.fn(),
getState: vi.fn().mockResolvedValue({}),
remoteControlEnabled: vi.fn().mockImplementation(async (enabled: boolean) => {
if (!enabled) {
await BridgeOrchestrator.disconnect()
}
}),
initializeCloudProfileSyncWhenReady: vi.fn().mockResolvedValue(undefined),
providerSettingsManager: {},
contextProxy: { getGlobalState: vi.fn() },
customModesManager: {},
upsertProviderProfile: vi.fn().mockResolvedValue(undefined),
}
return {
ClineProvider: Object.assign(
vi.fn().mockImplementation(() => mockInstance),
{
// Static method used by extension.ts
getVisibleInstance: vi.fn().mockReturnValue(mockInstance),
sideBarId: "roo-cline-sidebar",
},
),
}
})
// Mock modelCache to prevent network requests during module loading
const mockRefreshModels = vi.fn().mockResolvedValue({})
vi.mock("../api/providers/fetchers/modelCache", () => ({
flushModels: vi.fn(),
getModels: vi.fn().mockResolvedValue([]),
initializeModelCacheRefresh: vi.fn(),
refreshModels: mockRefreshModels,
}))
describe("extension.ts", () => {
let mockContext: vscode.ExtensionContext
let authStateChangedHandler:
| ((data: { state: AuthState; previousState: AuthState }) => void | Promise<void>)
| undefined
beforeEach(() => {
vi.clearAllMocks()
mockBridgeOrchestratorDisconnect.mockClear()
mockContext = {
extensionPath: "/test/path",
globalState: {
get: vi.fn().mockReturnValue(undefined),
update: vi.fn(),
},
subscriptions: [],
} as unknown as vscode.ExtensionContext
authStateChangedHandler = undefined
})
test("authStateChangedHandler calls BridgeOrchestrator.disconnect when logged-out event fires", async () => {
const { CloudService, BridgeOrchestrator } = await import("@roo-code/cloud")
// Capture the auth state changed handler.
vi.mocked(CloudService.createInstance).mockImplementation(async (_context, _logger, handlers) => {
if (handlers?.["auth-state-changed"]) {
authStateChangedHandler = handlers["auth-state-changed"]
}
return {
off: vi.fn(),
on: vi.fn(),
telemetryClient: null,
hasActiveSession: vi.fn().mockReturnValue(false),
authService: null,
} as any
})
// Activate the extension.
const { activate } = await import("../extension")
await activate(mockContext)
// Verify handler was registered.
expect(authStateChangedHandler).toBeDefined()
// Trigger logout.
await authStateChangedHandler!({
state: "logged-out" as AuthState,
previousState: "logged-in" as AuthState,
})
// Verify BridgeOrchestrator.disconnect was called
expect(mockBridgeOrchestratorDisconnect).toHaveBeenCalled()
})
test("authStateChangedHandler does not call BridgeOrchestrator.disconnect for other states", async () => {
const { CloudService } = await import("@roo-code/cloud")
// Capture the auth state changed handler.
vi.mocked(CloudService.createInstance).mockImplementation(async (_context, _logger, handlers) => {
if (handlers?.["auth-state-changed"]) {
authStateChangedHandler = handlers["auth-state-changed"]
}
return {
off: vi.fn(),
on: vi.fn(),
telemetryClient: null,
hasActiveSession: vi.fn().mockReturnValue(false),
authService: null,
} as any
})
// Activate the extension.
const { activate } = await import("../extension")
await activate(mockContext)
// Trigger login.
await authStateChangedHandler!({
state: "logged-in" as AuthState,
previousState: "logged-out" as AuthState,
})
// Verify BridgeOrchestrator.disconnect was NOT called.
expect(mockBridgeOrchestratorDisconnect).not.toHaveBeenCalled()
})
describe("Roo model cache refresh on auth state change (ROO-202)", () => {
beforeEach(() => {
vi.resetModules()
mockRefreshModels.mockClear()
})
test("refreshModels is called with session token when auth state changes to active-session", async () => {
const mockAuthService = {
getSessionToken: vi.fn().mockReturnValue("test-session-token"),
}
const { CloudService } = await import("@roo-code/cloud")
vi.mocked(CloudService.createInstance).mockImplementation(async (_context, _logger, handlers) => {
if (handlers?.["auth-state-changed"]) {
authStateChangedHandler = handlers["auth-state-changed"]
}
return {
off: vi.fn(),
on: vi.fn(),
telemetryClient: null,
authService: mockAuthService,
hasActiveSession: vi.fn().mockReturnValue(false),
} as any
})
vi.mocked(CloudService.hasInstance).mockReturnValue(true)
// Activate the extension
const { activate } = await import("../extension")
await activate(mockContext)
// Clear any calls during activation
mockRefreshModels.mockClear()
// Trigger active-session state
await authStateChangedHandler!({
state: "active-session" as AuthState,
previousState: "logged-out" as AuthState,
})
// Verify refreshModels was called with correct parameters including session token
expect(mockRefreshModels).toHaveBeenCalledWith({
provider: "roo",
baseUrl: expect.any(String),
apiKey: "test-session-token",
})
})
test("flushModels is called when auth state changes to logged-out", async () => {
const { flushModels } = await import("../api/providers/fetchers/modelCache")
const { CloudService } = await import("@roo-code/cloud")
vi.mocked(CloudService.createInstance).mockImplementation(async (_context, _logger, handlers) => {
if (handlers?.["auth-state-changed"]) {
authStateChangedHandler = handlers["auth-state-changed"]
}
return {
off: vi.fn(),
on: vi.fn(),
telemetryClient: null,
authService: null,
hasActiveSession: vi.fn().mockReturnValue(false),
} as any
})
vi.mocked(CloudService.hasInstance).mockReturnValue(true)
// Activate the extension
const { activate } = await import("../extension")
await activate(mockContext)
// Trigger logged-out state
await authStateChangedHandler!({
state: "logged-out" as AuthState,
previousState: "active-session" as AuthState,
})
// Verify flushModels was called to clear the cache on logout
expect(flushModels).toHaveBeenCalledWith({ provider: "roo" }, false)
})
})
})