From 2263977cefb290e5de9eeff48c4ccd705818c137 Mon Sep 17 00:00:00 2001 From: Jiahao Luo Date: Sun, 1 Mar 2026 23:11:54 +0800 Subject: [PATCH 1/8] fix: require model for claude-console account test endpoint --- src/routes/admin/claudeConsoleAccounts.js | 7 ++- tests/claudeConsoleAccounts.test.js | 57 +++++++++++++++++++++++ 2 files changed, 63 insertions(+), 1 deletion(-) create mode 100644 tests/claudeConsoleAccounts.test.js diff --git a/src/routes/admin/claudeConsoleAccounts.js b/src/routes/admin/claudeConsoleAccounts.js index 2f13f030..b907d3e6 100644 --- a/src/routes/admin/claudeConsoleAccounts.js +++ b/src/routes/admin/claudeConsoleAccounts.js @@ -485,10 +485,15 @@ router.post('/claude-console-accounts/reset-all-usage', authenticateAdmin, async // 测试Claude Console账户连通性(流式响应)- 复用 claudeConsoleRelayService router.post('/claude-console-accounts/:accountId/test', authenticateAdmin, async (req, res) => { const { accountId } = req.params + const model = typeof req.body?.model === 'string' ? req.body.model.trim() : '' + + if (!model) { + return res.status(400).json({ error: 'model is required' }) + } try { // 直接调用服务层的测试方法 - await claudeConsoleRelayService.testAccountConnection(accountId, res) + await claudeConsoleRelayService.testAccountConnection(accountId, res, model) } catch (error) { logger.error(`❌ Failed to test Claude Console account:`, error) // 错误已在服务层处理,这里仅做日志记录 diff --git a/tests/claudeConsoleAccounts.test.js b/tests/claudeConsoleAccounts.test.js new file mode 100644 index 00000000..7180696e --- /dev/null +++ b/tests/claudeConsoleAccounts.test.js @@ -0,0 +1,57 @@ +const express = require('express') +const request = require('supertest') + +jest.mock('../src/middleware/auth', () => ({ + authenticateAdmin: (req, res, next) => next() +})) + +jest.mock('../src/services/relay/claudeConsoleRelayService', () => ({ + testAccountConnection: jest.fn(async (accountId, res) => { + return res.status(200).json({ success: true, accountId }) + }) +})) + +jest.mock('../src/services/account/claudeConsoleAccountService', () => ({})) +jest.mock('../src/services/accountGroupService', () => ({})) +jest.mock('../src/services/apiKeyService', () => ({})) +jest.mock('../src/models/redis', () => ({})) +jest.mock('../src/utils/logger', () => ({ + debug: jest.fn(), + info: jest.fn(), + warn: jest.fn(), + error: jest.fn(), + success: jest.fn() +})) +jest.mock('../src/utils/webhookNotifier', () => ({})) +jest.mock('../src/routes/admin/utils', () => ({ + formatAccountExpiry: jest.fn((account) => account), + mapExpiryField: jest.fn((updates) => updates) +})) + +const claudeConsoleRelayService = require('../src/services/relay/claudeConsoleRelayService') +const claudeConsoleAccountsRouter = require('../src/routes/admin/claudeConsoleAccounts') + +describe('POST /admin/claude-console-accounts/:accountId/test', () => { + const buildApp = () => { + const app = express() + app.use(express.json()) + app.use('/admin', claudeConsoleAccountsRouter) + return app + } + + beforeEach(() => { + jest.clearAllMocks() + }) + + it('returns 400 when model is missing', async () => { + const app = buildApp() + + const response = await request(app) + .post('/admin/claude-console-accounts/account-1/test') + .send({}) + + expect(response.status).toBe(400) + expect(response.body).toEqual({ error: 'model is required' }) + expect(claudeConsoleRelayService.testAccountConnection).not.toHaveBeenCalled() + }) +}) From 75515f92e66c4a7c4bfedae375b4ff52e06d411f Mon Sep 17 00:00:00 2001 From: Jiahao Luo Date: Sun, 1 Mar 2026 23:16:02 +0800 Subject: [PATCH 2/8] test: cover claude-console test route model pass-through --- tests/claudeConsoleAccounts.test.js | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/tests/claudeConsoleAccounts.test.js b/tests/claudeConsoleAccounts.test.js index 7180696e..43285280 100644 --- a/tests/claudeConsoleAccounts.test.js +++ b/tests/claudeConsoleAccounts.test.js @@ -54,4 +54,20 @@ describe('POST /admin/claude-console-accounts/:accountId/test', () => { expect(response.body).toEqual({ error: 'model is required' }) expect(claudeConsoleRelayService.testAccountConnection).not.toHaveBeenCalled() }) + + it('passes model through to relay service when provided', async () => { + const app = buildApp() + + const response = await request(app) + .post('/admin/claude-console-accounts/account-1/test') + .send({ model: 'claude-sonnet-4-6' }) + + expect(response.status).toBe(200) + expect(claudeConsoleRelayService.testAccountConnection).toHaveBeenCalledTimes(1) + expect(claudeConsoleRelayService.testAccountConnection).toHaveBeenCalledWith( + 'account-1', + expect.any(Object), + 'claude-sonnet-4-6' + ) + }) }) From adf2dd61e0e4b565e4b3d277383d756c320d9b1c Mon Sep 17 00:00:00 2001 From: Jiahao Luo Date: Sun, 1 Mar 2026 23:20:24 +0800 Subject: [PATCH 3/8] fix: use selected model in claude-console connectivity test payload --- .../relay/claudeConsoleRelayService.js | 6 +- tests/claudeConsoleRelayService.test.js | 70 +++++++++++++++++++ 2 files changed, 74 insertions(+), 2 deletions(-) create mode 100644 tests/claudeConsoleRelayService.test.js diff --git a/src/services/relay/claudeConsoleRelayService.js b/src/services/relay/claudeConsoleRelayService.js index d7dcabc1..50c05275 100644 --- a/src/services/relay/claudeConsoleRelayService.js +++ b/src/services/relay/claudeConsoleRelayService.js @@ -1455,8 +1455,8 @@ class ClaudeConsoleRelayService { } // 🧪 测试账号连接(供Admin API使用) - async testAccountConnection(accountId, responseStream) { - const { sendStreamTestRequest } = require('../../utils/testPayloadHelper') + async testAccountConnection(accountId, responseStream, model) { + const { createClaudeTestPayload, sendStreamTestRequest } = require('../../utils/testPayloadHelper') try { const account = await claudeConsoleAccountService.getAccount(accountId) @@ -1470,11 +1470,13 @@ class ClaudeConsoleRelayService { const apiUrl = cleanUrl.endsWith('/v1/messages') ? cleanUrl : `${cleanUrl}/v1/messages?beta=true` + const payload = createClaudeTestPayload(model, { stream: true }) await sendStreamTestRequest({ apiUrl, authorization: `Bearer ${account.apiKey}`, responseStream, + payload, proxyAgent: claudeConsoleAccountService._createProxyAgent(account.proxy), extraHeaders: account.userAgent ? { 'User-Agent': account.userAgent } : {} }) diff --git a/tests/claudeConsoleRelayService.test.js b/tests/claudeConsoleRelayService.test.js new file mode 100644 index 00000000..3c5a88ca --- /dev/null +++ b/tests/claudeConsoleRelayService.test.js @@ -0,0 +1,70 @@ +jest.mock('../src/utils/logger', () => ({ + debug: jest.fn(), + info: jest.fn(), + warn: jest.fn(), + error: jest.fn() +})) + +jest.mock('../src/services/account/claudeConsoleAccountService', () => ({ + getAccount: jest.fn(), + _createProxyAgent: jest.fn() +})) + +jest.mock('/Users/mike/projects/claude-relay-service/config/config', () => ({}), { + virtual: true +}) +jest.mock('../src/models/redis', () => ({})) + +jest.mock('../src/utils/testPayloadHelper', () => ({ + createClaudeTestPayload: jest.fn(), + sendStreamTestRequest: jest.fn() +})) + +const claudeConsoleRelayService = require('../src/services/relay/claudeConsoleRelayService') +const claudeConsoleAccountService = require('../src/services/account/claudeConsoleAccountService') +const { + createClaudeTestPayload, + sendStreamTestRequest +} = require('../src/utils/testPayloadHelper') + +describe('claudeConsoleRelayService.testAccountConnection', () => { + beforeEach(() => { + jest.clearAllMocks() + }) + + it('passes selected model stream payload to sendStreamTestRequest', async () => { + claudeConsoleAccountService.getAccount.mockResolvedValue({ + name: 'Console A1', + apiUrl: 'https://console.example.com', + apiKey: 'test-key', + proxy: null, + userAgent: null + }) + claudeConsoleAccountService._createProxyAgent.mockReturnValue(undefined) + + const payload = { + model: 'claude-sonnet-4-6', + stream: true + } + createClaudeTestPayload.mockReturnValue(payload) + sendStreamTestRequest.mockResolvedValue(undefined) + + const res = {} + await claudeConsoleRelayService.testAccountConnection('a1', res, 'claude-sonnet-4-6') + + expect(createClaudeTestPayload).toHaveBeenCalledWith('claude-sonnet-4-6', { stream: true }) + expect(sendStreamTestRequest).toHaveBeenCalledWith( + expect.objectContaining({ + payload + }) + ) + expect(sendStreamTestRequest).toHaveBeenCalledWith( + expect.objectContaining({ + payload: expect.objectContaining({ + model: 'claude-sonnet-4-6', + stream: true + }) + }) + ) + }) +}) From f77a76ce1d4a88a407089a9ce8fef74f109a5b00 Mon Sep 17 00:00:00 2001 From: Jiahao Luo Date: Sun, 1 Mar 2026 23:24:03 +0800 Subject: [PATCH 4/8] test: make claude-console relay test portable --- tests/claudeConsoleRelayService.test.js | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/tests/claudeConsoleRelayService.test.js b/tests/claudeConsoleRelayService.test.js index 3c5a88ca..67dbfde4 100644 --- a/tests/claudeConsoleRelayService.test.js +++ b/tests/claudeConsoleRelayService.test.js @@ -10,7 +10,7 @@ jest.mock('../src/services/account/claudeConsoleAccountService', () => ({ _createProxyAgent: jest.fn() })) -jest.mock('/Users/mike/projects/claude-relay-service/config/config', () => ({}), { +jest.mock('../config/config', () => ({}), { virtual: true }) jest.mock('../src/models/redis', () => ({})) @@ -58,13 +58,5 @@ describe('claudeConsoleRelayService.testAccountConnection', () => { payload }) ) - expect(sendStreamTestRequest).toHaveBeenCalledWith( - expect.objectContaining({ - payload: expect.objectContaining({ - model: 'claude-sonnet-4-6', - stream: true - }) - }) - ) }) }) From b8c38adf70e0ee8283b73883245f7a29940c4cc6 Mon Sep 17 00:00:00 2001 From: Jiahao Luo Date: Sun, 1 Mar 2026 23:28:10 +0800 Subject: [PATCH 5/8] fix: align claude-console test auth with relay logic --- .../relay/claudeConsoleRelayService.js | 16 ++++++-- src/utils/testPayloadHelper.js | 2 +- tests/claudeConsoleRelayService.test.js | 38 ++++++++++++++++++- 3 files changed, 49 insertions(+), 7 deletions(-) diff --git a/src/services/relay/claudeConsoleRelayService.js b/src/services/relay/claudeConsoleRelayService.js index 50c05275..a8590c52 100644 --- a/src/services/relay/claudeConsoleRelayService.js +++ b/src/services/relay/claudeConsoleRelayService.js @@ -1472,14 +1472,22 @@ class ClaudeConsoleRelayService { : `${cleanUrl}/v1/messages?beta=true` const payload = createClaudeTestPayload(model, { stream: true }) - await sendStreamTestRequest({ + const extraHeaders = account.userAgent ? { 'User-Agent': account.userAgent } : {} + const requestOptions = { apiUrl, - authorization: `Bearer ${account.apiKey}`, responseStream, payload, proxyAgent: claudeConsoleAccountService._createProxyAgent(account.proxy), - extraHeaders: account.userAgent ? { 'User-Agent': account.userAgent } : {} - }) + extraHeaders + } + + if (account.apiKey && account.apiKey.startsWith('sk-ant-')) { + requestOptions.extraHeaders['x-api-key'] = account.apiKey + } else { + requestOptions.authorization = `Bearer ${account.apiKey}` + } + + await sendStreamTestRequest(requestOptions) } catch (error) { logger.error(`❌ Test account connection failed:`, error) if (!responseStream.headersSent) { diff --git a/src/utils/testPayloadHelper.js b/src/utils/testPayloadHelper.js index 4a2187b5..66df8305 100644 --- a/src/utils/testPayloadHelper.js +++ b/src/utils/testPayloadHelper.js @@ -146,7 +146,7 @@ async function sendStreamTestRequest(options) { 'Content-Type': 'application/json', 'anthropic-version': '2023-06-01', 'User-Agent': 'claude-cli/2.0.52 (external, cli)', - authorization, + ...(authorization ? { authorization } : {}), ...extraHeaders }, timeout, diff --git a/tests/claudeConsoleRelayService.test.js b/tests/claudeConsoleRelayService.test.js index 67dbfde4..a678a026 100644 --- a/tests/claudeConsoleRelayService.test.js +++ b/tests/claudeConsoleRelayService.test.js @@ -32,7 +32,7 @@ describe('claudeConsoleRelayService.testAccountConnection', () => { jest.clearAllMocks() }) - it('passes selected model stream payload to sendStreamTestRequest', async () => { + it('passes selected model stream payload and bearer auth for non sk-ant key', async () => { claudeConsoleAccountService.getAccount.mockResolvedValue({ name: 'Console A1', apiUrl: 'https://console.example.com', @@ -55,8 +55,42 @@ describe('claudeConsoleRelayService.testAccountConnection', () => { expect(createClaudeTestPayload).toHaveBeenCalledWith('claude-sonnet-4-6', { stream: true }) expect(sendStreamTestRequest).toHaveBeenCalledWith( expect.objectContaining({ - payload + payload, + authorization: 'Bearer test-key' }) ) }) + + it('passes selected model stream payload and x-api-key for sk-ant key', async () => { + claudeConsoleAccountService.getAccount.mockResolvedValue({ + name: 'Console A1', + apiUrl: 'https://console.example.com', + apiKey: 'sk-ant-test-key', + proxy: null, + userAgent: null + }) + claudeConsoleAccountService._createProxyAgent.mockReturnValue(undefined) + + const payload = { + model: 'claude-sonnet-4-6', + stream: true + } + createClaudeTestPayload.mockReturnValue(payload) + sendStreamTestRequest.mockResolvedValue(undefined) + + const res = {} + await claudeConsoleRelayService.testAccountConnection('a1', res, 'claude-sonnet-4-6') + + expect(createClaudeTestPayload).toHaveBeenCalledWith('claude-sonnet-4-6', { stream: true }) + const requestOptions = sendStreamTestRequest.mock.calls[0][0] + expect(requestOptions).toEqual( + expect.objectContaining({ + payload, + extraHeaders: expect.objectContaining({ + 'x-api-key': 'sk-ant-test-key' + }) + }) + ) + expect(requestOptions).not.toHaveProperty('authorization') + }) }) From 1813d6f6f6db2db0c3902e86cda6b6175f4f34a7 Mon Sep 17 00:00:00 2001 From: Jiahao Luo Date: Sun, 1 Mar 2026 23:31:04 +0800 Subject: [PATCH 6/8] feat: add claude sonnet 4.6 model option for tests --- config/models.js | 1 + tests/modelsConfig.test.js | 10 ++++++++++ 2 files changed, 11 insertions(+) create mode 100644 tests/modelsConfig.test.js diff --git a/config/models.js b/config/models.js index 16742bee..8391ff4f 100644 --- a/config/models.js +++ b/config/models.js @@ -5,6 +5,7 @@ const CLAUDE_MODELS = [ { value: 'claude-opus-4-6', label: 'Claude Opus 4.6' }, + { value: 'claude-sonnet-4-6', label: 'Claude Sonnet 4.6' }, { value: 'claude-opus-4-5-20251101', label: 'Claude Opus 4.5' }, { value: 'claude-sonnet-4-5-20250929', label: 'Claude Sonnet 4.5' }, { value: 'claude-sonnet-4-20250514', label: 'Claude Sonnet 4' }, diff --git a/tests/modelsConfig.test.js b/tests/modelsConfig.test.js new file mode 100644 index 00000000..0d3ce5ed --- /dev/null +++ b/tests/modelsConfig.test.js @@ -0,0 +1,10 @@ +const { CLAUDE_MODELS } = require('../config/models') + +describe('models config', () => { + it('places Claude Sonnet 4.6 as the second Claude model option', () => { + expect(CLAUDE_MODELS[1]).toEqual({ + value: 'claude-sonnet-4-6', + label: 'Claude Sonnet 4.6' + }) + }) +}) From b78cd35f8b0313938bfd5052c5c3c32ca61e2360 Mon Sep 17 00:00:00 2001 From: Jiahao Luo Date: Mon, 2 Mar 2026 15:34:50 +0800 Subject: [PATCH 7/8] style: format claude-console relay changes --- src/services/relay/claudeConsoleRelayService.js | 5 ++++- tests/claudeConsoleRelayService.test.js | 5 +---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/services/relay/claudeConsoleRelayService.js b/src/services/relay/claudeConsoleRelayService.js index a8590c52..2b8be5ce 100644 --- a/src/services/relay/claudeConsoleRelayService.js +++ b/src/services/relay/claudeConsoleRelayService.js @@ -1456,7 +1456,10 @@ class ClaudeConsoleRelayService { // 🧪 测试账号连接(供Admin API使用) async testAccountConnection(accountId, responseStream, model) { - const { createClaudeTestPayload, sendStreamTestRequest } = require('../../utils/testPayloadHelper') + const { + createClaudeTestPayload, + sendStreamTestRequest + } = require('../../utils/testPayloadHelper') try { const account = await claudeConsoleAccountService.getAccount(accountId) diff --git a/tests/claudeConsoleRelayService.test.js b/tests/claudeConsoleRelayService.test.js index a678a026..f81ad8a6 100644 --- a/tests/claudeConsoleRelayService.test.js +++ b/tests/claudeConsoleRelayService.test.js @@ -22,10 +22,7 @@ jest.mock('../src/utils/testPayloadHelper', () => ({ const claudeConsoleRelayService = require('../src/services/relay/claudeConsoleRelayService') const claudeConsoleAccountService = require('../src/services/account/claudeConsoleAccountService') -const { - createClaudeTestPayload, - sendStreamTestRequest -} = require('../src/utils/testPayloadHelper') +const { createClaudeTestPayload, sendStreamTestRequest } = require('../src/utils/testPayloadHelper') describe('claudeConsoleRelayService.testAccountConnection', () => { beforeEach(() => { From c31b4f45049d75e160186bfd88adfc7e36e1619f Mon Sep 17 00:00:00 2001 From: Jiahao Luo Date: Mon, 2 Mar 2026 16:10:34 +0800 Subject: [PATCH 8/8] test: fix eslint arrow-body-style in claude-console account tests --- tests/claudeConsoleAccounts.test.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/claudeConsoleAccounts.test.js b/tests/claudeConsoleAccounts.test.js index 43285280..8b9cb52b 100644 --- a/tests/claudeConsoleAccounts.test.js +++ b/tests/claudeConsoleAccounts.test.js @@ -6,9 +6,9 @@ jest.mock('../src/middleware/auth', () => ({ })) jest.mock('../src/services/relay/claudeConsoleRelayService', () => ({ - testAccountConnection: jest.fn(async (accountId, res) => { - return res.status(200).json({ success: true, accountId }) - }) + testAccountConnection: jest.fn(async (accountId, res) => + res.status(200).json({ success: true, accountId }) + ) })) jest.mock('../src/services/account/claudeConsoleAccountService', () => ({}))