cc-haha/adapters/common/__tests__/config.test.ts
程序员阿江(Relakkes) ddcfa5faae feat(adapters): add WhatsApp linked-device support (#573)
Add a WhatsApp adapter backed by Baileys linked-device auth, plus desktop QR binding UI, server config endpoints, sidecar startup wiring, tests, and documentation.

Constraint: Uses WhatsApp Web linked-device auth, not Meta WhatsApp Business Cloud API.

Tested:
- cd adapters && bun run check:adapters
- bun test src/server/__tests__/adapters.test.ts
- cd desktop && bun run check:desktop
- bun run check:native
- bun run check:persistence-upgrade
- bun run check:docs

Not-tested:
- Live WhatsApp QR pairing, because no WhatsApp account/device was exercised here.
- bun run check:server, because the existing src/server/__tests__/conversations.test.ts timeout still fails independently.

Confidence: medium
Scope-risk: moderate
2026-06-09 21:31:46 +08:00

140 lines
6.3 KiB
TypeScript

import { afterEach, describe, expect, it } from 'bun:test'
import * as fs from 'node:fs'
import * as os from 'node:os'
import * as path from 'node:path'
import { getConfiguredWorkDir, loadConfig } from '../config.js'
describe('adapter config defaults', () => {
const originalConfigDir = process.env.CLAUDE_CONFIG_DIR
const originalAdapterDefaultWorkDir = process.env.CLAUDE_ADAPTER_DEFAULT_WORK_DIR
const originalAdapterDefaultProjectDir = process.env.ADAPTER_DEFAULT_PROJECT_DIR
const originalDingtalkPermissionCardTemplateId = process.env.DINGTALK_PERMISSION_CARD_TEMPLATE_ID
const originalPwd = process.env.PWD
afterEach(() => {
restoreEnv('CLAUDE_CONFIG_DIR', originalConfigDir)
restoreEnv('CLAUDE_ADAPTER_DEFAULT_WORK_DIR', originalAdapterDefaultWorkDir)
restoreEnv('ADAPTER_DEFAULT_PROJECT_DIR', originalAdapterDefaultProjectDir)
restoreEnv('DINGTALK_PERMISSION_CARD_TEMPLATE_ID', originalDingtalkPermissionCardTemplateId)
restoreEnv('PWD', originalPwd)
})
it('uses the user shell working directory when no default project is configured', () => {
const configDir = fs.mkdtempSync(path.join(os.tmpdir(), 'adapter-config-'))
const workDir = fs.mkdtempSync(path.join(os.tmpdir(), 'adapter-workdir-'))
try {
process.env.CLAUDE_CONFIG_DIR = configDir
delete process.env.CLAUDE_ADAPTER_DEFAULT_WORK_DIR
process.env.PWD = workDir
const config = loadConfig()
expect(config.telegram.defaultWorkDir).toBe(fs.realpathSync(workDir))
expect(config.feishu.defaultWorkDir).toBe(fs.realpathSync(workDir))
expect(config.wechat.defaultWorkDir).toBe(fs.realpathSync(workDir))
expect(config.dingtalk.defaultWorkDir).toBe(fs.realpathSync(workDir))
expect(config.whatsapp.defaultWorkDir).toBe(fs.realpathSync(workDir))
expect(getConfiguredWorkDir(config, config.wechat)).toBe(fs.realpathSync(workDir))
expect(getConfiguredWorkDir(config, config.dingtalk)).toBe(fs.realpathSync(workDir))
expect(getConfiguredWorkDir(config, config.whatsapp)).toBe(fs.realpathSync(workDir))
} finally {
fs.rmSync(configDir, { recursive: true, force: true })
fs.rmSync(workDir, { recursive: true, force: true })
}
})
it('keeps the explicit default project ahead of the platform default work dir', () => {
const configDir = fs.mkdtempSync(path.join(os.tmpdir(), 'adapter-config-'))
const defaultProjectDir = fs.mkdtempSync(path.join(os.tmpdir(), 'adapter-project-'))
const workDir = fs.mkdtempSync(path.join(os.tmpdir(), 'adapter-workdir-'))
try {
fs.writeFileSync(
path.join(configDir, 'adapters.json'),
JSON.stringify({ defaultProjectDir }),
)
process.env.CLAUDE_CONFIG_DIR = configDir
process.env.CLAUDE_ADAPTER_DEFAULT_WORK_DIR = workDir
const config = loadConfig()
expect(getConfiguredWorkDir(config, config.wechat)).toBe(defaultProjectDir)
expect(getConfiguredWorkDir(config, config.dingtalk)).toBe(defaultProjectDir)
expect(getConfiguredWorkDir(config, config.whatsapp)).toBe(defaultProjectDir)
expect(config.wechat.defaultWorkDir).toBe(fs.realpathSync(workDir))
expect(config.dingtalk.defaultWorkDir).toBe(fs.realpathSync(workDir))
expect(config.whatsapp.defaultWorkDir).toBe(fs.realpathSync(workDir))
} finally {
fs.rmSync(configDir, { recursive: true, force: true })
fs.rmSync(defaultProjectDir, { recursive: true, force: true })
fs.rmSync(workDir, { recursive: true, force: true })
}
})
it('accepts ADAPTER_DEFAULT_PROJECT_DIR as a sidecar-friendly default work dir override', () => {
const configDir = fs.mkdtempSync(path.join(os.tmpdir(), 'adapter-config-'))
const defaultProjectDir = fs.mkdtempSync(path.join(os.tmpdir(), 'adapter-project-'))
try {
process.env.CLAUDE_CONFIG_DIR = configDir
process.env.ADAPTER_DEFAULT_PROJECT_DIR = defaultProjectDir
delete process.env.CLAUDE_ADAPTER_DEFAULT_WORK_DIR
delete process.env.PWD
const config = loadConfig()
expect(getConfiguredWorkDir(config, config.wechat)).toBe(fs.realpathSync(defaultProjectDir))
expect(getConfiguredWorkDir(config, config.dingtalk)).toBe(fs.realpathSync(defaultProjectDir))
expect(getConfiguredWorkDir(config, config.whatsapp)).toBe(fs.realpathSync(defaultProjectDir))
} finally {
fs.rmSync(configDir, { recursive: true, force: true })
fs.rmSync(defaultProjectDir, { recursive: true, force: true })
}
})
it('loads DingTalk permission card template id from file or env', () => {
const configDir = fs.mkdtempSync(path.join(os.tmpdir(), 'adapter-config-'))
try {
fs.writeFileSync(
path.join(configDir, 'adapters.json'),
JSON.stringify({ dingtalk: { permissionCardTemplateId: 'file-template' } }),
)
process.env.CLAUDE_CONFIG_DIR = configDir
delete process.env.DINGTALK_PERMISSION_CARD_TEMPLATE_ID
expect(loadConfig().dingtalk.permissionCardTemplateId).toBe('file-template')
process.env.DINGTALK_PERMISSION_CARD_TEMPLATE_ID = 'env-template'
expect(loadConfig().dingtalk.permissionCardTemplateId).toBe('env-template')
} finally {
fs.rmSync(configDir, { recursive: true, force: true })
}
})
it('loads WhatsApp auth dir from file or env', () => {
const configDir = fs.mkdtempSync(path.join(os.tmpdir(), 'adapter-config-'))
const originalWhatsAppAuthDir = process.env.WHATSAPP_AUTH_DIR
try {
fs.writeFileSync(
path.join(configDir, 'adapters.json'),
JSON.stringify({ whatsapp: { authDir: '~/custom-whatsapp-auth', accountJid: '15551234567@s.whatsapp.net' } }),
)
process.env.CLAUDE_CONFIG_DIR = configDir
delete process.env.WHATSAPP_AUTH_DIR
expect(loadConfig().whatsapp.authDir).toBe(path.join(os.homedir(), 'custom-whatsapp-auth'))
expect(loadConfig().whatsapp.accountJid).toBe('15551234567@s.whatsapp.net')
process.env.WHATSAPP_AUTH_DIR = path.join(configDir, 'wa-auth')
expect(loadConfig().whatsapp.authDir).toBe(path.join(configDir, 'wa-auth'))
} finally {
restoreEnv('WHATSAPP_AUTH_DIR', originalWhatsAppAuthDir)
fs.rmSync(configDir, { recursive: true, force: true })
}
})
})
function restoreEnv(key: string, value: string | undefined): void {
if (value === undefined) {
delete process.env[key]
} else {
process.env[key] = value
}
}