Skip to content

Commit 736f1dc

Browse files
committed
Add ContinueAsNew search attribute tests for test server
1 parent 1386d4b commit 736f1dc

2 files changed

Lines changed: 108 additions & 1 deletion

File tree

temporal-test-server/src/main/java/io/temporal/internal/testservice/StateMachines.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1464,6 +1464,9 @@ private static void continueAsNewWorkflow(
14641464
if (d.hasFailure()) {
14651465
a.setFailure(d.getFailure());
14661466
}
1467+
// Propagate the search attributes carried in the ContinueAsNew command to the new run, as the
1468+
// real server does. When the command omits them, the SDK has already copied the previous run's
1469+
// search attributes into the command, so there is no separate server-side inheritance here.
14671470
if (d.hasSearchAttributes()) {
14681471
a.setSearchAttributes(d.getSearchAttributes());
14691472
}

temporal-test-server/src/test/java/io/temporal/testserver/functional/ContinueAsNewTest.java

Lines changed: 105 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,24 @@
11
package io.temporal.testserver.functional;
22

3+
import io.temporal.api.common.v1.Payload;
34
import io.temporal.api.common.v1.WorkflowExecution;
5+
import io.temporal.api.enums.v1.EventType;
6+
import io.temporal.api.history.v1.HistoryEvent;
7+
import io.temporal.api.history.v1.WorkflowExecutionStartedEventAttributes;
48
import io.temporal.client.WorkflowOptions;
59
import io.temporal.client.WorkflowStub;
10+
import io.temporal.common.SearchAttributeKey;
11+
import io.temporal.common.SearchAttributes;
612
import io.temporal.common.WorkflowExecutionHistory;
13+
import io.temporal.common.converter.DefaultDataConverter;
714
import io.temporal.common.interceptors.*;
815
import io.temporal.testing.internal.SDKTestWorkflowRule;
916
import io.temporal.testserver.functional.common.TestWorkflows;
1017
import io.temporal.worker.WorkerFactoryOptions;
1118
import io.temporal.workflow.ContinueAsNewOptions;
1219
import io.temporal.workflow.Workflow;
20+
import io.temporal.workflow.WorkflowInterface;
21+
import io.temporal.workflow.WorkflowMethod;
1322
import java.time.Duration;
1423
import org.junit.Assert;
1524
import org.junit.Rule;
@@ -24,7 +33,7 @@ public class ContinueAsNewTest {
2433
WorkerFactoryOptions.newBuilder()
2534
.setWorkerInterceptors(new StripsTqFromCanInterceptor())
2635
.build())
27-
.setWorkflowTypes(TestWorkflow.class)
36+
.setWorkflowTypes(TestWorkflow.class, OverridingWorkflow.class)
2837
.build();
2938

3039
@Test
@@ -54,6 +63,81 @@ public void repeatedFailure() {
5463
.isEmpty());
5564
}
5665

66+
private static final SearchAttributeKey<String> CUSTOM_KEYWORD =
67+
SearchAttributeKey.forKeyword("CustomKeywordField");
68+
69+
@Test
70+
public void inheritsSearchAttributesAcrossContinueAsNew() {
71+
// The workflow continues-as-new without specifying search attributes, so the SDK carries the
72+
// current run's search attributes into the command and the new run must keep them. This matches
73+
// the real server, which propagates the command's search attributes to the new run.
74+
WorkflowOptions options =
75+
WorkflowOptions.newBuilder()
76+
.setWorkflowTaskTimeout(Duration.ofSeconds(1))
77+
.setTaskQueue(testWorkflowRule.getTaskQueue())
78+
.setTypedSearchAttributes(
79+
SearchAttributes.newBuilder().set(CUSTOM_KEYWORD, "initialSA").build())
80+
.build();
81+
82+
TestWorkflows.WorkflowTakesBool workflowStub =
83+
testWorkflowRule
84+
.getWorkflowClient()
85+
.newWorkflowStub(TestWorkflows.WorkflowTakesBool.class, options);
86+
workflowStub.execute(true);
87+
88+
WorkflowExecutionStartedEventAttributes started =
89+
getContinuedRunStartedAttributes(workflowStub);
90+
91+
Assert.assertTrue(
92+
"Search attributes should be inherited by the continued run",
93+
started.hasSearchAttributes());
94+
Assert.assertEquals(
95+
"initialSA",
96+
decodeString(started.getSearchAttributes().getIndexedFieldsOrThrow("CustomKeywordField")));
97+
}
98+
99+
@Test
100+
public void overridesSearchAttributesOnContinueAsNew() {
101+
WorkflowOptions options =
102+
WorkflowOptions.newBuilder()
103+
.setWorkflowTaskTimeout(Duration.ofSeconds(1))
104+
.setTaskQueue(testWorkflowRule.getTaskQueue())
105+
.setTypedSearchAttributes(
106+
SearchAttributes.newBuilder().set(CUSTOM_KEYWORD, "originalSA").build())
107+
.build();
108+
109+
OverridingWorkflowInterface workflowStub =
110+
testWorkflowRule
111+
.getWorkflowClient()
112+
.newWorkflowStub(OverridingWorkflowInterface.class, options);
113+
workflowStub.execute(true);
114+
115+
WorkflowExecutionStartedEventAttributes started =
116+
getContinuedRunStartedAttributes(workflowStub);
117+
118+
Assert.assertEquals(
119+
"overriddenSA",
120+
decodeString(started.getSearchAttributes().getIndexedFieldsOrThrow("CustomKeywordField")));
121+
}
122+
123+
private WorkflowExecutionStartedEventAttributes getContinuedRunStartedAttributes(
124+
Object workflowStub) {
125+
WorkflowExecution execution = WorkflowStub.fromTyped(workflowStub).getExecution();
126+
HistoryEvent firstEvent =
127+
testWorkflowRule.getExecutionHistory(execution.getWorkflowId()).getEvents().get(0);
128+
Assert.assertEquals(EventType.EVENT_TYPE_WORKFLOW_EXECUTION_STARTED, firstEvent.getEventType());
129+
WorkflowExecutionStartedEventAttributes started =
130+
firstEvent.getWorkflowExecutionStartedEventAttributes();
131+
Assert.assertFalse(
132+
"Inspected event must belong to the continued run",
133+
started.getContinuedExecutionRunId().isEmpty());
134+
return started;
135+
}
136+
137+
private static String decodeString(Payload payload) {
138+
return DefaultDataConverter.STANDARD_INSTANCE.fromPayload(payload, String.class, String.class);
139+
}
140+
57141
public static class TestWorkflow implements TestWorkflows.WorkflowTakesBool {
58142
@Override
59143
public void execute(boolean doContinue) {
@@ -63,6 +147,26 @@ public void execute(boolean doContinue) {
63147
}
64148
}
65149

150+
@WorkflowInterface
151+
public interface OverridingWorkflowInterface {
152+
@WorkflowMethod
153+
void execute(boolean doContinue);
154+
}
155+
156+
public static class OverridingWorkflow implements OverridingWorkflowInterface {
157+
@Override
158+
public void execute(boolean doContinue) {
159+
if (doContinue) {
160+
Workflow.continueAsNew(
161+
ContinueAsNewOptions.newBuilder()
162+
.setTypedSearchAttributes(
163+
SearchAttributes.newBuilder().set(CUSTOM_KEYWORD, "overriddenSA").build())
164+
.build(),
165+
false);
166+
}
167+
}
168+
}
169+
66170
// Verify that we can strip the TQ name and test server continues onto same TQ
67171
private static class StripsTqFromCanInterceptor extends WorkerInterceptorBase {
68172
@Override

0 commit comments

Comments
 (0)