From 32098682742a5a5a0680c35046678fc24e0e8631 Mon Sep 17 00:00:00 2001 From: matevip Date: Tue, 9 Jun 2026 10:00:15 +0800 Subject: [PATCH] test(trigger): await async dispatch in ChannelMessageTriggerTest MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Decoupling the channel-message event bridge onto an @Async listener means the downstream workflow run is produced off the event-publishing thread. The test read the run table synchronously right after publishEvent, racing the listener — the positive cases failed and the negative cases passed for the wrong reason. Poll briefly for the run (positive) / give the listener time then assert none (negative) so the test reflects the async dispatch semantics. --- .../trigger/ChannelMessageTriggerTest.java | 32 ++++++++++++++----- 1 file changed, 24 insertions(+), 8 deletions(-) diff --git a/mateclaw-server/src/test/java/vip/mate/trigger/ChannelMessageTriggerTest.java b/mateclaw-server/src/test/java/vip/mate/trigger/ChannelMessageTriggerTest.java index efe1964d..0c599e80 100644 --- a/mateclaw-server/src/test/java/vip/mate/trigger/ChannelMessageTriggerTest.java +++ b/mateclaw-server/src/test/java/vip/mate/trigger/ChannelMessageTriggerTest.java @@ -81,8 +81,7 @@ class ChannelMessageTriggerTest { publisher.publishEvent(new ChannelMessageReceivedEvent( workspace, "feishu", "msg-1", "alice", "Alice", "chat-1", "hello")); - List runs = runMapper.selectList( - new LambdaQueryWrapper().eq(WorkflowRunEntity::getWorkflowId, downstream)); + List runs = awaitRuns(downstream); assertEquals(1, runs.size(), "channel_message envelope should have triggered exactly one run"); assertEquals("succeeded", runs.get(0).getState()); assertTrue(runs.get(0).getTriggeredBy() != null @@ -116,8 +115,7 @@ class ChannelMessageTriggerTest { publisher.publishEvent(new ChannelMessageReceivedEvent( workspace, "feishu", "msg-2", "bob", "Bob", "chat-2", "hello")); - List runs = runMapper.selectList( - new LambdaQueryWrapper().eq(WorkflowRunEntity::getWorkflowId, downstream)); + List runs = awaitNoRuns(downstream); assertTrue(runs.isEmpty(), "channelType mismatch should leave the trigger dormant"); } @@ -148,8 +146,7 @@ class ChannelMessageTriggerTest { publisher.publishEvent(new ChannelMessageReceivedEvent( workspace, "feishu", "msg-3", "alice", "Alice", "chat-3", "Place an Order, please")); - List runs = runMapper.selectList( - new LambdaQueryWrapper().eq(WorkflowRunEntity::getWorkflowId, downstream)); + List runs = awaitRuns(downstream); assertEquals(1, runs.size(), "content_match should fire when the substring is present in the message"); } @@ -178,9 +175,28 @@ class ChannelMessageTriggerTest { publisher.publishEvent(new ChannelMessageReceivedEvent( workspace, "feishu", "msg-4", "alice", "Alice", "chat-4", "completely unrelated text")); - List runs = runMapper.selectList( - new LambdaQueryWrapper().eq(WorkflowRunEntity::getWorkflowId, downstream)); + List runs = awaitNoRuns(downstream); assertTrue(runs.isEmpty(), "missing substring should leave the content_match trigger dormant"); } + + // The channel-message event bridge dispatches on an @Async listener, so the + // downstream run is produced off the event-publishing thread. Poll briefly + // for it instead of reading immediately (which would race the listener). + private List awaitRuns(long workflowId) { + var q = new LambdaQueryWrapper().eq(WorkflowRunEntity::getWorkflowId, workflowId); + for (int i = 0; i < 50; i++) { + List runs = runMapper.selectList(q); + if (!runs.isEmpty()) return runs; + try { Thread.sleep(100); } catch (InterruptedException e) { Thread.currentThread().interrupt(); break; } + } + return runMapper.selectList(q); + } + + // Give the @Async listener time to run, then confirm it produced nothing. + private List awaitNoRuns(long workflowId) { + try { Thread.sleep(1000); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } + return runMapper.selectList( + new LambdaQueryWrapper().eq(WorkflowRunEntity::getWorkflowId, workflowId)); + } }