11package io .temporal .testserver .functional ;
22
3+ import io .temporal .api .common .v1 .Payload ;
34import 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 ;
48import io .temporal .client .WorkflowOptions ;
59import io .temporal .client .WorkflowStub ;
10+ import io .temporal .common .SearchAttributeKey ;
11+ import io .temporal .common .SearchAttributes ;
612import io .temporal .common .WorkflowExecutionHistory ;
13+ import io .temporal .common .converter .DefaultDataConverter ;
714import io .temporal .common .interceptors .*;
815import io .temporal .testing .internal .SDKTestWorkflowRule ;
916import io .temporal .testserver .functional .common .TestWorkflows ;
1017import io .temporal .worker .WorkerFactoryOptions ;
1118import io .temporal .workflow .ContinueAsNewOptions ;
1219import io .temporal .workflow .Workflow ;
20+ import io .temporal .workflow .WorkflowInterface ;
21+ import io .temporal .workflow .WorkflowMethod ;
1322import java .time .Duration ;
1423import org .junit .Assert ;
1524import 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