mateclaw/mateclaw-server/src/test/java/vip/mate/agent/AgentServiceUniquenessTest.java
matevip 0ffc224623 chore(test): backfill mateclaw-server/src/test/ that earlier PRIVATE_ITEMS pattern accidentally excluded
The PRIVATE_ITEMS list contained the bare 'test' entry, which rsync
interprets as 'any directory named test at any depth' — so it caught
the root-level /test/ scratch directory (intended) AND every src/test/
under each module (not intended).

Pattern is already anchored to /test (root-only). This commit rsyncs
the accumulated src/test/ tree forward so opensource has the unit tests
that have been written / updated against existing src/main/ code since
the pattern regression. Going forward each per-commit sync will carry
src/test/ files along with the main change.
2026-05-12 17:38:08 +08:00

146 lines
5.9 KiB
Java
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package vip.mate.agent;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.TestPropertySource;
import vip.mate.MateClawApplication;
import vip.mate.agent.model.AgentEntity;
import vip.mate.exception.MateClawException;
import java.util.concurrent.atomic.AtomicLong;
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
/**
* Pre-flight {@code (workspace_id, name)} uniqueness check that
* accompanies the V102 unique index.
*
* <p>The DB index alone would surface as {@code DataIntegrityViolation}
* with a vendor-specific message; the service-layer pre-check converts
* that to a stable {@code err.agent.duplicate_name} business code so the
* UI can show a localized message and clients can branch deterministically.
* These tests pin both the rejection paths and the false-positive guard
* (a same-name UPDATE on the row itself must not block).
*/
@SpringBootTest(
classes = MateClawApplication.class,
webEnvironment = SpringBootTest.WebEnvironment.NONE
)
@TestPropertySource(properties = {
"spring.datasource.url=jdbc:h2:mem:agent_unique_test_${random.uuid};MODE=MySQL;DATABASE_TO_LOWER=TRUE;CASE_INSENSITIVE_IDENTIFIERS=TRUE;DB_CLOSE_DELAY=-1",
"spring.ai.dashscope.api-key=test-key",
"spring.main.web-application-type=none"
})
class AgentServiceUniquenessTest {
/**
* Tests share one in-memory DB instance (a single
* {@code @SpringBootTest} class shares its application context across
* methods). Workspace ids are derived from this counter so concurrent
* methods can't trample one another's rows.
*/
private static final AtomicLong WS_SEQ = new AtomicLong(50_000L);
@Autowired
private AgentService agentService;
private long workspaceA;
private long workspaceB;
@BeforeEach
void setUp() {
workspaceA = WS_SEQ.getAndIncrement();
workspaceB = WS_SEQ.getAndIncrement();
}
private AgentEntity newAgent(String name, long workspaceId) {
AgentEntity a = new AgentEntity();
a.setName(name);
a.setDescription("uniqueness test agent");
a.setAgentType("react");
a.setSystemPrompt("");
a.setMaxIterations(10);
a.setWorkspaceId(workspaceId);
return a;
}
@Test
@DisplayName("createAgent 拒绝同 workspace 同名body code = 409 / msgKey = err.agent.duplicate_name")
void createRejectsDuplicateNameInSameWorkspace() {
agentService.createAgent(newAgent("Alpha", workspaceA));
MateClawException ex = assertThrows(MateClawException.class,
() -> agentService.createAgent(newAgent("Alpha", workspaceA)));
assertEquals(409, ex.getCode(), "应返回 409 业务码");
assertEquals("err.agent.duplicate_name", ex.getMsgKey());
}
@Test
@DisplayName("createAgent 允许不同 workspace 同名(隔离边界生效)")
void createAllowsSameNameInDifferentWorkspace() {
AgentEntity a = agentService.createAgent(newAgent("Bravo", workspaceA));
AgentEntity b = agentService.createAgent(newAgent("Bravo", workspaceB));
assertNotNull(a.getId());
assertNotNull(b.getId());
assertNotEquals(a.getId(), b.getId());
}
@Test
@DisplayName("createAgent 拒绝空名fail-fast 在 unique 检查之前)")
void createRejectsBlankName() {
AgentEntity blank = newAgent(null, workspaceA);
MateClawException ex = assertThrows(MateClawException.class,
() -> agentService.createAgent(blank));
assertEquals(400, ex.getCode());
assertEquals("err.agent.name_required", ex.getMsgKey());
}
@Test
@DisplayName("updateAgent 拒绝把名字改成 workspace 内已有的别人")
void updateRejectsRenamingToExistingName() {
AgentEntity first = agentService.createAgent(newAgent("Charlie", workspaceA));
AgentEntity second = agentService.createAgent(newAgent("Delta", workspaceA));
// Try renaming "Delta" → "Charlie" inside the same workspace.
second.setName("Charlie");
MateClawException ex = assertThrows(MateClawException.class,
() -> agentService.updateAgent(second));
assertEquals(409, ex.getCode());
assertEquals("err.agent.duplicate_name", ex.getMsgKey());
// The other row must not have been touched.
assertEquals("Charlie", agentService.getAgent(first.getId()).getName());
}
@Test
@DisplayName("updateAgent 元数据修改不触发误报excludeId 跳过自己)")
void updateAllowsMetadataEditWithoutFalsePositive() {
AgentEntity created = agentService.createAgent(newAgent("Echo", workspaceA));
// Edit description only, keep the same name. The unique check
// should detect "no name change" and skip the SELECT entirely;
// even if it didn't, the excludeId branch would filter self out.
created.setDescription("edited");
assertDoesNotThrow(() -> agentService.updateAgent(created));
assertEquals("edited", agentService.getAgent(created.getId()).getDescription());
}
@Test
@DisplayName("updateAgent 改名为新值(不冲突)允许")
void updateAllowsRenameToUnusedName() {
AgentEntity created = agentService.createAgent(newAgent("Foxtrot", workspaceA));
created.setName("Foxtrot-renamed");
assertDoesNotThrow(() -> agentService.updateAgent(created));
assertEquals("Foxtrot-renamed",
agentService.getAgent(created.getId()).getName());
}
}