mirror of
https://github.com/NanmiCoder/cc-haha
synced 2026-07-15 12:53:31 +08:00
Implement IM adapters allowing users to chat with Claude Code from Telegram and Feishu/Lark. Includes persistent session management (chatId→sessionId mapping), project selection via /projects command, and a web UI settings page for configuring bot tokens, allowed users, and default project directory. Key changes: - adapters/: Telegram and Feishu adapter scripts with shared common modules (WsBridge, MessageBuffer, SessionStore, HttpClient, config, formatting) - Backend: adapterService + REST API (GET/PUT /api/adapters) with secret masking - Frontend: AdapterSettings page in Settings tab with i18n support - DirectoryPicker: use React Portal for dropdown to fix overflow clipping Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
67 lines
2.2 KiB
TypeScript
67 lines
2.2 KiB
TypeScript
import { describe, it, expect, beforeEach, afterEach, mock } from 'bun:test'
|
|
import { AdapterHttpClient } from '../http-client.js'
|
|
|
|
describe('AdapterHttpClient', () => {
|
|
let client: AdapterHttpClient
|
|
const originalFetch = globalThis.fetch
|
|
|
|
beforeEach(() => {
|
|
client = new AdapterHttpClient('ws://127.0.0.1:3456')
|
|
})
|
|
|
|
afterEach(() => {
|
|
globalThis.fetch = originalFetch
|
|
})
|
|
|
|
it('derives HTTP URL from WS URL', () => {
|
|
expect(client.httpBaseUrl).toBe('http://127.0.0.1:3456')
|
|
|
|
const secure = new AdapterHttpClient('wss://example.com:443')
|
|
expect(secure.httpBaseUrl).toBe('https://example.com:443')
|
|
})
|
|
|
|
it('createSession calls POST /api/sessions', async () => {
|
|
const mockSessionId = 'a1b2c3d4-e5f6-7890-abcd-ef1234567890'
|
|
globalThis.fetch = mock(() =>
|
|
Promise.resolve(new Response(JSON.stringify({ sessionId: mockSessionId }), {
|
|
status: 201,
|
|
headers: { 'Content-Type': 'application/json' },
|
|
}))
|
|
) as any
|
|
|
|
const sessionId = await client.createSession('/path/to/project')
|
|
expect(sessionId).toBe(mockSessionId)
|
|
|
|
const call = (globalThis.fetch as any).mock.calls[0]
|
|
expect(call[0]).toBe('http://127.0.0.1:3456/api/sessions')
|
|
const body = JSON.parse(call[1].body)
|
|
expect(body.workDir).toBe('/path/to/project')
|
|
})
|
|
|
|
it('listRecentProjects calls GET /api/sessions/recent-projects', async () => {
|
|
const mockProjects = [
|
|
{ projectName: 'my-app', realPath: '/home/user/my-app', sessionCount: 3 },
|
|
]
|
|
globalThis.fetch = mock(() =>
|
|
Promise.resolve(new Response(JSON.stringify({ projects: mockProjects }), {
|
|
headers: { 'Content-Type': 'application/json' },
|
|
}))
|
|
) as any
|
|
|
|
const projects = await client.listRecentProjects()
|
|
expect(projects).toHaveLength(1)
|
|
expect(projects[0].projectName).toBe('my-app')
|
|
})
|
|
|
|
it('createSession throws on server error', async () => {
|
|
globalThis.fetch = mock(() =>
|
|
Promise.resolve(new Response(JSON.stringify({ error: 'BAD_REQUEST', message: 'workDir required' }), {
|
|
status: 400,
|
|
headers: { 'Content-Type': 'application/json' },
|
|
}))
|
|
) as any
|
|
|
|
expect(client.createSession('')).rejects.toThrow()
|
|
})
|
|
})
|