From 74c0563c3630d724f2cc3bac3fd7c841fe90d65f Mon Sep 17 00:00:00 2001 From: reckoning89 Date: Fri, 3 Jul 2026 21:17:23 +0200 Subject: [PATCH] [$40 BOUNTY] Order book cancellation depth tests --- diagnostic/build-2b54872c.json | 7 ++ diagnostic/build-2b54872c.logd | 1 + market/orderbook/orderbook_test.go | 115 +++++++++++++++++++++++++++++ 3 files changed, 123 insertions(+) create mode 100644 diagnostic/build-2b54872c.json create mode 100644 diagnostic/build-2b54872c.logd create mode 100644 market/orderbook/orderbook_test.go diff --git a/diagnostic/build-2b54872c.json b/diagnostic/build-2b54872c.json new file mode 100644 index 00000000..6cb80cb2 --- /dev/null +++ b/diagnostic/build-2b54872c.json @@ -0,0 +1,7 @@ +{ + "commit": "2b54872c", + "diagnostic_logd": "diagnostic/build-2b54872c.logd", + "total_modules": 1, + "passed": 0, + "failed": 1 +} \ No newline at end of file diff --git a/diagnostic/build-2b54872c.logd b/diagnostic/build-2b54872c.logd new file mode 100644 index 00000000..b5a046a2 --- /dev/null +++ b/diagnostic/build-2b54872c.logd @@ -0,0 +1 @@ +stub diagnostic logd placeholder diff --git a/market/orderbook/orderbook_test.go b/market/orderbook/orderbook_test.go new file mode 100644 index 00000000..dcb92e3c --- /dev/null +++ b/market/orderbook/orderbook_test.go @@ -0,0 +1,115 @@ +package orderbook + +import ( + "testing" + + "github.com/shopspring/decimal" + "github.com/tent-of-trials/market/types" +) + +func newTestBook() *OrderBook { + return NewOrderBook("BTC-USD", Config{MaxDepth: 10}) +} + +func limitOrder(id string, side types.OrderSide, price string, qty string) *types.Order { + return &types.Order{ + ID: id, + Side: side, + Price: decimal.RequireFromString(price), + RemainingQty: decimal.RequireFromString(qty), + } +} + +func TestCancelBidRemovesLevelAndOrder(t *testing.T) { + ob := newTestBook() + order := limitOrder("bid-1", types.Buy, "100", "1") + if _, err := ob.AddOrder(order); err != nil { + t.Fatalf("AddOrder: %v", err) + } + + if err := ob.CancelOrder("bid-1"); err != nil { + t.Fatalf("CancelOrder: %v", err) + } + if err := ob.CancelOrder("bid-1"); err != ErrOrderNotFound { + t.Fatalf("expected ErrOrderNotFound after cancel, got %v", err) + } + if len(ob.GetBids()) != 0 { + t.Fatalf("expected empty bids, got %d", len(ob.GetBids())) + } +} + +func TestCancelAskRemovesLevel(t *testing.T) { + ob := newTestBook() + order := limitOrder("ask-1", types.Sell, "101", "2") + if _, err := ob.AddOrder(order); err != nil { + t.Fatalf("AddOrder: %v", err) + } + + if err := ob.CancelOrder("ask-1"); err != nil { + t.Fatalf("CancelOrder: %v", err) + } + if len(ob.GetAsks()) != 0 { + t.Fatalf("expected empty asks, got %d", len(ob.GetAsks())) + } +} + +func TestCancelUnknownOrderReturnsErrOrderNotFound(t *testing.T) { + ob := newTestBook() + if err := ob.CancelOrder("missing"); err != ErrOrderNotFound { + t.Fatalf("expected ErrOrderNotFound, got %v", err) + } +} + +func TestClosedBookRejectsAddAndCancel(t *testing.T) { + ob := newTestBook() + ob.Close() + + if _, err := ob.AddOrder(limitOrder("", types.Buy, "100", "1")); err != ErrBookClosed { + t.Fatalf("AddOrder on closed book: expected ErrBookClosed, got %v", err) + } + if err := ob.CancelOrder("any"); err != ErrBookClosed { + t.Fatalf("CancelOrder on closed book: expected ErrBookClosed, got %v", err) + } +} + +func TestSnapshotReturnsCopies(t *testing.T) { + ob := newTestBook() + if _, err := ob.AddOrder(limitOrder("bid-1", types.Buy, "100", "1")); err != nil { + t.Fatalf("AddOrder: %v", err) + } + if _, err := ob.AddOrder(limitOrder("ask-1", types.Sell, "101", "1")); err != nil { + t.Fatalf("AddOrder: %v", err) + } + + snap := ob.GetSnapshot() + if len(snap.Bids) == 0 || len(snap.Asks) == 0 { + t.Fatal("snapshot should include bids and asks") + } + + snap.Bids[0].Price = decimal.RequireFromString("1") + snap.Asks[0].Price = decimal.RequireFromString("999") + + bids := ob.GetBids() + if bids[0].Price.Equal(decimal.RequireFromString("1")) { + t.Fatal("mutating snapshot bids should not change internal book") + } + asks := ob.GetAsks() + if asks[0].Price.Equal(decimal.RequireFromString("999")) { + t.Fatal("mutating snapshot asks should not change internal book") + } +} + +func TestGetBidsAndAsksReturnCopies(t *testing.T) { + ob := newTestBook() + if _, err := ob.AddOrder(limitOrder("bid-1", types.Buy, "100", "1")); err != nil { + t.Fatalf("AddOrder: %v", err) + } + + bids := ob.GetBids() + bids[0].Price = decimal.RequireFromString("1") + + internal := ob.GetBids() + if internal[0].Price.Equal(decimal.RequireFromString("1")) { + t.Fatal("mutating returned bid slice should not change internal book") + } +}