Commit e9ab2181 authored by Will Cory's avatar Will Cory

feat: hook up api to dao

parent fd0a77f7
......@@ -2,79 +2,12 @@ package api
import (
"encoding/json"
"net/http"
"strconv"
"time"
"github.com/ethereum-optimism/optimism/indexer/database"
"github.com/ethereum/go-ethereum/common"
"github.com/go-chi/chi"
"net/http"
)
// TODO in this pr most of these types should be coming from the ORM instead
// DepositsDAO represents the Database Access Object for deposits
type DepositDAO interface {
GetDeposits(limit int, cursor string, sortDirection string) ([]Deposit, string, bool, error)
}
// WithdrawalDAO represents the Database Access Object for deposits
type WithdrawalDAO interface {
GetWithdrawals(limit int, cursor string, sortDirection string, sortBy string) ([]Withdrawal, string, bool, error)
}
// Deposit data structure
type Deposit struct {
Guid string `json:"guid"`
Amount string `json:"amount"`
BlockNumber int `json:"blockNumber"`
BlockTimestamp time.Time `json:"blockTimestamp"`
From string `json:"from"`
To string `json:"to"`
TransactionHash string `json:"transactionHash"`
L1Token TokenListItem `json:"l1Token"`
L2Token TokenListItem `json:"l2Token"`
}
// Withdrawal data structure
type Withdrawal struct {
Guid string `json:"guid"`
Amount string `json:"amount"`
BlockNumber int `json:"blockNumber"`
BlockTimestamp time.Time `json:"blockTimestamp"`
From string `json:"from"`
To string `json:"to"`
TransactionHash string `json:"transactionHash"`
WithdrawalState string `json:"withdrawalState"`
Proof *ProofClaim `json:"proof"`
Claim *ProofClaim `json:"claim"`
L1Token TokenListItem `json:"l1Token"`
L2Token TokenListItem `json:"l2Token"`
}
// TokenListItem data structure
type TokenListItem struct {
ChainId int `json:"chainId"`
Address string `json:"address"`
Name string `json:"name"`
Symbol string `json:"symbol"`
Decimals int `json:"decimals"`
LogoURI string `json:"logoURI"`
Extensions Extensions `json:"extensions"`
}
// Extensions data structure
type Extensions struct {
OptimismBridgeAddress string `json:"optimismBridgeAddress"`
BridgeType string `json:"bridgeType"`
}
// ProofClaim data structure
type ProofClaim struct {
TransactionHash string `json:"transactionHash"`
BlockTimestamp time.Time `json:"blockTimestamp"`
BlockNumber int `json:"blockNumber"`
}
// PaginationResponse for paginated responses
type PaginationResponse struct {
// TODO type this better
Data interface{} `json:"data"`
......@@ -83,59 +16,59 @@ type PaginationResponse struct {
}
func (a *Api) DepositsHandler(w http.ResponseWriter, r *http.Request) {
bv := a.bridgeView
address := common.HexToAddress(chi.URLParam(r, "address"))
limit := getIntFromQuery(r, "limit", 10)
cursor := r.URL.Query().Get("cursor")
sortDirection := r.URL.Query().Get("sortDirection")
// limit := getIntFromQuery(r, "limit", 10)
// cursor := r.URL.Query().Get("cursor")
// sortDirection := r.URL.Query().Get("sortDirection")
deposits, err := bv.DepositsByAddress(address)
deposits, nextCursor, hasNextPage, err := a.DepositDAO.GetDeposits(limit, cursor, sortDirection)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
// This is not the shape of the response we want!!!
// will add in the individual features in future prs 1 by 1
response := PaginationResponse{
Data: deposits,
Cursor: nextCursor,
HasNextPage: hasNextPage,
// Cursor: nextCursor,
HasNextPage: false,
}
jsonResponse(w, response, http.StatusOK)
}
func (a *Api) WithdrawalsHandler(w http.ResponseWriter, r *http.Request) {
limit := getIntFromQuery(r, "limit", 10)
cursor := r.URL.Query().Get("cursor")
sortDirection := r.URL.Query().Get("sortDirection")
sortBy := r.URL.Query().Get("sortBy")
bv := a.bridgeView
address := common.HexToAddress(chi.URLParam(r, "address"))
// limit := getIntFromQuery(r, "limit", 10)
// cursor := r.URL.Query().Get("cursor")
// sortDirection := r.URL.Query().Get("sortDirection")
withdrawals, err := bv.WithdrawalsByAddress(address)
withdrawals, nextCursor, hasNextPage, err := a.WithdrawalDAO.GetWithdrawals(limit, cursor, sortDirection, sortBy)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
// This is not the shape of the response we want!!!
// will add in the individual features in future prs 1 by 1
response := PaginationResponse{
Data: withdrawals,
Cursor: nextCursor,
HasNextPage: hasNextPage,
// Cursor: nextCursor,
HasNextPage: false,
}
jsonResponse(w, response, http.StatusOK)
}
func getIntFromQuery(r *http.Request, key string, defaultValue int) int {
valueStr := r.URL.Query().Get(key)
if valueStr == "" {
return defaultValue
}
value, err := strconv.Atoi(valueStr)
if err != nil {
return defaultValue
}
return value
}
func jsonResponse(w http.ResponseWriter, data interface{}, statusCode int) {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(statusCode)
......@@ -144,21 +77,21 @@ func jsonResponse(w http.ResponseWriter, data interface{}, statusCode int) {
type Api struct {
Router *chi.Mux
DepositDAO DepositDAO
WithdrawalDAO WithdrawalDAO
bridgeView database.BridgeView
}
func NewApi(depositDAO DepositDAO, withdrawalDAO WithdrawalDAO) *Api {
func NewApi(bv database.BridgeView) *Api {
r := chi.NewRouter()
api := &Api{
Router: r,
DepositDAO: depositDAO,
WithdrawalDAO: withdrawalDAO,
bridgeView: bv,
}
r.Get("/api/v0/deposits", api.DepositsHandler)
r.Get("/api/v0/withdrawals", api.WithdrawalsHandler)
// these regex are .+ because I wasn't sure what they should be
// don't want a regex for addresses because would prefer to validate the address
// with go-ethereum and throw a friendly error message
r.Get("/api/v0/deposits/{address:.+}", api.DepositsHandler)
r.Get("/api/v0/withdrawals/{address:.+}", api.WithdrawalsHandler)
return api
......
package api
import (
"encoding/json"
"net/http"
"net/http/httptest"
"testing"
"time"
"github.com/ethereum-optimism/optimism/indexer/database"
"github.com/ethereum/go-ethereum/common"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
)
type MockDB struct {
mock.Mock
}
func (db *MockDB) GetDeposits(limit int, cursor string, sortDirection string) ([]Deposit, string, bool, error) {
args := db.Called(limit, cursor, sortDirection)
return args.Get(0).([]Deposit), args.String(1), args.Bool(2), args.Error(3)
}
func (db *MockDB) GetWithdrawals(limit int, cursor string, sortDirection string, sortBy string) ([]Withdrawal, string, bool, error) {
args := db.Called(limit, cursor, sortDirection, sortBy)
return args.Get(0).([]Withdrawal), args.String(1), args.Bool(2), args.Error(3)
}
// MockBridgeView mocks the BridgeView interface
type MockBridgeView struct{}
func TestApi(t *testing.T) {
mockDB := new(MockDB)
mockDeposits := []Deposit{
// DepositsByAddress mocks returning deposits by an address
func (mbv *MockBridgeView) DepositsByAddress(address common.Address) ([]*database.DepositWithTransactionHash, error) {
return []*database.DepositWithTransactionHash{
{
Guid: "test-guid",
Amount: "1000",
BlockNumber: 123,
BlockTimestamp: time.Unix(123456, 0),
From: "0x1",
To: "0x2",
TransactionHash: "0x3",
Deposit: database.Deposit{
GUID: "mockGUID1",
InitiatedL1EventGUID: "mockEventGUID1",
Tx: database.Transaction{},
TokenPair: database.TokenPair{},
},
L1TransactionHash: common.HexToHash("0x123"),
},
}
}, nil
}
mockWithdrawals := []Withdrawal{
// WithdrawalsByAddress mocks returning withdrawals by an address
func (mbv *MockBridgeView) WithdrawalsByAddress(address common.Address) ([]*database.WithdrawalWithTransactionHashes, error) {
return []*database.WithdrawalWithTransactionHashes{
{
Guid: "test-guid",
Amount: "1000",
BlockNumber: 123,
BlockTimestamp: time.Unix(123456, 0),
From: "0x1",
To: "0x2",
TransactionHash: "0x3",
Withdrawal: database.Withdrawal{
GUID: "mockGUID2",
InitiatedL2EventGUID: "mockEventGUID2",
WithdrawalHash: common.HexToHash("0x456"),
Tx: database.Transaction{},
TokenPair: database.TokenPair{},
},
}
mockDB.On("GetDeposits", 10, "", "").Return(mockDeposits, "nextCursor", false, nil)
mockDB.On("GetWithdrawals", 10, "", "", "").Return(mockWithdrawals, "nextCursor", false, nil)
testApi := NewApi(mockDB, mockDB)
req, _ := http.NewRequest("GET", "/api/v0/deposits", nil)
rr := httptest.NewRecorder()
testApi.Router.ServeHTTP(rr, req)
assert.Equal(t, http.StatusOK, rr.Code, "status code should be 200")
// TODO make this type exist
var depositsResponse DepositsResponse
err := json.Unmarshal(rr.Body.Bytes(), &depositsResponse)
assert.NoError(t, err)
L2TransactionHash: common.HexToHash("0x789"),
},
}, nil
}
assert.Equal(t, mockDeposits, depositsResponse.Data)
func TestDepositsHandler(t *testing.T) {
api := NewApi(&MockBridgeView{})
request, err := http.NewRequest("GET", "/api/v0/deposits/0x123", nil)
assert.Nil(t, err)
req, _ = http.NewRequest("GET", "/api/v0/withdrawals", nil)
rr = httptest.NewRecorder()
testApi.Router.ServeHTTP(rr, req)
responseRecorder := httptest.NewRecorder()
api.Router.ServeHTTP(responseRecorder, request)
assert.Equal(t, http.StatusOK, rr.Code, "status code should be 200")
assert.Equal(t, http.StatusOK, responseRecorder.Code)
}
// TODO make this type exist
var withdrawalsResponse WithdrawalsResponse
err = json
err = json.Unmarshal(rr.Body.Bytes(), &withdrawalsResponse)
assert.NoError(t, err)
func TestWithdrawalsHandler(t *testing.T) {
api := NewApi(&MockBridgeView{})
request, err := http.NewRequest("GET", "/api/v0/withdrawals/0x123", nil)
assert.Nil(t, err)
// Assert response data
assert.Equal(t, mockWithdrawals, withdrawalsResponse.Data)
responseRecorder := httptest.NewRecorder()
api.Router.ServeHTTP(responseRecorder, request)
// Finally, assert that the methods were called with the expected parameters
mockDB.AssertCalled(t, "GetDeposits", 10, "", "")
mockDB.AssertCalled(t, "GetWithdrawals", 10, "", "", "")
assert.Equal(t, http.StatusOK, responseRecorder.Code)
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment