From 65e147b56368f54d9d3986dd655e42e2dce07c2b Mon Sep 17 00:00:00 2001 From: Deividas Petraitis Date: Mon, 2 Sep 2024 15:07:51 +0300 Subject: [PATCH] SQS-375 | Unit test createFormattedLimitOrder Implements requested improvements: Define error messages as types --- orderbook/usecase/orderbook_usecase.go | 67 +++++++++++++++------ orderbook/usecase/orderbook_usecase_test.go | 39 ++++++------ 2 files changed, 70 insertions(+), 36 deletions(-) diff --git a/orderbook/usecase/orderbook_usecase.go b/orderbook/usecase/orderbook_usecase.go index e867128c7..1269519c9 100644 --- a/orderbook/usecase/orderbook_usecase.go +++ b/orderbook/usecase/orderbook_usecase.go @@ -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" @@ -262,16 +263,21 @@ 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{ + 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{ + Quantity: order.Quantity, + Err: err, + } } // Convert quantity to decimal for the calculations @@ -279,16 +285,22 @@ func (o *orderbookUseCaseImpl) createFormattedLimitOrder( 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{ + 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} } 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{ + PlacedQuantity: order.PlacedQuantity, + Err: err, + } } // Calculate percent claimed @@ -297,7 +309,11 @@ 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{ + BaseDenom: baseAsset.Symbol, + QuoteDenom: quoteAsset.Symbol, + Err: err, + } } // Determine tick values and unrealized cancels based on order direction @@ -305,30 +321,44 @@ func (o *orderbookUseCaseImpl) createFormattedLimitOrder( 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{ + 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{ + 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{ + 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{ + 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 @@ -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 @@ -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() diff --git a/orderbook/usecase/orderbook_usecase_test.go b/orderbook/usecase/orderbook_usecase_test.go index 5833048cf..f3874575b 100644 --- a/orderbook/usecase/orderbook_usecase_test.go +++ b/orderbook/usecase/orderbook_usecase_test.go @@ -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" @@ -58,7 +59,7 @@ 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 }{ { @@ -66,7 +67,7 @@ func (s *OrderbookUsecaseTestSuite) TestCreateFormattedLimitOrder() { 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 @@ -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") }, @@ -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") }, @@ -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") }, @@ -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") }, @@ -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") }, @@ -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) { @@ -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") }, @@ -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") }, @@ -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") }, @@ -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") }, @@ -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") }, @@ -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") }, @@ -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") }, @@ -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) { @@ -273,7 +274,7 @@ func (s *OrderbookUsecaseTestSuite) TestCreateFormattedLimitOrder() { return osmomath.NewDec(1), nil } }, - expectedError: "", + expectedError: nil, expectedOrder: orderbookdomain.LimitOrder{ TickId: 1, OrderId: 1, @@ -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)