From cdb3a76b6ac63bf548a416940eb88c4a6e264332 Mon Sep 17 00:00:00 2001 From: _Kerman Date: Wed, 21 Oct 2026 10:43:20 +0800 Subject: [PATCH] test(agent): complete model ownership migration coverage --- .../tests/commands-upload-file.host.spec.ts | 22 +++++++++++++++++++ .../tests/controller.host.spec.ts | 9 ++++---- .../tests/session-fork.host.spec.ts | 11 ++++++---- .../tests/config-session-id.spec.ts | 1 + .../tests/agent-loop-testkit.spec.ts | 9 ++++++++ 5 files changed, 44 insertions(+), 8 deletions(-) diff --git a/packages/api/session-controller/tests/commands-upload-file.host.spec.ts b/packages/api/session-controller/tests/commands-upload-file.host.spec.ts index d0507036b2..305bace577 100644 --- a/packages/api/session-controller/tests/commands-upload-file.host.spec.ts +++ b/packages/api/session-controller/tests/commands-upload-file.host.spec.ts @@ -229,6 +229,28 @@ describe('Session file uploads', () => { expect(saveImages).not.toHaveBeenCalled() }) + it('admits an image before the Agent has a model selection', async () => { + const { ctx, controller, agent, followup, saveImages } = await uploadHarness() + delete agent.options.provider + delete agent.options.model + expect(agent.model.current()).toBeUndefined() + const resolveModel = vi.spyOn(ctx.llm, 'resolveModelInfo') + const image: ImageAttachmentRef = { + attachmentId: AttachmentId(`sha256:${'ab'.repeat(32)}`), + mediaType: 'image/png', bytes: 3, width: 1, height: 1, + } + saveImages.mockResolvedValueOnce([image]) + + await expect(controller.prompt(promptRequest([ + { type: 'image', mediaType: 'image/png', data: 'AAAA' }, + ]), USER_SOURCE)).resolves.toEqual({ accepted: true }) + + expect(resolveModel).not.toHaveBeenCalled() + expect(followup).toHaveBeenCalledExactlyOnceWith(expect.objectContaining({ + content: [{ type: 'image', attachment: image }], + })) + }) + it('publishes no receipt when its exact Agent is disposed during storage', async () => { const { uploads, agent, saveFile, disposeAgent } = await uploadHarness() const saved = Promise.withResolvers() diff --git a/packages/api/session-controller/tests/controller.host.spec.ts b/packages/api/session-controller/tests/controller.host.spec.ts index 2920093d54..789caf78d5 100644 --- a/packages/api/session-controller/tests/controller.host.spec.ts +++ b/packages/api/session-controller/tests/controller.host.spec.ts @@ -2,11 +2,12 @@ import SessionProjectionRegistry from '@deepseek-ai/dsh-session-projection' import { Context } from '@deepseek-ai/cordis' import AgentRegistry from '@deepseek-ai/dsh-agent' import type { Agent } from '@deepseek-ai/dsh-agent' +import { mountAgentLoopTestDependencies, mountAgentLoopTestHarness } from '@deepseek-ai/dsh-agent-loop-testkit' import { createUserMessage } from '@deepseek-ai/dsh-llm' import SessionStore, { SESSION_FORMAT_VERSION, SessionId, SessionLogOffset } from '@deepseek-ai/dsh-session' import type { SessionEvent, SessionHeader } from '@deepseek-ai/dsh-session' import { RemoteError } from '@deepseek-ai/dsh-typert-protocol' -import { describe, expect, it, vi } from 'vitest' +import { describe, expect, it, onTestFinished, vi } from 'vitest' import SessionController from '../src/index.ts' import type { ApiSessionAgentController } from '../src/agent.ts' import { createSessionTestController, testSessionPersistence } from './test-remote.ts' @@ -23,9 +24,9 @@ describe('SessionController facade', () => { it('owns Host service methods and publishes Agent lifecycle projections', async () => { const ctx = new Context() - await ctx.plugin(SessionStore) - await ctx.plugin(SessionProjectionRegistry) - await ctx.plugin(AgentRegistry) + onTestFinished(() => ctx.fiber.dispose()) + await mountAgentLoopTestDependencies(ctx) + await mountAgentLoopTestHarness(ctx) const sessionId = SessionId('controller-session') const header: SessionHeader = { version: SESSION_FORMAT_VERSION, diff --git a/packages/api/session-controller/tests/session-fork.host.spec.ts b/packages/api/session-controller/tests/session-fork.host.spec.ts index 12a0c3f527..9d407a1378 100644 --- a/packages/api/session-controller/tests/session-fork.host.spec.ts +++ b/packages/api/session-controller/tests/session-fork.host.spec.ts @@ -1,7 +1,7 @@ /** Session Controller fork boundaries, lineage, and inherited model routing. */ import SessionProjectionRegistry from '@deepseek-ai/dsh-session-projection' -import { describe, expect, it, vi } from 'vitest' +import { describe, expect, it, onTestFinished, vi } from 'vitest' import { Context } from '@deepseek-ai/cordis' import AgentRegistry from '@deepseek-ai/dsh-agent' import type { Agent, AgentHandle, CreateAgentOptions } from '@deepseek-ai/dsh-agent' @@ -633,8 +633,12 @@ describe('sessions.fork', () => { }) it('inherits model selection through the completed turn and excludes later changes', async () => { - const ctx = await composed() - const source = await liveAgent(ctx, 'session-routed', 0) + const ctx = new Context() + onTestFinished(() => ctx.fiber.dispose()) + await mountAgentLoopTestDependencies(ctx) + const harness = await mountAgentLoopTestHarness(ctx) + ctx.provide('workspaceRegistry', { list: () => [], archivedSessionIds: [] } as never) + const { session: source } = await harness.create(sid('session-routed')) source.append('turn/start', { turn: 1 }) source.append('request/header', { header: { @@ -666,6 +670,5 @@ describe('sessions.fork', () => { expect(child.model.current()).toEqual({ provider: 'inherited-provider', model: 'inherited-model', reasoningEffort: 'high', }) - await ctx.fiber.dispose() }) }) diff --git a/packages/core/agent-loop/tests/config-session-id.spec.ts b/packages/core/agent-loop/tests/config-session-id.spec.ts index b7da13c624..d24e7f14d1 100644 --- a/packages/core/agent-loop/tests/config-session-id.spec.ts +++ b/packages/core/agent-loop/tests/config-session-id.spec.ts @@ -481,6 +481,7 @@ describe('config-driven session id', () => { }) it.each([ + { label: 'preserves the recorded effort on the same configured route', route: { provider: 'mock', model: 'mock' }, expected: { provider: 'mock', model: 'mock', reasoningEffort: 'high' }, appended: 0 }, { label: 'applies an explicit configured effort', route: { provider: 'mock', model: 'mock', reasoningEffort: ReasoningEffortId('low') }, expected: { provider: 'mock', model: 'mock', reasoningEffort: 'low' }, appended: 1 }, { label: 'keeps the recorded choice without a configured route', route: {}, expected: { provider: 'mock', model: 'mock', reasoningEffort: 'high' }, appended: 0 }, ])('config-driven resume $label', async ({ route, expected, appended }) => { diff --git a/packages/test-support/agent-loop-testkit/tests/agent-loop-testkit.spec.ts b/packages/test-support/agent-loop-testkit/tests/agent-loop-testkit.spec.ts index 7cdc154c72..ecf4721e3f 100644 --- a/packages/test-support/agent-loop-testkit/tests/agent-loop-testkit.spec.ts +++ b/packages/test-support/agent-loop-testkit/tests/agent-loop-testkit.spec.ts @@ -12,6 +12,7 @@ import { mountAgentLoopTestDependencies, mountAgentLoopTestHarness, unsupportedInbox, + unsupportedModel, } from '../src/index.ts' function message(text: string) { @@ -19,6 +20,14 @@ function message(text: string) { } describe('dsh-agent-loop-testkit', () => { + it('rejects reads and changes through an unsupported Agent stub Model', () => { + const model = unsupportedModel() + + expect(() => model.current()).toThrow('this test Agent does not support model selection') + expect(() => { model.select({ provider: 'mock', model: 'mock' }) }) + .toThrow('this test Agent does not support model selection') + }) + it('rejects mutations through an unsupported Agent stub Inbox', () => { const inbox = unsupportedInbox()