Skip to content

Commit

Permalink
SQS-375 | Unit test createFormattedLimitOrder
Browse files Browse the repository at this point in the history
Implements requested improvements: Define error messages as types
  • Loading branch information
deividaspetraitis committed Sep 2, 2024
1 parent e4ce755 commit 65e147b
Show file tree
Hide file tree
Showing 2 changed files with 70 additions and 36 deletions.
67 changes: 50 additions & 17 deletions orderbook/usecase/orderbook_usecase.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import (
orderbookgrpcclientdomain "github.com/osmosis-labs/sqs/domain/orderbook/grpcclient"
"github.com/osmosis-labs/sqs/log"
"github.com/osmosis-labs/sqs/orderbook/telemetry"
"github.com/osmosis-labs/sqs/orderbook/types"
"github.com/osmosis-labs/sqs/sqsdomain"
"go.uber.org/zap"

Expand Down Expand Up @@ -262,33 +263,44 @@ func (o *orderbookUseCaseImpl) createFormattedLimitOrder(
tickForOrder, ok := o.orderbookRepository.GetTickByID(poolID, order.TickId)
if !ok {
telemetry.GetTickByIDNotFoundCounter.Inc()
return orderbookdomain.LimitOrder{}, fmt.Errorf("tick not found %s, %d", orderbookAddress, order.TickId)
return orderbookdomain.LimitOrder{}, types.TickForOrderbookNotFoundError{

Check failure on line 266 in orderbook/usecase/orderbook_usecase.go

View workflow job for this annotation

GitHub Actions / Run linter

undefined: types.TickForOrderbookNotFoundError

Check failure on line 266 in orderbook/usecase/orderbook_usecase.go

View workflow job for this annotation

GitHub Actions / build

undefined: types.TickForOrderbookNotFoundError
OrderbookAddress: orderbookAddress,
TickID: order.TickId,
}
}

tickState := tickForOrder.TickState
unrealizedCancels := tickForOrder.UnrealizedCancels

// Parse quantity as int64
quantity, err := strconv.ParseInt(order.Quantity, 10, 64)
if err != nil {
return orderbookdomain.LimitOrder{}, fmt.Errorf("error parsing quantity: %w", err)
return orderbookdomain.LimitOrder{}, types.ParsingQuantityError{

Check failure on line 277 in orderbook/usecase/orderbook_usecase.go

View workflow job for this annotation

GitHub Actions / Run linter

undefined: types.ParsingQuantityError

Check failure on line 277 in orderbook/usecase/orderbook_usecase.go

View workflow job for this annotation

GitHub Actions / build

undefined: types.ParsingQuantityError
Quantity: order.Quantity,
Err: err,
}
}

// Convert quantity to decimal for the calculations
quantityDec := osmomath.NewDec(quantity)

placedQuantity, err := strconv.ParseInt(order.PlacedQuantity, 10, 64)
if err != nil {
return orderbookdomain.LimitOrder{}, fmt.Errorf("error parsing placed quantity: %w", err)
return orderbookdomain.LimitOrder{}, types.ParsingPlacedQuantityError{

Check failure on line 288 in orderbook/usecase/orderbook_usecase.go

View workflow job for this annotation

GitHub Actions / Run linter

undefined: types.ParsingPlacedQuantityError

Check failure on line 288 in orderbook/usecase/orderbook_usecase.go

View workflow job for this annotation

GitHub Actions / build

undefined: types.ParsingPlacedQuantityError
PlacedQuantity: order.PlacedQuantity,
Err: err,
}
}

if placedQuantity == 0 || placedQuantity < 0 {
return orderbookdomain.LimitOrder{}, fmt.Errorf("placed quantity is 0 or negative")
return orderbookdomain.LimitOrder{}, types.InvalidPlacedQuantityError{PlacedQuantity: placedQuantity}

Check failure on line 295 in orderbook/usecase/orderbook_usecase.go

View workflow job for this annotation

GitHub Actions / Run linter

undefined: types.InvalidPlacedQuantityError

Check failure on line 295 in orderbook/usecase/orderbook_usecase.go

View workflow job for this annotation

GitHub Actions / build

undefined: types.InvalidPlacedQuantityError
}

placedQuantityDec, err := osmomath.NewDecFromStr(order.PlacedQuantity)
if err != nil {
return orderbookdomain.LimitOrder{}, fmt.Errorf("error parsing placed quantity: %w", err)
return orderbookdomain.LimitOrder{}, types.ParsingPlacedQuantityError{

Check failure on line 300 in orderbook/usecase/orderbook_usecase.go

View workflow job for this annotation

GitHub Actions / Run linter

undefined: types.ParsingPlacedQuantityError

Check failure on line 300 in orderbook/usecase/orderbook_usecase.go

View workflow job for this annotation

GitHub Actions / build

undefined: types.ParsingPlacedQuantityError
PlacedQuantity: order.PlacedQuantity,
Err: err,
}
}

// Calculate percent claimed
Expand All @@ -297,38 +309,56 @@ func (o *orderbookUseCaseImpl) createFormattedLimitOrder(
// Calculate normalization factor for price
normalizationFactor, err := o.tokensUsecease.GetSpotPriceScalingFactorByDenom(baseAsset.Symbol, quoteAsset.Symbol)
if err != nil {
return orderbookdomain.LimitOrder{}, fmt.Errorf("error getting spot price scaling factor: %w", err)
return orderbookdomain.LimitOrder{}, types.GettingSpotPriceScalingFactorError{

Check failure on line 312 in orderbook/usecase/orderbook_usecase.go

View workflow job for this annotation

GitHub Actions / Run linter

undefined: types.GettingSpotPriceScalingFactorError

Check failure on line 312 in orderbook/usecase/orderbook_usecase.go

View workflow job for this annotation

GitHub Actions / build

undefined: types.GettingSpotPriceScalingFactorError
BaseDenom: baseAsset.Symbol,
QuoteDenom: quoteAsset.Symbol,
Err: err,
}
}

// Determine tick values and unrealized cancels based on order direction
var tickEtas, tickUnrealizedCancelled int64
if order.OrderDirection == "bid" {
tickEtas, err = strconv.ParseInt(tickState.BidValues.EffectiveTotalAmountSwapped, 10, 64)
if err != nil {
return orderbookdomain.LimitOrder{}, fmt.Errorf("error parsing bid effective total amount swapped: %w", err)
return orderbookdomain.LimitOrder{}, types.ParsingTickValuesError{

Check failure on line 324 in orderbook/usecase/orderbook_usecase.go

View workflow job for this annotation

GitHub Actions / Run linter

undefined: types.ParsingTickValuesError

Check failure on line 324 in orderbook/usecase/orderbook_usecase.go

View workflow job for this annotation

GitHub Actions / build

undefined: types.ParsingTickValuesError
Field: "EffectiveTotalAmountSwapped (bid)",
Err: err,
}
}

tickUnrealizedCancelled, err = strconv.ParseInt(unrealizedCancels.BidUnrealizedCancels.String(), 10, 64)
if err != nil {
return orderbookdomain.LimitOrder{}, fmt.Errorf("error parsing bid unrealized cancels: %w", err)
return orderbookdomain.LimitOrder{}, types.ParsingUnrealizedCancelsError{

Check failure on line 332 in orderbook/usecase/orderbook_usecase.go

View workflow job for this annotation

GitHub Actions / Run linter

undefined: types.ParsingUnrealizedCancelsError

Check failure on line 332 in orderbook/usecase/orderbook_usecase.go

View workflow job for this annotation

GitHub Actions / build

undefined: types.ParsingUnrealizedCancelsError
Field: "BidUnrealizedCancels",
Err: err,
}
}
} else {
tickEtas, err = strconv.ParseInt(tickState.AskValues.EffectiveTotalAmountSwapped, 10, 64)
if err != nil {
return orderbookdomain.LimitOrder{}, fmt.Errorf("error parsing ask effective total amount swapped: %w", err)
return orderbookdomain.LimitOrder{}, types.ParsingTickValuesError{

Check failure on line 340 in orderbook/usecase/orderbook_usecase.go

View workflow job for this annotation

GitHub Actions / Run linter

undefined: types.ParsingTickValuesError

Check failure on line 340 in orderbook/usecase/orderbook_usecase.go

View workflow job for this annotation

GitHub Actions / build

undefined: types.ParsingTickValuesError
Field: "EffectiveTotalAmountSwapped (ask)",
Err: err,
}
}

tickUnrealizedCancelled, err = strconv.ParseInt(unrealizedCancels.AskUnrealizedCancels.String(), 10, 64)
if err != nil {
return orderbookdomain.LimitOrder{}, fmt.Errorf("error parsing ask unrealized cancels: %w", err)
return orderbookdomain.LimitOrder{}, types.ParsingUnrealizedCancelsError{

Check failure on line 348 in orderbook/usecase/orderbook_usecase.go

View workflow job for this annotation

GitHub Actions / build

undefined: types.ParsingUnrealizedCancelsError
Field: "AskUnrealizedCancels",
Err: err,
}
}
}

// Calculate total ETAs and total filled

etas, err := strconv.ParseInt(order.Etas, 10, 64)
if err != nil {
return orderbookdomain.LimitOrder{}, fmt.Errorf("error parsing etas: %w", err)
return orderbookdomain.LimitOrder{}, types.ParsingEtasError{
Etas: order.Etas,
Err: err,
}
}

tickTotalEtas := tickEtas + tickUnrealizedCancelled
Expand All @@ -341,19 +371,19 @@ func (o *orderbookUseCaseImpl) createFormattedLimitOrder(
// Calculate percent filled using
percentFilled, err := osmomath.NewDecFromStr(strconv.FormatFloat(math.Min(float64(totalFilled)/float64(placedQuantity), 1), 'f', -1, 64))
if err != nil {
return orderbookdomain.LimitOrder{}, fmt.Errorf("error calculating percent filled: %w", err)
return orderbookdomain.LimitOrder{}, types.CalculatingPercentFilledError{Err: err}
}

// Determine order status based on percent filled
status, err := order.Status(percentFilled.MustFloat64())
if err != nil {
return orderbookdomain.LimitOrder{}, fmt.Errorf("mapping order status: %w", err)
return orderbookdomain.LimitOrder{}, types.MappingOrderStatusError{Err: err}
}

// Calculate price based on tick ID
price, err := clmath.TickToPrice(order.TickId)
if err != nil {
return orderbookdomain.LimitOrder{}, fmt.Errorf("converting tick to price: %w", err)
return orderbookdomain.LimitOrder{}, types.ConvertingTickToPriceError{TickID: order.TickId, Err: err}
}

// Calculate output based on order direction
Expand All @@ -370,7 +400,10 @@ func (o *orderbookUseCaseImpl) createFormattedLimitOrder(
// Convert placed_at to a nano second timestamp
placedAt, err := strconv.ParseInt(order.PlacedAt, 10, 64)
if err != nil {
return orderbookdomain.LimitOrder{}, fmt.Errorf("error parsing placed_at: %w", err)
return orderbookdomain.LimitOrder{}, types.ParsingPlacedAtError{
PlacedAt: order.PlacedAt,
Err: err,
}
}
placedAt = time.Unix(0, placedAt).Unix()

Expand Down
39 changes: 20 additions & 19 deletions orderbook/usecase/orderbook_usecase_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
cltypes "github.com/osmosis-labs/osmosis/v25/x/concentrated-liquidity/types"
"github.com/osmosis-labs/sqs/domain/mocks"
orderbookdomain "github.com/osmosis-labs/sqs/domain/orderbook"
"github.com/osmosis-labs/sqs/orderbook/types"
orderbookusecase "github.com/osmosis-labs/sqs/orderbook/usecase"

"github.com/osmosis-labs/osmosis/osmomath"
Expand Down Expand Up @@ -58,15 +59,15 @@ func (s *OrderbookUsecaseTestSuite) TestCreateFormattedLimitOrder() {
quoteAsset orderbookdomain.Asset
baseAsset orderbookdomain.Asset
setupMocks func(orderbookRepo *mocks.OrderbookRepositoryMock, tokensUsecase *mocks.TokensUsecaseMock)
expectedError string
expectedError error
expectedOrder orderbookdomain.LimitOrder
}{
{
name: "tick not found",
order: orderbookdomain.Order{
TickId: 99, // Non-existent tick ID
},
expectedError: "tick not found",
expectedError: &types.TickForOrderbookNotFoundError{},
setupMocks: func(orderbookRepo *mocks.OrderbookRepositoryMock, tokensUsecase *mocks.TokensUsecaseMock) {
orderbookRepo.GetTickByIDFunc = func(poolID uint64, tickID int64) (orderbookdomain.OrderbookTick, bool) {
return orderbookdomain.OrderbookTick{}, false
Expand All @@ -78,7 +79,7 @@ func (s *OrderbookUsecaseTestSuite) TestCreateFormattedLimitOrder() {
order: orderbookdomain.Order{
Quantity: "invalid", // Invalid quantity
},
expectedError: "error parsing quantity",
expectedError: &types.ParsingQuantityError{},
setupMocks: func(orderbookRepo *mocks.OrderbookRepositoryMock, tokensUsecase *mocks.TokensUsecaseMock) {
orderbookRepo.GetTickByIDFunc = getTickByIDFunc("6431", 935, "ask")
},
Expand All @@ -91,7 +92,7 @@ func (s *OrderbookUsecaseTestSuite) TestCreateFormattedLimitOrder() {
Etas: "500",
ClaimBounty: "10",
},
expectedError: "error parsing quantity",
expectedError: &types.ParsingQuantityError{},
setupMocks: func(orderbookRepo *mocks.OrderbookRepositoryMock, tokensUsecase *mocks.TokensUsecaseMock) {
orderbookRepo.GetTickByIDFunc = getTickByIDFunc("500", 100, "bid")
},
Expand All @@ -102,7 +103,7 @@ func (s *OrderbookUsecaseTestSuite) TestCreateFormattedLimitOrder() {
Quantity: "1000",
PlacedQuantity: "invalid", // Invalid placed quantity
},
expectedError: "error parsing placed quantity",
expectedError: &types.ParsingPlacedQuantityError{},
setupMocks: func(orderbookRepo *mocks.OrderbookRepositoryMock, tokensUsecase *mocks.TokensUsecaseMock) {
orderbookRepo.GetTickByIDFunc = getTickByIDFunc("813", 1331, "bid")
},
Expand All @@ -115,7 +116,7 @@ func (s *OrderbookUsecaseTestSuite) TestCreateFormattedLimitOrder() {
Etas: "500",
ClaimBounty: "10",
},
expectedError: "error parsing placed quantity",
expectedError: &types.ParsingPlacedQuantityError{},
setupMocks: func(orderbookRepo *mocks.OrderbookRepositoryMock, tokensUsecase *mocks.TokensUsecaseMock) {
orderbookRepo.GetTickByIDFunc = getTickByIDFunc("500", 100, "bid")
},
Expand All @@ -126,7 +127,7 @@ func (s *OrderbookUsecaseTestSuite) TestCreateFormattedLimitOrder() {
Quantity: "1000",
PlacedQuantity: "0", // division by zero
},
expectedError: "placed quantity is 0 or negative",
expectedError: &types.InvalidPlacedQuantityError{},
setupMocks: func(orderbookRepo *mocks.OrderbookRepositoryMock, tokensUsecase *mocks.TokensUsecaseMock) {
orderbookRepo.GetTickByIDFunc = getTickByIDFunc("813", 1331, "bid")
},
Expand All @@ -137,7 +138,7 @@ func (s *OrderbookUsecaseTestSuite) TestCreateFormattedLimitOrder() {
Quantity: "931",
PlacedQuantity: "183",
},
expectedError: "error getting spot price scaling factor",
expectedError: &types.GettingSpotPriceScalingFactorError{},
setupMocks: func(orderbookRepo *mocks.OrderbookRepositoryMock, tokensUsecase *mocks.TokensUsecaseMock) {
orderbookRepo.GetTickByIDFunc = getTickByIDFunc("130", 13, "ask")
tokensUsecase.GetSpotPriceScalingFactorByDenomFunc = func(baseDenom, quoteDenom string) (osmomath.Dec, error) {
Expand All @@ -152,7 +153,7 @@ func (s *OrderbookUsecaseTestSuite) TestCreateFormattedLimitOrder() {
PlacedQuantity: "131",
OrderDirection: "bid",
},
expectedError: "error parsing bid effective total amount swapped",
expectedError: &types.ParsingTickValuesError{},
setupMocks: func(orderbookRepo *mocks.OrderbookRepositoryMock, tokensUsecase *mocks.TokensUsecaseMock) {
orderbookRepo.GetTickByIDFunc = getTickByIDFunc("invalid", 13, "bid")
},
Expand All @@ -164,7 +165,7 @@ func (s *OrderbookUsecaseTestSuite) TestCreateFormattedLimitOrder() {
PlacedQuantity: "131",
OrderDirection: "ask",
},
expectedError: "error parsing ask effective total amount swapped",
expectedError: &types.ParsingTickValuesError{},
setupMocks: func(orderbookRepo *mocks.OrderbookRepositoryMock, tokensUsecase *mocks.TokensUsecaseMock) {
orderbookRepo.GetTickByIDFunc = getTickByIDFunc("invalid", 1, "ask")
},
Expand All @@ -176,7 +177,7 @@ func (s *OrderbookUsecaseTestSuite) TestCreateFormattedLimitOrder() {
PlacedQuantity: "153",
OrderDirection: "bid",
},
expectedError: "error parsing bid unrealized cancels",
expectedError: &types.ParsingUnrealizedCancelsError{},
setupMocks: func(orderbookRepo *mocks.OrderbookRepositoryMock, tokensUsecase *mocks.TokensUsecaseMock) {
orderbookRepo.GetTickByIDFunc = getTickByIDFunc("15", 0, "bid")
},
Expand All @@ -188,7 +189,7 @@ func (s *OrderbookUsecaseTestSuite) TestCreateFormattedLimitOrder() {
PlacedQuantity: "313",
OrderDirection: "ask",
},
expectedError: "error parsing ask unrealized cancels",
expectedError: &types.ParsingUnrealizedCancelsError{},
setupMocks: func(orderbookRepo *mocks.OrderbookRepositoryMock, tokensUsecase *mocks.TokensUsecaseMock) {
orderbookRepo.GetTickByIDFunc = getTickByIDFunc("13", 0, "ask")
},
Expand All @@ -201,7 +202,7 @@ func (s *OrderbookUsecaseTestSuite) TestCreateFormattedLimitOrder() {
OrderDirection: "bid",
Etas: "invalid", // Invalid ETAs
},
expectedError: "error parsing etas",
expectedError: &types.ParsingEtasError{},
setupMocks: func(orderbookRepo *mocks.OrderbookRepositoryMock, tokensUsecase *mocks.TokensUsecaseMock) {
orderbookRepo.GetTickByIDFunc = getTickByIDFunc("386", 830, "bid")
},
Expand All @@ -215,7 +216,7 @@ func (s *OrderbookUsecaseTestSuite) TestCreateFormattedLimitOrder() {
Etas: "9223372036854775808", // overflow value for int64
ClaimBounty: "10",
},
expectedError: "error parsing etas",
expectedError: &types.ParsingEtasError{},
setupMocks: func(orderbookRepo *mocks.OrderbookRepositoryMock, tokensUsecase *mocks.TokensUsecaseMock) {
orderbookRepo.GetTickByIDFunc = getTickByIDFunc("500", 100, "bid")
},
Expand All @@ -229,7 +230,7 @@ func (s *OrderbookUsecaseTestSuite) TestCreateFormattedLimitOrder() {
OrderDirection: "ask",
Etas: "100",
},
expectedError: "converting tick to price",
expectedError: &types.ConvertingTickToPriceError{},
setupMocks: func(orderbookRepo *mocks.OrderbookRepositoryMock, tokensUsecase *mocks.TokensUsecaseMock) {
orderbookRepo.GetTickByIDFunc = getTickByIDFunc("190", 150, "ask")
},
Expand All @@ -244,7 +245,7 @@ func (s *OrderbookUsecaseTestSuite) TestCreateFormattedLimitOrder() {
Etas: "100",
PlacedAt: "invalid", // Invalid timestamp
},
expectedError: "error parsing placed_at",
expectedError: &types.ParsingPlacedAtError{},
setupMocks: func(orderbookRepo *mocks.OrderbookRepositoryMock, tokensUsecase *mocks.TokensUsecaseMock) {
orderbookRepo.GetTickByIDFunc = getTickByIDFunc("100", 100, "ask")
tokensUsecase.GetSpotPriceScalingFactorByDenomFunc = func(baseDenom, quoteDenom string) (osmomath.Dec, error) {
Expand Down Expand Up @@ -273,7 +274,7 @@ func (s *OrderbookUsecaseTestSuite) TestCreateFormattedLimitOrder() {
return osmomath.NewDec(1), nil
}
},
expectedError: "",
expectedError: nil,
expectedOrder: orderbookdomain.LimitOrder{
TickId: 1,
OrderId: 1,
Expand Down Expand Up @@ -319,9 +320,9 @@ func (s *OrderbookUsecaseTestSuite) TestCreateFormattedLimitOrder() {
result, err := usecase.CreateFormattedLimitOrder(tc.poolID, tc.order, tc.quoteAsset, tc.baseAsset, "someOrderbookAddress")

// Assert the results
if tc.expectedError != "" {
if tc.expectedError != nil {
s.Assert().Error(err)
s.Assert().Contains(err.Error(), tc.expectedError)
s.Assert().ErrorAs(err, tc.expectedError)
} else {
s.Assert().NoError(err)
s.Assert().Equal(tc.expectedOrder, result)
Expand Down

0 comments on commit 65e147b

Please sign in to comment.