重构:使用 MyBatis-Flex 迁移应用 ORM
将应用自管表的 JdbcClient 数据访问迁移为实体、Mapper、构造器查询和必要的显式 SQL。 保留 AgentScope 自管表及原有业务语义,并补充事务、查询与数据库集成测试。
This commit is contained in:
@@ -0,0 +1,45 @@
|
||||
package tech.easyflow.manuagent.agent;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.mybatisflex.core.query.QueryWrapper;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import tech.easyflow.manuagent.entity.AgentEventEntity;
|
||||
import tech.easyflow.manuagent.mapper.AgentEventMapper;
|
||||
|
||||
/**
|
||||
* 验证 Agent 事件回放查询在 ORM 迁移后保持原 JDBC 字段和游标语义。
|
||||
*/
|
||||
class AgentEventServiceQueryTest {
|
||||
|
||||
/**
|
||||
* 事件回放只读取响应所需字段,不加载仅用于外部追踪的事件标识。
|
||||
*/
|
||||
@Test
|
||||
void shouldSelectOnlyEventViewColumnsWhenListingAfterCursor() {
|
||||
AgentEventMapper mapper = mock(AgentEventMapper.class);
|
||||
AgentEventEntity event = new AgentEventEntity();
|
||||
event.setId(1L);
|
||||
event.setProjectId(UUID.randomUUID());
|
||||
event.setPayloadJson("{}");
|
||||
when(mapper.selectListByQuery(any(QueryWrapper.class))).thenReturn(List.of(event));
|
||||
AgentEventService service = new AgentEventService(mapper, new ObjectMapper());
|
||||
|
||||
service.listAfter(event.getProjectId(), 0L, 100);
|
||||
|
||||
ArgumentCaptor<QueryWrapper> queryCaptor = ArgumentCaptor.forClass(QueryWrapper.class);
|
||||
verify(mapper).selectListByQuery(queryCaptor.capture());
|
||||
String sql = queryCaptor.getValue().toSQL().toLowerCase(java.util.Locale.ROOT);
|
||||
assertThat(sql)
|
||||
.contains("project_id", "run_id", "event_type", "payload", "created_at")
|
||||
.doesNotContain("event_id");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
package tech.easyflow.manuagent.agent;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.mybatisflex.core.query.QueryWrapper;
|
||||
import java.util.UUID;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import tech.easyflow.manuagent.entity.AgentRunEntity;
|
||||
import tech.easyflow.manuagent.common.ApiException;
|
||||
import tech.easyflow.manuagent.mapper.AgentEventMapper;
|
||||
import tech.easyflow.manuagent.mapper.AgentRunMapper;
|
||||
import tech.easyflow.manuagent.mapper.ModelConfigMapper;
|
||||
|
||||
/**
|
||||
* 验证 Agent 执行热路径只读取判断运行状态所需的最小列。
|
||||
*/
|
||||
class AgentRunStoreQueryTest {
|
||||
|
||||
/**
|
||||
* {@code ensureRunning} 可能被每个 Agent 检查点调用,因此不得加载 JSONB 和错误详情等整行数据。
|
||||
*/
|
||||
@Test
|
||||
void shouldSelectOnlyStatusWhenCheckingRunningState() {
|
||||
AgentRunMapper runMapper = mock(AgentRunMapper.class);
|
||||
AgentRunEntity running = new AgentRunEntity();
|
||||
running.setStatus("RUNNING");
|
||||
when(runMapper.selectOneByQuery(any(QueryWrapper.class))).thenReturn(running);
|
||||
AgentRunStore store = new AgentRunStore(
|
||||
runMapper,
|
||||
mock(AgentEventMapper.class),
|
||||
mock(ModelConfigMapper.class),
|
||||
new ObjectMapper());
|
||||
|
||||
store.ensureRunning(UUID.randomUUID());
|
||||
|
||||
ArgumentCaptor<QueryWrapper> queryCaptor = ArgumentCaptor.forClass(QueryWrapper.class);
|
||||
verify(runMapper).selectOneByQuery(queryCaptor.capture());
|
||||
String sql = queryCaptor.getValue().toSQL().toLowerCase(java.util.Locale.ROOT);
|
||||
assertThat(sql)
|
||||
.contains("status")
|
||||
.doesNotContain("pending_interrupt", "error_message", "trace_id");
|
||||
}
|
||||
|
||||
/**
|
||||
* 默认模型缺失属于数据库配置异常,不应在 ORM 迁移中新增 409 业务错误。
|
||||
*/
|
||||
@Test
|
||||
void shouldKeepMissingDefaultModelAsUnexpectedTechnicalFailure() {
|
||||
AgentRunMapper runMapper = mock(AgentRunMapper.class);
|
||||
when(runMapper.selectCountByQuery(any(QueryWrapper.class))).thenReturn(0L);
|
||||
AgentRunStore store = new AgentRunStore(
|
||||
runMapper,
|
||||
mock(AgentEventMapper.class),
|
||||
mock(ModelConfigMapper.class),
|
||||
new ObjectMapper());
|
||||
|
||||
assertThatThrownBy(() -> store.create(UUID.randomUUID(), "INITIAL", null))
|
||||
.isInstanceOf(IllegalStateException.class)
|
||||
.isNotInstanceOf(ApiException.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* 按 ID 强制读取或运行状态检查遇到不存在的 Run 时,不新增 404 或“已中断”业务语义。
|
||||
*/
|
||||
@Test
|
||||
void shouldKeepMissingRequiredRunAsUnexpectedTechnicalFailure() {
|
||||
AgentRunStore store = new AgentRunStore(
|
||||
mock(AgentRunMapper.class),
|
||||
mock(AgentEventMapper.class),
|
||||
mock(ModelConfigMapper.class),
|
||||
new ObjectMapper());
|
||||
UUID runId = UUID.randomUUID();
|
||||
|
||||
assertThatThrownBy(() -> store.require(runId))
|
||||
.isInstanceOf(IllegalStateException.class)
|
||||
.isNotInstanceOf(ApiException.class);
|
||||
assertThatThrownBy(() -> store.ensureRunning(runId))
|
||||
.isInstanceOf(IllegalStateException.class)
|
||||
.isNotInstanceOf(AgentExecutionService.RunInterruptedException.class);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user