Skip to content

Commit 4cdea3d

Browse files
authored
[KLC-1849] Fix response inconsistency in broadcastTx response (#2)
## Summary Fixes inconsistent response structure in the `/transaction/broadcast` endpoint. The endpoint now returns uniform fields for both single and bulk transaction broadcasts. ## Changes **Single Transaction Response:** - Added `txsHashes` array field (previously missing) - Now returns: `txHash`, `txCount`, and `txsHashes` **Bulk Transaction Response:** - Added `txCount` field (previously missing) - Now returns: `txsHashes` and `txCount` **Response Structure:** ```json { "txHash": "hash", // populated for single TX, null for bulk "txCount": 1, // always populated "txsHashes": ["hash", ...] // always populated as array } Test Coverage Added comprehensive test suite covering: - Error scenarios (nil context, invalid facade, invalid JSON) - Single transaction success and error paths - Bulk transaction success and error paths - Response structure validation for both modes Impact - Backward Compatible: Only adds missing fields, existing fields unchanged - API Consistency: Clients can now rely on predictable response structure - No Breaking Changes: Existing integrations will continue to work
1 parent 22769b3 commit 4cdea3d

2 files changed

Lines changed: 221 additions & 2 deletions

File tree

network/api/transaction/routes.go

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -272,8 +272,9 @@ func BroadcastTX(c *gin.Context) {
272272
}
273273

274274
response := &models.BroadcastTXResponse{
275-
TxHash: txHash,
276-
TxCount: 1,
275+
TxHash: txHash,
276+
TxCount: 1,
277+
TxsHashes: []string{txHash},
277278
}
278279

279280
c.JSON(
@@ -303,6 +304,7 @@ func BroadcastTX(c *gin.Context) {
303304

304305
response := &models.BroadcastTXResponse{
305306
TxsHashes: txsHashes,
307+
TxCount: len(txsHashes),
306308
}
307309

308310
c.JSON(

network/api/transaction/routes_test.go

Lines changed: 217 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -324,3 +324,220 @@ func TestEstimateTransactionFees_ShouldWork(t *testing.T) {
324324

325325
assert.Equal(t, shared.ReturnCodeSuccess, response.Code)
326326
}
327+
328+
func TestBroadcastTX_NilContextShouldError(t *testing.T) {
329+
t.Parallel()
330+
ws := startNodeServer(nil)
331+
332+
tx := transaction.NewBaseTransaction([]byte("sender"), 0, nil, 0, 0)
333+
requestData := tr.BroadcastTXRequest{
334+
TX: tx,
335+
}
336+
requestBytes, _ := json.Marshal(requestData)
337+
338+
req, _ := http.NewRequest("POST", "/transaction/broadcast", bytes.NewReader(requestBytes))
339+
resp := httptest.NewRecorder()
340+
ws.ServeHTTP(resp, req)
341+
342+
response := shared.GenericAPIResponse{}
343+
loadResponse(resp.Body, &response)
344+
345+
assert.Equal(t, shared.ReturnCodeInternalError, response.Code)
346+
assert.True(t, strings.Contains(response.Error, apiErrors.ErrNilAppContext.Error()))
347+
}
348+
349+
func TestBroadcastTX_WrongFacadeTypeShouldError(t *testing.T) {
350+
t.Parallel()
351+
ws := startNodeServerWrongFacade()
352+
353+
tx := transaction.NewBaseTransaction([]byte("sender"), 0, nil, 0, 0)
354+
requestData := tr.BroadcastTXRequest{
355+
TX: tx,
356+
}
357+
requestBytes, _ := json.Marshal(requestData)
358+
359+
req, _ := http.NewRequest("POST", "/transaction/broadcast", bytes.NewReader(requestBytes))
360+
resp := httptest.NewRecorder()
361+
ws.ServeHTTP(resp, req)
362+
363+
response := shared.GenericAPIResponse{}
364+
loadResponse(resp.Body, &response)
365+
366+
assert.Equal(t, http.StatusInternalServerError, resp.Code)
367+
assert.Equal(t, shared.ReturnCodeInternalError, response.Code)
368+
assert.Equal(t, apiErrors.ErrInvalidAppContext.Error(), response.Error)
369+
}
370+
371+
func TestBroadcastTX_InvalidJSONShouldError(t *testing.T) {
372+
t.Parallel()
373+
facade := mock.Facade{}
374+
ws := startNodeServer(&facade)
375+
376+
req, _ := http.NewRequest("POST", "/transaction/broadcast", bytes.NewReader([]byte("invalid json")))
377+
resp := httptest.NewRecorder()
378+
ws.ServeHTTP(resp, req)
379+
380+
response := shared.GenericAPIResponse{}
381+
loadResponse(resp.Body, &response)
382+
383+
assert.Equal(t, http.StatusBadRequest, resp.Code)
384+
assert.Equal(t, shared.ReturnCodeRequestError, response.Code)
385+
assert.True(t, strings.Contains(response.Error, apiErrors.ErrValidation.Error()))
386+
}
387+
388+
func TestBroadcastTX_SingleTransaction_ShouldWork(t *testing.T) {
389+
t.Parallel()
390+
391+
expectedTxHash := "expectedhash"
392+
facade := mock.Facade{
393+
SendTransactionHandler: func(tx *transaction.Transaction) (string, error) {
394+
return expectedTxHash, nil
395+
},
396+
}
397+
398+
ws := startNodeServer(&facade)
399+
400+
tx := transaction.NewBaseTransaction([]byte("sender"), 0, nil, 0, 0)
401+
requestData := tr.BroadcastTXRequest{
402+
TX: tx,
403+
}
404+
requestBytes, _ := json.Marshal(requestData)
405+
406+
req, _ := http.NewRequest("POST", "/transaction/broadcast", bytes.NewReader(requestBytes))
407+
resp := httptest.NewRecorder()
408+
ws.ServeHTTP(resp, req)
409+
410+
response := shared.GenericAPIResponse{}
411+
loadResponse(resp.Body, &response)
412+
413+
assert.Equal(t, http.StatusOK, resp.Code)
414+
assert.Equal(t, shared.ReturnCodeSuccess, response.Code)
415+
assert.Empty(t, response.Error)
416+
417+
// Parse the response data
418+
responseData, ok := response.Data.(map[string]interface{})
419+
require.True(t, ok)
420+
421+
assert.Equal(t, expectedTxHash, responseData["txHash"])
422+
assert.Equal(t, float64(1), responseData["txCount"])
423+
assert.Equal(t, []interface{}{expectedTxHash}, responseData["txsHashes"])
424+
425+
txsHashes, ok := responseData["txsHashes"].([]interface{})
426+
require.True(t, ok)
427+
require.Len(t, txsHashes, 1)
428+
assert.Equal(t, expectedTxHash, txsHashes[0])
429+
}
430+
431+
func TestBroadcastTX_SingleTransaction_SendTransactionError(t *testing.T) {
432+
t.Parallel()
433+
434+
expectedError := errors.New("send transaction failed")
435+
facade := mock.Facade{
436+
SendTransactionHandler: func(tx *transaction.Transaction) (string, error) {
437+
return "", expectedError
438+
},
439+
}
440+
441+
ws := startNodeServer(&facade)
442+
443+
tx := transaction.NewBaseTransaction([]byte("sender"), 0, nil, 0, 0)
444+
requestData := tr.BroadcastTXRequest{
445+
TX: tx,
446+
}
447+
requestBytes, _ := json.Marshal(requestData)
448+
449+
req, _ := http.NewRequest("POST", "/transaction/broadcast", bytes.NewReader(requestBytes))
450+
resp := httptest.NewRecorder()
451+
ws.ServeHTTP(resp, req)
452+
453+
response := shared.GenericAPIResponse{}
454+
loadResponse(resp.Body, &response)
455+
456+
assert.Equal(t, http.StatusBadRequest, resp.Code)
457+
assert.Equal(t, shared.ReturnCodeRequestError, response.Code)
458+
assert.True(t, strings.Contains(response.Error, apiErrors.ErrValidation.Error()))
459+
assert.True(t, strings.Contains(response.Error, expectedError.Error()))
460+
assert.Nil(t, response.Data)
461+
}
462+
463+
func TestBroadcastTX_BulkTransactions_ShouldWork(t *testing.T) {
464+
t.Parallel()
465+
466+
expectedTxHashes := []string{"hash1", "hash2", "hash3"}
467+
facade := mock.Facade{
468+
SendBulkTransactionsHandler: func(txs []*transaction.Transaction) ([]string, error) {
469+
require.Len(t, txs, 3)
470+
return expectedTxHashes, nil
471+
},
472+
}
473+
474+
ws := startNodeServer(&facade)
475+
476+
tx1 := transaction.NewBaseTransaction([]byte("sender1"), 0, nil, 0, 0)
477+
tx2 := transaction.NewBaseTransaction([]byte("sender2"), 1, nil, 0, 0)
478+
tx3 := transaction.NewBaseTransaction([]byte("sender3"), 2, nil, 0, 0)
479+
480+
requestData := tr.BroadcastTXRequest{
481+
TXs: []*transaction.Transaction{tx1, tx2, tx3},
482+
}
483+
requestBytes, _ := json.Marshal(requestData)
484+
485+
req, _ := http.NewRequest("POST", "/transaction/broadcast", bytes.NewReader(requestBytes))
486+
resp := httptest.NewRecorder()
487+
ws.ServeHTTP(resp, req)
488+
489+
response := shared.GenericAPIResponse{}
490+
loadResponse(resp.Body, &response)
491+
492+
assert.Equal(t, http.StatusOK, resp.Code)
493+
assert.Equal(t, shared.ReturnCodeSuccess, response.Code)
494+
assert.Empty(t, response.Error)
495+
496+
// Parse the response data
497+
responseData, ok := response.Data.(map[string]interface{})
498+
require.True(t, ok)
499+
500+
assert.Nil(t, responseData["txHash"]) // Should be nil for bulk
501+
assert.Equal(t, float64(3), responseData["txCount"]) // JSON numbers are float64
502+
503+
txsHashes, ok := responseData["txsHashes"].([]interface{})
504+
require.True(t, ok)
505+
require.Len(t, txsHashes, 3)
506+
for i, expectedHash := range expectedTxHashes {
507+
assert.Equal(t, expectedHash, txsHashes[i])
508+
}
509+
}
510+
511+
func TestBroadcastTX_BulkTransactions_SendBulkTransactionsError(t *testing.T) {
512+
t.Parallel()
513+
514+
expectedError := errors.New("bulk transaction send failed")
515+
facade := mock.Facade{
516+
SendBulkTransactionsHandler: func(txs []*transaction.Transaction) ([]string, error) {
517+
return nil, expectedError
518+
},
519+
}
520+
521+
ws := startNodeServer(&facade)
522+
523+
tx1 := transaction.NewBaseTransaction([]byte("sender1"), 0, nil, 0, 0)
524+
tx2 := transaction.NewBaseTransaction([]byte("sender2"), 1, nil, 0, 0)
525+
526+
requestData := tr.BroadcastTXRequest{
527+
TXs: []*transaction.Transaction{tx1, tx2},
528+
}
529+
requestBytes, _ := json.Marshal(requestData)
530+
531+
req, _ := http.NewRequest("POST", "/transaction/broadcast", bytes.NewReader(requestBytes))
532+
resp := httptest.NewRecorder()
533+
ws.ServeHTTP(resp, req)
534+
535+
response := shared.GenericAPIResponse{}
536+
loadResponse(resp.Body, &response)
537+
538+
assert.Equal(t, http.StatusBadRequest, resp.Code)
539+
assert.Equal(t, shared.ReturnCodeRequestError, response.Code)
540+
assert.True(t, strings.Contains(response.Error, apiErrors.ErrValidation.Error()))
541+
assert.True(t, strings.Contains(response.Error, expectedError.Error()))
542+
assert.Nil(t, response.Data)
543+
}

0 commit comments

Comments
 (0)