diff --git a/access/http/client.go b/access/http/client.go index 768a01856..1eda4c09e 100644 --- a/access/http/client.go +++ b/access/http/client.go @@ -189,11 +189,11 @@ func (c *Client) GetTransactionResultsByBlockID(ctx context.Context, blockID flo } func (c *Client) GetScheduledTransaction(ctx context.Context, scheduledTxID uint64) (*flow.Transaction, error) { - return nil, fmt.Errorf("not implemented") + return c.httpClient.GetScheduledTransaction(ctx, scheduledTxID) } func (c *Client) GetScheduledTransactionResult(ctx context.Context, scheduledTxID uint64) (*flow.TransactionResult, error) { - return nil, fmt.Errorf("not implemented") + return c.httpClient.GetScheduledTransactionResult(ctx, scheduledTxID) } func (c *Client) GetSystemTransaction(ctx context.Context, blockID flow.Identifier) (*flow.Transaction, error) { diff --git a/access/http/client_test.go b/access/http/client_test.go index 0246431e8..87e67a43b 100644 --- a/access/http/client_test.go +++ b/access/http/client_test.go @@ -416,6 +416,74 @@ func TestBaseClient_GetTransactionResult(t *testing.T) { })) } +func TestBaseClient_GetScheduledTransaction(t *testing.T) { + const handlerName = "getScheduledTransaction" + + t.Run("Success", clientTest(func(ctx context.Context, t *testing.T, handler *mockHandler, client *Client) { + httpTx := unittest.TransactionFlowFixture() + expectedTx, err := convert.ToTransaction(&httpTx) + assert.NoError(t, err) + + var scheduledTxID uint64 = 42 + handler. + On(handlerName, mock.Anything, scheduledTxID, false). + Return(&httpTx, nil) + + tx, err := client.GetScheduledTransaction(ctx, scheduledTxID) + assert.NoError(t, err) + assert.Equal(t, tx, expectedTx) + })) + + t.Run("Not Found", clientTest(func(ctx context.Context, t *testing.T, handler *mockHandler, client *Client) { + handler.On(handlerName, mock.Anything, mock.Anything, mock.Anything).Return(nil, HTTPError{ + Url: "/", + Code: 404, + Message: "scheduled tx not found", + }) + + tx, err := client.GetScheduledTransaction(ctx, uint64(99)) + assert.EqualError(t, err, "scheduled tx not found") + assert.Nil(t, tx) + })) +} + +func TestBaseClient_GetScheduledTransactionResult(t *testing.T) { + const handlerName = "getScheduledTransaction" + + t.Run("Success", clientTest(func(ctx context.Context, t *testing.T, handler *mockHandler, client *Client) { + httpTx := unittest.TransactionFlowFixture() + httpTxRes := unittest.TransactionResultFlowFixture(flow.EventEncodingVersionJSONCDC) + httpTx.Result = &httpTxRes + expectedTx, err := convert.ToTransaction(&httpTx) + assert.NoError(t, err) + + expectedTxRes, err := convert.ToTransactionResult(&httpTxRes, nil) + assert.NoError(t, err) + + var scheduledTxID uint64 = 42 + handler. + On(handlerName, mock.Anything, scheduledTxID, true). + Return(&httpTx, nil) + + txRes, err := client.GetScheduledTransactionResult(ctx, scheduledTxID) + assert.NoError(t, err) + assert.Equal(t, txRes, expectedTxRes) + _ = expectedTx // suppress unused + })) + + t.Run("Not Found", clientTest(func(ctx context.Context, t *testing.T, handler *mockHandler, client *Client) { + handler.On(handlerName, mock.Anything, mock.Anything, true).Return(nil, HTTPError{ + Url: "/", + Code: 404, + Message: "scheduled tx result not found", + }) + + tx, err := client.GetScheduledTransactionResult(ctx, uint64(99)) + assert.EqualError(t, err, "scheduled tx result not found") + assert.Nil(t, tx) + })) +} + func TestBaseClient_GetAccount(t *testing.T) { const handlerName = "getAccount" diff --git a/access/http/handler.go b/access/http/handler.go index 6f5fb1bf8..17e077afd 100644 --- a/access/http/handler.go +++ b/access/http/handler.go @@ -363,6 +363,29 @@ func (h *httpHandler) getTransaction( return &transaction, nil } +func (h *httpHandler) getScheduledTransaction( + ctx context.Context, + scheduledTxID uint64, + includeResult bool, + opts ...queryOpts, +) (*models.Transaction, error) { + var transaction models.Transaction + u := h.mustBuildURL(fmt.Sprintf("/transactions/%d", scheduledTxID), opts...) + + if includeResult { + q := u.Query() + q.Add("expand", "result") + u.RawQuery = q.Encode() + } + + err := h.get(ctx, u, &transaction) + if err != nil { + return nil, errors.Wrap(err, fmt.Sprintf("get scheduled transaction ID %d failed", scheduledTxID)) + } + + return &transaction, nil +} + func (h *httpHandler) sendTransaction(ctx context.Context, transaction []byte, opts ...queryOpts) error { var tx models.Transaction return h.post(ctx, h.mustBuildURL("/transactions", opts...), transaction, &tx) diff --git a/access/http/handler_test.go b/access/http/handler_test.go index 977e09fb3..d686248b5 100644 --- a/access/http/handler_test.go +++ b/access/http/handler_test.go @@ -461,6 +461,40 @@ func TestHandler_GetTransaction(t *testing.T) { })) } +func newScheduledTransactionURL(scheduledTxID uint64, query map[string]string) url.URL { + u, _ := url.Parse(fmt.Sprintf("/transactions/%d", scheduledTxID)) + return addQuery(u, query) +} + +func TestHandler_GetScheduledTransaction(t *testing.T) { + + t.Run("Success", handlerTest(func(ctx context.Context, t *testing.T, handler httpHandler, req *testRequest) { + httpTx := unittest.TransactionFlowFixture() + var scheduledTxID uint64 = 42 + + txURL := newScheduledTransactionURL(scheduledTxID, nil) + req.SetData(txURL, httpTx) + + tx, err := handler.getScheduledTransaction(ctx, scheduledTxID, false) + assert.NoError(t, err) + assert.Equal(t, *tx, httpTx) + })) + + t.Run("Success With Results", handlerTest(func(ctx context.Context, t *testing.T, handler httpHandler, req *testRequest) { + httpTx := unittest.TransactionFlowFixture() + var scheduledTxID uint64 = 42 + + txURL := newScheduledTransactionURL(scheduledTxID, map[string]string{ + "expand": "result", + }) + req.SetData(txURL, httpTx) + + tx, err := handler.getScheduledTransaction(ctx, scheduledTxID, true) + assert.NoError(t, err) + assert.Equal(t, *tx, httpTx) + })) +} + func newEventsURL(query map[string]string, ids []string) url.URL { u, _ := url.Parse("/events") if query == nil { diff --git a/access/http/http.go b/access/http/http.go index e6b88b9a7..025a12cef 100644 --- a/access/http/http.go +++ b/access/http/http.go @@ -48,6 +48,7 @@ type handler interface { executeScriptAtBlockHeight(ctx context.Context, height string, script string, arguments []string, opts ...queryOpts) (string, error) executeScriptAtBlockID(ctx context.Context, ID string, script string, arguments []string, opts ...queryOpts) (string, error) getTransaction(ctx context.Context, ID string, includeResult bool, opts ...queryOpts) (*models.Transaction, error) + getScheduledTransaction(ctx context.Context, scheduledTxID uint64, includeResult bool, opts ...queryOpts) (*models.Transaction, error) sendTransaction(ctx context.Context, transaction []byte, opts ...queryOpts) error getEvents(ctx context.Context, eventType string, start string, end string, blockIDs []string, opts ...queryOpts) ([]models.BlockEvents, error) getExecutionResultByID(ctx context.Context, id string, opts ...queryOpts) (*models.ExecutionResult, error) @@ -299,6 +300,32 @@ func (c *BaseClient) GetTransactionResult( return convert.ToTransactionResult(tx.Result, c.jsonOptions) } +func (c *BaseClient) GetScheduledTransaction( + ctx context.Context, + scheduledTxID uint64, + opts ...queryOpts, +) (*flow.Transaction, error) { + tx, err := c.handler.getScheduledTransaction(ctx, scheduledTxID, false, opts...) + if err != nil { + return nil, err + } + + return convert.ToTransaction(tx) +} + +func (c *BaseClient) GetScheduledTransactionResult( + ctx context.Context, + scheduledTxID uint64, + opts ...queryOpts, +) (*flow.TransactionResult, error) { + tx, err := c.handler.getScheduledTransaction(ctx, scheduledTxID, true, opts...) + if err != nil { + return nil, err + } + + return convert.ToTransactionResult(tx.Result, c.jsonOptions) +} + func (c *BaseClient) GetAccountAtBlockHeight( ctx context.Context, address flow.Address, diff --git a/access/http/mock_handler.go b/access/http/mock_handler.go index 3e2d6aac2..2f1377a18 100644 --- a/access/http/mock_handler.go +++ b/access/http/mock_handler.go @@ -417,6 +417,43 @@ func (_m *mockHandler) getNodeVersionInfo(ctx context.Context, opts ...queryOpts return r0, r1 } +// getScheduledTransaction provides a mock function with given fields: ctx, scheduledTxID, includeResult, opts +func (_m *mockHandler) getScheduledTransaction(ctx context.Context, scheduledTxID uint64, includeResult bool, opts ...queryOpts) (*models.Transaction, error) { + _va := make([]interface{}, len(opts)) + for _i := range opts { + _va[_i] = opts[_i] + } + var _ca []interface{} + _ca = append(_ca, ctx, scheduledTxID, includeResult) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + if len(ret) == 0 { + panic("no return value specified for getScheduledTransaction") + } + + var r0 *models.Transaction + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, uint64, bool, ...queryOpts) (*models.Transaction, error)); ok { + return rf(ctx, scheduledTxID, includeResult, opts...) + } + if rf, ok := ret.Get(0).(func(context.Context, uint64, bool, ...queryOpts) *models.Transaction); ok { + r0 = rf(ctx, scheduledTxID, includeResult, opts...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*models.Transaction) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, uint64, bool, ...queryOpts) error); ok { + r1 = rf(ctx, scheduledTxID, includeResult, opts...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + // getTransaction provides a mock function with given fields: ctx, ID, includeResult, opts func (_m *mockHandler) getTransaction(ctx context.Context, ID string, includeResult bool, opts ...queryOpts) (*models.Transaction, error) { _va := make([]interface{}, len(opts))