Files
goodbuddy/src/preload/preload-sandbox.test.ts
T
mesalogo cf598121b1 feat: add Telegram private messaging channel
Connect a user-created Telegram bot to channel projects with encrypted credentials, numeric sender allowlists, unified execution and text replies. Use Electron networking for system proxy support, with polling recovery, cancellable delivery retries and live status updates.

Add schema 61 channel support, lifecycle regression tests, Electron integration probes and feature documentation. Require the performance principles in AGENTS.md and record three alternating baseline/current Main benchmark rounds.

Validation: typecheck, lint and diff checks passed. Full suite: 6087 passed, 87 skipped, one intermittent inline-help Electron failure that passed unchanged in isolation. Real Bot plus model acceptance remains pending; the local TLS proxy probe is opt-in.

Release note: 新增 Telegram 私聊通道,可配置 Bot Token 和用户白名单,通过通道项目处理文字请求并回复结果。支持系统代理、断线重连和停用取消;真实 Bot 与模型的完整联调仍待完成。
2026-10-05 02:42:28 +08:00

456 lines
16 KiB
TypeScript

import { readFileSync } from 'node:fs'
import { join } from 'node:path'
import { describe, expect, it, vi } from 'vitest'
import type { DesktopApi } from '../shared/contracts'
import { ipcChannels } from '../shared/ipc-channels'
const electron = vi.hoisted(() => ({
contextBridge: { exposeInMainWorld: vi.fn() },
ipcRenderer: { invoke: vi.fn(), on: vi.fn(), removeListener: vi.fn() },
webUtils: {}
}))
vi.mock('electron', () => electron)
describe('sandboxed preload', () => {
it('forwards status deltas without the Electron event and removes the exact subscription', async () => {
await import('./index')
const api = electron.contextBridge.exposeInMainWorld.mock.calls[0]![1] as DesktopApi
const listener = vi.fn()
const unsubscribe = api.channels!.onStatusChanged(listener)
const [channel, handler] = electron.ipcRenderer.on.mock.calls.at(-1)!
expect(channel).toBe(ipcChannels.channelStatusChanged)
const change = { channel: 'telegram', status: { state: 'error', lastError: 'Network unavailable' } }
handler({ sender: 'main' }, change)
expect(listener).toHaveBeenCalledExactlyOnceWith(change)
unsubscribe()
expect(electron.ipcRenderer.removeListener).toHaveBeenCalledExactlyOnceWith(channel, handler)
})
it('forwards Telegram settings and draft or saved connection tests through channel IPC', async () => {
await import('./index')
const api = electron.contextBridge.exposeInMainWorld.mock.calls[0]![1] as DesktopApi
const settings = {
enabled: false, secret: { action: 'replace' as const, value: '456:test-token' },
allowedSenderIds: ['123'], allowGroupMessages: false as const
}
await api.channels!.getSnapshot()
await api.channels!.apply({ telegram: settings })
await api.channels!.testConnection('telegram', settings)
await api.channels!.testConnection('telegram')
expect(electron.ipcRenderer.invoke.mock.calls).toEqual([
[ipcChannels.channelSettingsGet],
[ipcChannels.channelSettingsApply, { telegram: settings }],
[ipcChannels.channelSettingsTest, { channel: 'telegram', settings }],
[ipcChannels.channelSettingsTest, { channel: 'telegram', settings: undefined }]
])
})
it('does not import Node built-ins unavailable in Electron sandbox', () => {
const source = readFileSync(
join(process.cwd(), 'src', 'preload', 'index.ts'),
'utf8'
)
expect(source).not.toMatch(/\bfrom\s+['"]node:/u)
expect(source).not.toMatch(/\brequire\(\s*['"]node:/u)
})
it('does not load runtime schema libraries in the Electron sandbox', () => {
const source = readFileSync(
join(process.cwd(), 'src', 'preload', 'index.ts'),
'utf8'
)
expect(source).not.toMatch(/\b\w+Schema\b/u)
expect(source).not.toMatch(/\bfrom\s+['"]zod['"]/u)
})
it('exposes only explicit clipboard text reading and writing', () => {
const source = readFileSync(
join(process.cwd(), 'src', 'preload', 'index.ts'),
'utf8'
)
const clipboard =
source.match(
/clipboard: \{(?<body>[\s\S]*?)\r?\n {2}\},\r?\n {2}agent:/u
)?.groups?.body ?? ''
expect(clipboard).toContain('readText: ()')
expect(clipboard).toContain('ipcChannels.clipboardReadText')
expect(clipboard).toContain('writeText: async (text: string)')
expect(clipboard).toContain('ipcChannels.clipboardWriteText')
expect(clipboard).not.toMatch(/\b(?:image|html|rtf|bookmark)\b/iu)
})
it('exposes only narrow browser-control methods', () => {
const source = readFileSync(
join(process.cwd(), 'src', 'preload', 'index.ts'),
'utf8'
)
const browser =
source.match(
/browser: \{(?<body>[\s\S]*?)\r?\n {2}\},\r?\n {2}terminal:/u
)?.groups?.body ?? ''
for (const method of [
'createTab:',
'listTabs:',
'closeTab:',
'navigate:',
'back:',
'reload:',
'stopLoading:',
'snapshot:',
'click:',
'type:',
'select:',
'screenshot:',
'setViewport:',
'stop:',
'onState:'
]) {
expect(browser).toContain(method)
}
for (const channel of [
'browserCreateTab',
'browserListTabs',
'browserCloseTab',
'browserSnapshot',
'browserClick',
'browserType',
'browserSelect',
'browserScreenshot',
'browserSetViewport'
]) {
expect(browser).toContain(`ipcChannels.${channel}`)
}
expect(browser).toContain('requestOrConversation: BrowserNavigateRequest')
expect(browser).toContain('setViewport: async (request: BrowserSetViewportRequest)')
expect(browser).not.toContain('leaseId')
expect(browser).not.toMatch(
/\b(?:webContents|debugger|session|partition|cookie)\b/iu
)
})
it('exposes bounded local tool environment operations without execution inputs', () => {
const source = readFileSync(
join(process.cwd(), 'src', 'preload', 'index.ts'),
'utf8'
)
const localTools =
source.match(
/localToolEnvironment: \{(?<body>[\s\S]*?)\r?\n {2}\},\r?\n {2}feedback:/u
)?.groups?.body ?? ''
for (const method of [
'getSnapshot:',
'updateSettings:',
'refreshCandidates:',
'selectExecutable:',
'diagnose:',
'installPython:',
'cancelPython:',
'removePython:',
'onProgress:'
]) {
expect(localTools).toContain(method)
}
expect(localTools).not.toMatch(
/\b(?:command|env|url|hash|installDirectory|shim)\b/iu
)
expect(localTools).toContain('ipcRenderer.removeListener(')
})
it('exposes only explicit computer capability and managed profile IPC methods', () => {
const source = readFileSync(
join(process.cwd(), 'src', 'preload', 'index.ts'),
'utf8'
)
expect(source).toContain('setComputerCapabilityEnabled:')
expect(source).toContain('diagnoseComputerCapability:')
expect(source).toContain('createBrowserProfile:')
expect(source).toContain('renameBrowserProfile:')
expect(source).toContain('setDefaultBrowserProfile:')
expect(source).toContain('removeBrowserProfile:')
expect(source).not.toMatch(
/(?:setComputerCapability|BrowserProfile).{0,80}(?:executablePath|command|env|args)/su
)
})
it('exposes model ZIP dialogs without renderer-controlled paths', () => {
const source = readFileSync(
join(process.cwd(), 'src', 'preload', 'index.ts'),
'utf8'
)
expect(source).toContain('importArchive: (modelId: string)')
expect(source).toContain('exportArchive: (modelId: string)')
expect(source).toContain('importOcrModelArchive: (modelId: string)')
expect(source).toContain('exportOcrModelArchive: (modelId: string)')
expect(source).toContain('importModelArchive: (modelId: string)')
expect(source).toContain('ipcChannels.embeddingModelsImportArchive')
expect(source).not.toContain('importLocalDirectory:')
expect(source).not.toContain('importOcrModel:')
expect(source).not.toMatch(
/importModelArchive:\s*\([^)]*(?:path|directory|archivePath)/u
)
})
it('exposes explicit embedding settings operations without vectors or credentials', () => {
const source = readFileSync(
join(process.cwd(), 'src', 'preload', 'index.ts'),
'utf8'
)
const embeddings =
source.match(
/embeddings: \{(?<body>[\s\S]*?)\r?\n {2}\},\r?\n {2}documentParsing:/u
)?.groups?.body ?? ''
expect(embeddings).toContain('getSnapshot:')
expect(embeddings).toContain('getModelProgress:')
expect(embeddings).toContain('diagnose: (connectionId: string)')
expect(embeddings).toContain('setCurrent: (connectionId: string)')
expect(embeddings).toContain('installModel:')
expect(embeddings).toContain('cancelModelOperation:')
expect(embeddings).toContain('removeModel:')
expect(embeddings).not.toMatch(/\b(?:apiKey|credential|vector)\b/iu)
})
it('passes an explicit parsing scenario to document diagnostics', () => {
const source = readFileSync(
join(process.cwd(), 'src', 'preload', 'index.ts'),
'utf8'
)
expect(source).toContain(
'test: (purpose: DocumentParsingTestPurpose, operationId?: string)'
)
expect(source).toMatch(
/ipcChannels\.documentParsingTest,\r?\n {8}\{ purpose, \.\.\.\(operationId/u
)
})
it('exposes a narrow OCR operation progress snapshot', () => {
const source = readFileSync(
join(process.cwd(), 'src', 'preload', 'index.ts'),
'utf8'
)
expect(source).toContain('getOcrModelProgress:')
expect(source).toContain('ipcChannels.documentOcrModelsProgress')
})
it('exposes a removable attachment parsing progress listener', () => {
const source = readFileSync(
join(process.cwd(), 'src', 'preload', 'index.ts'),
'utf8'
)
expect(source).toContain('onFileSelectionProgress:')
expect(source).toContain('contextFileSelectionProgress')
expect(source).toContain('ipcRenderer.removeListener(')
})
it('exposes only bounded release-note actions', () => {
const source = readFileSync(
join(process.cwd(), 'src', 'preload', 'index.ts'),
'utf8'
)
expect(source).toContain('releaseNotes: {')
expect(source).toContain('getPending:')
expect(source).toContain('acknowledge: async (version: string)')
expect(source).toContain('ipcChannels.releaseNotesGetPending')
expect(source).toContain('ipcChannels.releaseNotesAcknowledge')
})
it('exposes only the narrow feedback submission method', () => {
const source = readFileSync(
join(process.cwd(), 'src', 'preload', 'index.ts'),
'utf8'
)
const feedback =
source.match(
/feedback: \{(?<body>[\s\S]*?)\r?\n {2}\},\r?\n {2}shortcuts:/u
)?.groups?.body ?? ''
expect(feedback).toContain('submit: (input: FeedbackSubmitInput)')
expect(feedback).toContain('ipcChannels.feedbackSubmit')
expect(feedback).not.toMatch(
/\b(?:url|endpoint|header|cookie|authorization|path)\b/iu
)
})
it('exposes only get and validated-update shortcut settings methods', () => {
const source = readFileSync(
join(process.cwd(), 'src', 'preload', 'index.ts'),
'utf8'
)
expect(source).toContain('shortcuts: {')
expect(source).toContain('ipcChannels.shortcutSettingsGet')
expect(source).toContain('ipcChannels.shortcutSettingsUpdate')
expect(source).not.toContain('globalShortcut.')
})
it('exposes only explicit SSH host-management methods', () => {
const source = readFileSync(
join(process.cwd(), 'src', 'preload', 'index.ts'),
'utf8'
)
expect(source).toContain('sshHosts: {')
expect(source).toContain('getSnapshot:')
expect(source).toContain('onAgentConnectionStatus:')
expect(source).toContain(
'ipcChannels.sshHostsAgentConnectionStatus'
)
expect(source).toContain(
'getAgentPackageInventory: (refresh = false)'
)
expect(source).toContain('downloadAgentPackage:')
expect(source).toContain('importAgentPackage:')
expect(source).toContain('exportAgentPackage: async')
expect(source).toContain('onAgentPackageProgress:')
expect(source).toContain('inspectDraftHostKey:')
expect(source).toContain('discardCandidate:')
expect(source).toContain('validateAndSave:')
expect(source).toContain(
'browseDirectories: (hostId: string, path?: string)'
)
expect(source).toContain('cancelDirectoryBrowse: async ()')
expect(source).toContain('getRemoteEnvironment: (hostId: string)')
expect(source).toContain(
'input: RemoteEnvironmentUpdateRequest'
)
expect(source).toContain(
'cancelRemoteEnvironmentUpdate: async (hostId: string)'
)
expect(source).toContain(
'onRemoteEnvironmentUpdateProgress:'
)
expect(source).toContain(
'ipcChannels.sshHostsRemoteEnvironment'
)
expect(source).toContain(
'ipcChannels.sshHostsAgentPackageInventory'
)
expect(source).toContain(
'ipcChannels.sshHostsAgentPackageDownload'
)
expect(source).toContain(
'ipcChannels.sshHostsAgentPackageImport'
)
expect(source).toContain(
'ipcChannels.sshHostsAgentPackageExport'
)
expect(source).toContain(
'ipcChannels.sshHostsAgentPackageProgress'
)
expect(source).toContain(
'ipcChannels.sshHostsUpdateRemoteEnvironment'
)
expect(source).toContain(
'ipcChannels.sshHostsCancelRemoteEnvironmentUpdate'
)
expect(source).toContain(
'ipcChannels.sshHostsRemoteEnvironmentUpdateProgress'
)
expect(source).toContain('ipcChannels.sshHostsBrowseDirectories')
expect(source).toContain(
'ipcChannels.sshHostsCancelDirectoryBrowse'
)
expect(source).not.toContain('sshHostsCreate')
expect(source).not.toContain('sshHostsAcceptKey')
expect(source).not.toContain('privateKey:')
expect(source).not.toContain('agentForward: true')
const sshHostsSource =
source.match(
/sshHosts: \{(?<body>[\s\S]*?)\n {2}\},\n {2}channels:/u
)
?.groups?.body ?? ''
expect(sshHostsSource).not.toMatch(
/\b(?:credential|password|revision|sftp|shell|command)\b/iu
)
expect(sshHostsSource).not.toMatch(
/\b(?:detail|phaseName|installationId)\b/iu
)
})
it('exposes only bounded terminal session operations', () => {
const source = readFileSync(
join(process.cwd(), 'src', 'preload', 'index.ts'),
'utf8'
)
const terminal =
source.match(
/terminal: \{(?<body>[\s\S]*?)\r?\n {2}\},\r?\n {2}settings:/u
)?.groups?.body ?? ''
expect(terminal).toContain('create:')
expect(terminal).toContain('write:')
expect(terminal).toContain('resize:')
expect(terminal).toContain('close:')
expect(terminal).toContain('getSnapshot:')
expect(terminal).toContain('ack:')
expect(terminal).toContain('onEvent:')
expect(terminal).toContain('ipcRenderer.removeListener(')
expect(terminal).not.toMatch(
/\b(?:shell|cwd|command|environment|password|privateKey|pid)\b/u
)
})
it('exposes narrow awaited remote project activation and save operations', () => {
const source = readFileSync(
join(process.cwd(), 'src', 'preload', 'index.ts'),
'utf8'
)
expect(source).toContain('remote: {')
expect(source).toContain('remoteProjectSave')
expect(source).toContain('remoteProjectCancelCurrent')
expect(source).toContain('remoteProjectSaveProgress')
expect(source).toContain('RemoteProjectSaveRequest')
expect(source).not.toContain('commitToken')
expect(source).toContain('ipcRenderer.removeListener(')
})
it('exposes removable project recovery snapshot, retry, and progress APIs', () => {
const source = readFileSync(
join(process.cwd(), 'src', 'preload', 'index.ts'),
'utf8'
)
expect(source).toContain('getRecoverySnapshot:')
expect(source).toContain('retryRecovery: (projectId: string)')
expect(source).toContain('onRecoveryProgress:')
expect(source).toContain('ipcChannels.remoteProjectRecoveryGet')
expect(source).toContain('ipcChannels.remoteProjectRecoveryRetry')
expect(source).toContain(
'ipcChannels.remoteProjectRecoveryProgress'
)
expect(source).toMatch(
/remoteProjectRecoveryProgress,[\s\S]*?removeListener\([\s\S]*?remoteProjectRecoveryProgress/u
)
})
it('exposes bounded knowledge task actions', () => {
const source = readFileSync(
join(process.cwd(), 'src', 'preload', 'index.ts'),
'utf8'
)
expect(source).toContain('cancelTask: (taskId: string)')
expect(source).toContain('retryTask: async (taskId: string)')
expect(source).toContain('ipcChannels.knowledgeTaskCancel')
expect(source).toContain('ipcChannels.knowledgeTaskRetry')
})
it('exposes only explicit Conversation queue operations and listeners', () => {
const source = readFileSync(
join(process.cwd(), 'src', 'preload', 'index.ts'),
'utf8'
)
expect(source).toContain('conversationQueue: {')
expect(source).toContain('ipcChannels.conversationQueueEnqueueUser')
expect(source).toContain(
'ipcChannels.conversationQueueInterruptAndRun'
)
expect(source).toContain('ipcChannels.conversationQueueDispatch')
expect(source).toContain('listener(conversationId)')
})
it('exposes a narrow local conversation branch operation', () => {
const source = readFileSync(
join(process.cwd(), 'src', 'preload', 'index.ts'),
'utf8'
)
expect(source).toContain(
'branchLocal: (input: ConversationBranchInput)'
)
expect(source).toContain('ipcChannels.conversationsBranchLocal')
})
})