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 }