Files
deepseek-harness/packages/test-support/client-runtime/tests/access.client.spec.ts
T
imccyu 09899f3a0f feat(remote-access): 插件注册跟随访问状态(whenAccess),index 按 Peer 注入访问快照
- remote-access 新增 whenAccess / whenResourceAccess,ui-session、ui-workspace 各自提供 whenAccess
- schedule、plugin-manager、settings-general、settings-account、directory-picker-native 的入口包进 global whenAccess;消息反馈按钮按会话 useAccess 渲染
- webserver/index-inject 带 Peer,renderIndex / collectIndexInjections 需要 Peer,remote-access 注入 snapshotFor(peer.principal)
- client-test-runtime 新增 StaticRemoteAccess、staticAccessPlugin 与 TestClientOptions.access
- implemented Agent Note 2026-10-21-when-access 与升级指南 webserver-index-peer

Refs #2940
2026-10-21 17:57:51 +08:00

83 lines
4.0 KiB
TypeScript

/** Fixed-answer access stand-ins: the `remoteAccess` service and the plugin that installs it with the global Hook. */
import { Context } from '@deepseek-ai/cordis'
import type { RemoteEndpoint } from '@deepseek-ai/dsh-api-remote-access/client'
import type { RootStandardSourceContribution } from '@deepseek-ai/dsh-client-ui-slots'
import { describe, expect, it, vi } from 'vitest'
import { allowAllAccess, allowAllAccessPlugin, StaticRemoteAccess, staticAccessPlugin } from '../src/access.ts'
/** An endpoint the access stand-ins answer; the stand-ins read only its string. */
const GUARDED = 'session/list' as RemoteEndpoint
describe('StaticRemoteAccess', () => {
it('answers each action once for every resource and installs only an allowed action\'s effects in the caller', async () => {
const root = new Context()
const allows = vi.fn((action: string) => action === GUARDED)
new StaticRemoteAccess(root, allows)
expect(root.remoteAccess.source('session', 's1', GUARDED).getSnapshot()).toBe(true)
expect(root.remoteAccess.source('session', 's1', 'session/fork').getSnapshot()).toBe(false)
// Fixed answers never notify.
for (const action of [GUARDED, 'session/fork']) {
const listener = vi.fn()
root.remoteAccess.source('session', 's1', action).subscribe(listener)()
expect(listener).not.toHaveBeenCalled()
}
const allowedDispose = vi.fn()
const refused = vi.fn(() => () => {})
let stopAllowed = (): void => {}
let stopRefused = (): void => {}
const caller = root.plugin({
inject: ['remoteAccess'],
apply: (ctx: Context) => {
stopAllowed = ctx.remoteAccess.whenResourceAccess('session', 's1', GUARDED, () => allowedDispose)
stopRefused = ctx.remoteAccess.whenAccess('session/fork', refused)
},
})
await caller.await()
expect(refused).not.toHaveBeenCalled()
expect(() => { stopRefused() }).not.toThrow()
stopAllowed()
await vi.waitFor(() => { expect(allowedDispose).toHaveBeenCalledOnce() })
await caller.dispose()
await root.fiber.dispose()
})
})
describe('allow-all access', () => {
it('allows every action through the props Hooks and a predicate-less service', async () => {
expect([
allowAllAccess.useRemoteAccess(GUARDED),
allowAllAccess.useAccess(GUARDED),
allowAllAccess.useSessionAccess('s1' as Parameters<typeof allowAllAccess.useSessionAccess>[0], GUARDED),
allowAllAccess.useWorkspaceAccess('w1' as Parameters<typeof allowAllAccess.useWorkspaceAccess>[0], GUARDED),
]).toEqual([true, true, true, true])
const root = new Context()
new StaticRemoteAccess(root)
expect(root.remoteAccess.source('workspace', 'w1', 'workspace/follow').getSnapshot()).toBe(true)
await root.fiber.dispose()
})
})
describe('staticAccessPlugin', () => {
it('provides the fixed-answer service and the matching global-resource Hook', async () => {
const root = new Context()
let contribution: RootStandardSourceContribution | undefined
root.provide('slots', { provideRoot: (value: RootStandardSourceContribution) => { contribution = value; return () => {} } } as never)
await root.plugin(staticAccessPlugin(action => action === GUARDED)).await()
const hook = contribution?.keyedHooks?.['remoteAccess']
expect(hook?.(GUARDED)?.getSnapshot()).toBe(true)
expect(hook?.('session/fork')?.getSnapshot()).toBe(false)
expect(root.get('remoteAccess')?.source('session', 's1', 'session/fork').getSnapshot()).toBe(false)
await root.fiber.dispose()
})
it('allows every action in its allow-all form', async () => {
const root = new Context()
let contribution: RootStandardSourceContribution | undefined
root.provide('slots', { provideRoot: (value: RootStandardSourceContribution) => { contribution = value; return () => {} } } as never)
await root.plugin(allowAllAccessPlugin).await()
expect(contribution?.keyedHooks?.['remoteAccess']?.('session/fork')?.getSnapshot()).toBe(true)
await root.fiber.dispose()
})
})