github.com/koko1123/flow-go-1@v0.29.6/engine/access/rest/transactions_test.go (about)

     1  package rest
     2  
     3  import (
     4  	"bytes"
     5  	"encoding/json"
     6  	"fmt"
     7  	"net/http"
     8  	"net/url"
     9  	"strings"
    10  	"testing"
    11  
    12  	mocks "github.com/stretchr/testify/mock"
    13  	"golang.org/x/text/cases"
    14  	"golang.org/x/text/language"
    15  	"google.golang.org/grpc/codes"
    16  	"google.golang.org/grpc/status"
    17  
    18  	"github.com/koko1123/flow-go-1/access"
    19  	"github.com/koko1123/flow-go-1/access/mock"
    20  	"github.com/koko1123/flow-go-1/engine/access/rest/models"
    21  	"github.com/koko1123/flow-go-1/engine/access/rest/util"
    22  	"github.com/koko1123/flow-go-1/model/flow"
    23  	"github.com/koko1123/flow-go-1/utils/unittest"
    24  )
    25  
    26  func getTransactionReq(id string, expandResult bool) *http.Request {
    27  	u, _ := url.Parse(fmt.Sprintf("/v1/transactions/%s", id))
    28  	if expandResult {
    29  		q := u.Query()
    30  		// by default expand all since we test expanding with converters
    31  		q.Add("expand", "result")
    32  		u.RawQuery = q.Encode()
    33  	}
    34  
    35  	req, _ := http.NewRequest("GET", u.String(), nil)
    36  	return req
    37  }
    38  
    39  func getTransactionResultReq(id string) *http.Request {
    40  	req, _ := http.NewRequest("GET", fmt.Sprintf("/v1/transaction_results/%s", id), nil)
    41  	return req
    42  }
    43  
    44  func createTransactionReq(body interface{}) *http.Request {
    45  	jsonBody, _ := json.Marshal(body)
    46  	req, _ := http.NewRequest("POST", "/v1/transactions", bytes.NewBuffer(jsonBody))
    47  	return req
    48  }
    49  
    50  func validCreateBody(tx flow.TransactionBody) map[string]interface{} {
    51  	tx.Arguments = [][]uint8{} // fix how fixture creates nil values
    52  	auth := make([]string, len(tx.Authorizers))
    53  	for i, a := range tx.Authorizers {
    54  		auth[i] = a.String()
    55  	}
    56  
    57  	return map[string]interface{}{
    58  		"script":             util.ToBase64(tx.Script),
    59  		"arguments":          tx.Arguments,
    60  		"reference_block_id": tx.ReferenceBlockID.String(),
    61  		"gas_limit":          fmt.Sprintf("%d", tx.GasLimit),
    62  		"payer":              tx.Payer.String(),
    63  		"proposal_key": map[string]interface{}{
    64  			"address":         tx.ProposalKey.Address.String(),
    65  			"key_index":       fmt.Sprintf("%d", tx.ProposalKey.KeyIndex),
    66  			"sequence_number": fmt.Sprintf("%d", tx.ProposalKey.SequenceNumber),
    67  		},
    68  		"authorizers": auth,
    69  		"payload_signatures": []map[string]interface{}{{
    70  			"address":   tx.PayloadSignatures[0].Address.String(),
    71  			"key_index": fmt.Sprintf("%d", tx.PayloadSignatures[0].KeyIndex),
    72  			"signature": util.ToBase64(tx.PayloadSignatures[0].Signature),
    73  		}},
    74  		"envelope_signatures": []map[string]interface{}{{
    75  			"address":   tx.EnvelopeSignatures[0].Address.String(),
    76  			"key_index": fmt.Sprintf("%d", tx.EnvelopeSignatures[0].KeyIndex),
    77  			"signature": util.ToBase64(tx.EnvelopeSignatures[0].Signature),
    78  		}},
    79  	}
    80  }
    81  
    82  func TestGetTransactions(t *testing.T) {
    83  
    84  	t.Run("get by ID without results", func(t *testing.T) {
    85  		backend := &mock.API{}
    86  		tx := unittest.TransactionFixture()
    87  		req := getTransactionReq(tx.ID().String(), false)
    88  
    89  		backend.Mock.
    90  			On("GetTransaction", mocks.Anything, tx.ID()).
    91  			Return(&tx.TransactionBody, nil)
    92  
    93  		expected := fmt.Sprintf(`
    94  			{
    95  			   "id":"%s",
    96  			   "script":"cHViIGZ1biBtYWluKCkge30=",
    97                 "arguments": [],
    98  			   "reference_block_id":"%s",
    99  			   "gas_limit":"10",
   100  			   "payer":"8c5303eaa26202d6",
   101  			   "proposal_key":{
   102  				  "address":"8c5303eaa26202d6",
   103  				  "key_index":"1",
   104  				  "sequence_number":"0"
   105  			   },
   106  			   "authorizers":[
   107  				  "8c5303eaa26202d6"
   108  			   ],
   109                 "payload_signatures": [],
   110  			   "envelope_signatures":[
   111  				  {
   112  					 "address":"8c5303eaa26202d6",
   113  					 "key_index":"1",
   114  					 "signature":"%s"
   115  				  }
   116  			   ],
   117  			   "_links":{
   118  				  "_self":"/v1/transactions/%s"
   119  			   },
   120  				"_expandable": {
   121  					"result": "/v1/transaction_results/%s"
   122  				}
   123  			}`,
   124  			tx.ID(), tx.ReferenceBlockID, util.ToBase64(tx.EnvelopeSignatures[0].Signature), tx.ID(), tx.ID())
   125  
   126  		assertOKResponse(t, req, expected, backend)
   127  	})
   128  
   129  	t.Run("Get by ID with results", func(t *testing.T) {
   130  		backend := &mock.API{}
   131  		tx := unittest.TransactionFixture()
   132  		txr := transactionResultFixture(tx)
   133  
   134  		backend.Mock.
   135  			On("GetTransaction", mocks.Anything, tx.ID()).
   136  			Return(&tx.TransactionBody, nil)
   137  
   138  		backend.Mock.
   139  			On("GetTransactionResult", mocks.Anything, tx.ID()).
   140  			Return(txr, nil)
   141  
   142  		req := getTransactionReq(tx.ID().String(), true)
   143  
   144  		expected := fmt.Sprintf(`
   145  			{
   146  			   "id":"%s",
   147  			   "script":"cHViIGZ1biBtYWluKCkge30=",
   148                 "arguments": [],
   149  			   "reference_block_id":"%s",
   150  			   "gas_limit":"10",
   151  			   "payer":"8c5303eaa26202d6",
   152  			   "proposal_key":{
   153  				  "address":"8c5303eaa26202d6",
   154  				  "key_index":"1",
   155  				  "sequence_number":"0"
   156  			   },
   157  			   "authorizers":[
   158  				  "8c5303eaa26202d6"
   159  			   ],
   160                 "payload_signatures": [],
   161  			   "envelope_signatures":[
   162  				  {
   163  					 "address":"8c5303eaa26202d6",
   164  					 "key_index":"1",
   165  					 "signature":"%s"
   166  				  }
   167  			   ],
   168  				"result": {
   169  					"block_id": "%s",
   170  					"execution": "Success",
   171  					"status": "Sealed",
   172  					"status_code": 1,
   173  					"error_message": "",
   174  					"computation_used": "0",
   175  					"events": [
   176  						{
   177  							"type": "flow.AccountCreated",
   178  							"transaction_id": "%s",
   179  							"transaction_index": "0",
   180  							"event_index": "0",
   181  							"payload": ""
   182  						}
   183  					],
   184  					"_links": {
   185  						"_self": "/v1/transaction_results/%s"
   186  					}
   187  				},
   188                 "_expandable": {},
   189  			   "_links":{
   190  				  "_self":"/v1/transactions/%s"
   191  			   }
   192  			}`,
   193  			tx.ID(), tx.ReferenceBlockID, util.ToBase64(tx.EnvelopeSignatures[0].Signature), tx.ReferenceBlockID, tx.ID(), tx.ID(), tx.ID())
   194  		assertOKResponse(t, req, expected, backend)
   195  	})
   196  
   197  	t.Run("get by ID Invalid", func(t *testing.T) {
   198  		backend := &mock.API{}
   199  
   200  		req := getTransactionReq("invalid", false)
   201  		expected := `{"code":400, "message":"invalid ID format"}`
   202  		assertResponse(t, req, http.StatusBadRequest, expected, backend)
   203  	})
   204  
   205  	t.Run("get by ID non-existing", func(t *testing.T) {
   206  		backend := &mock.API{}
   207  		tx := unittest.TransactionFixture()
   208  		req := getTransactionReq(tx.ID().String(), false)
   209  
   210  		backend.Mock.
   211  			On("GetTransaction", mocks.Anything, tx.ID()).
   212  			Return(nil, status.Error(codes.NotFound, "transaction not found"))
   213  
   214  		expected := `{"code":404, "message":"Flow resource not found: transaction not found"}`
   215  		assertResponse(t, req, http.StatusNotFound, expected, backend)
   216  	})
   217  }
   218  
   219  func TestGetTransactionResult(t *testing.T) {
   220  
   221  	t.Run("get by ID", func(t *testing.T) {
   222  		backend := &mock.API{}
   223  		id := unittest.IdentifierFixture()
   224  		bid := unittest.IdentifierFixture()
   225  		txr := &access.TransactionResult{
   226  			Status:     flow.TransactionStatusSealed,
   227  			StatusCode: 10,
   228  			Events: []flow.Event{
   229  				unittest.EventFixture(flow.EventAccountCreated, 1, 0, id, 200),
   230  			},
   231  			ErrorMessage: "",
   232  			BlockID:      bid,
   233  		}
   234  		txr.Events[0].Payload = []byte(`test payload`)
   235  
   236  		req := getTransactionResultReq(id.String())
   237  
   238  		backend.Mock.
   239  			On("GetTransactionResult", mocks.Anything, id).
   240  			Return(txr, nil)
   241  
   242  		expected := fmt.Sprintf(`{
   243  			"block_id": "%s",
   244  			"execution": "Success",
   245  			"status": "Sealed",
   246  			"status_code": 10,
   247  			"error_message": "",
   248  			"computation_used": "0",
   249  			"events": [
   250  				{
   251  					"type": "flow.AccountCreated",
   252  					"transaction_id": "%s",
   253  					"transaction_index": "1",
   254  					"event_index": "0",
   255  					"payload": "%s"
   256  				}
   257  			],
   258  			"_links": {
   259  				"_self": "/v1/transaction_results/%s"
   260  			}
   261  		}`, bid.String(), id.String(), util.ToBase64(txr.Events[0].Payload), id.String())
   262  		assertOKResponse(t, req, expected, backend)
   263  	})
   264  
   265  	t.Run("get execution statuses", func(t *testing.T) {
   266  		backend := &mock.API{}
   267  		id := unittest.IdentifierFixture()
   268  		bid := unittest.IdentifierFixture()
   269  
   270  		testVectors := map[*access.TransactionResult]string{{
   271  			Status:       flow.TransactionStatusExpired,
   272  			ErrorMessage: "",
   273  		}: string(models.FAILURE_RESULT), {
   274  			Status:       flow.TransactionStatusSealed,
   275  			ErrorMessage: "cadence runtime exception",
   276  		}: string(models.FAILURE_RESULT), {
   277  			Status:       flow.TransactionStatusFinalized,
   278  			ErrorMessage: "",
   279  		}: string(models.PENDING_RESULT), {
   280  			Status:       flow.TransactionStatusPending,
   281  			ErrorMessage: "",
   282  		}: string(models.PENDING_RESULT), {
   283  			Status:       flow.TransactionStatusExecuted,
   284  			ErrorMessage: "",
   285  		}: string(models.PENDING_RESULT), {
   286  			Status:       flow.TransactionStatusSealed,
   287  			ErrorMessage: "",
   288  		}: string(models.SUCCESS_RESULT)}
   289  
   290  		for txr, err := range testVectors {
   291  			txr.BlockID = bid
   292  			req := getTransactionResultReq(id.String())
   293  			backend.Mock.
   294  				On("GetTransactionResult", mocks.Anything, id).
   295  				Return(txr, nil).
   296  				Once()
   297  
   298  			expected := fmt.Sprintf(`{
   299  				"block_id": "%s",
   300  				"execution": "%s",
   301  				"status": "%s",
   302  				"status_code": 0,
   303  				"error_message": "%s",
   304  				"computation_used": "0",
   305  				"events": [],
   306  				"_links": {
   307  					"_self": "/v1/transaction_results/%s"
   308  				}
   309  			}`, bid.String(), err, cases.Title(language.English).String(strings.ToLower(txr.Status.String())), txr.ErrorMessage, id.String())
   310  			assertOKResponse(t, req, expected, backend)
   311  		}
   312  	})
   313  
   314  	t.Run("get by ID Invalid", func(t *testing.T) {
   315  		backend := &mock.API{}
   316  		req := getTransactionResultReq("invalid")
   317  
   318  		expected := `{"code":400, "message":"invalid ID format"}`
   319  		assertResponse(t, req, http.StatusBadRequest, expected, backend)
   320  	})
   321  }
   322  
   323  func TestCreateTransaction(t *testing.T) {
   324  
   325  	t.Run("create", func(t *testing.T) {
   326  		backend := &mock.API{}
   327  		tx := unittest.TransactionBodyFixture()
   328  		tx.PayloadSignatures = []flow.TransactionSignature{unittest.TransactionSignatureFixture()}
   329  		tx.Arguments = [][]uint8{}
   330  		req := createTransactionReq(validCreateBody(tx))
   331  
   332  		backend.Mock.
   333  			On("SendTransaction", mocks.Anything, &tx).
   334  			Return(nil)
   335  
   336  		expected := fmt.Sprintf(`
   337  			{
   338  			   "id":"%s",
   339  			   "script":"cHViIGZ1biBtYWluKCkge30=",
   340  			   "arguments": [],
   341  			   "reference_block_id":"%s",
   342  			   "gas_limit":"10",
   343  			   "payer":"8c5303eaa26202d6",
   344  			   "proposal_key":{
   345  				  "address":"8c5303eaa26202d6",
   346  				  "key_index":"1",
   347  				  "sequence_number":"0"
   348  			   },
   349  			   "authorizers":[
   350  				  "8c5303eaa26202d6"
   351  			   ],
   352                 "payload_signatures":[
   353  				  {
   354  					 "address":"8c5303eaa26202d6",
   355  					 "key_index":"1",
   356  					 "signature":"%s"
   357  				  }
   358  			   ],
   359  			   "envelope_signatures":[
   360  				  {
   361  					 "address":"8c5303eaa26202d6",
   362  					 "key_index":"1",
   363  					 "signature":"%s"
   364  				  }
   365  			   ],
   366  				"_expandable": {
   367  					"result": "/v1/transaction_results/%s"
   368  				},
   369  			   "_links":{
   370  				  "_self":"/v1/transactions/%s"
   371  			   }
   372  			}`,
   373  			tx.ID(), tx.ReferenceBlockID, util.ToBase64(tx.PayloadSignatures[0].Signature), util.ToBase64(tx.EnvelopeSignatures[0].Signature), tx.ID(), tx.ID())
   374  		assertOKResponse(t, req, expected, backend)
   375  	})
   376  
   377  	t.Run("post invalid transaction", func(t *testing.T) {
   378  		backend := &mock.API{}
   379  		tests := []struct {
   380  			inputField string
   381  			inputValue string
   382  			output     string
   383  		}{
   384  			{"reference_block_id", "-1", `{"code":400, "message":"invalid reference block ID: invalid ID format"}`},
   385  			{"reference_block_id", "", `{"code":400, "message":"reference block not provided"}`},
   386  			{"gas_limit", "-1", `{"code":400, "message":"invalid gas limit: value must be an unsigned 64 bit integer"}`},
   387  			{"payer", "yo", `{"code":400, "message":"invalid payer: invalid address"}`},
   388  			{"proposal_key", "yo", `{"code":400, "message":"request body contains an invalid value for the \"proposal_key\" field (at position 461)"}`},
   389  			{"authorizers", "", `{"code":400, "message":"request body contains an invalid value for the \"authorizers\" field (at position 32)"}`},
   390  			{"authorizers", "yo", `{"code":400, "message":"request body contains an invalid value for the \"authorizers\" field (at position 34)"}`},
   391  			{"envelope_signatures", "", `{"code":400, "message":"request body contains an invalid value for the \"envelope_signatures\" field (at position 75)"}`},
   392  			{"payload_signatures", "", `{"code":400, "message":"request body contains an invalid value for the \"payload_signatures\" field (at position 292)"}`},
   393  		}
   394  
   395  		for _, test := range tests {
   396  			tx := unittest.TransactionBodyFixture()
   397  			tx.PayloadSignatures = []flow.TransactionSignature{unittest.TransactionSignatureFixture()}
   398  			testTx := validCreateBody(tx)
   399  			testTx[test.inputField] = test.inputValue
   400  			req := createTransactionReq(testTx)
   401  
   402  			assertResponse(t, req, http.StatusBadRequest, test.output, backend)
   403  		}
   404  	})
   405  }
   406  
   407  func transactionResultFixture(tx flow.Transaction) *access.TransactionResult {
   408  	return &access.TransactionResult{
   409  		Status:     flow.TransactionStatusSealed,
   410  		StatusCode: 1,
   411  		Events: []flow.Event{
   412  			unittest.EventFixture(flow.EventAccountCreated, 0, 0, tx.ID(), 255),
   413  		},
   414  		ErrorMessage: "",
   415  		BlockID:      tx.ReferenceBlockID,
   416  	}
   417  }