claude-code-best/packages/acp-link/src/__tests__/server.test.ts
James Feng 39ba9a56fd feat: pull in Vite build system + complete packages/builtin-tools/
- Add Vite as alternative build (vite.config.ts, scripts/vite-plugin-*)
- Add dab04af7 RSS fix (distRoot, ripgrep path, post-build)
- Pull in packages/builtin-tools/ (+354 files)
- Pull in packages/agent-tools/, mcp-client/, acp-link/, weixin/
- Copy defines.ts from upstream for Vite compatibility
- Update package.json with Vite scripts and deps
2026-06-01 18:18:03 +08:00

307 lines
9.4 KiB
TypeScript

import { describe, test, expect, mock } from 'bun:test'
import {
__testing,
decodeClientWsMessage,
MAX_CLIENT_WS_PAYLOAD_BYTES,
resolveNewSessionPermissionMode,
type ServerConfig,
} from '../server.js'
import {
authTokensEqual,
decodeWebSocketAuthProtocol,
encodeWebSocketAuthProtocol,
extractWebSocketAuthToken,
} from '../ws-auth.js'
import { buildRcsWsUrl } from '../rcs-upstream.js'
function makeTestWs(sent: unknown[]) {
type TestWs = Parameters<typeof __testing.dispatchClientMessage>[0]
return {
readyState: 1,
send: mock((message: string) => {
sent.push(JSON.parse(message))
}),
close: mock(() => {}),
raw: null,
isInner: false,
url: '',
origin: '',
protocol: '',
} as unknown as TestWs
}
describe('Server HTTP endpoints', () => {
test('package.json has correct bin and main entries', async () => {
const pkg = await import('../../package.json', { with: { type: 'json' } })
expect(pkg.default.name).toBe('acp-link')
expect(pkg.default.main).toBe('./dist/server.js')
expect(pkg.default.bin).toBeDefined()
expect(pkg.default.bin['acp-link']).toBe('dist/cli/bin.js')
})
test('ServerConfig interface accepts all expected fields', () => {
const config: ServerConfig = {
port: 9315,
host: 'localhost',
command: 'echo',
args: [],
cwd: '/tmp',
debug: false,
token: 'test-token',
https: false,
}
expect(config.port).toBe(9315)
expect(config.token).toBe('test-token')
})
test('ServerConfig allows optional fields to be omitted', () => {
const config: ServerConfig = {
port: 9315,
host: 'localhost',
command: 'echo',
args: [],
cwd: '/tmp',
}
expect(config.debug).toBeUndefined()
expect(config.token).toBeUndefined()
expect(config.https).toBeUndefined()
})
})
describe('WebSocket message types', () => {
const clientMessageTypes = [
'connect',
'disconnect',
'new_session',
'prompt',
'permission_response',
'cancel',
'set_session_model',
'list_sessions',
'load_session',
'resume_session',
'ping',
]
test('all client message types are recognized', () => {
expect(clientMessageTypes.length).toBe(11)
expect(clientMessageTypes).toContain('ping')
expect(clientMessageTypes).toContain('connect')
expect(clientMessageTypes).toContain('cancel')
})
test('decodes supported client message payloads', () => {
expect(decodeClientWsMessage('{"type":"ping"}')).toEqual({ type: 'ping' })
expect(
decodeClientWsMessage(
Buffer.from('{"type":"prompt","payload":{"content":[]}}'),
),
).toEqual({ type: 'prompt', payload: { content: [] } })
expect(
decodeClientWsMessage(
new TextEncoder().encode('{"type":"cancel"}').buffer,
),
).toEqual({ type: 'cancel' })
expect(
decodeClientWsMessage([
Buffer.from('{"type":"list_sessions","payload":{"cursor":"'),
Buffer.from('next"}}'),
]),
).toEqual({
type: 'list_sessions',
payload: { cwd: undefined, cursor: 'next' },
})
})
test('rejects malformed typed client payloads', () => {
expect(() => decodeClientWsMessage('{"type":"prompt"}')).toThrow(
'Invalid prompt payload',
)
expect(() =>
decodeClientWsMessage('{"type":"load_session","payload":{}}'),
).toThrow('Invalid load_session payload')
expect(() => decodeClientWsMessage('{"type":"unknown"}')).toThrow(
'Unknown message type',
)
expect(() =>
decodeClientWsMessage(
'{"type":"new_session","payload":{"permissionMode":123}}',
),
).toThrow('Invalid new_session.permissionMode')
expect(() =>
decodeClientWsMessage(
'{"type":"new_session","payload":{"permissionMode":{}}}',
),
).toThrow('Invalid new_session.permissionMode')
expect(() =>
decodeClientWsMessage(
'{"type":"new_session","payload":{"permissionMode":null}}',
),
).toThrow('Invalid new_session.permissionMode')
})
test('rejects oversized client message payloads before decoding', () => {
const payload = 'x'.repeat(MAX_CLIENT_WS_PAYLOAD_BYTES + 1)
expect(() => decodeClientWsMessage(payload)).toThrow(
'WebSocket message too large',
)
})
})
describe('WebSocket auth protocol', () => {
test('round-trips tokens through a WebSocket subprotocol token', () => {
const protocol = encodeWebSocketAuthProtocol('secret/token+with=symbols')
expect(protocol).toStartWith('rcs.auth.')
expect(protocol).not.toContain('secret/token')
expect(decodeWebSocketAuthProtocol(protocol)).toBe(
'secret/token+with=symbols',
)
})
test('ignores query-token style inputs', () => {
expect(decodeWebSocketAuthProtocol(undefined)).toBeUndefined()
expect(decodeWebSocketAuthProtocol('token=secret')).toBeUndefined()
expect(decodeWebSocketAuthProtocol('other, rcs.auth.')).toBeUndefined()
})
test('prefers Authorization headers and supports protocol auth', () => {
expect(
extractWebSocketAuthToken({
authorization: 'Bearer header-token',
protocol: encodeWebSocketAuthProtocol('protocol-token'),
}),
).toBe('header-token')
expect(
extractWebSocketAuthToken({
protocol: encodeWebSocketAuthProtocol('protocol-token'),
}),
).toBe('protocol-token')
})
test('compares auth tokens through the shared constant-time path', () => {
expect(authTokensEqual('secret-token', 'secret-token')).toBe(true)
expect(authTokensEqual('secret-token', 'wrong-token')).toBe(false)
expect(authTokensEqual(undefined, 'secret-token')).toBe(false)
})
})
describe('RCS upstream URL normalization', () => {
test('removes legacy token query params from WebSocket URLs', () => {
expect(
buildRcsWsUrl('http://example.test/acp/ws?token=old-secret&x=1'),
).toBe('ws://example.test/acp/ws?x=1')
})
test('adds /acp/ws for base URLs', () => {
expect(buildRcsWsUrl('https://example.test/')).toBe(
'wss://example.test/acp/ws',
)
})
})
describe('permission mode resolution', () => {
test('uses client requested non-bypass modes', () => {
expect(resolveNewSessionPermissionMode('plan', 'acceptEdits')).toBe('plan')
})
test('uses local default when client does not request a mode', () => {
expect(resolveNewSessionPermissionMode(undefined, 'acceptEdits')).toBe(
'acceptEdits',
)
})
test('rejects client requested bypassPermissions without local default', () => {
expect(() =>
resolveNewSessionPermissionMode('bypassPermissions', 'acceptEdits'),
).toThrow('bypassPermissions requires local ACP_PERMISSION_MODE')
expect(() =>
resolveNewSessionPermissionMode('bypass', 'acceptEdits'),
).toThrow('bypassPermissions requires local ACP_PERMISSION_MODE')
expect(() =>
resolveNewSessionPermissionMode('bypasspermissions', 'acceptEdits'),
).toThrow('bypassPermissions requires local ACP_PERMISSION_MODE')
expect(() =>
resolveNewSessionPermissionMode('bypassPermissions', undefined),
).toThrow('bypassPermissions requires local ACP_PERMISSION_MODE')
})
test('rejects unknown client permission modes before forwarding', () => {
expect(() =>
resolveNewSessionPermissionMode('unknown-mode', 'acceptEdits'),
).toThrow('Invalid permissionMode: unknown-mode')
})
test('allows bypassPermissions when local default already enables it', () => {
expect(
resolveNewSessionPermissionMode('bypassPermissions', 'bypassPermissions'),
).toBe('bypassPermissions')
expect(resolveNewSessionPermissionMode('bypass', 'bypassPermissions')).toBe(
'bypassPermissions',
)
expect(resolveNewSessionPermissionMode('bypassPermissions', 'bypass')).toBe(
'bypassPermissions',
)
})
test('new_session rejects client bypass before forwarding to the agent', async () => {
const sent: unknown[] = []
const ws = makeTestWs(sent)
const originalTestInternals = process.env.ACP_LINK_TEST_INTERNALS
process.env.ACP_LINK_TEST_INTERNALS = '1'
let unregisterClient = () => {}
let restoreMode = () => {}
try {
const newSession = mock(async () => ({
sessionId: 'should-not-be-created',
}))
unregisterClient = __testing.registerClient(ws, {
connection: { newSession },
})
restoreMode = __testing.setDefaultPermissionMode('acceptEdits')
await __testing.dispatchClientMessage(ws, {
type: 'new_session',
payload: {
cwd: '/tmp',
permissionMode: 'bypass',
},
})
expect(newSession).not.toHaveBeenCalled()
expect(__testing.getClientSessionId(ws)).toBeNull()
expect(sent).toEqual([
{
type: 'error',
payload: {
message: expect.stringContaining(
'bypassPermissions requires local ACP_PERMISSION_MODE',
),
},
},
])
} finally {
restoreMode()
unregisterClient()
if (originalTestInternals === undefined) {
delete process.env.ACP_LINK_TEST_INTERNALS
} else {
process.env.ACP_LINK_TEST_INTERNALS = originalTestInternals
}
}
})
})
describe('Heartbeat constants', () => {
test('PERMISSION_TIMEOUT_MS is 5 minutes', () => {
const PERMISSION_TIMEOUT_MS = 5 * 60 * 1000
expect(PERMISSION_TIMEOUT_MS).toBe(300_000)
})
test('HEARTBEAT_INTERVAL_MS is 30 seconds', () => {
const HEARTBEAT_INTERVAL_MS = 30_000
expect(HEARTBEAT_INTERVAL_MS).toBe(30_000)
})
})