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
11 changes: 9 additions & 2 deletions packages/cloud/src/CloudService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ import type {
import { TelemetryService } from "@roo-code/telemetry"

import { CloudServiceCallbacks } from "./types"
import { AuthService } from "./AuthService"
import type { AuthService } from "./auth"
import { WebAuthService, StaticTokenAuthService } from "./auth"
import { SettingsService } from "./SettingsService"
import { TelemetryClient } from "./TelemetryClient"
import { ShareService, TaskNotFoundError } from "./ShareService"
Expand Down Expand Up @@ -43,7 +44,13 @@ export class CloudService {
}

try {
this.authService = new AuthService(this.context, this.log)
const cloudToken = process.env.ROO_CODE_CLOUD_TOKEN
if (cloudToken && cloudToken.length > 0) {
this.authService = new StaticTokenAuthService(this.context, cloudToken, this.log)
} else {
this.authService = new WebAuthService(this.context, this.log)
}

await this.authService.initialize()

this.authService.on("attempting-session", this.authListener)
Expand Down
2 changes: 1 addition & 1 deletion packages/cloud/src/SettingsService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import {
} from "@roo-code/types"

import { getRooCodeApiUrl } from "./Config"
import { AuthService } from "./AuthService"
import type { AuthService } from "./auth"
import { RefreshTimer } from "./RefreshTimer"

const ORGANIZATION_SETTINGS_CACHE_KEY = "organization-settings"
Expand Down
2 changes: 1 addition & 1 deletion packages/cloud/src/ShareService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import * as vscode from "vscode"

import { shareResponseSchema } from "@roo-code/types"
import { getRooCodeApiUrl } from "./Config"
import type { AuthService } from "./AuthService"
import type { AuthService } from "./auth"
import type { SettingsService } from "./SettingsService"
import { getUserAgent } from "./utils"

Expand Down
2 changes: 1 addition & 1 deletion packages/cloud/src/TelemetryClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import {
import { BaseTelemetryClient } from "@roo-code/telemetry"

import { getRooCodeApiUrl } from "./Config"
import { AuthService } from "./AuthService"
import type { AuthService } from "./auth"
import { SettingsService } from "./SettingsService"

export class TelemetryClient extends BaseTelemetryClient {
Expand Down
8 changes: 4 additions & 4 deletions packages/cloud/src/__tests__/CloudService.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import * as vscode from "vscode"
import type { ClineMessage } from "@roo-code/types"

import { CloudService } from "../CloudService"
import { AuthService } from "../AuthService"
import { WebAuthService } from "../auth/WebAuthService"
import { SettingsService } from "../SettingsService"
import { ShareService, TaskNotFoundError } from "../ShareService"
import { TelemetryClient } from "../TelemetryClient"
Expand All @@ -27,7 +27,7 @@ vi.mock("vscode", () => ({

vi.mock("@roo-code/telemetry")

vi.mock("../AuthService")
vi.mock("../auth/WebAuthService")

vi.mock("../SettingsService")

Expand Down Expand Up @@ -149,7 +149,7 @@ describe("CloudService", () => {
},
}

vi.mocked(AuthService).mockImplementation(() => mockAuthService as unknown as AuthService)
vi.mocked(WebAuthService).mockImplementation(() => mockAuthService as unknown as WebAuthService)
vi.mocked(SettingsService).mockImplementation(() => mockSettingsService as unknown as SettingsService)
vi.mocked(ShareService).mockImplementation(() => mockShareService as unknown as ShareService)
vi.mocked(TelemetryClient).mockImplementation(() => mockTelemetryClient as unknown as TelemetryClient)
Expand All @@ -175,7 +175,7 @@ describe("CloudService", () => {
const cloudService = await CloudService.createInstance(mockContext, callbacks)

expect(cloudService).toBeInstanceOf(CloudService)
expect(AuthService).toHaveBeenCalledWith(mockContext, expect.any(Function))
expect(WebAuthService).toHaveBeenCalledWith(mockContext, expect.any(Function))
expect(SettingsService).toHaveBeenCalledWith(
mockContext,
mockAuthService,
Expand Down
2 changes: 1 addition & 1 deletion packages/cloud/src/__tests__/ShareService.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import type { MockedFunction } from "vitest"
import * as vscode from "vscode"

import { ShareService, TaskNotFoundError } from "../ShareService"
import type { AuthService } from "../AuthService"
import type { AuthService } from "../auth"
import type { SettingsService } from "../SettingsService"

// Mock fetch
Expand Down
174 changes: 174 additions & 0 deletions packages/cloud/src/__tests__/auth/StaticTokenAuthService.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
import { describe, it, expect, beforeEach, vi } from "vitest"
import * as vscode from "vscode"

import { StaticTokenAuthService } from "../../auth/StaticTokenAuthService"

// Mock vscode
vi.mock("vscode", () => ({
window: {
showInformationMessage: vi.fn(),
},
env: {
openExternal: vi.fn(),
uriScheme: "vscode",
},
Uri: {
parse: vi.fn(),
},
}))

describe("StaticTokenAuthService", () => {
let authService: StaticTokenAuthService
let mockContext: vscode.ExtensionContext
let mockLog: (...args: unknown[]) => void
const testToken = "test-static-token"

beforeEach(() => {
mockLog = vi.fn()

// Create a minimal mock that satisfies the constructor requirements
const mockContextPartial = {
extension: {
packageJSON: {
publisher: "TestPublisher",
name: "test-extension",
},
},
globalState: {
get: vi.fn(),
update: vi.fn(),
},
secrets: {
get: vi.fn(),
store: vi.fn(),
delete: vi.fn(),
onDidChange: vi.fn(),
},
subscriptions: [],
}

// Use type assertion for test mocking
mockContext = mockContextPartial as unknown as vscode.ExtensionContext

authService = new StaticTokenAuthService(mockContext, testToken, mockLog)
})

afterEach(() => {
vi.clearAllMocks()
})

describe("constructor", () => {
it("should create instance and log static token mode", () => {
expect(authService).toBeInstanceOf(StaticTokenAuthService)
expect(mockLog).toHaveBeenCalledWith("[auth] Using static token authentication mode")
})

it("should use console.log as default logger", () => {
const serviceWithoutLog = new StaticTokenAuthService(
mockContext as unknown as vscode.ExtensionContext,
testToken,
)
// Can't directly test console.log usage, but constructor should not throw
expect(serviceWithoutLog).toBeInstanceOf(StaticTokenAuthService)
})
})

describe("initialize", () => {
it("should start in active-session state", async () => {
await authService.initialize()
expect(authService.getState()).toBe("active-session")
})

it("should emit active-session event on initialize", async () => {
const spy = vi.fn()
authService.on("active-session", spy)

await authService.initialize()

expect(spy).toHaveBeenCalledWith({ previousState: "initializing" })
})

it("should log successful initialization", async () => {
await authService.initialize()
expect(mockLog).toHaveBeenCalledWith("[auth] Static token auth service initialized in active-session state")
})
})

describe("getSessionToken", () => {
it("should return the provided token", () => {
expect(authService.getSessionToken()).toBe(testToken)
})

it("should return different token when constructed with different token", () => {
const differentToken = "different-token"
const differentService = new StaticTokenAuthService(mockContext, differentToken, mockLog)
expect(differentService.getSessionToken()).toBe(differentToken)
})
})

describe("getUserInfo", () => {
it("should return empty object", () => {
expect(authService.getUserInfo()).toEqual({})
})
})

describe("getStoredOrganizationId", () => {
it("should return null", () => {
expect(authService.getStoredOrganizationId()).toBeNull()
})
})

describe("authentication state methods", () => {
it("should always return true for isAuthenticated", () => {
expect(authService.isAuthenticated()).toBe(true)
})

it("should always return true for hasActiveSession", () => {
expect(authService.hasActiveSession()).toBe(true)
})

it("should always return true for hasOrIsAcquiringActiveSession", () => {
expect(authService.hasOrIsAcquiringActiveSession()).toBe(true)
})

it("should return active-session for getState", () => {
expect(authService.getState()).toBe("active-session")
})
})

describe("disabled authentication methods", () => {
const expectedErrorMessage = "Authentication methods are disabled in StaticTokenAuthService"

it("should throw error for login", async () => {
await expect(authService.login()).rejects.toThrow(expectedErrorMessage)
})

it("should throw error for logout", async () => {
await expect(authService.logout()).rejects.toThrow(expectedErrorMessage)
})

it("should throw error for handleCallback", async () => {
await expect(authService.handleCallback("code", "state")).rejects.toThrow(expectedErrorMessage)
})

it("should throw error for handleCallback with organization", async () => {
await expect(authService.handleCallback("code", "state", "org_123")).rejects.toThrow(expectedErrorMessage)
})
})

describe("event emission", () => {
it("should be able to register and emit events", async () => {
const activeSessionSpy = vi.fn()
const userInfoSpy = vi.fn()

authService.on("active-session", activeSessionSpy)
authService.on("user-info", userInfoSpy)

await authService.initialize()

expect(activeSessionSpy).toHaveBeenCalledWith({ previousState: "initializing" })
// user-info event is not emitted in static token mode
expect(userInfoSpy).not.toHaveBeenCalled()
})
})
})
Loading
Loading