mirror of
https://github.com/cline/cline.git
synced 2026-09-15 04:14:34 +08:00
feat(sdk): add fetchFeaturebaseToken method [ENG-1673] (#10005)
* feat(sdk): add fetchFeaturebaseToken method - Add fetchFeaturebaseToken() to ClineAccountService - Add FeaturebaseTokenResponse type and endpoint constant - Add E2E mock server handler and unit tests Ref: ENG-1673 * fix: address review feedback - narrow test glob and add JSDoc - Fix P2: scope mocharc glob to **/*.test.ts to avoid matching non-test files - Fix P2: add JSDoc comment to fetchFeaturebaseToken for consistency --------- Co-authored-by: John Choi <john.choi@cline.bot> Co-authored-by: John Choi <johnwschoi@users.noreply.github.com>
This commit is contained in:
co-authored by
John Choi
John Choi
parent
65e9727c65
commit
03211f1364
+2
-1
@@ -3,7 +3,8 @@
|
||||
"ts"
|
||||
],
|
||||
"spec": [
|
||||
"src/**/__tests__/*.ts"
|
||||
"src/**/__tests__/*.ts",
|
||||
"src/test/services/**/*.test.ts"
|
||||
],
|
||||
"require": [
|
||||
"ts-node/register",
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import type {
|
||||
BalanceResponse,
|
||||
FeaturebaseTokenResponse,
|
||||
OrganizationBalanceResponse,
|
||||
OrganizationUsageTransaction,
|
||||
PaymentTransaction,
|
||||
@@ -82,9 +83,8 @@ export class ClineAccountService {
|
||||
}
|
||||
if (response.statusText === "No Content") {
|
||||
return {} as T // Return empty object if no content
|
||||
} else {
|
||||
return response.data.data as T
|
||||
}
|
||||
return response.data.data as T
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -160,6 +160,20 @@ export class ClineAccountService {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches a short-lived Featurebase SSO JWT for the current user
|
||||
* @returns FeaturebaseTokenResponse or undefined if failed
|
||||
*/
|
||||
async fetchFeaturebaseToken(): Promise<FeaturebaseTokenResponse | undefined> {
|
||||
try {
|
||||
const data = await this.authenticatedRequest<FeaturebaseTokenResponse>(CLINE_API_ENDPOINT.FEATUREBASE_TOKEN)
|
||||
return data
|
||||
} catch (error) {
|
||||
Logger.error("Failed to fetch Featurebase token:", error)
|
||||
return undefined
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches the current user's organizations
|
||||
* @returns UserResponse["organizations"] or undefined if failed
|
||||
|
||||
@@ -21,6 +21,10 @@ export interface BalanceResponse {
|
||||
userId: string
|
||||
}
|
||||
|
||||
export interface FeaturebaseTokenResponse {
|
||||
featurebaseJwt: string
|
||||
}
|
||||
|
||||
export interface UsageTransaction {
|
||||
aiInferenceProviderName: string
|
||||
aiModelName: string
|
||||
|
||||
@@ -6,6 +6,7 @@ enum CLINE_API_AUTH_ENDPOINTS {
|
||||
enum CLINE_API_ENDPOINT_V1 {
|
||||
TOKEN_EXCHANGE = "/api/v1/auth/token",
|
||||
USER_INFO = "/api/v1/users/me",
|
||||
FEATUREBASE_TOKEN = "/api/v1/users/me/featurebase-token",
|
||||
ACTIVE_ACCOUNT = "/api/v1/users/active-account",
|
||||
REMOTE_CONFIG = "/api/v1/organizations/{id}/remote-config",
|
||||
API_KEYS = "/api/v1/organizations/{id}/api-keys",
|
||||
|
||||
@@ -7,6 +7,7 @@ export const E2E_REGISTERED_MOCK_ENDPOINTS = {
|
||||
"/organizations/{orgId}/api-keys",
|
||||
"/organizations/{orgId}/remote-config",
|
||||
"/users/me",
|
||||
"/users/me/featurebase-token",
|
||||
"/users/{userId}/balance",
|
||||
"/users/{userId}/usages",
|
||||
"/users/{userId}/payments",
|
||||
@@ -53,7 +54,7 @@ The user wants me to replace the name "john" with "cline" in the test.ts file. I
|
||||
export const name = "john"
|
||||
\`\`\`
|
||||
|
||||
I need to change "john" to "cline". This is a simple targeted edit, so I should use the replace_in_file tool rather than write_to_file since I\'m only changing one small part of the file.
|
||||
I need to change "john" to "cline". This is a simple targeted edit, so I should use the replace_in_file tool rather than write_to_file since I'm only changing one small part of the file.
|
||||
|
||||
I need to:
|
||||
1. Use replace_in_file to change "john" to "cline" in the test.ts file
|
||||
@@ -61,7 +62,7 @@ I need to:
|
||||
3. The REPLACE block should be: \`export const name = "cline"\`
|
||||
</thinking>
|
||||
|
||||
I\'ll replace "john" with "cline" in the test.ts file.
|
||||
I'll replace "john" with "cline" in the test.ts file.
|
||||
|
||||
<replace_in_file>
|
||||
<path>test.ts</path>
|
||||
|
||||
@@ -213,6 +213,16 @@ export class ClineApiServerMock {
|
||||
return sendApiResponse(currentUser)
|
||||
}
|
||||
|
||||
if (endpoint === "/users/me/featurebase-token" && method === "GET") {
|
||||
const currentUser = controller.currentUser
|
||||
if (!currentUser) {
|
||||
return sendApiError("Unauthorized", 401)
|
||||
}
|
||||
return sendApiResponse({
|
||||
featurebaseJwt: `mock-featurebase-jwt-${currentUser.id}`,
|
||||
})
|
||||
}
|
||||
|
||||
if (endpoint === "/users/{userId}/balance" && method === "GET") {
|
||||
const { userId } = params
|
||||
const balance: BalanceResponse = {
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
import * as assert from "assert"
|
||||
import { afterEach, beforeEach, describe, it } from "mocha"
|
||||
import sinon from "sinon"
|
||||
import { ClineAccountService } from "@/services/account/ClineAccountService"
|
||||
import { AuthService } from "@/services/auth/AuthService"
|
||||
|
||||
describe("ClineAccountService.fetchFeaturebaseToken", () => {
|
||||
let service: ClineAccountService
|
||||
let sandbox: sinon.SinonSandbox
|
||||
|
||||
beforeEach(() => {
|
||||
sandbox = sinon.createSandbox()
|
||||
sandbox.stub(AuthService, "getInstance").returns({} as AuthService)
|
||||
service = new ClineAccountService()
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
sandbox.restore()
|
||||
})
|
||||
|
||||
it("returns featurebaseJwt on a successful authenticated request", async () => {
|
||||
sandbox
|
||||
.stub(service as unknown as { authenticatedRequest: () => unknown }, "authenticatedRequest")
|
||||
.resolves({ featurebaseJwt: "test-jwt-token-123" })
|
||||
|
||||
const result = await service.fetchFeaturebaseToken()
|
||||
|
||||
assert.ok(result !== undefined, "result should not be undefined")
|
||||
assert.strictEqual(result?.featurebaseJwt, "test-jwt-token-123")
|
||||
})
|
||||
|
||||
it("returns undefined when the request throws a network error", async () => {
|
||||
sandbox
|
||||
.stub(service as unknown as { authenticatedRequest: () => unknown }, "authenticatedRequest")
|
||||
.rejects(new Error("Network error"))
|
||||
|
||||
const result = await service.fetchFeaturebaseToken()
|
||||
|
||||
assert.strictEqual(result, undefined)
|
||||
})
|
||||
|
||||
it("returns undefined when the request throws due to missing auth token", async () => {
|
||||
sandbox
|
||||
.stub(service as unknown as { authenticatedRequest: () => unknown }, "authenticatedRequest")
|
||||
.rejects(new Error("No Cline account auth token found"))
|
||||
|
||||
const result = await service.fetchFeaturebaseToken()
|
||||
|
||||
assert.strictEqual(result, undefined)
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user