package client import ( "context" "errors" "fmt" "net/http" "net/http/httptest" "sync/atomic" "testing" "time" ) func server(seq int, delay time.Duration, status int, hits *int32) *httptest.Server { return httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if hits != nil { atomic.AddInt32(hits, 1) } time.Sleep(delay) if status != 200 { w.WriteHeader(status) return } fmt.Fprintf(w, `{"buys_price_level":[{"p":"9.000000000000000000","q":"1.000000000000000000"}], "sells_price_level":[{"p":"10.000000000000000000","q":"2.000000000000000000"}],"seq":"%d"}`, seq) })) } func TestFailoverOnError(t *testing.T) { bad, good := server(0, 0, 503, nil), server(7, 0, 200, nil) defer bad.Close() defer good.Close() c := New(bad.URL, good.URL) s, err := c.Orderbook(context.Background(), "spot", "m", 5) if err != nil || s.Endpoint != good.URL || s.Book.Seq != 7 { t.Fatalf("got %+v err %v", s, err) } if st := c.Stats(); st[0].URL != good.URL { t.Errorf("winner should become primary, stats %+v", st) } } func TestHedgeBeatsSlowPrimary(t *testing.T) { slow, fast := server(5, 2*time.Second, 200, nil), server(5, 0, 200, nil) defer slow.Close() defer fast.Close() c := New(slow.URL, fast.URL) c.HedgeDelay = 50 * time.Millisecond t0 := time.Now() s, err := c.Orderbook(context.Background(), "spot", "m", 5) if err != nil || s.Endpoint != fast.URL || time.Since(t0) > time.Second { t.Fatalf("hedge did not win: %+v err %v in %s", s, err, time.Since(t0)) } } func TestStaleSnapshotRejected(t *testing.T) { fresh, lagging := server(100, 0, 200, nil), server(90, 0, 200, nil) defer fresh.Close() defer lagging.Close() c := New(fresh.URL) if _, err := c.Orderbook(context.Background(), "spot", "m", 5); err != nil { t.Fatal(err) } c2 := New(lagging.URL) c2.seen = c.seen _, err := c2.Orderbook(context.Background(), "spot", "m", 5) if !errors.Is(err, ErrStale) { t.Fatalf("lagging node accepted: %v", err) } // With a fresh node behind it, the client skips the lagging one. c3 := New(lagging.URL, fresh.URL) c3.seen = c.seen s, err := c3.Orderbook(context.Background(), "spot", "m", 5) if err != nil || s.Endpoint != fresh.URL { t.Fatalf("expected fallback to fresh node: %+v %v", s, err) } }