feat: 完善智能体图片聊天与会话恢复

- 增加私有图片上传、绑定、历史回显与生命周期清理

- 支持输入草稿恢复、图片交互和模型图片能力约束

- 修复旧脏会话幂等删除与前端会话恢复
This commit is contained in:
2026-07-17 19:54:26 +08:00
parent 62d763199f
commit 1e6158be77
62 changed files with 5333 additions and 189 deletions

View File

@@ -0,0 +1,152 @@
package tech.easyflow.admin.service.agent;
import org.mockito.Mockito;
import org.testng.Assert;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;
import tech.easyflow.agent.runtime.AgentRuntimeStateCleanupService;
import tech.easyflow.agent.runtime.composer.AgentComposerDraftService;
import tech.easyflow.agent.runtime.media.AgentMediaService;
import tech.easyflow.agent.service.AgentService;
import tech.easyflow.ai.service.DocumentCollectionService;
import tech.easyflow.chatlog.domain.dto.ChatSessionSummary;
import tech.easyflow.chatlog.service.ChatSessionCommandService;
import tech.easyflow.chatlog.service.ChatSessionQueryService;
import tech.easyflow.chatlog.support.ChatJsonSupport;
import tech.easyflow.common.entity.LoginAccount;
import tech.easyflow.common.web.exceptions.BusinessException;
import tech.easyflow.system.service.ResourceAccessService;
import java.math.BigInteger;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
/**
* {@link AgentSessionService} 会话删除测试。
*/
public class AgentSessionServiceTest {
private static final BigInteger ACCOUNT_ID = BigInteger.valueOf(7);
private static final BigInteger SESSION_ID = BigInteger.valueOf(101);
private ChatSessionQueryService chatSessionQueryService;
private ChatSessionCommandService chatSessionCommandService;
private AgentRuntimeStateCleanupService agentRuntimeStateCleanupService;
private AgentMediaService agentMediaService;
private AgentComposerDraftService agentComposerDraftService;
private AgentSessionService service;
private LoginAccount account;
/**
* 初始化测试依赖。
*/
@BeforeMethod
public void setUp() {
chatSessionQueryService = mock(ChatSessionQueryService.class);
chatSessionCommandService = mock(ChatSessionCommandService.class);
agentRuntimeStateCleanupService = mock(AgentRuntimeStateCleanupService.class);
agentMediaService = mock(AgentMediaService.class);
agentComposerDraftService = mock(AgentComposerDraftService.class);
service = new AgentSessionService(
chatSessionQueryService,
chatSessionCommandService,
mock(AgentService.class),
mock(DocumentCollectionService.class),
mock(ResourceAccessService.class),
agentRuntimeStateCleanupService,
agentMediaService,
agentComposerDraftService,
mock(ChatJsonSupport.class)
);
account = new LoginAccount();
account.setId(ACCOUNT_ID);
}
/**
* 验证正常删除会清理运行态、写入删除命令并删除媒体目录。
*/
@Test
public void shouldDeleteActiveOwnedAgentSession() {
when(chatSessionQueryService.getSessionSummary(SESSION_ID))
.thenReturn(buildSession(ACCOUNT_ID, 0, "AGENT"));
service.deleteCurrentUserSession(account, SESSION_ID);
verify(agentRuntimeStateCleanupService).clearChatSession(SESSION_ID, ACCOUNT_ID);
verify(chatSessionCommandService).deleteSession(SESSION_ID, ACCOUNT_ID, ACCOUNT_ID);
verify(agentComposerDraftService).delete(AgentMediaService.MODE_FORMAL, "9", "101", account);
verify(agentMediaService).deleteFormalSession(SESSION_ID.toString(), account);
}
/**
* 验证会话已删除时重复请求仍会重试媒体目录清理并成功返回。
*/
@Test
public void shouldRetryMediaCleanupForDeletedSession() {
when(chatSessionQueryService.getSessionSummary(SESSION_ID))
.thenReturn(buildSession(ACCOUNT_ID, 1, "AGENT"));
service.deleteCurrentUserSession(account, SESSION_ID);
verify(agentRuntimeStateCleanupService, never()).clearChatSession(Mockito.any(), Mockito.any());
verify(chatSessionCommandService, never()).deleteSession(Mockito.any(), Mockito.any(), Mockito.any());
verify(agentComposerDraftService).delete(AgentMediaService.MODE_FORMAL, "9", "101", account);
verify(agentMediaService).deleteFormalSession(SESSION_ID.toString(), account);
}
/**
* 验证查询不到会话时删除保持幂等,并按当前用户目录重试媒体清理。
*/
@Test
public void shouldRetryMediaCleanupWhenSessionIsMissing() {
when(chatSessionQueryService.getSessionSummary(SESSION_ID)).thenReturn(null);
service.deleteCurrentUserSession(account, SESSION_ID);
verify(agentRuntimeStateCleanupService, never()).clearChatSession(Mockito.any(), Mockito.any());
verify(chatSessionCommandService, never()).deleteSession(Mockito.any(), Mockito.any(), Mockito.any());
verify(agentComposerDraftService, never()).delete(Mockito.any(), Mockito.any(), Mockito.any(), Mockito.any());
verify(agentMediaService).deleteFormalSession(SESSION_ID.toString(), account);
}
/**
* 验证活动会话属于其他用户时仍拒绝删除,且不执行任何清理。
*/
@Test
public void shouldRejectActiveSessionOwnedByAnotherUser() {
when(chatSessionQueryService.getSessionSummary(SESSION_ID))
.thenReturn(buildSession(BigInteger.valueOf(8), 0, "AGENT"));
BusinessException exception = Assert.expectThrows(
BusinessException.class,
() -> service.deleteCurrentUserSession(account, SESSION_ID)
);
Assert.assertEquals(exception.getMessage(), "无权访问该 Agent 会话");
verify(agentRuntimeStateCleanupService, never()).clearChatSession(Mockito.any(), Mockito.any());
verify(chatSessionCommandService, never()).deleteSession(Mockito.any(), Mockito.any(), Mockito.any());
verify(agentComposerDraftService, never()).delete(Mockito.any(), Mockito.any(), Mockito.any(), Mockito.any());
verify(agentMediaService, never()).deleteFormalSession(Mockito.any(), Mockito.any());
}
/**
* 构造会话摘要。
*
* @param userId 用户 ID
* @param isDeleted 删除标记
* @param assistantCode 助手类型
* @return 会话摘要
*/
private ChatSessionSummary buildSession(BigInteger userId, Integer isDeleted, String assistantCode) {
ChatSessionSummary summary = new ChatSessionSummary();
summary.setId(SESSION_ID);
summary.setUserId(userId);
summary.setIsDeleted(isDeleted);
summary.setAssistantCode(assistantCode);
summary.setAssistantId(BigInteger.valueOf(9));
return summary;
}
}