Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
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
47 changes: 31 additions & 16 deletions src/__tests__/extension.spec.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
// npx vitest run __tests__/extension.spec.ts

import type * as vscode from "vscode"
import type { AuthState } from "@roo-code/types"

vi.mock("vscode", () => ({
window: {
Expand Down Expand Up @@ -189,7 +188,7 @@ vi.mock("../core/webview/ClineProvider", async () => {
resolveWebviewView: vi.fn(),
postMessageToWebview: vi.fn(),
postStateToWebview: vi.fn(),
postStateToWebviewWithoutClineMessages: vi.fn(),
postStateToWebviewWithoutClineMessages: vi.fn().mockResolvedValue(undefined),
getState: vi.fn().mockResolvedValue({}),
initializeCloudProfileSyncWhenReady: vi.fn().mockResolvedValue(undefined),
providerSettingsManager: {},
Expand All @@ -215,15 +214,13 @@ vi.mock("../core/webview/ClineProvider", async () => {
vi.mock("../api/providers/fetchers/modelCache", () => ({
flushModels: vi.fn(),
getModels: vi.fn().mockResolvedValue([]),
initializeModelCacheRefresh: vi.fn(),
initializeModelCacheRefresh: vi.fn().mockResolvedValue(undefined),
refreshModels: vi.fn().mockResolvedValue({}),
}))

describe("extension.ts", () => {
let mockContext: vscode.ExtensionContext
let authStateChangedHandler:
| ((data: { state: AuthState; previousState: AuthState }) => void | Promise<void>)
| undefined
let settingsUpdatedHandler: ((data: Record<string, never>) => void | Promise<void>) | undefined

beforeEach(() => {
vi.clearAllMocks()
Expand All @@ -237,7 +234,7 @@ describe("extension.ts", () => {
subscriptions: [],
} as unknown as vscode.ExtensionContext

authStateChangedHandler = undefined
settingsUpdatedHandler = undefined
})

test("does not call dotenv.config when optional .env does not exist", async () => {
Expand Down Expand Up @@ -270,19 +267,17 @@ describe("extension.ts", () => {
expect(dotenv.config).toHaveBeenCalledTimes(1)
})

describe("cloud auth state handling", () => {
describe("cloud organization settings handling", () => {
beforeEach(() => {
vi.resetModules()
})

test("auth state changes still post webview state without Roo model cache side effects", async () => {
test("settings updates refresh webview state and contain failures", async () => {
const { CloudService } = await import("@roo-code/cloud")
const { ClineProvider } = await import("../core/webview/ClineProvider")

vi.mocked(CloudService.createInstance).mockImplementation(async (_context, _logger, handlers) => {
if (handlers?.["auth-state-changed"]) {
authStateChangedHandler = handlers["auth-state-changed"]
}
settingsUpdatedHandler = handlers?.["settings-updated"]
return {
off: vi.fn(),
on: vi.fn(),
Expand All @@ -304,13 +299,33 @@ describe("extension.ts", () => {
}
).getVisibleInstance()
provider.postStateToWebviewWithoutClineMessages.mockClear()
const refreshError = new Error("state refresh failed")
provider.postStateToWebviewWithoutClineMessages.mockRejectedValueOnce(refreshError)

await authStateChangedHandler!({
state: "active-session" as AuthState,
previousState: "logged-out" as AuthState,
})
settingsUpdatedHandler!({})
await Promise.resolve()

expect(provider.postStateToWebviewWithoutClineMessages).toHaveBeenCalledTimes(1)
const vscode = await import("vscode")
const channel = vi.mocked(vscode.window.createOutputChannel).mock.results.at(-1)?.value
expect(channel?.appendLine).toHaveBeenCalledWith(
"[CloudService] Failed to refresh state after settings update: state refresh failed",
)
})

test("activation continues when model cache refresh initialization fails", async () => {
const { initializeModelCacheRefresh } = await import("../api/providers/fetchers/modelCache")
vi.mocked(initializeModelCacheRefresh).mockRejectedValueOnce(new Error("cache startup failed"))

const { activate } = await import("../extension")
await expect(activate(mockContext)).resolves.toBeDefined()
await Promise.resolve()

const vscode = await import("vscode")
const channel = vi.mocked(vscode.window.createOutputChannel).mock.results.at(-1)?.value
expect(channel?.appendLine).toHaveBeenCalledWith(
"[ModelCache] Background refresh initialization failed: cache startup failed",
)
})

test("activation continues when CloudService initialization fails", async () => {
Expand Down
5 changes: 0 additions & 5 deletions src/eslint-suppressions.json
Original file line number Diff line number Diff line change
Expand Up @@ -1134,11 +1134,6 @@
"count": 5
}
},
"extension.ts": {
"@typescript-eslint/no-explicit-any": {
"count": 1
}
},
"extension/__tests__/api-delete-queued-message.spec.ts": {
"@typescript-eslint/no-explicit-any": {
"count": 1
Expand Down
1 change: 1 addition & 0 deletions src/eslint.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ export default [
"core/task/**/*.ts",
"core/tools/**/*.ts",
"core/webview/**/*.ts",
"extension.ts",
"integrations/**/*.ts",
"services/**/*.ts",
],
Expand Down
39 changes: 13 additions & 26 deletions src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ if (fs.existsSync(envPath)) {
}
}

import type { CloudUserInfo, AuthState } from "@roo-code/types"
import { isTelemetryOptedIn } from "@roo-code/types"
import { CloudService } from "@roo-code/cloud"
import { TelemetryService, PostHogTelemetryClient } from "@roo-code/telemetry"
Expand Down Expand Up @@ -64,9 +63,7 @@ let outputChannel: vscode.OutputChannel
let extensionContext: vscode.ExtensionContext
let cloudService: CloudService | undefined

let authStateChangedHandler: ((data: { state: AuthState; previousState: AuthState }) => Promise<void>) | undefined
let settingsUpdatedHandler: (() => void) | undefined
let userInfoHandler: ((data: { userInfo: CloudUserInfo }) => Promise<void>) | undefined

/**
* Check if we should auto-open the Zoo Code sidebar after switching to a worktree.
Expand Down Expand Up @@ -225,25 +222,19 @@ export async function activate(context: vscode.ExtensionContext) {
const provider = new ClineProvider(context, outputChannel, "sidebar", contextProxy, mdmService)

// Initialize Roo Code Cloud service.
const postStateListener = () => ClineProvider.getVisibleInstance()?.postStateToWebviewWithoutClineMessages()

authStateChangedHandler = async (_data: { state: AuthState; previousState: AuthState }) => {
postStateListener()
}

settingsUpdatedHandler = async () => {
postStateListener()
}

userInfoHandler = async ({ userInfo }: { userInfo: CloudUserInfo }) => {
postStateListener()
settingsUpdatedHandler = () => {
void ClineProvider.getVisibleInstance()
?.postStateToWebviewWithoutClineMessages()
.catch((error) => {
outputChannel.appendLine(
`[CloudService] Failed to refresh state after settings update: ${error instanceof Error ? error.message : String(error)}`,
)
})
}

try {
cloudService = await CloudService.createInstance(context, cloudLogger, {
"auth-state-changed": authStateChangedHandler,
"settings-updated": settingsUpdatedHandler,
"user-info": userInfoHandler,
})

// Add to subscriptions for proper cleanup on deactivate.
Expand Down Expand Up @@ -380,7 +371,11 @@ export async function activate(context: vscode.ExtensionContext) {
}

// Initialize background model cache refresh
initializeModelCacheRefresh()
void initializeModelCacheRefresh().catch((error) => {
outputChannel.appendLine(
`[ModelCache] Background refresh initialization failed: ${error instanceof Error ? error.message : String(error)}`,
)
})

return new API(outputChannel, provider, socketPath, enableLogging)
}
Expand All @@ -391,18 +386,10 @@ export async function deactivate() {

if (cloudService && CloudService.hasInstance()) {
try {
if (authStateChangedHandler) {
CloudService.instance.off("auth-state-changed", authStateChangedHandler)
}

if (settingsUpdatedHandler) {
CloudService.instance.off("settings-updated", settingsUpdatedHandler)
}

if (userInfoHandler) {
CloudService.instance.off("user-info", userInfoHandler as any)
}

outputChannel.appendLine("CloudService event handlers cleaned up")
} catch (error) {
outputChannel.appendLine(
Expand Down
Loading