package socket import ( "context" "errors" "testing" "time" altv1 "git.toki-labs.com/toki/alt/packages/contracts/gen/go/alt/v1" "git.toki-labs.com/toki/alt/packages/domain/market" "git.toki-labs.com/toki/alt/packages/domain/trading" protoSocket "git.toki-labs.com/toki/proto-socket/go" ) type fakeLiveService struct { cap trading.BrokerCapability err error } func (f *fakeLiveService) GetLiveBrokerCapabilities(ctx context.Context, accountID string) (trading.BrokerCapability, error) { return f.cap, f.err } func sampleLiveCapability() trading.BrokerCapability { return trading.BrokerCapability{ Broker: trading.BrokerKIS, Markets: map[market.Market]bool{market.MarketKR: true}, Venues: map[market.Venue]bool{market.VenueKRX: true}, OrderTypes: []trading.OrderType{trading.OrderTypeMarket, trading.OrderTypeLimit}, SupportsCancel: true, SupportsAccount: true, SupportsAuditTags: false, CheckedAt: time.Date(2026, 6, 1, 0, 0, 0, 0, time.UTC), } } func TestHandleGetLiveBrokerCapabilitiesSuccess(t *testing.T) { svc := &fakeLiveService{cap: sampleLiveCapability()} deps := Deps{Live: svc} resp, err := handleGetLiveBrokerCapabilities(deps, &altv1.GetLiveBrokerCapabilitiesRequest{AccountId: "test"}) if err != nil { t.Fatalf("unexpected error: %v", err) } if resp.GetError() != nil { t.Fatalf("unexpected typed error: %+v", resp.GetError()) } cap := resp.GetBrokerCapabilities() if cap == nil { t.Fatal("expected broker capabilities in response") } if cap.GetBroker() != "kis" { t.Errorf("broker mismatch: %q", cap.GetBroker()) } if !cap.GetSupportsCancel() { t.Error("expected supports_cancel to be true") } } func TestHandleGetLiveBrokerCapabilitiesUnavailable(t *testing.T) { deps := Deps{} resp, err := handleGetLiveBrokerCapabilities(deps, &altv1.GetLiveBrokerCapabilitiesRequest{AccountId: "test"}) if err != nil { t.Fatalf("expected typed unavailable response, got error: %v", err) } if resp.GetError() == nil { t.Fatal("expected error, got nil") } if resp.GetError().GetCode() != backtestErrorUnavailable { t.Errorf("expected unavailable code, got %q", resp.GetError().GetCode()) } } func TestHandleGetLiveBrokerCapabilitiesEmptyAccountID(t *testing.T) { svc := &fakeLiveService{cap: sampleLiveCapability()} deps := Deps{Live: svc} resp, err := handleGetLiveBrokerCapabilities(deps, &altv1.GetLiveBrokerCapabilitiesRequest{AccountId: ""}) if err != nil { t.Fatalf("expected typed invalid_request response, got error: %v", err) } if resp.GetError() == nil { t.Fatal("expected error, got nil") } if resp.GetError().GetCode() != backtestErrorInvalidRequest { t.Errorf("expected invalid_request code, got %q", resp.GetError().GetCode()) } if resp.GetError().GetMessage() != "invalid live trading request: account_id is required" { t.Errorf("unexpected error message: %q", resp.GetError().GetMessage()) } } func TestHandleGetLiveBrokerCapabilitiesServiceError(t *testing.T) { svc := &fakeLiveService{err: errors.New("broker connection failed")} deps := Deps{Live: svc} resp, err := handleGetLiveBrokerCapabilities(deps, &altv1.GetLiveBrokerCapabilitiesRequest{AccountId: "test"}) if err != nil { t.Fatalf("expected typed internal response, got error: %v", err) } if resp.GetError() == nil { t.Fatal("expected error, got nil") } if resp.GetError().GetCode() != backtestErrorInternal { t.Errorf("expected internal code, got %q", resp.GetError().GetCode()) } } func TestLiveHandlersRegistration(t *testing.T) { registered := make(map[string]bool) for _, h := range liveHandlers(Deps{}) { if h.requestType == "" { t.Error("handler has empty request type") } if h.register == nil { t.Errorf("handler %q has nil register", h.requestType) } registered[h.requestType] = true } if !registered["alt.v1.GetLiveBrokerCapabilitiesRequest"] { t.Error("missing live capability handler registration") } } func TestLiveHandlersWithNilLiveService(t *testing.T) { // liveHandlers should still register even when deps.Live is nil. // The handler returns unavailable error at runtime. handlers := liveHandlers(Deps{}) if len(handlers) == 0 { t.Error("expected live handlers to be registered even with nil Live service") } } func TestLiveSocketUnavailableReturnsTypedError(t *testing.T) { client := startBacktestWorkerTestClient(t, Deps{}) resp, err := protoSocket.SendRequestTyped[*altv1.GetLiveBrokerCapabilitiesRequest, *altv1.GetLiveBrokerCapabilitiesResponse]( &client.Communicator, &altv1.GetLiveBrokerCapabilitiesRequest{AccountId: "test"}, 500*time.Millisecond, ) if err != nil { t.Fatalf("request should return typed error response without timeout: %v", err) } if resp.GetError() == nil { t.Fatal("expected error, got nil") } if resp.GetError().GetCode() != backtestErrorUnavailable { t.Errorf("expected unavailable code, got %q", resp.GetError().GetCode()) } } func TestLiveSocketRoundTripWithFakeService(t *testing.T) { svc := &fakeLiveService{cap: sampleLiveCapability()} client := startBacktestWorkerTestClient(t, Deps{Live: svc}) resp, err := protoSocket.SendRequestTyped[*altv1.GetLiveBrokerCapabilitiesRequest, *altv1.GetLiveBrokerCapabilitiesResponse]( &client.Communicator, &altv1.GetLiveBrokerCapabilitiesRequest{AccountId: "test"}, 1*time.Second, ) if err != nil { t.Fatalf("round trip failed: %v", err) } if resp.GetError() != nil { t.Fatalf("unexpected typed error: %+v", resp.GetError()) } cap := resp.GetBrokerCapabilities() if cap == nil { t.Fatal("expected broker capabilities in response") } if cap.GetBroker() != "kis" { t.Errorf("broker mismatch: %q", cap.GetBroker()) } } func TestLiveSocketEmptyAccountIDReturnsInvalidRequest(t *testing.T) { svc := &fakeLiveService{cap: sampleLiveCapability()} client := startBacktestWorkerTestClient(t, Deps{Live: svc}) resp, err := protoSocket.SendRequestTyped[*altv1.GetLiveBrokerCapabilitiesRequest, *altv1.GetLiveBrokerCapabilitiesResponse]( &client.Communicator, &altv1.GetLiveBrokerCapabilitiesRequest{AccountId: ""}, 500*time.Millisecond, ) if err != nil { t.Fatalf("request should return typed error response without timeout: %v", err) } if resp.GetError() == nil { t.Fatal("expected error, got nil") } if resp.GetError().GetCode() != backtestErrorInvalidRequest { t.Errorf("expected invalid_request code, got %q", resp.GetError().GetCode()) } if resp.GetBrokerCapabilities() != nil { t.Error("expected no broker capabilities when account_id is empty") } } func TestSessionHandlersIncludeLive(t *testing.T) { handlers := sessionHandlers(Deps{}) seenLive := false for _, h := range handlers { if h.requestType == "alt.v1.GetLiveBrokerCapabilitiesRequest" { seenLive = true break } } if !seenLive { t.Error("sessionHandlers should include live capability handler") } }