Files
s1d3sw1ped_bot 9e711957c0 docs: Remove sibling product and lab path leaks
Outsiders reading this module should not see private sibling
names (tradey), unfinished rewire notes, or /fast/projects
lab paths. Keep the library self-contained in README, design,
plan, and test fixtures/identity strings.
2026-09-01 19:43:35 +00:00

308 lines
8.3 KiB
Go

package watchlists_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/watchlists"
)
func TestWatchlists_toolNames(t *testing.T) {
t.Parallel()
tests := []struct {
name string
call func(*watchlists.Client) error
wantName string
wantArgs map[string]any
}{
{
name: "Lists",
call: func(c *watchlists.Client) error {
_, err := c.Lists(context.Background(), watchlists.ListsRequest{})
return err
},
wantName: "get_watchlists",
wantArgs: map[string]any{},
},
{
name: "Items",
call: func(c *watchlists.Client) error {
_, err := c.Items(context.Background(), watchlists.ItemsRequest{ListID: "wl-1"})
return err
},
wantName: "get_watchlist_items",
wantArgs: map[string]any{"list_id": "wl-1"},
},
{
name: "OptionList",
call: func(c *watchlists.Client) error {
_, err := c.OptionList(context.Background(), watchlists.OptionListRequest{})
return err
},
wantName: "get_option_watchlist",
wantArgs: map[string]any{},
},
{
name: "Popular",
call: func(c *watchlists.Client) error {
_, err := c.Popular(context.Background(), watchlists.PopularRequest{})
return err
},
wantName: "get_popular_watchlists",
wantArgs: map[string]any{},
},
{
name: "Create",
call: func(c *watchlists.Client) error {
return c.Create(context.Background(), watchlists.CreateRequest{
DisplayName: "Tech",
IconEmoji: "📈",
DisplayDescription: "vwap book",
})
},
wantName: "create_watchlist",
wantArgs: map[string]any{
"display_name": "Tech",
"icon_emoji": "📈",
"display_description": "vwap book",
},
},
{
name: "Update",
call: func(c *watchlists.Client) error {
return c.Update(context.Background(), watchlists.UpdateRequest{
ListID: "wl-1",
DisplayName: "Tech",
IconEmoji: "📈",
DisplayDescription: "vwap book",
})
},
wantName: "update_watchlist",
wantArgs: map[string]any{
"list_id": "wl-1",
"display_name": "Tech",
"icon_emoji": "📈",
"display_description": "vwap book",
},
},
{
name: "Follow",
call: func(c *watchlists.Client) error {
return c.Follow(context.Background(), watchlists.ListIDRequest{ListID: "wl-pop"})
},
wantName: "follow_watchlist",
wantArgs: map[string]any{"list_id": "wl-pop"},
},
{
name: "Unfollow",
call: func(c *watchlists.Client) error {
return c.Unfollow(context.Background(), watchlists.ListIDRequest{ListID: "wl-pop"})
},
wantName: "unfollow_watchlist",
wantArgs: map[string]any{"list_id": "wl-pop"},
},
{
name: "Add",
call: func(c *watchlists.Client) error {
return c.Add(context.Background(), watchlists.AddRequest{
ListID: "wl-1",
Symbols: []string{"MU", "SPY"},
CurrencyPairIDs: []string{"btc-1"},
IndexIDs: []string{"idx-spx"},
})
},
wantName: "add_to_watchlist",
wantArgs: map[string]any{
"list_id": "wl-1",
"symbols": []string{"MU", "SPY"},
"currency_pair_ids": []string{"btc-1"},
"index_ids": []string{"idx-spx"},
},
},
{
name: "Remove",
call: func(c *watchlists.Client) error {
return c.Remove(context.Background(), watchlists.RemoveRequest{
ListID: "wl-1",
Symbols: []string{"MU"},
CurrencyPairIDs: []string{"btc-1"},
IndexIDs: []string{"idx-spx"},
})
},
wantName: "remove_from_watchlist",
wantArgs: map[string]any{
"list_id": "wl-1",
"symbols": []string{"MU"},
"currency_pair_ids": []string{"btc-1"},
"index_ids": []string{"idx-spx"},
},
},
{
name: "AddOption",
call: func(c *watchlists.Client) error {
return c.AddOption(context.Background(), watchlists.OptionMutateRequest{
OptionIDs: []string{"opt-1", "opt-2"},
PositionType: "long",
})
},
wantName: "add_option_to_watchlist",
wantArgs: map[string]any{
"option_ids": []string{"opt-1", "opt-2"},
"position_type": "long",
},
},
{
name: "RemoveOption",
call: func(c *watchlists.Client) error {
return c.RemoveOption(context.Background(), watchlists.OptionMutateRequest{
OptionIDs: []string{"opt-1"},
PositionType: "short",
})
},
wantName: "remove_option_from_watchlist",
wantArgs: map[string]any{
"option_ids": []string{"opt-1"},
"position_type": "short",
},
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
var gotName string
var gotArgs map[string]any
c := watchlists.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{
"add_option_to_watchlist",
"add_to_watchlist",
"create_watchlist",
"follow_watchlist",
"get_option_watchlist",
"get_popular_watchlists",
"get_watchlist_items",
"get_watchlists",
"remove_from_watchlist",
"remove_option_from_watchlist",
"unfollow_watchlist",
"update_watchlist",
}
got := append([]string(nil), watchlists.Tools()...)
sort.Strings(got)
if diff := cmp.Diff(want, got); diff != "" {
t.Fatal(diff)
}
}
func TestLists_rhntest(t *testing.T) {
t.Parallel()
s := rhntest.New(t)
s.Set("get_watchlists", json.RawMessage(`{"watchlists":[{"id":"wl-1","title":"Tech"}]}`))
c := watchlists.New(&client.Client{URL: s.URL})
got, err := c.Lists(context.Background(), watchlists.ListsRequest{})
if err != nil {
t.Fatal(err)
}
if len(got.Watchlists) != 1 || got.Watchlists[0].ID != "wl-1" || got.Watchlists[0].Title != "Tech" {
t.Fatalf("%+v", got)
}
if s.LastName() != "get_watchlists" {
t.Fatalf("%s", s.LastName())
}
if diff := cmp.Diff(map[string]any{}, s.LastArgs()); diff != "" {
t.Fatal(diff)
}
}
func TestItems_rhntest(t *testing.T) {
t.Parallel()
s := rhntest.New(t)
s.Set("get_watchlist_items", json.RawMessage(`{"items":[{"symbol":"MU","object_type":"equity"}]}`))
c := watchlists.New(&client.Client{URL: s.URL})
got, err := c.Items(context.Background(), watchlists.ItemsRequest{ListID: "wl-1"})
if err != nil {
t.Fatal(err)
}
if len(got.Items) != 1 || got.Items[0].Symbol != "MU" || got.Items[0].ObjectType != "equity" {
t.Fatalf("%+v", got)
}
if s.LastName() != "get_watchlist_items" {
t.Fatalf("%s", s.LastName())
}
want := map[string]any{"list_id": "wl-1"}
if diff := cmp.Diff(want, s.LastArgs()); diff != "" {
t.Fatal(diff)
}
}
func TestLists_titleFallsBackToName(t *testing.T) {
t.Parallel()
c := watchlists.New(client.Func(func(ctx context.Context, name string, args map[string]any) (json.RawMessage, error) {
return json.RawMessage(`{"watchlists":[{"id":"wl-1","title":"tech"},{"id":"wl-2","name":"Other"}]}`), nil
}))
got, err := c.Lists(context.Background(), watchlists.ListsRequest{})
if err != nil {
t.Fatal(err)
}
want := watchlists.ListsResult{
Watchlists: []watchlists.Watchlist{
{ID: "wl-1", Title: "tech"},
{ID: "wl-2", Title: "Other"},
},
}
if diff := cmp.Diff(want, got); diff != "" {
t.Fatal(diff)
}
}
func TestItems_keepsObjectTypesAndNestedSymbol(t *testing.T) {
t.Parallel()
c := watchlists.New(client.Func(func(ctx context.Context, name string, args map[string]any) (json.RawMessage, error) {
return json.RawMessage(`{"items":[
{"symbol":"MU","object_type":"equity"},
{"symbol":"BTC","object_type":"crypto"},
{"instrument":{"symbol":"SPY","type":"etf"}}
]}`), nil
}))
got, err := c.Items(context.Background(), watchlists.ItemsRequest{ListID: "wl-1"})
if err != nil {
t.Fatal(err)
}
want := watchlists.ItemsResult{
Items: []watchlists.Item{
{Symbol: "MU", ObjectType: "equity"},
{Symbol: "BTC", ObjectType: "crypto"},
{
Symbol: "SPY",
ObjectType: "etf",
Instrument: &watchlists.Instrument{Symbol: "SPY", Type: "etf"},
},
},
}
if diff := cmp.Diff(want, got); diff != "" {
t.Fatal(diff)
}
}