package vip.mate.trigger; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; 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.boot.test.context.TestConfiguration; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Import; import org.springframework.context.annotation.Primary; import org.springframework.test.context.TestPropertySource; import vip.mate.MateClawApplication; import vip.mate.trigger.dispatch.TriggerDispatcher; import vip.mate.trigger.dispatch.WorkflowGraphLoader; import vip.mate.trigger.model.TriggerEntity; import vip.mate.trigger.repository.TriggerMapper; import vip.mate.trigger.scheduler.TriggerScheduler; import vip.mate.trigger.service.TriggerService; import vip.mate.workflow.compiler.WorkflowParser; import vip.mate.workflow.compiler.ir.WorkflowGraph; import vip.mate.workflow.model.WorkflowRunEntity; import vip.mate.workflow.repository.WorkflowRunMapper; import vip.mate.workflow.runtime.WorkflowRunResult; import java.util.List; import java.util.Map; import java.util.concurrent.atomic.AtomicReference; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertTrue; /** * Drives the trigger dispatch path end-to-end against a stub workflow * loader and a stub agent invoker: a fired trigger should produce exactly * one {@code mate_workflow_run} row whose triggered_by column points back * at the trigger id, and the rendered payload template should land in the * run's initial inputs. * *
Also exercises the lamport-coordination path on the scheduler: a
* fire dispatched with a stale captured version is silently dropped (the
* scheduler self-cancels), no workflow run row appears, and the
* registration is cleared.
*/
@SpringBootTest(
classes = MateClawApplication.class,
webEnvironment = SpringBootTest.WebEnvironment.NONE
)
@TestPropertySource(properties = {
"spring.datasource.url=jdbc:h2:mem:trigger_dispatch_${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",
"mateclaw.workflow.trigger.async-dispatch=false"
})
@Import({vip.mate.workflow.runtime.StubAgentInvokerConfig.class,
TriggerDispatcherWorkflowTest.StubGraphLoaderConfig.class})
class TriggerDispatcherWorkflowTest {
@Autowired private TriggerService triggerService;
@Autowired private TriggerMapper triggerMapper;
@Autowired private TriggerScheduler scheduler;
@Autowired private TriggerDispatcher dispatcher;
@Autowired private WorkflowRunMapper runMapper;
@Autowired private vip.mate.workflow.runtime.StubAgentInvoker stubInvoker;
@Autowired private StubGraphLoader stubGraphLoader;
@Test
@DisplayName("Dispatching a cron trigger creates a workflow run with payload-rendered inputs.")
void dispatchProducesWorkflowRun() {
stubInvoker.reset();
stubInvoker.respond("greeter", "hello world");
stubGraphLoader.reset();
stubGraphLoader.bind(7000L, 11L,
"{\"steps\":[{\"name\":\"a\",\"agentName\":\"greeter\","
+ "\"mode\":{\"type\":\"sequential\"},\"promptTemplate\":\"hi {{ inputs.who }}\"}]}");
TriggerEntity trigger = triggerService.create(cronTrigger(
"hello-cron", "0 0 * * * *", 7000L,
"{\"who\":\"{{ event.who }}\"}"));
vip.mate.trigger.dispatch.DispatchResult result = dispatcher.dispatch(trigger,
Map.of("who", "alice"));
assertNotNull(result);
assertEquals(vip.mate.trigger.dispatch.DispatchResult.Kind.FIRED, result.kind());
assertEquals("hi alice", stubInvoker.lastPromptFor("greeter"));
WorkflowRunEntity runRow = runMapper.selectById(result.runId());
assertNotNull(runRow);
assertEquals(7000L, runRow.getWorkflowId());
assertEquals(11L, runRow.getRevisionId());
assertEquals("trigger:" + trigger.getId(), runRow.getTriggeredBy());
}
@Test
@DisplayName("Dispatching a workflow with no published revision skips fire and records nothing.")
void missingRevisionSkipsRun() {
stubGraphLoader.reset();
stubGraphLoader.bindMissing(8001L);
TriggerEntity trigger = triggerService.create(cronTrigger(
"ghost", "0 0 * * * *", 8001L, null));
vip.mate.trigger.dispatch.DispatchResult result = dispatcher.dispatch(trigger, Map.of());
assertNotNull(result);
assertEquals(vip.mate.trigger.dispatch.DispatchResult.Kind.SKIPPED, result.kind(),
"missing revision should yield a SKIPPED outcome, not silent null");
List