package accounts_test import ( "context" "encoding/json" "os" "sort" "testing" decimal "github.com/alpacahq/alpacadecimal" "github.com/google/go-cmp/cmp" "s1d3sw1ped/robinhood-agentic-mcp/accounts" "s1d3sw1ped/robinhood-agentic-mcp/client" "s1d3sw1ped/robinhood-agentic-mcp/internal/rhntest" ) func TestAccounts_toolNames(t *testing.T) { t.Parallel() tests := []struct { name string call func(*accounts.Client) error wantName string wantArgs map[string]any }{ { name: "Accounts", call: func(c *accounts.Client) error { _, err := c.Accounts(context.Background(), accounts.AccountsRequest{}) return err }, wantName: "get_accounts", wantArgs: map[string]any{}, }, { name: "Portfolio", call: func(c *accounts.Client) error { _, err := c.Portfolio(context.Background(), accounts.PortfolioRequest{AccountNumber: "acct-1"}) return err }, wantName: "get_portfolio", wantArgs: map[string]any{"account_number": "acct-1"}, }, { name: "RealizedPnL", call: func(c *accounts.Client) error { _, err := c.RealizedPnL(context.Background(), accounts.RealizedPnLRequest{ AccountNumber: "acct-1", Span: "month", StartDate: "2026-01-01", EndDate: "2026-01-31", AssetClasses: []string{"equity"}, DisplayCurrency: "USD", Timezone: "America/New_York", }) return err }, wantName: "get_realized_pnl", wantArgs: map[string]any{ "account_number": "acct-1", "span": "month", "start_date": "2026-01-01", "end_date": "2026-01-31", "asset_classes": []string{"equity"}, "display_currency": "USD", "timezone": "America/New_York", }, }, { name: "PnLTradeHistory", call: func(c *accounts.Client) error { _, err := c.PnLTradeHistory(context.Background(), accounts.PnLTradeHistoryRequest{ AccountNumber: "acct-1", Span: "week", Symbol: "MU", Cursor: "c1", }) return err }, wantName: "get_pnl_trade_history", wantArgs: map[string]any{ "account_number": "acct-1", "span": "week", "symbol": "MU", "cursor": "c1", }, }, { name: "LimitedMarginUpgradeInfo", call: func(c *accounts.Client) error { _, err := c.LimitedMarginUpgradeInfo(context.Background(), accounts.AccountNumberRequest{AccountNumber: "acct-1"}) return err }, wantName: "get_limited_margin_upgrade_info", wantArgs: map[string]any{"account_number": "acct-1"}, }, { name: "OptionLevelUpgradeInfo", call: func(c *accounts.Client) error { _, err := c.OptionLevelUpgradeInfo(context.Background(), accounts.AccountNumberRequest{AccountNumber: "acct-1"}) return err }, wantName: "get_option_level_upgrade_info", wantArgs: map[string]any{"account_number": "acct-1"}, }, { name: "CryptoOnboardingInfo", call: func(c *accounts.Client) error { _, err := c.CryptoOnboardingInfo(context.Background(), struct{}{}) return err }, wantName: "get_crypto_account_onboarding_info", wantArgs: map[string]any{}, }, { name: "Search", call: func(c *accounts.Client) error { _, err := c.Search(context.Background(), accounts.SearchRequest{ Query: "apple", AssetType: "instrument", Limit: 5, }) return err }, wantName: "search", wantArgs: map[string]any{ "query": "apple", "asset_type": "instrument", "limit": 5, }, }, } for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { t.Parallel() var gotName string var gotArgs map[string]any c := accounts.New(client.Func(func(ctx context.Context, name string, args map[string]any) (json.RawMessage, error) { gotName, gotArgs = name, args return json.RawMessage(`{}`), 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) } }) } } func TestTools(t *testing.T) { t.Parallel() want := []string{ "get_accounts", "get_crypto_account_onboarding_info", "get_limited_margin_upgrade_info", "get_option_level_upgrade_info", "get_pnl_trade_history", "get_portfolio", "get_realized_pnl", "search", } got := append([]string(nil), accounts.Tools()...) sort.Strings(got) if diff := cmp.Diff(want, got); diff != "" { t.Fatal(diff) } } func TestAccounts_rhntest(t *testing.T) { t.Parallel() raw, err := os.ReadFile("testdata/accounts.json") if err != nil { t.Fatal(err) } s := rhntest.New(t) s.Set("get_accounts", raw) c := accounts.New(&client.Client{URL: s.URL}) got, err := c.Accounts(context.Background(), accounts.AccountsRequest{}) if err != nil { t.Fatal(err) } if len(got.Accounts) != 1 { t.Fatalf("%+v", got) } a := got.Accounts[0] if a.ID != "acct-1" || a.Type != "cash" || !a.AgenticAllowed || !a.Cash { t.Fatalf("%+v", a) } if a.BuyingPower == nil || !a.BuyingPower.Equal(decimal.RequireFromString("1000")) { t.Fatalf("buying power %v", a.BuyingPower) } } func TestPortfolio_rhntest(t *testing.T) { t.Parallel() s := rhntest.New(t) s.Set("get_portfolio", json.RawMessage(`{"data":{"buying_power":{"buying_power":"1000.0"}}}`)) c := accounts.New(&client.Client{URL: s.URL}) got, err := c.Portfolio(context.Background(), accounts.PortfolioRequest{AccountNumber: "acct-1"}) if err != nil { t.Fatal(err) } if !got.BuyingPower.Equal(decimal.RequireFromString("1000.0")) { t.Fatalf("buying power %s", got.BuyingPower) } } func TestSearch_parsesResults(t *testing.T) { t.Parallel() c := accounts.New(client.Func(func(ctx context.Context, name string, args map[string]any) (json.RawMessage, error) { return json.RawMessage(`{"results":[{"symbol":"AAPL","name":"Apple","instrument_id":"i1"}]}`), nil })) got, err := c.Search(context.Background(), accounts.SearchRequest{Query: "apple"}) if err != nil { t.Fatal(err) } if len(got.Results) != 1 || got.Results[0].Symbol != "AAPL" || got.Results[0].InstrumentID != "i1" { t.Fatalf("%+v", got) } }