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
15 changes: 13 additions & 2 deletions src/server/infra/transport/handlers/provider-handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,17 @@ import {
ProviderCallbackTimeoutError,
} from '../../provider-oauth/index.js'

const BYTEROVER_AUTH_REQUIRED_MESSAGE = [
'ByteRover Provider requires a ByteRover account.',
'',
' • Interactive shell: brv login',
' • Headless / SSH / CI: create an account at https://app.byterover.dev,',
' generate an API key at https://app.byterover.dev/settings/keys, then:',
' brv login --api-key <key>',
'',
'Once signed in, retry: brv providers connect byterover',
].join('\n')

async function defaultValidateOpenAICompatibleEndpoint(params: {
apiKey: string
baseUrl: string
Expand Down Expand Up @@ -257,7 +268,7 @@ export class ProviderHandler {
const {apiKey, baseUrl, providerId} = data

if (providerId === 'byterover' && !this.isByteRoverAuthSatisfied()) {
return {error: 'ByteRover Provider requires authentication. Run /login or brv login to sign in', success: false}
return {error: BYTEROVER_AUTH_REQUIRED_MESSAGE, success: false}
}

// Verify openai-compatible endpoint is reachable before persisting anything —
Expand Down Expand Up @@ -388,7 +399,7 @@ export class ProviderHandler {
ProviderEvents.SET_ACTIVE,
async (data) => {
if (data.providerId === 'byterover' && !this.isByteRoverAuthSatisfied()) {
return {error: 'ByteRover Provider requires authentication. Run /login or brv login to sign in', success: false}
return {error: BYTEROVER_AUTH_REQUIRED_MESSAGE, success: false}
}

await this.providerConfigStore.setActiveProvider(data.providerId)
Expand Down
16 changes: 9 additions & 7 deletions test/commands/providers/connect.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {expect} from 'chai'
import sinon, {restore, stub} from 'sinon'

import ProviderConnect from '../../../src/oclif/commands/providers/connect.js'
import {STUB_BYTEROVER_AUTH_ERROR} from '../../helpers/provider-fixtures.js'

// ==================== TestableProviderConnectCommand ====================

Expand Down Expand Up @@ -361,14 +362,14 @@ describe('Provider Connect Command', () => {
providers: [{id: 'byterover', isConnected: false, name: 'ByteRover', requiresApiKey: false}],
})
requestStub.onSecondCall().resolves({
error: 'ByteRover Provider requires authentication. Run /login or brv login to sign in',
error: STUB_BYTEROVER_AUTH_ERROR,
success: false,
})

await createCommand('byterover').run()

expect(loggedMessages.some((m) => m.includes('authentication'))).to.be.true
expect(loggedMessages.some((m) => m.includes('login'))).to.be.true
expect(loggedMessages.some((m) => m.includes('ByteRover account'))).to.be.true
expect(loggedMessages.some((m) => m.includes('brv login --api-key'))).to.be.true
})

it('should show auth error when server resolves SET_ACTIVE with success:false', async () => {
Expand All @@ -377,13 +378,14 @@ describe('Provider Connect Command', () => {
providers: [{id: 'byterover', isConnected: true, name: 'ByteRover', requiresApiKey: false}],
})
requestStub.onSecondCall().resolves({
error: 'ByteRover Provider requires authentication. Run /login or brv login to sign in',
error: STUB_BYTEROVER_AUTH_ERROR,
success: false,
})

await createCommand('byterover').run()

expect(loggedMessages.some((m) => m.includes('authentication'))).to.be.true
expect(loggedMessages.some((m) => m.includes('ByteRover account'))).to.be.true
expect(loggedMessages.some((m) => m.includes('brv login --api-key'))).to.be.true
})

it('should show fallback error when CONNECT resolves with success:false and no error message', async () => {
Expand Down Expand Up @@ -434,15 +436,15 @@ describe('Provider Connect Command', () => {
providers: [{id: 'byterover', isConnected: false, name: 'ByteRover', requiresApiKey: false}],
})
requestStub.onSecondCall().resolves({
error: 'ByteRover Provider requires authentication. Run /login or brv login to sign in',
error: STUB_BYTEROVER_AUTH_ERROR,
success: false,
})

await createJsonCommand('byterover').run()

const json = parseJsonOutput()
expect(json.success).to.be.false
expect(json.data).to.have.property('error')
expect(json.data.error).to.equal(STUB_BYTEROVER_AUTH_ERROR)
})
})

Expand Down
11 changes: 6 additions & 5 deletions test/commands/providers/switch.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {expect} from 'chai'
import sinon, {restore, stub} from 'sinon'

import ProviderSwitch from '../../../src/oclif/commands/providers/switch.js'
import {STUB_BYTEROVER_AUTH_ERROR} from '../../helpers/provider-fixtures.js'

// ==================== TestableProviderSwitchCommand ====================

Expand Down Expand Up @@ -150,14 +151,14 @@ describe('Provider Switch Command', () => {
providers: [{id: 'byterover', isConnected: true, name: 'ByteRover'}],
})
requestStub.onSecondCall().resolves({
error: 'ByteRover Provider requires authentication. Run /login or brv login to sign in',
error: STUB_BYTEROVER_AUTH_ERROR,
success: false,
})

await createCommand('byterover').run()

expect(loggedMessages.some((m) => m.includes('authentication'))).to.be.true
expect(loggedMessages.some((m) => m.includes('login'))).to.be.true
expect(loggedMessages.some((m) => m.includes('ByteRover account'))).to.be.true
expect(loggedMessages.some((m) => m.includes('brv login --api-key'))).to.be.true
})
})

Expand Down Expand Up @@ -196,15 +197,15 @@ describe('Provider Switch Command', () => {
providers: [{id: 'byterover', isConnected: true, name: 'ByteRover'}],
})
requestStub.onSecondCall().resolves({
error: 'ByteRover Provider requires authentication. Run /login or brv login to sign in',
error: STUB_BYTEROVER_AUTH_ERROR,
success: false,
})

await createJsonCommand('byterover').run()

const json = parseJsonOutput()
expect(json.success).to.be.false
expect(json.data).to.have.property('error')
expect(json.data.error).to.equal(STUB_BYTEROVER_AUTH_ERROR)
})
})

Expand Down
12 changes: 12 additions & 0 deletions test/helpers/provider-fixtures.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
/**
* Test fixture providing a representative ByteRover auth-gate error for
* mocking daemon responses in CLI command tests. Keyword anchors
* ('ByteRover account', 'brv login --api-key') are validated by the
* consuming tests, so the real `BYTEROVER_AUTH_REQUIRED_MESSAGE` in
* `provider-handler.ts` must always contain those substrings.
*/
export const STUB_BYTEROVER_AUTH_ERROR = [
'ByteRover Provider requires a ByteRover account.',
' • Interactive shell: brv login',
' • Headless: brv login --api-key <key>',
].join('\n')
6 changes: 4 additions & 2 deletions test/unit/infra/transport/handlers/provider-handler.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1037,7 +1037,8 @@ describe('ProviderHandler', () => {
const result = await handler!({providerId: 'byterover'}, 'client-1')

expect(result.success).to.be.false
expect(result.error).to.include('authentication')
expect(result.error).to.include('ByteRover account')
expect(result.error).to.include('brv login --api-key')
})

it('should succeed when connecting byterover with valid auth', async () => {
Expand Down Expand Up @@ -1069,7 +1070,8 @@ describe('ProviderHandler', () => {
const result = await handler!({providerId: 'byterover'}, 'client-1')

expect(result.success).to.be.false
expect(result.error).to.include('authentication')
expect(result.error).to.include('ByteRover account')
expect(result.error).to.include('brv login --api-key')
})

it('should succeed when setting byterover active with valid auth', async () => {
Expand Down
Loading