package polymarket import ( "context" "encoding/json" "net/http" "net/http/httptest" "testing" ) func TestStringOrArray_Array(t *testing.T) { input := `["Yes","No"]` var s stringOrArray if err := json.Unmarshal([]byte(input), &s); err != nil { t.Fatalf("Unmarshal error: %v", err) } if len(s) != 2 || s[0] != "Yes" || s[1] != "No" { t.Errorf("got %v, want [Yes No]", s) } } func TestStringOrArray_StringifiedArray(t *testing.T) { input := `"[\"Yes\",\"No\"]"` var s stringOrArray if err := json.Unmarshal([]byte(input), &s); err != nil { t.Fatalf("Unmarshal error: %v", err) } if len(s) != 2 || s[0] != "Yes" || s[1] != "No" { t.Errorf("got %v, want [Yes No]", s) } } func TestFloatOrArray_Array(t *testing.T) { input := `[0.65, 0.35]` var f floatOrArray if err := json.Unmarshal([]byte(input), &f); err != nil { t.Fatalf("Unmarshal error: %v", err) } if len(f) != 2 || f[0] != 0.65 || f[1] != 0.35 { t.Errorf("got %v, want [0.65 0.35]", f) } } func TestFloatOrArray_StringArray(t *testing.T) { input := `["0.65","0.35"]` var f floatOrArray if err := json.Unmarshal([]byte(input), &f); err != nil { t.Fatalf("Unmarshal error: %v", err) } if len(f) != 2 || f[0] != 0.65 || f[1] != 0.35 { t.Errorf("got %v, want [0.65 0.35]", f) } } func TestFloatOrArray_StringifiedStringArray(t *testing.T) { input := `"[\"0.0125\",\"0.9875\"]"` var f floatOrArray if err := json.Unmarshal([]byte(input), &f); err != nil { t.Fatalf("Unmarshal error: %v", err) } if len(f) != 2 || f[0] != 0.0125 || f[1] != 0.9875 { t.Errorf("got %v, want [0.0125 0.9875]", f) } } func TestFlexFloat_Number(t *testing.T) { input := `1234.56` var f flexFloat if err := json.Unmarshal([]byte(input), &f); err != nil { t.Fatalf("Unmarshal error: %v", err) } if float64(f) != 1234.56 { t.Errorf("got %v, want 1234.56", f) } } func TestFlexFloat_String(t *testing.T) { input := `"1787746.945826"` var f flexFloat if err := json.Unmarshal([]byte(input), &f); err != nil { t.Fatalf("Unmarshal error: %v", err) } if float64(f) != 1787746.945826 { t.Errorf("got %v, want 1787746.945826", f) } } func TestParsePolymarketURL(t *testing.T) { tests := []struct { name string url string wantType string wantSlug string wantMarket string wantErr bool }{ { name: "event URL", url: "https://polymarket.com/event/microstrategy-sell-any-bitcoin-in-2025", wantType: "event", wantSlug: "microstrategy-sell-any-bitcoin-in-2025", }, { name: "event with market slug", url: "https://polymarket.com/event/measles-cases/will-there-be-10000-cases", wantType: "event", wantSlug: "measles-cases", wantMarket: "will-there-be-10000-cases", }, { name: "www prefix", url: "https://www.polymarket.com/event/some-event", wantType: "event", wantSlug: "some-event", }, { name: "wrong host", url: "https://example.com/event/foo", wantErr: true, }, { name: "too short path", url: "https://polymarket.com/event", wantErr: true, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { typ, slug, mkt, err := parsePolymarketURL(tt.url) if tt.wantErr { if err == nil { t.Fatal("expected error, got nil") } return } if err != nil { t.Fatalf("unexpected error: %v", err) } if typ != tt.wantType { t.Errorf("type = %q, want %q", typ, tt.wantType) } if slug != tt.wantSlug { t.Errorf("slug = %q, want %q", slug, tt.wantSlug) } if mkt != tt.wantMarket { t.Errorf("market = %q, want %q", mkt, tt.wantMarket) } }) } } func newTestServer(t *testing.T, handler http.HandlerFunc) (*httptest.Server, Config) { t.Helper() srv := httptest.NewServer(handler) t.Cleanup(srv.Close) return srv, Config{BaseURL: srv.URL} } func TestSearch(t *testing.T) { _, cfg := newTestServer(t, func(w http.ResponseWriter, r *http.Request) { if r.URL.Path != "/public-search" { http.NotFound(w, r) return } q := r.URL.Query().Get("q") if q != "olympic hockey" { t.Errorf("query = %q, want %q", q, "olympic hockey") } resp := apiSearchResponse{ Events: []apiEvent{ { ID: "100", Title: "USA vs CAN Olympic Hockey (Men)", Slug: "usa-vs-can-olympic-hockey-men", Markets: []apiMarket{ { ID: "200", Question: "Will USA win?", Slug: "will-usa-win", Outcomes: stringOrArray{"Yes", "No"}, OutcomePrices: floatOrArray{0.45, 0.55}, Active: true, }, }, Active: true, }, { ID: "101", Title: "USA vs CAN Olympic Hockey (Women)", Slug: "usa-vs-can-olympic-hockey-women", Active: true, }, }, } w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(resp) }) events, err := cfg.Search(context.Background(), "olympic hockey") if err != nil { t.Fatalf("Search error: %v", err) } if len(events) != 2 { t.Fatalf("got %d events, want 2", len(events)) } if events[0].Title != "USA vs CAN Olympic Hockey (Men)" { t.Errorf("title = %q", events[0].Title) } if len(events[0].Markets) != 1 { t.Fatalf("got %d markets, want 1", len(events[0].Markets)) } m := events[0].Markets[0] if m.Question != "Will USA win?" { t.Errorf("question = %q", m.Question) } if len(m.OutcomePrices) != 2 || m.OutcomePrices[0] != 0.45 { t.Errorf("prices = %v", m.OutcomePrices) } } func TestGetEvent(t *testing.T) { _, cfg := newTestServer(t, func(w http.ResponseWriter, r *http.Request) { if r.URL.Path != "/events/slug/my-event" { http.NotFound(w, r) return } resp := apiEvent{ ID: "42", Title: "My Event", Slug: "my-event", Markets: []apiMarket{ { ID: "100", Question: "Will it happen?", Slug: "will-it-happen", Outcomes: stringOrArray{"Yes", "No"}, OutcomePrices: floatOrArray{0.7, 0.3}, BestBid: 0.69, BestAsk: 0.71, LastTradePrice: 0.7, Active: true, }, }, Volume: 500000, Liquidity: 25000, Active: true, } w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(resp) }) ev, err := cfg.GetEvent(context.Background(), "my-event") if err != nil { t.Fatalf("GetEvent error: %v", err) } if ev.Title != "My Event" { t.Errorf("title = %q", ev.Title) } if ev.Volume != 500000 { t.Errorf("volume = %v", ev.Volume) } if len(ev.Markets) != 1 { t.Fatalf("got %d markets, want 1", len(ev.Markets)) } if ev.Markets[0].BestBid != 0.69 { t.Errorf("best bid = %v", ev.Markets[0].BestBid) } if ev.URL() != "https://polymarket.com/event/my-event" { t.Errorf("URL = %q", ev.URL()) } } func TestGetMarket(t *testing.T) { _, cfg := newTestServer(t, func(w http.ResponseWriter, r *http.Request) { if r.URL.Path != "/markets/slug/will-it-happen" { http.NotFound(w, r) return } resp := apiMarket{ ID: "100", Question: "Will it happen?", Slug: "will-it-happen", Outcomes: stringOrArray{"Yes", "No"}, OutcomePrices: floatOrArray{0.7, 0.3}, Volume: flexFloat(500000), Volume24hr: 12000, LiquidityClob: 25000, BestBid: 0.69, BestAsk: 0.71, LastTradePrice: 0.7, Active: true, } w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(resp) }) m, err := cfg.GetMarket(context.Background(), "will-it-happen") if err != nil { t.Fatalf("GetMarket error: %v", err) } if m.Question != "Will it happen?" { t.Errorf("question = %q", m.Question) } if m.Volume != 500000 { t.Errorf("volume = %v", m.Volume) } if m.LastPrice != 0.7 { t.Errorf("last price = %v", m.LastPrice) } } func TestGetEventFromURL(t *testing.T) { _, cfg := newTestServer(t, func(w http.ResponseWriter, r *http.Request) { if r.URL.Path != "/events/slug/my-event" { http.NotFound(w, r) return } resp := apiEvent{ ID: "42", Title: "My Event", Slug: "my-event", } w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(resp) }) // Override the URL to point to our test server via the config ev, err := cfg.GetEventFromURL(context.Background(), "https://polymarket.com/event/my-event") if err != nil { t.Fatalf("GetEventFromURL error: %v", err) } if ev.Title != "My Event" { t.Errorf("title = %q", ev.Title) } } func TestSearch_Empty(t *testing.T) { _, cfg := newTestServer(t, func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(apiSearchResponse{}) }) events, err := cfg.Search(context.Background(), "nonexistent") if err != nil { t.Fatalf("Search error: %v", err) } if len(events) != 0 { t.Errorf("got %d events, want 0", len(events)) } } func TestEventURL(t *testing.T) { e := Event{Slug: "test-event"} if e.URL() != "https://polymarket.com/event/test-event" { t.Errorf("URL = %q", e.URL()) } e2 := Event{} if e2.URL() != "" { t.Errorf("empty slug URL = %q, want empty", e2.URL()) } }