mirror of
https://github.com/luxfi/graph.git
synced 2026-09-24 00:26:07 +00:00
Asking the store for one pool's last N trades read the N newest swaps on the whole chain and then kept the ones that matched. The limit belongs to the database and ran first, so the filter was filtering what the limit had already thrown away. One pair does most of the trading on a live chain and fills any recent window by itself. On the Lux C-Chain that is WLUX/LZOO, and every other pool answered with nothing — LETH's 145,386 trades, LSOL's 52,266, LBTC's 14,857, all present in the store and unreachable through the query that asks for them. The pool column has been indexed for exactly this since the table was created. The test gives a quiet pool five trades and buries it under five hundred. Before this it returns none of them. Co-authored-by: Hanzo Dev <[email protected]>
214 lines
5.8 KiB
Go
214 lines
5.8 KiB
Go
package storage
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
func TestFilterResults_ExactMatch(t *testing.T) {
|
|
results := []interface{}{
|
|
map[string]interface{}{"id": "1", "pool": "0xA"},
|
|
map[string]interface{}{"id": "2", "pool": "0xB"},
|
|
map[string]interface{}{"id": "3", "pool": "0xA"},
|
|
}
|
|
|
|
where := map[string]interface{}{"pool": "0xA"}
|
|
filtered := FilterResults(results, where)
|
|
|
|
if len(filtered) != 2 {
|
|
t.Fatalf("expected 2, got %d", len(filtered))
|
|
}
|
|
}
|
|
|
|
func TestFilterResults_GTE(t *testing.T) {
|
|
results := []interface{}{
|
|
map[string]interface{}{"id": "1", "date": int64(100)},
|
|
map[string]interface{}{"id": "2", "date": int64(200)},
|
|
map[string]interface{}{"id": "3", "date": int64(300)},
|
|
}
|
|
|
|
where := map[string]interface{}{"date_gte": "200"}
|
|
filtered := FilterResults(results, where)
|
|
|
|
if len(filtered) != 2 {
|
|
t.Fatalf("expected 2 (date >= 200), got %d", len(filtered))
|
|
}
|
|
}
|
|
|
|
func TestFilterResults_LT(t *testing.T) {
|
|
results := []interface{}{
|
|
map[string]interface{}{"id": "1", "amount": "50.5"},
|
|
map[string]interface{}{"id": "2", "amount": "150.0"},
|
|
map[string]interface{}{"id": "3", "amount": "200.0"},
|
|
}
|
|
|
|
where := map[string]interface{}{"amount_lt": "100"}
|
|
filtered := FilterResults(results, where)
|
|
|
|
if len(filtered) != 1 {
|
|
t.Fatalf("expected 1 (amount < 100), got %d", len(filtered))
|
|
}
|
|
}
|
|
|
|
func TestFilterResults_Empty(t *testing.T) {
|
|
results := []interface{}{
|
|
map[string]interface{}{"id": "1"},
|
|
}
|
|
filtered := FilterResults(results, nil)
|
|
if len(filtered) != 1 {
|
|
t.Fatal("nil where should return all")
|
|
}
|
|
|
|
filtered = FilterResults(results, map[string]interface{}{})
|
|
if len(filtered) != 1 {
|
|
t.Fatal("empty where should return all")
|
|
}
|
|
}
|
|
|
|
func TestFilterResults_NoMatch(t *testing.T) {
|
|
results := []interface{}{
|
|
map[string]interface{}{"id": "1", "pool": "0xA"},
|
|
}
|
|
where := map[string]interface{}{"pool": "0xNONE"}
|
|
filtered := FilterResults(results, where)
|
|
if len(filtered) != 0 {
|
|
t.Fatalf("expected 0, got %d", len(filtered))
|
|
}
|
|
}
|
|
|
|
func TestSortResults_Numeric(t *testing.T) {
|
|
results := []interface{}{
|
|
map[string]interface{}{"id": "a", "volumeUSD": "9.0"},
|
|
map[string]interface{}{"id": "b", "volumeUSD": "100000.0"},
|
|
map[string]interface{}{"id": "c", "volumeUSD": "50.0"},
|
|
}
|
|
|
|
sortResults(results, "volumeUSD", "desc")
|
|
|
|
first := results[0].(map[string]interface{})
|
|
if first["id"] != "b" {
|
|
t.Errorf("expected b (100000) first in desc sort, got %v", first["id"])
|
|
}
|
|
|
|
last := results[2].(map[string]interface{})
|
|
if last["id"] != "a" {
|
|
t.Errorf("expected a (9) last in desc sort, got %v", last["id"])
|
|
}
|
|
}
|
|
|
|
func TestSortResults_Asc(t *testing.T) {
|
|
results := []interface{}{
|
|
map[string]interface{}{"id": "x", "date": int64(300)},
|
|
map[string]interface{}{"id": "y", "date": int64(100)},
|
|
map[string]interface{}{"id": "z", "date": int64(200)},
|
|
}
|
|
|
|
sortResults(results, "date", "asc")
|
|
|
|
first := results[0].(map[string]interface{})
|
|
if first["id"] != "y" {
|
|
t.Errorf("expected y (100) first in asc sort, got %v", first["id"])
|
|
}
|
|
}
|
|
|
|
func TestSortResults_StringFallback(t *testing.T) {
|
|
results := []interface{}{
|
|
map[string]interface{}{"id": "c", "name": "Charlie"},
|
|
map[string]interface{}{"id": "a", "name": "Alice"},
|
|
map[string]interface{}{"id": "b", "name": "Bob"},
|
|
}
|
|
|
|
sortResults(results, "name", "asc")
|
|
|
|
first := results[0].(map[string]interface{})
|
|
if first["name"] != "Alice" {
|
|
t.Errorf("expected Alice first, got %v", first["name"])
|
|
}
|
|
}
|
|
|
|
func TestCompareValues_NumericStrings(t *testing.T) {
|
|
// "9" vs "100000" — must sort numerically, not lexicographically
|
|
cmp := compareValues("9.0", "100000.0")
|
|
if cmp >= 0 {
|
|
t.Error("9.0 should be less than 100000.0")
|
|
}
|
|
|
|
cmp = compareValues("100000", "9")
|
|
if cmp <= 0 {
|
|
t.Error("100000 should be greater than 9")
|
|
}
|
|
|
|
// Equal
|
|
cmp = compareValues("42", "42")
|
|
if cmp != 0 {
|
|
t.Error("42 should equal 42")
|
|
}
|
|
}
|
|
|
|
func TestCompareValues_MixedTypes(t *testing.T) {
|
|
// int64 vs string
|
|
cmp := compareValues(int64(100), "50")
|
|
if cmp <= 0 {
|
|
t.Error("100 should be greater than 50")
|
|
}
|
|
|
|
// Non-numeric strings fall back to lexicographic
|
|
cmp = compareValues("abc", "xyz")
|
|
if cmp >= 0 {
|
|
t.Error("abc should be less than xyz lexicographically")
|
|
}
|
|
}
|
|
|
|
// A quiet pool's trades are its own, however much the busy pool beside it trades.
|
|
//
|
|
// The limit belongs to the database, so a filter that runs afterwards filters
|
|
// what the limit already threw away: asking for one pool's last N trades read
|
|
// the N newest chain-wide and kept the few that matched. One pair does most of
|
|
// the trading on a live chain and fills any recent window by itself, so every
|
|
// other pool answered with nothing — its history present, and unreachable.
|
|
func TestGetSwapsFiltersBeforeItLimits(t *testing.T) {
|
|
dir := t.TempDir()
|
|
s, err := New(dir)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
t.Cleanup(func() { s.Close() })
|
|
if err := s.Init(context.Background()); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
const busy, quiet = "0xbusy", "0xquiet"
|
|
|
|
// The quiet pool trades first, then the busy one buries it.
|
|
for i := 0; i < 5; i++ {
|
|
s.SeedSwap(fmt.Sprintf("q%d", i), &SeedSwapData{
|
|
Timestamp: int64(1000 + i), Block: uint64(1 + i), Pool: quiet,
|
|
Amount0: "1", Amount1: "-2", AmountUSD: "1",
|
|
})
|
|
}
|
|
for i := 0; i < 500; i++ {
|
|
s.SeedSwap(fmt.Sprintf("b%d", i), &SeedSwapData{
|
|
Timestamp: int64(2000 + i), Block: uint64(100 + i), Pool: busy,
|
|
Amount0: "1", Amount1: "-2", AmountUSD: "1",
|
|
})
|
|
}
|
|
|
|
got, err := s.GetSwaps(context.Background(), 100, "timestamp", "desc",
|
|
map[string]interface{}{"pool": quiet})
|
|
if err != nil {
|
|
t.Fatalf("GetSwaps: %v", err)
|
|
}
|
|
rows, _ := got.([]interface{})
|
|
if len(rows) != 5 {
|
|
t.Errorf("quiet pool returned %d of its 5 trades — the limit ran before the filter", len(rows))
|
|
}
|
|
for _, r := range rows {
|
|
m := r.(map[string]interface{})
|
|
id, _ := m["id"].(string)
|
|
if !strings.HasPrefix(id, "q") {
|
|
t.Errorf("row %q is not the filtered pool's", id)
|
|
}
|
|
}
|
|
}
|