package scanner_test import ( "context" "encoding/json" "sort" "testing" "github.com/google/go-cmp/cmp" "s1d3sw1ped/robinhood-agentic-mcp/client" "s1d3sw1ped/robinhood-agentic-mcp/internal/rhntest" "s1d3sw1ped/robinhood-agentic-mcp/scanner" ) func TestScanner_toolNames(t *testing.T) { t.Parallel() visible := true order := 1 enumFilter := scanner.Filter{ FilterType: "FILTER_TYPE_RSI", Predicate: "PREDICATE_GREATER_THAN", Values: []string{"70"}, Interval: "1d", Length: 14, Plot: "close", } enumFilterWire := []map[string]any{ { "filter_type": "FILTER_TYPE_RSI", "predicate": "PREDICATE_GREATER_THAN", "values": []string{"70"}, "interval": "1d", "length": 14, "plot": "close", }, } exprFilter := scanner.Filter{ Predicate: "=", Values: []string{"True"}, Expression: `dayVolume / volumeAvg(candleCount=30, candlePeriod="1d", session="all")`, DisplayTitle: "Relative volume (30D)", } exprFilterWire := []map[string]any{ { "predicate": "=", "values": []string{"True"}, "expression": `dayVolume / volumeAvg(candleCount=30, candlePeriod="1d", session="all")`, "display_title": "Relative volume (30D)", }, } col := scanner.Column{ DisplayName: "Put/Call volume", Expression: "optionsPutDayVolume / optionsCallDayVolume", Visible: &visible, Order: &order, } colWire := []map[string]any{ { "display_name": "Put/Call volume", "expression": "optionsPutDayVolume / optionsCallDayVolume", "visible": true, "order": 1, }, } tests := []struct { name string call func(*scanner.Client) error wantName string wantArgs map[string]any }{ { name: "FilterSpecs", call: func(c *scanner.Client) error { _, err := c.FilterSpecs(context.Background(), scanner.FilterSpecsRequest{}) return err }, wantName: "get_scanner_filter_specs", wantArgs: map[string]any{}, }, { name: "Datapoints", call: func(c *scanner.Client) error { _, err := c.Datapoints(context.Background(), scanner.DatapointsRequest{}) return err }, wantName: "get_scanner_datapoints", wantArgs: map[string]any{}, }, { name: "Scans", call: func(c *scanner.Client) error { _, err := c.Scans(context.Background(), scanner.ScansRequest{}) return err }, wantName: "get_scans", wantArgs: map[string]any{}, }, { name: "Create", call: func(c *scanner.Client) error { _, err := c.Create(context.Background(), scanner.CreateRequest{ ScanID: "scan-1", Preset: "INITIAL", Filters: []scanner.Filter{enumFilter}, Columns: []scanner.Column{col}, Title: "RSI overbought", }) return err }, wantName: "create_scan", wantArgs: map[string]any{ "scan_id": "scan-1", "preset": "INITIAL", "filters": enumFilterWire, "columns": colWire, "title": "RSI overbought", }, }, { name: "Preview", call: func(c *scanner.Client) error { _, err := c.Preview(context.Background(), scanner.PreviewRequest{ Filters: []scanner.Filter{exprFilter}, Columns: []scanner.Column{col}, }) return err }, wantName: "preview_scan", wantArgs: map[string]any{ "filters": exprFilterWire, "columns": colWire, }, }, { name: "Run", call: func(c *scanner.Client) error { _, err := c.Run(context.Background(), scanner.RunRequest{ScanID: "scan-1"}) return err }, wantName: "run_scan", wantArgs: map[string]any{"scan_id": "scan-1"}, }, { name: "UpdateFilters", call: func(c *scanner.Client) error { _, err := c.UpdateFilters(context.Background(), scanner.UpdateFiltersRequest{ ScanID: "scan-1", Filters: []scanner.Filter{enumFilter}, }) return err }, wantName: "update_scan_filters", wantArgs: map[string]any{ "scan_id": "scan-1", "filters": enumFilterWire, }, }, { name: "UpdateConfig", call: func(c *scanner.Client) error { _, err := c.UpdateConfig(context.Background(), scanner.UpdateConfigRequest{ ScanID: "scan-1", SortingColumn: "Volume", SortingDirection: "desc", Columns: []scanner.Column{col}, }) return err }, wantName: "update_scan_config", wantArgs: map[string]any{ "scan_id": "scan-1", "sorting_column": "Volume", "sorting_direction": "desc", "columns": colWire, }, }, } for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { t.Parallel() var gotName string var gotArgs map[string]any c := scanner.New(client.Func(func(ctx context.Context, name string, args map[string]any) (json.RawMessage, error) { gotName, gotArgs = name, args return json.RawMessage(`{"scans":[]}`), nil })) if err := tc.call(c); err != nil { t.Fatal(err) } if gotName != tc.wantName { t.Fatalf("%s %+v", gotName, gotArgs) } if diff := cmp.Diff(tc.wantArgs, gotArgs); diff != "" { t.Fatal(diff) } if tc.name == "Run" { if gotArgs["scan_id"] != "scan-1" { t.Fatalf("scan_id %v", gotArgs["scan_id"]) } } }) } } func TestTools(t *testing.T) { t.Parallel() want := []string{ "create_scan", "get_scanner_datapoints", "get_scanner_filter_specs", "get_scans", "preview_scan", "run_scan", "update_scan_config", "update_scan_filters", } got := append([]string(nil), scanner.Tools()...) sort.Strings(got) if diff := cmp.Diff(want, got); diff != "" { t.Fatal(diff) } } func TestScans_rhntest(t *testing.T) { t.Parallel() s := rhntest.New(t) s.Set("get_scans", json.RawMessage(`{"scans":[]}`)) c := scanner.New(&client.Client{URL: s.URL}) got, err := c.Scans(context.Background(), scanner.ScansRequest{}) if err != nil { t.Fatal(err) } if len(got.Scans) != 0 { t.Fatalf("%+v", got) } if s.LastName() != "get_scans" { t.Fatalf("%s", s.LastName()) } if diff := cmp.Diff(map[string]any{}, s.LastArgs()); diff != "" { t.Fatal(diff) } }