88 "testing"
99
1010 "github.com/keeperhub/cli/cmd/run"
11- khhttp "github.com/keeperhub/cli/internal/http"
1211 "github.com/keeperhub/cli/internal/config"
12+ khhttp "github.com/keeperhub/cli/internal/http"
1313 "github.com/keeperhub/cli/pkg/cmdutil"
1414 "github.com/keeperhub/cli/pkg/iostreams"
1515)
@@ -50,7 +50,7 @@ func makeRunFactory(ios *iostreams.IOStreams, host string) *cmdutil.Factory {
5050 HTTPClient : func () (* khhttp.Client , error ) {
5151 return khhttp .NewClient (khhttp.ClientOptions {
5252 Host : host ,
53- IOStreams : ios ,
53+ IOStreams : ios ,
5454 }), nil
5555 },
5656 }
@@ -94,7 +94,7 @@ func TestStatusCmd_SingleShot(t *testing.T) {
9494func TestStatusCmd_JSONOutput (t * testing.T ) {
9595 srv := makeStatusServer (t , []map [string ]any {
9696 {
97- "status" : "running" ,
97+ "status" : "running" ,
9898 "nodeStatuses" : []map [string ]any {},
9999 "progress" : map [string ]any {
100100 "totalSteps" : 2 ,
@@ -131,7 +131,7 @@ func TestStatusCmd_JSONOutput(t *testing.T) {
131131func TestStatusCmd_WatchSucceeds (t * testing.T ) {
132132 srv := makeStatusServer (t , []map [string ]any {
133133 {
134- "status" : "running" ,
134+ "status" : "running" ,
135135 "nodeStatuses" : []map [string ]any {},
136136 "progress" : map [string ]any {
137137 "totalSteps" : 2 ,
@@ -144,7 +144,7 @@ func TestStatusCmd_WatchSucceeds(t *testing.T) {
144144 "errorContext" : nil ,
145145 },
146146 {
147- "status" : "success" ,
147+ "status" : "success" ,
148148 "nodeStatuses" : []map [string ]any {},
149149 "progress" : map [string ]any {
150150 "totalSteps" : 2 ,
@@ -177,7 +177,7 @@ func TestStatusCmd_WatchSucceeds(t *testing.T) {
177177func TestStatusCmd_WatchError (t * testing.T ) {
178178 srv := makeStatusServer (t , []map [string ]any {
179179 {
180- "status" : "error" ,
180+ "status" : "error" ,
181181 "nodeStatuses" : []map [string ]any {},
182182 "progress" : map [string ]any {
183183 "totalSteps" : 1 ,
@@ -204,7 +204,7 @@ func TestStatusCmd_WatchError(t *testing.T) {
204204func TestStatusCmd_WatchNonTTY (t * testing.T ) {
205205 srv := makeStatusServer (t , []map [string ]any {
206206 {
207- "status" : "running" ,
207+ "status" : "running" ,
208208 "nodeStatuses" : []map [string ]any {},
209209 "progress" : map [string ]any {
210210 "totalSteps" : 2 ,
@@ -217,7 +217,7 @@ func TestStatusCmd_WatchNonTTY(t *testing.T) {
217217 "errorContext" : nil ,
218218 },
219219 {
220- "status" : "success" ,
220+ "status" : "success" ,
221221 "nodeStatuses" : []map [string ]any {},
222222 "progress" : map [string ]any {
223223 "totalSteps" : 2 ,
@@ -250,7 +250,7 @@ func TestStatusCmd_WatchNonTTY(t *testing.T) {
250250func TestStatusCmd_WatchJSONMode (t * testing.T ) {
251251 srv := makeStatusServer (t , []map [string ]any {
252252 {
253- "status" : "running" ,
253+ "status" : "running" ,
254254 "nodeStatuses" : []map [string ]any {},
255255 "progress" : map [string ]any {
256256 "totalSteps" : 1 ,
@@ -261,7 +261,7 @@ func TestStatusCmd_WatchJSONMode(t *testing.T) {
261261 "errorContext" : nil ,
262262 },
263263 {
264- "status" : "success" ,
264+ "status" : "success" ,
265265 "nodeStatuses" : []map [string ]any {},
266266 "progress" : map [string ]any {
267267 "totalSteps" : 1 ,
@@ -314,3 +314,213 @@ func TestStatusCmd_401AuthHint(t *testing.T) {
314314}
315315
316316func strPtr (s string ) * string { return & s }
317+
318+ // TestStatusCmd_TxHashes_Empty: when transactionHashes is [] the section is suppressed.
319+ func TestStatusCmd_TxHashes_Empty (t * testing.T ) {
320+ srv := makeStatusServer (t , []map [string ]any {
321+ {
322+ "status" : "success" ,
323+ "nodeStatuses" : []map [string ]any {},
324+ "progress" : map [string ]any {"totalSteps" : 1 , "completedSteps" : 1 , "percentage" : 100 },
325+ "errorContext" : nil ,
326+ "transactionHashes" : []any {},
327+ },
328+ })
329+ defer srv .Close ()
330+
331+ ios , buf , _ , _ := iostreams .Test ()
332+ cmd := run .NewStatusCmd (makeRunFactory (ios , srv .URL ))
333+ cmd .SetArgs ([]string {"run-abc" })
334+ if err := cmd .Execute (); err != nil {
335+ t .Fatalf ("unexpected error: %v" , err )
336+ }
337+ if strings .Contains (buf .String (), "Transactions" ) {
338+ t .Errorf ("did not expect Transactions section for empty array, got: %q" , buf .String ())
339+ }
340+ }
341+
342+ // TestStatusCmd_TxHashes_NetworkPreferred: network wins over chainId when both present.
343+ func TestStatusCmd_TxHashes_NetworkPreferred (t * testing.T ) {
344+ srv := makeStatusServer (t , []map [string ]any {
345+ {
346+ "status" : "success" ,
347+ "nodeStatuses" : []map [string ]any {},
348+ "progress" : map [string ]any {"totalSteps" : 1 , "completedSteps" : 1 , "percentage" : 100 },
349+ "errorContext" : nil ,
350+ "transactionHashes" : []map [string ]any {
351+ {
352+ "hash" : "0xabc123" ,
353+ "nodeId" : "n1" ,
354+ "nodeName" : "approveStep" ,
355+ "chainId" : 11155111 ,
356+ "network" : "sepolia" ,
357+ },
358+ },
359+ },
360+ })
361+ defer srv .Close ()
362+
363+ ios , buf , _ , _ := iostreams .Test ()
364+ cmd := run .NewStatusCmd (makeRunFactory (ios , srv .URL ))
365+ cmd .SetArgs ([]string {"run-abc" })
366+ if err := cmd .Execute (); err != nil {
367+ t .Fatalf ("unexpected error: %v" , err )
368+ }
369+ out := buf .String ()
370+ if ! strings .Contains (out , "Transactions (1):" ) {
371+ t .Errorf ("expected 'Transactions (1):' header, got: %q" , out )
372+ }
373+ if ! strings .Contains (out , "0xabc123" ) || ! strings .Contains (out , "approveStep" ) || ! strings .Contains (out , "sepolia" ) {
374+ t .Errorf ("expected hash/node/network in row, got: %q" , out )
375+ }
376+ if strings .Contains (out , "11155111" ) {
377+ t .Errorf ("network should be preferred over chainId, got: %q" , out )
378+ }
379+ }
380+
381+ // TestStatusCmd_TxHashes_ChainIdFallback: chainId renders as a bare number when network is absent.
382+ func TestStatusCmd_TxHashes_ChainIdFallback (t * testing.T ) {
383+ srv := makeStatusServer (t , []map [string ]any {
384+ {
385+ "status" : "success" ,
386+ "nodeStatuses" : []map [string ]any {},
387+ "progress" : map [string ]any {"totalSteps" : 1 , "completedSteps" : 1 , "percentage" : 100 },
388+ "errorContext" : nil ,
389+ "transactionHashes" : []map [string ]any {
390+ {"hash" : "0xdeadbeef" , "nodeId" : "n1" , "nodeName" : "swapStep" , "chainId" : 1 },
391+ },
392+ },
393+ })
394+ defer srv .Close ()
395+
396+ ios , buf , _ , _ := iostreams .Test ()
397+ cmd := run .NewStatusCmd (makeRunFactory (ios , srv .URL ))
398+ cmd .SetArgs ([]string {"run-abc" })
399+ if err := cmd .Execute (); err != nil {
400+ t .Fatalf ("unexpected error: %v" , err )
401+ }
402+ out := buf .String ()
403+ if ! strings .Contains (out , "0xdeadbeef swapStep 1\n " ) {
404+ t .Errorf ("expected bare chainId column, got: %q" , out )
405+ }
406+ }
407+
408+ // TestStatusCmd_TxHashes_NoChainColumn: when neither network nor chainId is set, the chain column is omitted.
409+ func TestStatusCmd_TxHashes_NoChainColumn (t * testing.T ) {
410+ srv := makeStatusServer (t , []map [string ]any {
411+ {
412+ "status" : "success" ,
413+ "nodeStatuses" : []map [string ]any {},
414+ "progress" : map [string ]any {"totalSteps" : 1 , "completedSteps" : 1 , "percentage" : 100 },
415+ "errorContext" : nil ,
416+ "transactionHashes" : []map [string ]any {
417+ {"hash" : "0xcafe" , "nodeId" : "n1" , "nodeName" : "stepX" },
418+ },
419+ },
420+ })
421+ defer srv .Close ()
422+
423+ ios , buf , _ , _ := iostreams .Test ()
424+ cmd := run .NewStatusCmd (makeRunFactory (ios , srv .URL ))
425+ cmd .SetArgs ([]string {"run-abc" })
426+ if err := cmd .Execute (); err != nil {
427+ t .Fatalf ("unexpected error: %v" , err )
428+ }
429+ if ! strings .Contains (buf .String (), "0xcafe stepX\n " ) {
430+ t .Errorf ("expected hash + label only when chain info absent, got: %q" , buf .String ())
431+ }
432+ }
433+
434+ // TestStatusCmd_TxHashes_IterationLabel: iterationIndex appears as [#N] (zero-indexed, raw).
435+ func TestStatusCmd_TxHashes_IterationLabel (t * testing.T ) {
436+ srv := makeStatusServer (t , []map [string ]any {
437+ {
438+ "status" : "success" ,
439+ "nodeStatuses" : []map [string ]any {},
440+ "progress" : map [string ]any {"totalSteps" : 1 , "completedSteps" : 1 , "percentage" : 100 },
441+ "errorContext" : nil ,
442+ "transactionHashes" : []map [string ]any {
443+ {"hash" : "0x1" , "nodeId" : "n1" , "nodeName" : "transferBatch" , "iterationIndex" : 0 , "network" : "sepolia" },
444+ {"hash" : "0x2" , "nodeId" : "n1" , "nodeName" : "transferBatch" , "iterationIndex" : 1 , "network" : "sepolia" },
445+ },
446+ },
447+ })
448+ defer srv .Close ()
449+
450+ ios , buf , _ , _ := iostreams .Test ()
451+ cmd := run .NewStatusCmd (makeRunFactory (ios , srv .URL ))
452+ cmd .SetArgs ([]string {"run-abc" })
453+ if err := cmd .Execute (); err != nil {
454+ t .Fatalf ("unexpected error: %v" , err )
455+ }
456+ out := buf .String ()
457+ if ! strings .Contains (out , "Transactions (2):" ) {
458+ t .Errorf ("expected 'Transactions (2):' header, got: %q" , out )
459+ }
460+ if ! strings .Contains (out , "transferBatch[#0]" ) || ! strings .Contains (out , "transferBatch[#1]" ) {
461+ t .Errorf ("expected [#0] and [#1] labels, got: %q" , out )
462+ }
463+ }
464+
465+ // TestStatusCmd_TxHashes_JSONIncludesField: JSON output includes the transactionHashes field
466+ // and the human Transactions section is suppressed.
467+ func TestStatusCmd_TxHashes_JSONIncludesField (t * testing.T ) {
468+ srv := makeStatusServer (t , []map [string ]any {
469+ {
470+ "status" : "success" ,
471+ "nodeStatuses" : []map [string ]any {},
472+ "progress" : map [string ]any {"totalSteps" : 1 , "completedSteps" : 1 , "percentage" : 100 },
473+ "errorContext" : nil ,
474+ "transactionHashes" : []map [string ]any {
475+ {"hash" : "0xabc" , "nodeId" : "n1" , "nodeName" : "swapStep" , "network" : "sepolia" },
476+ },
477+ },
478+ })
479+ defer srv .Close ()
480+
481+ ios , buf , _ , _ := iostreams .Test ()
482+ cmd := run .NewStatusCmd (makeRunFactory (ios , srv .URL ))
483+ cmd .Flags ().Bool ("json" , false , "" )
484+ cmd .Flags ().String ("jq" , "" , "" )
485+ cmd .SetArgs ([]string {"run-abc" , "--json" })
486+ if err := cmd .Execute (); err != nil {
487+ t .Fatalf ("unexpected error: %v" , err )
488+ }
489+ out := buf .String ()
490+ if ! strings .Contains (out , `"transactionHashes"` ) {
491+ t .Errorf ("expected transactionHashes field in JSON output, got: %q" , out )
492+ }
493+ if strings .Contains (out , "Transactions (" ) {
494+ t .Errorf ("human Transactions section should not appear in JSON mode, got: %q" , out )
495+ }
496+ }
497+
498+ // TestStatusCmd_TxHashes_WatchTerminalRendersBeforeError: in watch mode with error status,
499+ // the Transactions section appears before the error stderr line.
500+ func TestStatusCmd_TxHashes_WatchTerminalRendersBeforeError (t * testing.T ) {
501+ srv := makeStatusServer (t , []map [string ]any {
502+ {
503+ "status" : "error" ,
504+ "nodeStatuses" : []map [string ]any {},
505+ "progress" : map [string ]any {"totalSteps" : 2 , "completedSteps" : 1 , "percentage" : 50 },
506+ "errorContext" : "step 2 reverted" ,
507+ "transactionHashes" : []map [string ]any {
508+ {"hash" : "0xfeed" , "nodeId" : "n1" , "nodeName" : "approveStep" , "network" : "sepolia" },
509+ },
510+ },
511+ })
512+ defer srv .Close ()
513+
514+ ios , buf , errBuf , _ := iostreams .Test ()
515+ cmd := run .NewStatusCmd (makeRunFactory (ios , srv .URL ))
516+ cmd .SetArgs ([]string {"run-abc" , "--watch" })
517+ if err := cmd .Execute (); err == nil {
518+ t .Fatal ("expected error from watch on error status, got nil" )
519+ }
520+ if ! strings .Contains (buf .String (), "0xfeed" ) {
521+ t .Errorf ("expected tx hash in stdout, got: %q" , buf .String ())
522+ }
523+ if ! strings .Contains (errBuf .String (), "step 2 reverted" ) {
524+ t .Errorf ("expected errorContext on stderr, got: %q" , errBuf .String ())
525+ }
526+ }
0 commit comments