|
1 | 1 | package io.temporal.common.interceptors; |
2 | 2 |
|
3 | 3 | import static org.junit.Assert.*; |
4 | | -import static org.mockito.Mockito.*; |
| 4 | +import static org.junit.Assume.assumeTrue; |
5 | 5 |
|
| 6 | +import io.temporal.activity.ActivityInterface; |
| 7 | +import io.temporal.activity.ActivityMethod; |
| 8 | +import io.temporal.client.ActivityClient; |
| 9 | +import io.temporal.client.ActivityClientOptions; |
6 | 10 | import io.temporal.client.StartActivityOptions; |
7 | 11 | import io.temporal.common.interceptors.ActivityClientCallsInterceptor.*; |
| 12 | +import io.temporal.testing.internal.SDKTestWorkflowRule; |
8 | 13 | import java.time.Duration; |
9 | 14 | import java.util.ArrayList; |
10 | 15 | import java.util.Arrays; |
11 | | -import java.util.Collections; |
12 | 16 | import java.util.List; |
| 17 | +import java.util.UUID; |
| 18 | +import org.junit.Rule; |
13 | 19 | import org.junit.Test; |
14 | 20 |
|
15 | | -/** Tests for the {@link ActivityClientInterceptor} factory pattern and chain-building behavior. */ |
| 21 | +/** |
| 22 | + * Tests for interceptor chain ordering using a real Temporal server. Each test creates an {@link |
| 23 | + * ActivityClient} with two interceptors and verifies the call order through a client method. |
| 24 | + */ |
16 | 25 | public class ActivityClientCallsInterceptorChainTest { |
17 | 26 |
|
18 | | - private static StartActivityInput minimalInput() { |
19 | | - return new StartActivityInput( |
20 | | - "MyActivity", |
21 | | - Collections.emptyList(), |
22 | | - StartActivityOptions.newBuilder() |
23 | | - .setId("act-id") |
24 | | - .setTaskQueue("tq") |
25 | | - .setStartToCloseTimeout(Duration.ofSeconds(10)) |
26 | | - .build(), |
27 | | - Header.empty()); |
| 27 | + @ActivityInterface |
| 28 | + public interface EchoActivity { |
| 29 | + @ActivityMethod(name = "ChainTestEcho") |
| 30 | + String echo(String input); |
28 | 31 | } |
29 | 32 |
|
30 | | - /** |
31 | | - * Builds a chain from a list of interceptors and a root, replicating ActivityClientImpl logic. |
32 | | - */ |
33 | | - private static ActivityClientCallsInterceptor buildChain( |
34 | | - List<ActivityClientInterceptor> interceptors, ActivityClientCallsInterceptor root) { |
35 | | - ActivityClientCallsInterceptor invoker = root; |
36 | | - for (ActivityClientInterceptor interceptor : interceptors) { |
37 | | - invoker = interceptor.activityClientCallsInterceptor(invoker); |
| 33 | + static class EchoActivityImpl implements EchoActivity { |
| 34 | + @Override |
| 35 | + public String echo(String input) { |
| 36 | + return input; |
38 | 37 | } |
39 | | - return invoker; |
40 | 38 | } |
41 | 39 |
|
42 | | - // ---- Chain ordering ---- |
| 40 | + @Rule |
| 41 | + public SDKTestWorkflowRule testRule = |
| 42 | + SDKTestWorkflowRule.newBuilder().setActivityImplementations(new EchoActivityImpl()).build(); |
43 | 43 |
|
44 | 44 | @Test |
45 | | - public void testSingleInterceptorExecutesBeforeRoot() { |
| 45 | + public void testTwoInterceptorsCalledInOrderOnStart() { |
| 46 | + assumeTrue(SDKTestWorkflowRule.useExternalService); |
46 | 47 | List<String> events = new ArrayList<>(); |
47 | | - ActivityClientCallsInterceptor root = mock(ActivityClientCallsInterceptor.class); |
48 | | - when(root.startActivity(any())) |
49 | | - .thenAnswer( |
50 | | - inv -> { |
51 | | - events.add("root"); |
52 | | - return new StartActivityOutput("id", null); |
53 | | - }); |
54 | | - |
55 | | - ActivityClientInterceptor interceptor = |
56 | | - new ActivityClientInterceptorBase() { |
57 | | - @Override |
58 | | - public ActivityClientCallsInterceptor activityClientCallsInterceptor( |
59 | | - ActivityClientCallsInterceptor next) { |
60 | | - return new ActivityClientCallsInterceptorBase(next) { |
61 | | - @Override |
62 | | - public StartActivityOutput startActivity(StartActivityInput input) { |
63 | | - events.add("A"); |
64 | | - return super.startActivity(input); |
65 | | - } |
66 | | - }; |
67 | | - } |
68 | | - }; |
69 | 48 |
|
70 | | - ActivityClientCallsInterceptor chain = buildChain(Collections.singletonList(interceptor), root); |
71 | | - chain.startActivity(minimalInput()); |
| 49 | + ActivityClient client = newClient(startInterceptor("A", events), startInterceptor("B", events)); |
| 50 | + String result = client.execute(EchoActivity.class, EchoActivity::echo, opts(uniqueId()), "hi"); |
72 | 51 |
|
73 | | - assertEquals(Arrays.asList("A", "root"), events); |
| 52 | + assertEquals("hi", result); |
| 53 | + // B is last in the list → B wraps A → B is outermost → B called first |
| 54 | + assertEquals(Arrays.asList("B", "A"), events); |
74 | 55 | } |
75 | 56 |
|
76 | 57 | @Test |
77 | | - public void testTwoInterceptorsLastIsOutermost() { |
| 58 | + public void testInterceptorListOrderDeterminesCallOrder() { |
| 59 | + assumeTrue(SDKTestWorkflowRule.useExternalService); |
78 | 60 | List<String> events = new ArrayList<>(); |
79 | | - ActivityClientCallsInterceptor root = mock(ActivityClientCallsInterceptor.class); |
80 | | - when(root.startActivity(any())) |
81 | | - .thenAnswer( |
82 | | - inv -> { |
83 | | - events.add("root"); |
84 | | - return new StartActivityOutput("id", null); |
85 | | - }); |
86 | | - |
87 | | - ActivityClientInterceptor first = factoryInterceptor("A", events); |
88 | | - ActivityClientInterceptor second = factoryInterceptor("B", events); |
89 | | - |
90 | | - ActivityClientCallsInterceptor chain = buildChain(Arrays.asList(first, second), root); |
91 | | - chain.startActivity(minimalInput()); |
92 | 61 |
|
93 | | - assertEquals(Arrays.asList("B", "A", "root"), events); |
94 | | - } |
| 62 | + // reversed list order: A is last → A wraps B → A is outermost → A called first |
| 63 | + ActivityClient client = newClient(startInterceptor("B", events), startInterceptor("A", events)); |
| 64 | + client.execute(EchoActivity.class, EchoActivity::echo, opts(uniqueId()), "hi"); |
95 | 65 |
|
96 | | - @Test |
97 | | - public void testThreeInterceptorsLastIsOutermost() { |
98 | | - List<String> events = new ArrayList<>(); |
99 | | - ActivityClientCallsInterceptor root = mock(ActivityClientCallsInterceptor.class); |
100 | | - when(root.startActivity(any())) |
101 | | - .thenAnswer( |
102 | | - inv -> { |
103 | | - events.add("root"); |
104 | | - return new StartActivityOutput("id", null); |
105 | | - }); |
106 | | - |
107 | | - ActivityClientCallsInterceptor chain = |
108 | | - buildChain( |
109 | | - Arrays.asList( |
110 | | - factoryInterceptor("A", events), |
111 | | - factoryInterceptor("B", events), |
112 | | - factoryInterceptor("C", events)), |
113 | | - root); |
114 | | - chain.startActivity(minimalInput()); |
115 | | - |
116 | | - assertEquals(Arrays.asList("C", "B", "A", "root"), events); |
| 66 | + assertEquals(Arrays.asList("A", "B"), events); |
117 | 67 | } |
118 | 68 |
|
119 | | - // ---- ActivityClientInterceptorBase defaults ---- |
120 | | - |
121 | | - @Test |
122 | | - public void testActivityClientInterceptorBaseDefaultPassesThrough() { |
123 | | - // ActivityClientInterceptorBase.activityClientCallsInterceptor returns next unchanged. |
124 | | - ActivityClientCallsInterceptor root = mock(ActivityClientCallsInterceptor.class); |
125 | | - when(root.startActivity(any())).thenReturn(new StartActivityOutput("id", null)); |
126 | | - |
127 | | - ActivityClientInterceptor passthrough = new ActivityClientInterceptorBase() {}; |
128 | | - |
129 | | - ActivityClientCallsInterceptor chain = buildChain(Collections.singletonList(passthrough), root); |
130 | | - StartActivityOutput output = chain.startActivity(minimalInput()); |
| 69 | + // ---- Helpers ---- |
131 | 70 |
|
132 | | - assertNotNull(output); |
133 | | - verify(root).startActivity(any()); |
| 71 | + private ActivityClient newClient(ActivityClientInterceptor... interceptors) { |
| 72 | + return ActivityClient.newInstance( |
| 73 | + testRule.getWorkflowServiceStubs(), |
| 74 | + ActivityClientOptions.newBuilder() |
| 75 | + .setNamespace(SDKTestWorkflowRule.NAMESPACE) |
| 76 | + .setInterceptors(Arrays.asList(interceptors)) |
| 77 | + .build()); |
134 | 78 | } |
135 | 79 |
|
136 | | - @Test |
137 | | - public void testInterceptorBaseCanWrapAndInterceptCalls() { |
138 | | - List<String> events = new ArrayList<>(); |
139 | | - ActivityClientCallsInterceptor root = mock(ActivityClientCallsInterceptor.class); |
140 | | - when(root.startActivity(any())) |
141 | | - .thenAnswer( |
142 | | - inv -> { |
143 | | - events.add("root"); |
144 | | - return new StartActivityOutput("id", null); |
145 | | - }); |
146 | | - |
147 | | - ActivityClientInterceptor factory = |
148 | | - new ActivityClientInterceptorBase() { |
149 | | - @Override |
150 | | - public ActivityClientCallsInterceptor activityClientCallsInterceptor( |
151 | | - ActivityClientCallsInterceptor next) { |
152 | | - return new ActivityClientCallsInterceptorBase(next) { |
153 | | - @Override |
154 | | - public StartActivityOutput startActivity(StartActivityInput input) { |
155 | | - events.add("intercepted"); |
156 | | - return super.startActivity(input); |
157 | | - } |
158 | | - }; |
159 | | - } |
160 | | - }; |
161 | | - |
162 | | - buildChain(Collections.singletonList(factory), root).startActivity(minimalInput()); |
163 | | - |
164 | | - assertEquals(Arrays.asList("intercepted", "root"), events); |
| 80 | + private String uniqueId() { |
| 81 | + return "act-" + UUID.randomUUID(); |
165 | 82 | } |
166 | 83 |
|
167 | | - // ---- Helper ---- |
| 84 | + private StartActivityOptions opts(String id) { |
| 85 | + return StartActivityOptions.newBuilder() |
| 86 | + .setId(id) |
| 87 | + .setTaskQueue(testRule.getTaskQueue()) |
| 88 | + .setScheduleToCloseTimeout(Duration.ofMinutes(1)) |
| 89 | + .build(); |
| 90 | + } |
168 | 91 |
|
169 | | - private static ActivityClientInterceptor factoryInterceptor(String name, List<String> events) { |
| 92 | + private static ActivityClientInterceptor startInterceptor(String name, List<String> events) { |
170 | 93 | return new ActivityClientInterceptorBase() { |
171 | 94 | @Override |
172 | 95 | public ActivityClientCallsInterceptor activityClientCallsInterceptor( |
|
0 commit comments