Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
98 changes: 0 additions & 98 deletions internal/order/order_service.go

This file was deleted.

21 changes: 19 additions & 2 deletions internal/orderbook/orderbook.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,16 @@ import (
"exchange/internal/order"
"fmt"
"time"

"github.com/google/uuid"
)

// OrderCreator defines the interface required for creating an order persistence mechanism.
// This allows mocking the database interaction during tests.
type OrderCreator interface {
CreateOrder(ctx context.Context, arg db.CreateOrderParams) (uuid.UUID, error)
}

type OrderBook struct {
Bids *OrderHeap // max-heap (isMax = true)
Asks *OrderHeap // min-heap (isMax = false)
Expand All @@ -24,7 +32,7 @@ func NewOrderBook(ticker string) *OrderBook {
}

// Submit implements the OrderBookInterface
func (ob *OrderBook) Submit(o *order.Order, queries *db.Queries) bool {
func (ob *OrderBook) Submit(o *order.Order, creator OrderCreator) bool {
if !o.IsValid() {
fmt.Println("Order not valid. WRONG_ORDER")
return false
Expand All @@ -47,7 +55,8 @@ func (ob *OrderBook) Submit(o *order.Order, queries *db.Queries) bool {
return false
}

o_id, _ := queries.CreateOrder(context.Background(), db.CreateOrderParams{
// Use the creator interface to persist the order
o_id, err := creator.CreateOrder(context.Background(), db.CreateOrderParams{
Price: o.Price,
Amount: o.Amount,
Side: o.Side,
Expand All @@ -56,6 +65,14 @@ func (ob *OrderBook) Submit(o *order.Order, queries *db.Queries) bool {
CreatedBy: o.CreatedBy,
})

// Handle potential error from CreateOrder
if err != nil {
fmt.Printf("Failed to create order in DB: %v\n", err)
// Decide how to handle DB error - perhaps mark order as failed?
// For now, we'll return false as the submission wasn't fully successful.
return false
}

o.ID = o_id

fmt.Printf("%s %s %s Order submitted %d @ %.2f ID: %s\n",
Expand Down
77 changes: 51 additions & 26 deletions internal/orderbook/orderbook_test.go
Original file line number Diff line number Diff line change
@@ -1,13 +1,29 @@
package orderbook

import (
"context"
"exchange/internal/db"
"exchange/internal/order"
"testing"

"github.com/google/uuid"
)

// mockDBQueries is a mock implementation for db.Queries, focusing on CreateOrder.
// It implements the orderbook.OrderCreator interface.
type mockDBQueries struct {
// Store the last CreateOrderParams received for potential assertions
lastCreateParams *db.CreateOrderParams
}

// Override CreateOrder to mock database interaction.
func (m *mockDBQueries) CreateOrder(ctx context.Context, arg db.CreateOrderParams) (uuid.UUID, error) {
// Store args for assertion
m.lastCreateParams = &arg
// Return a new UUID as if the DB generated it, and no error.
return uuid.New(), nil
}

func TestNewOrderBook(t *testing.T) {
ob := NewOrderBook("LINK")

Expand All @@ -26,22 +42,23 @@ func TestNewOrderBook(t *testing.T) {
}
}

// func TestSubmitWrongOrder(t *testing.T) {
// ob := NewOrderBook("LINK")

// new_bad_order := order.NewOrder(-10, 100, db.OrderSideTypeSELL, db.OrderTypeLIMIT, "LINK", uuid.New())
// ob.Submit(new_bad_order, nil)
func TestSubmitWrongOrder(t *testing.T) {
ob := NewOrderBook("LINK")
mockQueries := &mockDBQueries{}
new_bad_order := order.NewOrder(-10, 100, db.OrderSideTypeSELL, db.OrderTypeLIMIT, "LINK", uuid.New())
ob.Submit(new_bad_order, mockQueries)

// if ob.Asks.Len() > 0 {
// t.Errorf("Expected order not to be submitted to the orderbook.")
// }
// }
if ob.Asks.Len() > 0 {
t.Errorf("Expected order not to be submitted to the orderbook.")
}
}

func TestSubmitLimitSellOrder(t *testing.T) {
ob := NewOrderBook("LINK")
test_order := order.NewOrder(60, 100, db.OrderSideTypeBUY, db.OrderTypeLIMIT, "LINK", uuid.New())
mockQueries := &mockDBQueries{}
test_order := order.NewOrder(60, 100, db.OrderSideTypeSELL, db.OrderTypeLIMIT, "LINK", uuid.New())

ob.Submit(test_order, nil)
ob.Submit(test_order, mockQueries)
if ob.Bids.Len() > 0 {
t.Error("SELL order was transmitted as a BUY order.")
}
Expand All @@ -53,9 +70,10 @@ func TestSubmitLimitSellOrder(t *testing.T) {

func TestSubmitLimitBuyOrder(t *testing.T) {
ob := NewOrderBook("LINK")
mockQueries := &mockDBQueries{}
test_order := order.NewOrder(60, 100, db.OrderSideTypeBUY, db.OrderTypeLIMIT, "LINK", uuid.New())

ob.Submit(test_order, nil)
ob.Submit(test_order, mockQueries)
if ob.Asks.Len() > 0 {
t.Error("BUY order was transmitted as a SELL order.")
}
Expand All @@ -67,11 +85,12 @@ func TestSubmitLimitBuyOrder(t *testing.T) {

func TestSubmitMarketBuyOrder(t *testing.T) {
ob := NewOrderBook("LINK")
mockQueries := &mockDBQueries{}
test_order := order.NewOrder(10, 100, db.OrderSideTypeBUY, db.OrderTypeLIMIT, "LINK", uuid.New())
test_order_sell := order.NewOrder(10, 100, db.OrderSideTypeSELL, db.OrderTypeMARKET, "LINK", uuid.New())

ob.Submit(test_order, nil)
ob.Submit(test_order_sell, nil)
ob.Submit(test_order, mockQueries)
ob.Submit(test_order_sell, mockQueries)

if ob.Asks.Len() > 0 {
t.Error("BUY order was transmitted as a SELL order.")
Expand All @@ -88,9 +107,10 @@ func TestSubmitMarketBuyOrder(t *testing.T) {

func TestSubmitMarketSellOrder(t *testing.T) {
ob := NewOrderBook("LINK")
mockQueries := &mockDBQueries{}
test_order_sell := order.NewOrder(10, 100, db.OrderSideTypeSELL, db.OrderTypeLIMIT, "LINK", uuid.New())

ob.Submit(test_order_sell, nil)
ob.Submit(test_order_sell, mockQueries)

if ob.Bids.Len() > 0 {
t.Error("BUY order was transmitted as a SELL order.")
Expand All @@ -103,23 +123,25 @@ func TestSubmitMarketSellOrder(t *testing.T) {

func TestMarketOrderNoLiquidity(t *testing.T) {
ob := NewOrderBook("LINK")
mockQueries := &mockDBQueries{}
test_order := order.NewOrder(10, 100, db.OrderSideTypeBUY, db.OrderTypeMARKET, "LINK", uuid.New())

result := ob.Submit(test_order, nil)
result := ob.Submit(test_order, mockQueries)
if result == true {
t.Error("Market order was processed despite insufficient liquidity.")
}
}

func TestWithdrawOrder(t *testing.T) {
ob := NewOrderBook("LINK")
mockQueries := &mockDBQueries{}
test_order := order.NewOrder(12, 100, db.OrderSideTypeBUY, db.OrderTypeLIMIT, "LINK", uuid.New())
buy_order_to_withdraw := order.NewOrder(60, 100, db.OrderSideTypeBUY, db.OrderTypeLIMIT, "LINK", uuid.New())
sell_order_to_withdraw := order.NewOrder(120, 100, db.OrderSideTypeSELL, db.OrderTypeLIMIT, "LINK", uuid.New())

ob.Submit(test_order, nil)
ob.Submit(buy_order_to_withdraw, nil)
ob.Submit(sell_order_to_withdraw, nil)
ob.Submit(test_order, mockQueries)
ob.Submit(buy_order_to_withdraw, mockQueries)
ob.Submit(sell_order_to_withdraw, mockQueries)

if ob.Bids.Len()+ob.Asks.Len() != 3 {
t.Error("Not all orders were submitted.")
Expand Down Expand Up @@ -163,13 +185,14 @@ func TestWithdrawBadOrder(t *testing.T) {

func TestOrdersMatched(t *testing.T) {
ob := NewOrderBook("LINK")
mockQueries := &mockDBQueries{}

// Case 1: similar orders
order_1 := order.NewOrder(60, 100, db.OrderSideTypeBUY, db.OrderTypeLIMIT, "LINK", uuid.New())
order_2 := order.NewOrder(60, 100, db.OrderSideTypeSELL, db.OrderTypeLIMIT, "LINK", uuid.New())

ob.Submit(order_1, nil)
ob.Submit(order_2, nil)
ob.Submit(order_1, mockQueries)
ob.Submit(order_2, mockQueries)

if ob.Asks.Len()-ob.Bids.Len() != 0 {
t.Errorf("Expected an empty order book.")
Expand All @@ -179,26 +202,27 @@ func TestOrdersMatched(t *testing.T) {

func TestOrdersPartiallyMatched(t *testing.T) {
ob := NewOrderBook("LINK")
mockQueries := &mockDBQueries{}

order_1 := order.NewOrder(60, 100, db.OrderSideTypeBUY, db.OrderTypeLIMIT, "LINK", uuid.New())
order_2 := order.NewOrder(60, 50, db.OrderSideTypeSELL, db.OrderTypeLIMIT, "LINK", uuid.New())
order_3 := order.NewOrder(60, 165, db.OrderSideTypeSELL, db.OrderTypeMARKET, "LINK", uuid.New())
order_4 := order.NewOrder(62, 150, db.OrderSideTypeBUY, db.OrderTypeLIMIT, "LINK", uuid.New())

ob.Submit(order_1, nil)
ob.Submit(order_2, nil)
ob.Submit(order_1, mockQueries)
ob.Submit(order_2, mockQueries)

if order_1.Status != db.OrderStatusTypePARTIALLYFILLED {
t.Errorf("Expected status to be partially filled.")
}

ob.Submit(order_3, nil)
ob.Submit(order_3, mockQueries)

if order_3.Status != db.OrderStatusTypePARTIALLYFILLED {
t.Errorf("Expected status to be partially filled.")
}

ob.Submit(order_4, nil)
ob.Submit(order_4, mockQueries)

if order_3.Status != db.OrderStatusTypeFILLED {
t.Errorf("Expected status to be fully filled.")
Expand All @@ -224,9 +248,10 @@ func TestOrdersPartiallyMatched(t *testing.T) {

func TestWrongOrderBook(t *testing.T) {
ob := NewOrderBook("XRP")
mockQueries := &mockDBQueries{}
order_1 := order.NewOrder(60, 100, db.OrderSideTypeBUY, db.OrderTypeLIMIT, "LINK", uuid.New())

result := ob.Submit(order_1, nil)
result := ob.Submit(order_1, mockQueries)
if result {
t.Errorf("Order was submitted to the wrong orderbook.")
}
Expand Down