package vip.mate.architecture; import org.junit.jupiter.api.Test; import vip.mate.agent.graph.state.MateClawStateKeys; import java.lang.reflect.Modifier; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.util.Set; import java.util.TreeSet; import java.util.regex.Matcher; import java.util.regex.Pattern; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.fail; /** * Architecture guard — every state key declared on * {@link MateClawStateKeys} that participates in graph state (i.e. is not a * node-name constant) MUST be registered in * {@link vip.mate.agent.AgentGraphBuilder}'s {@code KeyStrategyFactory} * for at least one of the two graphs (ReAct + Plan-Execute). * *
Regression rationale: the post-deploy bug where {@code CHAT_ORIGIN} was * declared on {@link MateClawStateKeys} but missing from both * {@code KeyStrategyFactory} blocks shipped silently, and {@code spring-ai-alibaba-graph} * dropped the key on multi-node merges, causing the channel-binding flakiness * reported by the user. This test parses the source of * {@code AgentGraphBuilder.java} for all * {@code .addStrategy(MateClawStateKeys.X, ...)} mentions and asserts the * coverage so the same kind of "forgot to register" can never ship again. * *
Excluded by suffix: any constant whose name ends with {@code _NODE} —
* those are graph-node identifiers used by {@code addNode(...)}, not state
* keys.
*/
class StateKeyRegistrationCoverageTest {
private static final Pattern ADD_STRATEGY = Pattern.compile(
"\\.addStrategy\\(\\s*MateClawStateKeys\\.([A-Z_]+)");
@Test
void everyStateKeyMustBeRegisteredInKeyStrategyFactory() throws Exception {
// Read the AgentGraphBuilder source — relative to mateclaw-server module root.
Path source = Paths.get("src/main/java/vip/mate/agent/AgentGraphBuilder.java")
.toAbsolutePath();
if (!Files.exists(source)) {
fail("Cannot find AgentGraphBuilder.java at " + source
+ " — has the file moved? Update this test's path.");
}
String content = Files.readString(source);
Set