Skip to content
Merged
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
2 changes: 1 addition & 1 deletion src/__mocks__/vscode.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ export const workspace = {
getWorkspaceFolder: () => null,
onDidChangeWorkspaceFolders: () => mockDisposable,
getConfiguration: () => ({
get: () => null,
get: (key, defaultValue) => defaultValue,
}),
createFileSystemWatcher: () => ({
onDidCreate: () => mockDisposable,
Expand Down
38 changes: 38 additions & 0 deletions src/__tests__/extension.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,44 @@ vi.mock("../i18n", () => ({
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
vi.mock("../api/providers/fetchers/modelCache", () => ({
flushModels: vi.fn(),
getModels: vi.fn().mockResolvedValue([]),
initializeModelCacheRefresh: vi.fn(),
}))

describe("extension.ts", () => {
let mockContext: vscode.ExtensionContext
let authStateChangedHandler:
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
// npx vitest run api/providers/__tests__/base-openai-compatible-provider-timeout.spec.ts

import type { ModelInfo } from "@roo-code/types"

import { BaseOpenAiCompatibleProvider } from "../base-openai-compatible-provider"

// Mock the timeout config utility
vitest.mock("../utils/timeout-config", () => ({
getApiRequestTimeout: vitest.fn(),
}))

import { getApiRequestTimeout } from "../utils/timeout-config"

// Mock OpenAI and capture constructor calls
const mockOpenAIConstructor = vitest.fn()

vitest.mock("openai", () => {
return {
__esModule: true,
default: vitest.fn().mockImplementation((config) => {
mockOpenAIConstructor(config)
return {
chat: {
completions: {
create: vitest.fn(),
},
},
}
}),
}
})

// Create a concrete test implementation of the abstract base class
class TestOpenAiCompatibleProvider extends BaseOpenAiCompatibleProvider<"test-model"> {
constructor(apiKey: string) {
const testModels: Record<"test-model", ModelInfo> = {
"test-model": {
maxTokens: 4096,
contextWindow: 128000,
supportsImages: false,
supportsPromptCache: false,
inputPrice: 0.5,
outputPrice: 1.5,
},
}

super({
providerName: "TestProvider",
baseURL: "https://test.example.com/v1",
defaultProviderModelId: "test-model",
providerModels: testModels,
apiKey,
})
}
}

describe("BaseOpenAiCompatibleProvider Timeout Configuration", () => {
beforeEach(() => {
vitest.clearAllMocks()
})

it("should call getApiRequestTimeout when creating the provider", () => {
;(getApiRequestTimeout as any).mockReturnValue(600000)

new TestOpenAiCompatibleProvider("test-api-key")

expect(getApiRequestTimeout).toHaveBeenCalled()
})

it("should pass the default timeout to the OpenAI client constructor", () => {
;(getApiRequestTimeout as any).mockReturnValue(600000) // 600 seconds in ms

new TestOpenAiCompatibleProvider("test-api-key")

expect(mockOpenAIConstructor).toHaveBeenCalledWith(
expect.objectContaining({
baseURL: "https://test.example.com/v1",
apiKey: "test-api-key",
timeout: 600000,
}),
)
})

it("should use custom timeout value from getApiRequestTimeout", () => {
;(getApiRequestTimeout as any).mockReturnValue(1800000) // 30 minutes in ms

new TestOpenAiCompatibleProvider("test-api-key")

expect(mockOpenAIConstructor).toHaveBeenCalledWith(
expect.objectContaining({
timeout: 1800000,
}),
)
})

it("should handle zero timeout (no timeout)", () => {
;(getApiRequestTimeout as any).mockReturnValue(0)

new TestOpenAiCompatibleProvider("test-api-key")

expect(mockOpenAIConstructor).toHaveBeenCalledWith(
expect.objectContaining({
timeout: 0,
}),
)
})

it("should pass DEFAULT_HEADERS to the OpenAI client constructor", () => {
;(getApiRequestTimeout as any).mockReturnValue(600000)

new TestOpenAiCompatibleProvider("test-api-key")

expect(mockOpenAIConstructor).toHaveBeenCalledWith(
expect.objectContaining({
defaultHeaders: expect.any(Object),
}),
)
})
})
3 changes: 0 additions & 3 deletions src/api/providers/__tests__/groq.spec.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
// npx vitest run src/api/providers/__tests__/groq.spec.ts

// Mock vscode first to avoid import errors
vitest.mock("vscode", () => ({}))

import OpenAI from "openai"
import { Anthropic } from "@anthropic-ai/sdk"

Expand Down
3 changes: 0 additions & 3 deletions src/api/providers/__tests__/sambanova.spec.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
// npx vitest run src/api/providers/__tests__/sambanova.spec.ts

// Mock vscode first to avoid import errors
vitest.mock("vscode", () => ({}))

import OpenAI from "openai"
import { Anthropic } from "@anthropic-ai/sdk"

Expand Down
3 changes: 0 additions & 3 deletions src/api/providers/__tests__/zai.spec.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
// npx vitest run src/api/providers/__tests__/zai.spec.ts

// Mock vscode first to avoid import errors
vitest.mock("vscode", () => ({}))

import OpenAI from "openai"
import { Anthropic } from "@anthropic-ai/sdk"

Expand Down
2 changes: 2 additions & 0 deletions src/api/providers/base-openai-compatible-provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { DEFAULT_HEADERS } from "./constants"
import { BaseProvider } from "./base-provider"
import { handleOpenAIError } from "./utils/openai-error-handler"
import { calculateApiCostOpenAI } from "../../shared/cost"
import { getApiRequestTimeout } from "./utils/timeout-config"

type BaseOpenAiCompatibleProviderOptions<ModelName extends string> = ApiHandlerOptions & {
providerName: string
Expand Down Expand Up @@ -62,6 +63,7 @@ export abstract class BaseOpenAiCompatibleProvider<ModelName extends string>
baseURL,
apiKey: this.options.apiKey,
defaultHeaders: DEFAULT_HEADERS,
timeout: getApiRequestTimeout(),
})
}

Expand Down
95 changes: 87 additions & 8 deletions src/core/task/__tests__/grounding-sources.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
import { describe, it, expect, vi, beforeEach, beforeAll } from "vitest"
import { describe, it, expect, vi, beforeEach } from "vitest"
import type { ClineProvider } from "../../webview/ClineProvider"
import type { ProviderSettings } from "@roo-code/types"

// All vi.mock() calls are hoisted to the top of the file by Vitest
// and are applied before any imports are resolved

// Mock vscode module before importing Task
vi.mock("vscode", () => ({
workspace: {
Expand Down Expand Up @@ -72,16 +75,92 @@ vi.mock("@roo-code/telemetry", () => ({
},
}))

// Mock @roo-code/cloud to prevent socket.io-client initialization issues
vi.mock("@roo-code/cloud", () => ({
CloudService: {
isEnabled: () => false,
},
BridgeOrchestrator: {
subscribeToTask: vi.fn(),
},
}))

// Mock delay to prevent actual delays
vi.mock("delay", () => ({
__esModule: true,
default: vi.fn().mockResolvedValue(undefined),
}))

// Mock p-wait-for to prevent hanging on async conditions
vi.mock("p-wait-for", () => ({
default: vi.fn().mockResolvedValue(undefined),
}))

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

// Mock fs/promises
vi.mock("fs/promises", () => ({
mkdir: vi.fn().mockResolvedValue(undefined),
writeFile: vi.fn().mockResolvedValue(undefined),
readFile: vi.fn().mockResolvedValue("[]"),
unlink: vi.fn().mockResolvedValue(undefined),
rmdir: vi.fn().mockResolvedValue(undefined),
}))

// Mock mentions
vi.mock("../../mentions", () => ({
parseMentions: vi.fn().mockImplementation((text) => Promise.resolve(text)),
openMention: vi.fn(),
getLatestTerminalOutput: vi.fn(),
}))

// Mock extract-text
vi.mock("../../../integrations/misc/extract-text", () => ({
extractTextFromFile: vi.fn().mockResolvedValue("Mock file content"),
}))

// Mock getEnvironmentDetails
vi.mock("../../environment/getEnvironmentDetails", () => ({
getEnvironmentDetails: vi.fn().mockResolvedValue(""),
}))

// Mock RooIgnoreController
vi.mock("../../ignore/RooIgnoreController")

// Mock condense
vi.mock("../../condense", () => ({
summarizeConversation: vi.fn().mockResolvedValue({
messages: [],
summary: "summary",
cost: 0,
newContextTokens: 1,
}),
}))

// Mock storage utilities
vi.mock("../../../utils/storage", () => ({
getTaskDirectoryPath: vi
.fn()
.mockImplementation((globalStoragePath, taskId) => Promise.resolve(`${globalStoragePath}/tasks/${taskId}`)),
getSettingsDirectoryPath: vi
.fn()
.mockImplementation((globalStoragePath) => Promise.resolve(`${globalStoragePath}/settings`)),
}))

// Mock fs utilities
vi.mock("../../../utils/fs", () => ({
fileExistsAtPath: vi.fn().mockReturnValue(false),
}))

// Import Task AFTER all vi.mock() calls - Vitest hoists mocks so this works
import { Task } from "../Task"

describe("Task grounding sources handling", () => {
let mockProvider: Partial<ClineProvider>
let mockApiConfiguration: ProviderSettings
let Task: any

beforeAll(async () => {
// Import Task after mocks are set up
const taskModule = await import("../Task")
Task = taskModule.Task
})

beforeEach(() => {
// Mock provider with necessary methods
Expand Down
Loading
Loading