Skip to content

Commit af2d88f

Browse files
Add tests for temporal-spring-ai (T1-T4)
T1: ChatModelActivityImplTest (10 tests) - type conversion between ChatModelTypes and Spring AI types, multi-model resolution, tool definition passthrough, model options mapping. T2: TemporalToolUtilTest (22 tests) - tool detection and conversion for @DeterministicTool, @SideEffectTool, stub type detection, error cases for unknown/null types. T3: WorkflowDeterminismTest (2 tests) - verifies workflows using ActivityChatModel with tools complete without non-determinism errors in the Temporal test environment. T4: SpringAiPluginTest (10 tests) - plugin registration with various bean combinations, multi-model support, default model resolution. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 2009df0 commit af2d88f

4 files changed

Lines changed: 919 additions & 0 deletions

File tree

Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
package io.temporal.springai;
2+
3+
import static org.junit.jupiter.api.Assertions.*;
4+
5+
import io.temporal.client.WorkflowClient;
6+
import io.temporal.client.WorkflowOptions;
7+
import io.temporal.springai.activity.ChatModelActivityImpl;
8+
import io.temporal.springai.chat.TemporalChatClient;
9+
import io.temporal.springai.model.ActivityChatModel;
10+
import io.temporal.springai.tool.DeterministicTool;
11+
import io.temporal.springai.tool.SideEffectTool;
12+
import io.temporal.testing.TestWorkflowEnvironment;
13+
import io.temporal.worker.Worker;
14+
import io.temporal.workflow.WorkflowInterface;
15+
import io.temporal.workflow.WorkflowMethod;
16+
import java.util.List;
17+
import org.junit.jupiter.api.AfterEach;
18+
import org.junit.jupiter.api.BeforeEach;
19+
import org.junit.jupiter.api.Test;
20+
import org.springframework.ai.chat.client.ChatClient;
21+
import org.springframework.ai.chat.messages.AssistantMessage;
22+
import org.springframework.ai.chat.model.ChatModel;
23+
import org.springframework.ai.chat.model.ChatResponse;
24+
import org.springframework.ai.chat.model.Generation;
25+
import org.springframework.ai.chat.prompt.Prompt;
26+
import org.springframework.ai.tool.annotation.Tool;
27+
28+
/**
29+
* Verifies that workflows using ActivityChatModel with tools execute without non-determinism
30+
* errors.
31+
*/
32+
class WorkflowDeterminismTest {
33+
34+
private static final String TASK_QUEUE = "test-spring-ai";
35+
36+
private TestWorkflowEnvironment testEnv;
37+
private WorkflowClient client;
38+
39+
@BeforeEach
40+
void setUp() {
41+
testEnv = TestWorkflowEnvironment.newInstance();
42+
client = testEnv.getWorkflowClient();
43+
}
44+
45+
@AfterEach
46+
void tearDown() {
47+
testEnv.close();
48+
}
49+
50+
@Test
51+
void workflowWithChatModel_completesSuccessfully() {
52+
Worker worker = testEnv.newWorker(TASK_QUEUE);
53+
worker.registerWorkflowImplementationTypes(ChatWorkflowImpl.class);
54+
55+
// Register a ChatModelActivityImpl backed by a mock model that returns a canned response
56+
ChatModel mockModel = new StubChatModel("Hello from the model!");
57+
worker.registerActivitiesImplementations(new ChatModelActivityImpl(mockModel));
58+
59+
testEnv.start();
60+
61+
TestChatWorkflow workflow =
62+
client.newWorkflowStub(
63+
TestChatWorkflow.class, WorkflowOptions.newBuilder().setTaskQueue(TASK_QUEUE).build());
64+
65+
String result = workflow.chat("Hi");
66+
assertEquals("Hello from the model!", result);
67+
}
68+
69+
@Test
70+
void workflowWithDeterministicTool_completesSuccessfully() {
71+
Worker worker = testEnv.newWorker(TASK_QUEUE);
72+
worker.registerWorkflowImplementationTypes(ChatWithToolsWorkflowImpl.class);
73+
74+
// Model returns a simple response (no tool calls)
75+
ChatModel mockModel = new StubChatModel("I used the tools!");
76+
worker.registerActivitiesImplementations(new ChatModelActivityImpl(mockModel));
77+
78+
testEnv.start();
79+
80+
TestChatWorkflow workflow =
81+
client.newWorkflowStub(
82+
TestChatWorkflow.class, WorkflowOptions.newBuilder().setTaskQueue(TASK_QUEUE).build());
83+
84+
String result = workflow.chat("Use tools");
85+
assertEquals("I used the tools!", result);
86+
}
87+
88+
// --- Workflow interfaces and implementations ---
89+
90+
@WorkflowInterface
91+
public interface TestChatWorkflow {
92+
@WorkflowMethod
93+
String chat(String message);
94+
}
95+
96+
public static class ChatWorkflowImpl implements TestChatWorkflow {
97+
@Override
98+
public String chat(String message) {
99+
ActivityChatModel chatModel = ActivityChatModel.forDefault();
100+
ChatClient chatClient = TemporalChatClient.builder(chatModel).build();
101+
return chatClient.prompt().user(message).call().content();
102+
}
103+
}
104+
105+
public static class ChatWithToolsWorkflowImpl implements TestChatWorkflow {
106+
@Override
107+
public String chat(String message) {
108+
ActivityChatModel chatModel = ActivityChatModel.forDefault();
109+
TestDeterministicTools deterministicTools = new TestDeterministicTools();
110+
TestSideEffectTools sideEffectTools = new TestSideEffectTools();
111+
ChatClient chatClient =
112+
TemporalChatClient.builder(chatModel)
113+
.defaultTools(deterministicTools, sideEffectTools)
114+
.build();
115+
return chatClient.prompt().user(message).call().content();
116+
}
117+
}
118+
119+
// --- Test tool classes ---
120+
121+
@DeterministicTool
122+
public static class TestDeterministicTools {
123+
@Tool(description = "Add two numbers")
124+
public int add(int a, int b) {
125+
return a + b;
126+
}
127+
}
128+
129+
@SideEffectTool
130+
public static class TestSideEffectTools {
131+
@Tool(description = "Get a timestamp")
132+
public String timestamp() {
133+
return "2025-01-01T00:00:00Z";
134+
}
135+
}
136+
137+
// --- Stub ChatModel that returns a canned response ---
138+
139+
private static class StubChatModel implements ChatModel {
140+
private final String response;
141+
142+
StubChatModel(String response) {
143+
this.response = response;
144+
}
145+
146+
@Override
147+
public ChatResponse call(Prompt prompt) {
148+
return ChatResponse.builder()
149+
.generations(List.of(new Generation(new AssistantMessage(response))))
150+
.build();
151+
}
152+
153+
@Override
154+
public reactor.core.publisher.Flux<ChatResponse> stream(Prompt prompt) {
155+
throw new UnsupportedOperationException();
156+
}
157+
}
158+
}

0 commit comments

Comments
 (0)