mirror of
https://github.com/xinnan-tech/xiaozhi-esp32-server.git
synced 2026-07-25 00:23:53 +08:00
fix: 修复智能体快照基线与查看逻辑
This commit is contained in:
@@ -10,7 +10,11 @@ import xiaozhi.modules.agent.entity.AgentSnapshotEntity;
|
||||
public interface AgentSnapshotDao extends BaseDao<AgentSnapshotEntity> {
|
||||
Integer selectMaxVersionNo(@Param("agentId") String agentId);
|
||||
|
||||
AgentSnapshotEntity selectLatestSnapshot(@Param("agentId") String agentId);
|
||||
|
||||
AgentSnapshotEntity selectNextSnapshot(@Param("agentId") String agentId, @Param("versionNo") Integer versionNo);
|
||||
|
||||
int insertWithNextVersion(@Param("snapshot") AgentSnapshotEntity snapshot);
|
||||
|
||||
int deleteOlderThanKeepLimit(@Param("agentId") String agentId, @Param("keepLimit") int keepLimit);
|
||||
}
|
||||
|
||||
+1
-2
@@ -3,12 +3,11 @@ package xiaozhi.modules.agent.service;
|
||||
import xiaozhi.common.page.PageData;
|
||||
import xiaozhi.common.service.BaseService;
|
||||
import xiaozhi.modules.agent.dto.AgentSnapshotPageDTO;
|
||||
import xiaozhi.modules.agent.dto.AgentUpdateDTO;
|
||||
import xiaozhi.modules.agent.entity.AgentSnapshotEntity;
|
||||
import xiaozhi.modules.agent.vo.AgentSnapshotVO;
|
||||
|
||||
public interface AgentSnapshotService extends BaseService<AgentSnapshotEntity> {
|
||||
void createSnapshot(String agentId, String source, AgentUpdateDTO pendingUpdate);
|
||||
void createSnapshot(String agentId, String source);
|
||||
|
||||
PageData<AgentSnapshotVO> page(String agentId, AgentSnapshotPageDTO params);
|
||||
|
||||
|
||||
+13
-11
@@ -311,15 +311,14 @@ public class AgentServiceImpl extends BaseServiceImpl<AgentDao, AgentEntity> imp
|
||||
|
||||
// 锁定后查询现有实体和关联配置
|
||||
AgentEntity existingEntity = this.getAgentById(agentId);
|
||||
|
||||
if (createSnapshot) {
|
||||
agentSnapshotService.createSnapshot(agentId, "config", dto);
|
||||
if (createSnapshot && agentSnapshotService.getCurrentVersionNo(agentId) == 0) {
|
||||
agentSnapshotService.createSnapshot(agentId, "initial");
|
||||
}
|
||||
|
||||
// 只更新提供的非空字段
|
||||
if (dto.getAgentName() != null) {
|
||||
existingEntity.setAgentName(dto.getAgentName());
|
||||
}
|
||||
if (dto.getAgentName() != null) {
|
||||
existingEntity.setAgentName(dto.getAgentName());
|
||||
}
|
||||
if (dto.getAgentCode() != null) {
|
||||
existingEntity.setAgentCode(dto.getAgentCode());
|
||||
}
|
||||
@@ -470,11 +469,14 @@ public class AgentServiceImpl extends BaseServiceImpl<AgentDao, AgentEntity> imp
|
||||
}
|
||||
|
||||
boolean b = validateLLMIntentParams(existingEntity.getLlmModelId(), existingEntity.getIntentModelId());
|
||||
if (!b) {
|
||||
throw new RenException(ErrorCode.LLM_INTENT_PARAMS_MISMATCH);
|
||||
}
|
||||
this.updateById(existingEntity);
|
||||
}
|
||||
if (!b) {
|
||||
throw new RenException(ErrorCode.LLM_INTENT_PARAMS_MISMATCH);
|
||||
}
|
||||
this.updateById(existingEntity);
|
||||
if (createSnapshot) {
|
||||
agentSnapshotService.createSnapshot(agentId, "config");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证大语言模型和意图识别的参数是否符合匹配
|
||||
|
||||
+27
-31
@@ -82,11 +82,20 @@ public class AgentSnapshotServiceImpl extends BaseServiceImpl<AgentSnapshotDao,
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void createSnapshot(String agentId, String source, AgentUpdateDTO pendingUpdate) {
|
||||
public void createSnapshot(String agentId, String source) {
|
||||
lockAgent(agentId);
|
||||
AgentInfoVO agent = getAgentInfo(agentId);
|
||||
AgentSnapshotDataDTO snapshotData = buildSnapshotData(agent);
|
||||
List<String> changedFields = getChangedFields(snapshotData, pendingUpdate);
|
||||
if (pendingUpdate != null && changedFields.isEmpty()) {
|
||||
AgentSnapshotEntity previousSnapshot = agentSnapshotDao.selectLatestSnapshot(agentId);
|
||||
List<String> changedFields;
|
||||
if (previousSnapshot == null) {
|
||||
changedFields = List.of("initial");
|
||||
} else {
|
||||
AgentSnapshotDataDTO previousData = JsonUtils.parseObject(previousSnapshot.getSnapshotData(),
|
||||
AgentSnapshotDataDTO.class);
|
||||
changedFields = getChangedFields(previousData, snapshotData);
|
||||
}
|
||||
if (changedFields.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -96,7 +105,6 @@ public class AgentSnapshotServiceImpl extends BaseServiceImpl<AgentSnapshotDao,
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public PageData<AgentSnapshotVO> page(String agentId, AgentSnapshotPageDTO params) {
|
||||
ensureInitialSnapshot(agentId);
|
||||
AgentSnapshotPageDTO pageParams = params == null ? new AgentSnapshotPageDTO() : params;
|
||||
Page<AgentSnapshotEntity> page = new Page<>(pageParams.pageOrDefault(), pageParams.limitOrDefault());
|
||||
page.addOrder(OrderItem.desc("version_no"));
|
||||
@@ -134,8 +142,9 @@ public class AgentSnapshotServiceImpl extends BaseServiceImpl<AgentSnapshotDao,
|
||||
AgentSnapshotDataDTO currentData = buildSnapshotData(currentAgent);
|
||||
AgentSnapshotDataDTO restoreData = preserveCurrentSensitiveValues(data, currentData);
|
||||
List<String> changedFields = getChangedFields(currentData, restoreData);
|
||||
insertSnapshot(agentId, currentAgent.getUserId(), "restore", currentData, changedFields,
|
||||
snapshot.getId(), snapshot.getVersionNo());
|
||||
if (changedFields.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
applyAgentFields(agent, restoreData);
|
||||
validateRestoreParams(agent);
|
||||
@@ -148,6 +157,8 @@ public class AgentSnapshotServiceImpl extends BaseServiceImpl<AgentSnapshotDao,
|
||||
restoreContextProviders(agentId, restoreData.getContextProviders());
|
||||
correctWordFileService.saveAgentCorrectWords(agentId, nullToEmpty(restoreData.getCorrectWordFileIds()));
|
||||
restoreTags(agentId, restoreData);
|
||||
insertSnapshot(agentId, currentAgent.getUserId(), "restore", restoreData, changedFields,
|
||||
snapshot.getId(), snapshot.getVersionNo());
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -164,7 +175,7 @@ public class AgentSnapshotServiceImpl extends BaseServiceImpl<AgentSnapshotDao,
|
||||
@Override
|
||||
public Integer getCurrentVersionNo(String agentId) {
|
||||
Integer maxVersionNo = agentSnapshotDao.selectMaxVersionNo(agentId);
|
||||
return (maxVersionNo == null ? 0 : maxVersionNo) + 1;
|
||||
return maxVersionNo == null ? 0 : maxVersionNo;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -181,9 +192,9 @@ public class AgentSnapshotServiceImpl extends BaseServiceImpl<AgentSnapshotDao,
|
||||
private void insertSnapshot(String agentId, Long userId, String source, AgentSnapshotDataDTO snapshotData,
|
||||
List<String> changedFields, String restoreFromSnapshotId, Integer restoreFromVersionNo) {
|
||||
AgentSnapshotEntity entity = new AgentSnapshotEntity();
|
||||
entity.setId(UUID.randomUUID().toString().replace("-", ""));
|
||||
entity.setAgentId(agentId);
|
||||
entity.setUserId(userId);
|
||||
entity.setVersionNo(allocateVersionNo(agentId));
|
||||
entity.setSnapshotData(JsonUtils.toJsonString(redactSnapshotData(snapshotData)));
|
||||
entity.setChangedFields(JsonUtils.toJsonString(changedFields));
|
||||
entity.setSource(StringUtils.defaultIfBlank(source, "config"));
|
||||
@@ -191,39 +202,23 @@ public class AgentSnapshotServiceImpl extends BaseServiceImpl<AgentSnapshotDao,
|
||||
entity.setRestoreFromVersionNo(restoreFromVersionNo);
|
||||
entity.setCreator(SecurityUser.getUserId());
|
||||
entity.setCreatedAt(new Date());
|
||||
agentSnapshotDao.insert(entity);
|
||||
int inserted = agentSnapshotDao.insertWithNextVersion(entity);
|
||||
if (inserted != 1) {
|
||||
throw new RenException("快照版本号生成失败");
|
||||
}
|
||||
pruneSnapshots(agentId);
|
||||
}
|
||||
|
||||
private void ensureInitialSnapshot(String agentId) {
|
||||
Integer maxVersionNo = agentSnapshotDao.selectMaxVersionNo(agentId);
|
||||
if (maxVersionNo != null && maxVersionNo > 0) {
|
||||
return;
|
||||
}
|
||||
AgentEntity agent = agentDao.selectByIdForUpdate(agentId);
|
||||
if (agent == null) {
|
||||
private void lockAgent(String agentId) {
|
||||
if (agentDao.selectByIdForUpdate(agentId) == null) {
|
||||
throw new RenException(ErrorCode.AGENT_NOT_FOUND);
|
||||
}
|
||||
maxVersionNo = agentSnapshotDao.selectMaxVersionNo(agentId);
|
||||
if (maxVersionNo != null && maxVersionNo > 0) {
|
||||
return;
|
||||
}
|
||||
AgentInfoVO agentInfo = getAgentInfo(agentId);
|
||||
insertSnapshot(agentId, agentInfo.getUserId(), "initial", buildSnapshotData(agentInfo), List.of("initial"));
|
||||
}
|
||||
|
||||
private void pruneSnapshots(String agentId) {
|
||||
agentSnapshotDao.deleteOlderThanKeepLimit(agentId, MAX_SNAPSHOTS_PER_AGENT);
|
||||
}
|
||||
|
||||
private Integer allocateVersionNo(String agentId) {
|
||||
Integer maxVersionNo = agentSnapshotDao.selectMaxVersionNo(agentId);
|
||||
if (maxVersionNo == null || maxVersionNo < 0) {
|
||||
throw new RenException("快照版本号生成失败");
|
||||
}
|
||||
return maxVersionNo + 1;
|
||||
}
|
||||
|
||||
private AgentSnapshotEntity getSnapshotEntity(String agentId, String snapshotId) {
|
||||
AgentSnapshotEntity entity = selectById(snapshotId);
|
||||
if (entity == null || !Objects.equals(agentId, entity.getAgentId())) {
|
||||
@@ -434,7 +429,8 @@ public class AgentSnapshotServiceImpl extends BaseServiceImpl<AgentSnapshotDao,
|
||||
}
|
||||
map.forEach((key, value) -> {
|
||||
if (key != null) {
|
||||
result.put(String.valueOf(key), normalizeValue(value));
|
||||
String keyText = String.valueOf(key);
|
||||
result.put(keyText, isSensitiveKey(keyText) ? SECRET_PLACEHOLDER : normalizeValue(value));
|
||||
}
|
||||
});
|
||||
return result;
|
||||
|
||||
@@ -1,17 +1,20 @@
|
||||
-- liquibase formatted sql
|
||||
|
||||
-- changeset xiaozhi:202607071530
|
||||
-- changeset tykechen:202607071530
|
||||
CREATE TABLE IF NOT EXISTS `ai_agent_snapshot` (
|
||||
`id` VARCHAR(32) NOT NULL COMMENT '快照ID',
|
||||
`agent_id` VARCHAR(32) NOT NULL COMMENT '智能体ID',
|
||||
`user_id` BIGINT DEFAULT NULL COMMENT '所属用户ID',
|
||||
`version_no` INT UNSIGNED NOT NULL COMMENT '版本号',
|
||||
`snapshot_data` JSON NOT NULL COMMENT '快照数据',
|
||||
`snapshot_data` JSON NOT NULL DEFAULT (JSON_OBJECT()) COMMENT '快照数据',
|
||||
`changed_fields` JSON DEFAULT NULL COMMENT '变更字段',
|
||||
`source` VARCHAR(32) DEFAULT 'config' COMMENT '快照来源',
|
||||
`restore_from_snapshot_id` VARCHAR(32) DEFAULT NULL COMMENT '恢复来源快照ID',
|
||||
`restore_from_version_no` INT UNSIGNED DEFAULT NULL COMMENT '恢复来源版本号',
|
||||
`creator` BIGINT DEFAULT NULL COMMENT '创建者',
|
||||
`created_at` DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `uk_agent_version` (`agent_id`, `version_no`),
|
||||
INDEX `idx_agent_created_at` (`agent_id`, `created_at`)
|
||||
INDEX `idx_agent_created_at` (`agent_id`, `created_at`),
|
||||
INDEX `idx_snapshot_user_created_at` (`user_id`, `created_at`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='智能体配置快照表';
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
-- liquibase formatted sql
|
||||
|
||||
-- changeset codex:202607081150
|
||||
ALTER TABLE `ai_agent_snapshot`
|
||||
ADD COLUMN `restore_from_snapshot_id` VARCHAR(32) DEFAULT NULL COMMENT '恢复来源快照ID' AFTER `source`,
|
||||
ADD COLUMN `restore_from_version_no` INT UNSIGNED DEFAULT NULL COMMENT '恢复来源版本号' AFTER `restore_from_snapshot_id`,
|
||||
ADD INDEX `idx_snapshot_user_created_at` (`user_id`, `created_at`);
|
||||
@@ -1,5 +0,0 @@
|
||||
-- liquibase formatted sql
|
||||
|
||||
-- changeset codex:202607081230
|
||||
ALTER TABLE `ai_agent_snapshot`
|
||||
ALTER COLUMN `snapshot_data` SET DEFAULT (JSON_OBJECT());
|
||||
@@ -699,22 +699,8 @@ databaseChangeLog:
|
||||
path: classpath:db/changelog/202607011405.sql
|
||||
- changeSet:
|
||||
id: 202607071530
|
||||
author: Codex
|
||||
author: tykechen
|
||||
changes:
|
||||
- sqlFile:
|
||||
encoding: utf8
|
||||
path: classpath:db/changelog/202607071530.sql
|
||||
- changeSet:
|
||||
id: 202607081150
|
||||
author: Codex
|
||||
changes:
|
||||
- sqlFile:
|
||||
encoding: utf8
|
||||
path: classpath:db/changelog/202607081150.sql
|
||||
- changeSet:
|
||||
id: 202607081230
|
||||
author: Codex
|
||||
changes:
|
||||
- sqlFile:
|
||||
encoding: utf8
|
||||
path: classpath:db/changelog/202607081230.sql
|
||||
|
||||
@@ -8,6 +8,24 @@
|
||||
WHERE agent_id = #{agentId}
|
||||
</select>
|
||||
|
||||
<select id="selectLatestSnapshot" resultType="xiaozhi.modules.agent.entity.AgentSnapshotEntity">
|
||||
SELECT id,
|
||||
agent_id AS agentId,
|
||||
user_id AS userId,
|
||||
version_no AS versionNo,
|
||||
snapshot_data AS snapshotData,
|
||||
changed_fields AS changedFields,
|
||||
source,
|
||||
restore_from_snapshot_id AS restoreFromSnapshotId,
|
||||
restore_from_version_no AS restoreFromVersionNo,
|
||||
creator,
|
||||
created_at AS createdAt
|
||||
FROM ai_agent_snapshot
|
||||
WHERE agent_id = #{agentId}
|
||||
ORDER BY version_no DESC
|
||||
LIMIT 1
|
||||
</select>
|
||||
|
||||
<select id="selectNextSnapshot" resultType="xiaozhi.modules.agent.entity.AgentSnapshotEntity">
|
||||
SELECT id,
|
||||
agent_id AS agentId,
|
||||
@@ -27,6 +45,35 @@
|
||||
LIMIT 1
|
||||
</select>
|
||||
|
||||
<insert id="insertWithNextVersion">
|
||||
INSERT INTO ai_agent_snapshot (
|
||||
id,
|
||||
agent_id,
|
||||
user_id,
|
||||
version_no,
|
||||
snapshot_data,
|
||||
changed_fields,
|
||||
source,
|
||||
restore_from_snapshot_id,
|
||||
restore_from_version_no,
|
||||
creator,
|
||||
created_at
|
||||
)
|
||||
SELECT #{snapshot.id},
|
||||
#{snapshot.agentId},
|
||||
#{snapshot.userId},
|
||||
COALESCE(MAX(version_no), 0) + 1,
|
||||
#{snapshot.snapshotData},
|
||||
#{snapshot.changedFields},
|
||||
#{snapshot.source},
|
||||
#{snapshot.restoreFromSnapshotId},
|
||||
#{snapshot.restoreFromVersionNo},
|
||||
#{snapshot.creator},
|
||||
#{snapshot.createdAt}
|
||||
FROM ai_agent_snapshot
|
||||
WHERE agent_id = #{snapshot.agentId}
|
||||
</insert>
|
||||
|
||||
<delete id="deleteOlderThanKeepLimit">
|
||||
DELETE FROM ai_agent_snapshot
|
||||
WHERE agent_id = #{agentId}
|
||||
|
||||
+94
-24
@@ -6,10 +6,11 @@ import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyInt;
|
||||
import static org.mockito.ArgumentMatchers.argThat;
|
||||
import static org.mockito.Mockito.inOrder;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
@@ -25,6 +26,8 @@ import java.util.Map;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.InOrder;
|
||||
import org.springframework.test.util.ReflectionTestUtils;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import xiaozhi.common.utils.JsonUtils;
|
||||
@@ -41,6 +44,7 @@ import xiaozhi.modules.agent.entity.AgentEntity;
|
||||
import xiaozhi.modules.agent.entity.AgentSnapshotEntity;
|
||||
import xiaozhi.modules.agent.entity.AgentTagEntity;
|
||||
import xiaozhi.modules.agent.service.AgentContextProviderService;
|
||||
import xiaozhi.modules.agent.service.AgentSnapshotService;
|
||||
import xiaozhi.modules.agent.vo.AgentSnapshotVO;
|
||||
import xiaozhi.modules.agent.vo.AgentInfoVO;
|
||||
import xiaozhi.modules.correctword.service.CorrectWordFileService;
|
||||
@@ -178,6 +182,40 @@ class AgentSnapshotServiceImplTest {
|
||||
assertFalse(roleConfig.contains("this.handleSaveAgentTags(agentId, tagNames)"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void firstAgentSaveKeepsInitialBaselineBeforePersistingNewState() {
|
||||
AgentDao agentDao = mock(AgentDao.class);
|
||||
AgentContextProviderService contextProviderService = mock(AgentContextProviderService.class);
|
||||
CorrectWordFileService correctWordFileService = mock(CorrectWordFileService.class);
|
||||
AgentSnapshotService snapshotService = mock(AgentSnapshotService.class);
|
||||
AgentServiceImpl service = new AgentServiceImpl(agentDao, null, null, null, null, null, null, null,
|
||||
null, null, contextProviderService, null, correctWordFileService, snapshotService);
|
||||
ReflectionTestUtils.setField(service, "baseDao", agentDao);
|
||||
|
||||
String agentId = "agent-id";
|
||||
AgentEntity lockedAgent = new AgentEntity();
|
||||
lockedAgent.setId(agentId);
|
||||
AgentInfoVO currentAgent = new AgentInfoVO();
|
||||
currentAgent.setId(agentId);
|
||||
currentAgent.setAgentName("old-name");
|
||||
AgentUpdateDTO update = new AgentUpdateDTO();
|
||||
update.setAgentName("new-name");
|
||||
|
||||
when(agentDao.selectByIdForUpdate(agentId)).thenReturn(lockedAgent);
|
||||
when(agentDao.selectAgentInfoById(agentId)).thenReturn(currentAgent);
|
||||
when(snapshotService.getCurrentVersionNo(agentId)).thenReturn(0);
|
||||
when(contextProviderService.getByAgentId(agentId)).thenReturn(null);
|
||||
when(correctWordFileService.getAgentCorrectWordFileIds(agentId)).thenReturn(List.of());
|
||||
when(agentDao.updateById(any())).thenReturn(1);
|
||||
|
||||
service.updateAgentById(agentId, update);
|
||||
|
||||
InOrder inOrder = inOrder(agentDao, snapshotService);
|
||||
inOrder.verify(snapshotService).createSnapshot(agentId, "initial");
|
||||
inOrder.verify(agentDao).updateById(argThat(agent -> "new-name".equals(agent.getAgentName())));
|
||||
inOrder.verify(snapshotService).createSnapshot(agentId, "config");
|
||||
}
|
||||
|
||||
@Test
|
||||
void agentSnapshotFieldAppliesRestorableAgentFields() {
|
||||
AgentSnapshotDataDTO data = new AgentSnapshotDataDTO();
|
||||
@@ -218,40 +256,38 @@ class AgentSnapshotServiceImplTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
void versionAllocationDoesNotDependOnSequenceTable() throws Exception {
|
||||
String xml = Files.readString(Path.of("src/main/resources/mapper/agent/AgentSnapshotDao.xml"));
|
||||
void snapshotInsertAllocatesVersionInSingleSqlStatement() throws Exception {
|
||||
String xml = normalizeWhitespace(Files.readString(Path.of("src/main/resources/mapper/agent/AgentSnapshotDao.xml")));
|
||||
String dao = Files.readString(Path.of("src/main/java/xiaozhi/modules/agent/dao/AgentSnapshotDao.java"));
|
||||
String createMigration = Files.readString(Path.of("src/main/resources/db/changelog/202607071530.sql"));
|
||||
String master = Files.readString(Path.of("src/main/resources/db/changelog/db.changelog-master.yaml"));
|
||||
|
||||
assertFalse(xml.contains("ai_agent_snapshot_sequence"));
|
||||
assertTrue(dao.contains("insertWithNextVersion"));
|
||||
assertTrue(xml.contains("<insert id=\"insertWithNextVersion\">"));
|
||||
assertTrue(xml.contains("INSERT INTO ai_agent_snapshot"));
|
||||
assertTrue(xml.contains("COALESCE(MAX(version_no), 0) + 1"));
|
||||
assertFalse(xml.contains("LAST_INSERT_ID"));
|
||||
assertFalse(dao.contains("allocateVersionNo"));
|
||||
assertFalse(dao.contains("selectLastInsertId"));
|
||||
assertFalse(createMigration.contains("ai_agent_snapshot_sequence"));
|
||||
assertFalse(master.contains("202607081430.sql"));
|
||||
assertFalse(xml.contains("ai_agent_snapshot_sequence"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void snapshotMigrationAddsRestoreTraceAndUserIndex() throws Exception {
|
||||
String sql = Files.readString(Path.of("src/main/resources/db/changelog/202607081150.sql"));
|
||||
String defaultSql = Files.readString(Path.of("src/main/resources/db/changelog/202607081230.sql"));
|
||||
void snapshotMigrationIsMergedIntoSingleChangeLog() throws Exception {
|
||||
String sql = Files.readString(Path.of("src/main/resources/db/changelog/202607071530.sql"));
|
||||
String master = Files.readString(Path.of("src/main/resources/db/changelog/db.changelog-master.yaml"));
|
||||
|
||||
assertTrue(sql.contains("restore_from_snapshot_id"));
|
||||
assertTrue(sql.contains("restore_from_version_no"));
|
||||
assertTrue(sql.contains("idx_snapshot_user_created_at"));
|
||||
assertTrue(master.contains("202607081150.sql"));
|
||||
assertTrue(defaultSql.contains("DEFAULT (JSON_OBJECT())"));
|
||||
assertTrue(master.contains("202607081230.sql"));
|
||||
assertTrue(sql.contains("DEFAULT (JSON_OBJECT())"));
|
||||
assertTrue(master.contains("202607071530.sql"));
|
||||
assertFalse(master.contains("202607081150.sql"));
|
||||
assertFalse(master.contains("202607081230.sql"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void selectNextSnapshotLoadsRestoreTraceColumns() throws Exception {
|
||||
String xml = Files.readString(Path.of("src/main/resources/mapper/agent/AgentSnapshotDao.xml"));
|
||||
String xml = normalizeWhitespace(Files.readString(Path.of("src/main/resources/mapper/agent/AgentSnapshotDao.xml")));
|
||||
|
||||
assertTrue(xml.contains("restore_from_snapshot_id AS restoreFromSnapshotId"));
|
||||
assertTrue(xml.contains("restore_from_version_no AS restoreFromVersionNo"));
|
||||
assertTrue(xml.contains("restore_from_version_no AS restoreFromVersionNo"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -296,7 +332,35 @@ class AgentSnapshotServiceImplTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
void pageCreatesInitialSnapshotWhenAgentHasNoHistory() {
|
||||
void pageDoesNotCreateInitialSnapshotWhenAgentHasNoHistory() {
|
||||
AgentSnapshotDao snapshotDao = mock(AgentSnapshotDao.class);
|
||||
AgentDao agentDao = mock(AgentDao.class);
|
||||
AgentSnapshotServiceImpl service = new AgentSnapshotServiceImpl(snapshotDao, agentDao, null, null, null,
|
||||
null, null, null, null, null);
|
||||
|
||||
when(snapshotDao.selectPage(any(), any())).thenReturn(new Page<AgentSnapshotEntity>(1, 10));
|
||||
|
||||
service.page("agent-id", new AgentSnapshotPageDTO());
|
||||
|
||||
verify(agentDao, never()).selectByIdForUpdate(any());
|
||||
verify(snapshotDao, never()).selectMaxVersionNo(any());
|
||||
verify(snapshotDao, never()).insertWithNextVersion(any());
|
||||
verify(snapshotDao, never()).deleteOlderThanKeepLimit(any(), anyInt());
|
||||
}
|
||||
|
||||
@Test
|
||||
void getCurrentVersionNoReturnsPersistedMaxVersion() {
|
||||
AgentSnapshotDao snapshotDao = mock(AgentSnapshotDao.class);
|
||||
AgentSnapshotServiceImpl service = new AgentSnapshotServiceImpl(snapshotDao, null, null, null, null,
|
||||
null, null, null, null, null);
|
||||
|
||||
when(snapshotDao.selectMaxVersionNo("agent-id")).thenReturn(7);
|
||||
|
||||
assertEquals(7, service.getCurrentVersionNo("agent-id"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void createSnapshotStoresCurrentStateAsInitialVersionWhenHistoryIsEmpty() {
|
||||
AgentSnapshotDao snapshotDao = mock(AgentSnapshotDao.class);
|
||||
AgentDao agentDao = mock(AgentDao.class);
|
||||
AgentTagDao tagDao = mock(AgentTagDao.class);
|
||||
@@ -311,17 +375,19 @@ class AgentSnapshotServiceImplTest {
|
||||
agentInfo.setId("agent-id");
|
||||
agentInfo.setUserId(7L);
|
||||
agentInfo.setAgentName("agent");
|
||||
when(snapshotDao.selectMaxVersionNo("agent-id")).thenReturn(0, 0, 0);
|
||||
when(agentDao.selectByIdForUpdate("agent-id")).thenReturn(lockedAgent);
|
||||
when(agentDao.selectAgentInfoById("agent-id")).thenReturn(agentInfo);
|
||||
when(snapshotDao.selectLatestSnapshot("agent-id")).thenReturn(null);
|
||||
when(snapshotDao.insertWithNextVersion(any())).thenReturn(1);
|
||||
when(correctWordFileService.getAgentCorrectWordFileIds("agent-id")).thenReturn(List.of());
|
||||
when(contextProviderService.getByAgentId("agent-id")).thenReturn(null);
|
||||
when(tagDao.selectByAgentId("agent-id")).thenReturn(List.of());
|
||||
when(snapshotDao.selectPage(any(), any())).thenReturn(new Page<AgentSnapshotEntity>(1, 10));
|
||||
|
||||
service.page("agent-id", new AgentSnapshotPageDTO());
|
||||
service.createSnapshot("agent-id", "config");
|
||||
|
||||
verify(snapshotDao, times(3)).selectMaxVersionNo("agent-id");
|
||||
verify(snapshotDao).insert(argThat(snapshot -> Integer.valueOf(1).equals(snapshot.getVersionNo())));
|
||||
verify(snapshotDao).insertWithNextVersion(argThat(snapshot -> "config".equals(snapshot.getSource())
|
||||
&& snapshot.getVersionNo() == null
|
||||
&& snapshot.getChangedFields().contains("initial")));
|
||||
verify(snapshotDao).deleteOlderThanKeepLimit("agent-id", 100);
|
||||
}
|
||||
|
||||
@@ -377,4 +443,8 @@ class AgentSnapshotServiceImplTest {
|
||||
data.setContextProviders(List.of(provider));
|
||||
return data;
|
||||
}
|
||||
|
||||
private String normalizeWhitespace(String value) {
|
||||
return value.replaceAll("\\s+", " ").trim();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user