11package io .temporal .internal .common ;
22
3+ import io .nexusrpc .FailureInfo ;
4+ import io .nexusrpc .handler .HandlerException ;
5+ import io .temporal .api .failure .v1 .ApplicationFailureInfo ;
6+ import io .temporal .api .failure .v1 .CanceledFailureInfo ;
7+ import io .temporal .api .failure .v1 .Failure ;
8+ import io .temporal .api .nexus .v1 .HandlerError ;
9+ import io .temporal .common .converter .DataConverter ;
10+ import io .temporal .common .converter .DefaultDataConverter ;
11+ import io .temporal .failure .ApplicationFailure ;
312import org .junit .Assert ;
413import org .junit .Test ;
514
615public class NexusUtilTest {
16+ private static final DataConverter DATA_CONVERTER = DefaultDataConverter .STANDARD_INSTANCE ;
17+
718 @ Test
819 public void testParseRequestTimeout () {
920 Assert .assertThrows (
@@ -15,4 +26,236 @@ public void testParseRequestTimeout() {
1526 Assert .assertEquals (java .time .Duration .ofMinutes (999 ), NexusUtil .parseRequestTimeout ("999m" ));
1627 Assert .assertEquals (java .time .Duration .ofMillis (1300 ), NexusUtil .parseRequestTimeout ("1.3s" ));
1728 }
29+
30+ @ Test
31+ public void testTemporalFailureToNexusFailureRoundTrip () {
32+ // Create a Temporal failure with details
33+ Failure temporalFailure =
34+ Failure .newBuilder ()
35+ .setMessage ("test failure" )
36+ .setStackTrace ("at test.Class.method(Class.java:123)" )
37+ .setApplicationFailureInfo (
38+ ApplicationFailureInfo .newBuilder ()
39+ .setType ("TestFailure" )
40+ .setNonRetryable (true )
41+ .build ())
42+ .build ();
43+
44+ // Convert to Nexus failure
45+ io .temporal .api .nexus .v1 .Failure nexusFailure =
46+ NexusUtil .temporalFailureToNexusFailure (temporalFailure );
47+
48+ // Verify message is preserved
49+ Assert .assertEquals ("test failure" , nexusFailure .getMessage ());
50+
51+ // Verify metadata indicates this is a Temporal failure
52+ Assert .assertTrue (nexusFailure .getMetadataMap ().containsKey ("type" ));
53+ Assert .assertEquals (
54+ "temporal.api.failure.v1.Failure" , nexusFailure .getMetadataMap ().get ("type" ));
55+
56+ // Convert back via FailureInfo
57+ FailureInfo .Builder failureInfoBuilder =
58+ FailureInfo .newBuilder ()
59+ .setMessage (nexusFailure .getMessage ())
60+ .setDetailsJson (nexusFailure .getDetails ().toStringUtf8 ())
61+ .setStackTrace (nexusFailure .getStackTrace ());
62+ // Add metadata entries individually
63+ for (String key : nexusFailure .getMetadataMap ().keySet ()) {
64+ failureInfoBuilder .putMetadata (key , nexusFailure .getMetadataMap ().get (key ));
65+ }
66+ FailureInfo failureInfo = failureInfoBuilder .build ();
67+
68+ Failure reconstructed = NexusUtil .nexusFailureToAPIFailure (failureInfo , true );
69+
70+ // Verify round-trip preserves all fields
71+ Assert .assertEquals ("test failure" , reconstructed .getMessage ());
72+ Assert .assertEquals ("at test.Class.method(Class.java:123)" , reconstructed .getStackTrace ());
73+ Assert .assertTrue (reconstructed .hasApplicationFailureInfo ());
74+ Assert .assertEquals ("TestFailure" , reconstructed .getApplicationFailureInfo ().getType ());
75+ Assert .assertTrue (reconstructed .getApplicationFailureInfo ().getNonRetryable ());
76+ }
77+
78+ @ Test
79+ public void testTemporalFailureToNexusFailureInfoRoundTrip () {
80+ // Create a Temporal failure with nested cause
81+ Failure innerFailure =
82+ Failure .newBuilder ()
83+ .setMessage ("inner cause" )
84+ .setApplicationFailureInfo (
85+ ApplicationFailureInfo .newBuilder ().setType ("InnerFailure" ).build ())
86+ .build ();
87+
88+ Failure outerFailure =
89+ Failure .newBuilder ()
90+ .setMessage ("outer failure" )
91+ .setCause (innerFailure )
92+ .setApplicationFailureInfo (
93+ ApplicationFailureInfo .newBuilder ().setType ("OuterFailure" ).build ())
94+ .build ();
95+
96+ // Convert to FailureInfo and back
97+ FailureInfo failureInfo = NexusUtil .temporalFailureToNexusFailureInfo (outerFailure );
98+ Failure reconstructed = NexusUtil .nexusFailureToAPIFailure (failureInfo , false );
99+
100+ // Verify nested structure is preserved
101+ Assert .assertEquals ("outer failure" , reconstructed .getMessage ());
102+ Assert .assertEquals ("OuterFailure" , reconstructed .getApplicationFailureInfo ().getType ());
103+ Assert .assertTrue (reconstructed .hasCause ());
104+ Assert .assertEquals ("inner cause" , reconstructed .getCause ().getMessage ());
105+ Assert .assertEquals (
106+ "InnerFailure" , reconstructed .getCause ().getApplicationFailureInfo ().getType ());
107+ }
108+
109+ @ Test
110+ public void testHandlerErrorToNexusErrorWithCause () {
111+ ApplicationFailure cause = ApplicationFailure .newFailure ("test error" , "TestType" , "detail" );
112+ HandlerException exception =
113+ new HandlerException (HandlerException .ErrorType .BAD_REQUEST , cause );
114+
115+ HandlerError nexusError = NexusUtil .handlerErrorToNexusError (exception , DATA_CONVERTER );
116+
117+ Assert .assertEquals ("BAD_REQUEST" , nexusError .getErrorType ());
118+ Assert .assertTrue (nexusError .hasFailure ());
119+ Assert .assertEquals ("test error" , nexusError .getFailure ().getMessage ());
120+ }
121+
122+ @ Test
123+ public void testHandlerErrorToNexusErrorWithoutCause () {
124+ HandlerException exception =
125+ new HandlerException (
126+ HandlerException .ErrorType .BAD_REQUEST , "handler message" , (Throwable ) null );
127+
128+ HandlerError nexusError = NexusUtil .handlerErrorToNexusError (exception , DATA_CONVERTER );
129+
130+ Assert .assertEquals ("BAD_REQUEST" , nexusError .getErrorType ());
131+ Assert .assertTrue (nexusError .hasFailure ());
132+ Assert .assertEquals ("handler message" , nexusError .getFailure ().getMessage ());
133+ }
134+
135+ @ Test
136+ public void testHandlerErrorToNexusErrorWithEmptyMessage () {
137+ HandlerException exception =
138+ new HandlerException (HandlerException .ErrorType .INTERNAL , "" , (Throwable ) null );
139+
140+ HandlerError nexusError = NexusUtil .handlerErrorToNexusError (exception , DATA_CONVERTER );
141+
142+ Assert .assertEquals ("INTERNAL" , nexusError .getErrorType ());
143+ // Should not have failure when message is empty
144+ Assert .assertFalse (nexusError .hasFailure ());
145+ }
146+
147+ @ Test
148+ public void testNexusFailureWithStackTracePreservation () {
149+ String stackTrace =
150+ "at io.temporal.test.Method1(Test.java:100)\n "
151+ + "at io.temporal.test.Method2(Test.java:200)\n "
152+ + "at io.temporal.test.Method3(Test.java:300)" ;
153+
154+ Failure failure =
155+ Failure .newBuilder ()
156+ .setMessage ("failure with stack" )
157+ .setStackTrace (stackTrace )
158+ .setApplicationFailureInfo (
159+ ApplicationFailureInfo .newBuilder ().setType ("TestFailure" ).build ())
160+ .build ();
161+
162+ io .temporal .api .nexus .v1 .Failure nexusFailure =
163+ NexusUtil .temporalFailureToNexusFailure (failure );
164+ Assert .assertEquals (stackTrace , nexusFailure .getStackTrace ());
165+
166+ // Convert back
167+ FailureInfo .Builder failureInfoBuilder =
168+ FailureInfo .newBuilder ()
169+ .setMessage (nexusFailure .getMessage ())
170+ .setDetailsJson (nexusFailure .getDetails ().toStringUtf8 ())
171+ .setStackTrace (nexusFailure .getStackTrace ());
172+ // Add metadata entries individually
173+ for (String key : nexusFailure .getMetadataMap ().keySet ()) {
174+ failureInfoBuilder .putMetadata (key , nexusFailure .getMetadataMap ().get (key ));
175+ }
176+ FailureInfo failureInfo = failureInfoBuilder .build ();
177+
178+ Failure reconstructed = NexusUtil .nexusFailureToAPIFailure (failureInfo , true );
179+ Assert .assertEquals (stackTrace , reconstructed .getStackTrace ());
180+ }
181+
182+ @ Test
183+ public void testNexusFailureWithoutTemporalMetadata () {
184+ // Test handling of non-Temporal Nexus failures
185+ FailureInfo failureInfo =
186+ FailureInfo .newBuilder ()
187+ .setMessage ("generic nexus failure" )
188+ .putMetadata ("custom-key" , "custom-value" )
189+ .setDetailsJson ("{\" detail\" :\" some data\" }" )
190+ .build ();
191+
192+ Failure apiFailure = NexusUtil .nexusFailureToAPIFailure (failureInfo , true );
193+
194+ // Should be wrapped as NexusFailure type
195+ Assert .assertEquals ("generic nexus failure" , apiFailure .getMessage ());
196+ Assert .assertTrue (apiFailure .hasApplicationFailureInfo ());
197+ Assert .assertEquals ("NexusFailure" , apiFailure .getApplicationFailureInfo ().getType ());
198+ Assert .assertFalse (apiFailure .getApplicationFailureInfo ().getNonRetryable ());
199+ }
200+
201+ @ Test
202+ public void testDeeplyNestedFailureCauses () {
203+ // Test 4 levels of nesting
204+ Failure level4 =
205+ Failure .newBuilder ()
206+ .setMessage ("level 4" )
207+ .setApplicationFailureInfo (
208+ ApplicationFailureInfo .newBuilder ().setType ("Level4" ).build ())
209+ .build ();
210+
211+ Failure level3 =
212+ Failure .newBuilder ()
213+ .setMessage ("level 3" )
214+ .setCause (level4 )
215+ .setApplicationFailureInfo (
216+ ApplicationFailureInfo .newBuilder ().setType ("Level3" ).build ())
217+ .build ();
218+
219+ Failure level2 =
220+ Failure .newBuilder ()
221+ .setMessage ("level 2" )
222+ .setCause (level3 )
223+ .setApplicationFailureInfo (
224+ ApplicationFailureInfo .newBuilder ().setType ("Level2" ).build ())
225+ .build ();
226+
227+ Failure level1 =
228+ Failure .newBuilder ()
229+ .setMessage ("level 1" )
230+ .setCause (level2 )
231+ .setApplicationFailureInfo (
232+ ApplicationFailureInfo .newBuilder ().setType ("Level1" ).build ())
233+ .build ();
234+
235+ // Convert through Nexus format and back
236+ io .temporal .api .nexus .v1 .Failure nexusFailure = NexusUtil .temporalFailureToNexusFailure (level1 );
237+ FailureInfo failureInfo = NexusUtil .temporalFailureToNexusFailureInfo (level1 );
238+ Failure reconstructed = NexusUtil .nexusFailureToAPIFailure (failureInfo , true );
239+
240+ // Verify all levels are preserved
241+ Assert .assertEquals ("level 1" , reconstructed .getMessage ());
242+ Assert .assertEquals ("level 2" , reconstructed .getCause ().getMessage ());
243+ Assert .assertEquals ("level 3" , reconstructed .getCause ().getCause ().getMessage ());
244+ Assert .assertEquals ("level 4" , reconstructed .getCause ().getCause ().getCause ().getMessage ());
245+ }
246+
247+ @ Test
248+ public void testCanceledFailureConversion () {
249+ Failure canceledFailure =
250+ Failure .newBuilder ()
251+ .setMessage ("operation canceled" )
252+ .setCanceledFailureInfo (CanceledFailureInfo .newBuilder ().build ())
253+ .build ();
254+
255+ FailureInfo failureInfo = NexusUtil .temporalFailureToNexusFailureInfo (canceledFailure );
256+ Failure reconstructed = NexusUtil .nexusFailureToAPIFailure (failureInfo , true );
257+
258+ Assert .assertEquals ("operation canceled" , reconstructed .getMessage ());
259+ Assert .assertTrue (reconstructed .hasCanceledFailureInfo ());
260+ }
18261}
0 commit comments