package socket import ( "context" "errors" "fmt" "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" "git.toki-labs.com/toki/alt/services/worker/internal/livetrading" protoSocket "git.toki-labs.com/toki/proto-socket/go" ) type fakeLiveService struct { cap trading.BrokerCapability capErr error submitOrder livetrading.LiveOrder submitErr error cancelOrder livetrading.LiveOrder cancelErr error getOrder livetrading.LiveOrder getErr error policy trading.RiskPolicy killSwitch trading.KillSwitchState syncSnap trading.AccountSnapshot syncSnapErr error getSnap trading.AccountSnapshot getSnapErr error listAuditEvents []trading.AuditEvent listAuditErr error } func (f *fakeLiveService) GetLiveBrokerCapabilities(ctx context.Context, accountID string) (trading.BrokerCapability, error) { return f.cap, f.capErr } func (f *fakeLiveService) SubmitLiveOrder(ctx context.Context, req livetrading.SubmitRequest) (livetrading.LiveOrder, error) { return f.submitOrder, f.submitErr } func (f *fakeLiveService) CancelLiveOrder(ctx context.Context, accountID, liveOrderID string) (livetrading.LiveOrder, error) { return f.cancelOrder, f.cancelErr } func (f *fakeLiveService) GetLiveOrder(ctx context.Context, accountID, liveOrderID string) (livetrading.LiveOrder, error) { return f.getOrder, f.getErr } func (f *fakeLiveService) GetRiskPolicy() trading.RiskPolicy { return f.policy } func (f *fakeLiveService) GetKillSwitch() trading.KillSwitchState { return f.killSwitch } func (f *fakeLiveService) SetKillSwitch(ks trading.KillSwitchState) trading.KillSwitchState { f.killSwitch = ks return ks } 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{capErr: 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") } } func TestLiveHandlersRegistrationIncludesOrderLifecycle(t *testing.T) { required := map[string]bool{ "alt.v1.GetLiveBrokerCapabilitiesRequest": false, "alt.v1.SubmitLiveOrderRequest": false, "alt.v1.CancelLiveOrderRequest": false, "alt.v1.GetLiveOrderRequest": false, } for _, h := range liveHandlers(Deps{}) { if _, ok := required[h.requestType]; ok { required[h.requestType] = true } } for name, seen := range required { if !seen { t.Errorf("missing live handler registration: %s", name) } } } func TestHandleSubmitLiveOrderConfirmationRequired(t *testing.T) { svc := &fakeLiveService{submitErr: livetrading.ErrConfirmationRequired} deps := Deps{Live: svc} resp, err := handleSubmitLiveOrder(deps, &altv1.SubmitLiveOrderRequest{ AccountId: "acct-1", Intent: &altv1.LiveOrderIntent{InstrumentId: "KRX:005930", Side: "buy", Type: "market"}, OperatorConfirmation: &altv1.OperatorConfirmation{Confirmed: false}, }) if err != nil { t.Fatalf("unexpected go error: %v", err) } if resp.GetError() == nil { t.Fatal("expected typed error, got nil") } if resp.GetError().GetCode() != backtestErrorInvalidRequest { t.Errorf("expected invalid_request, got %q", resp.GetError().GetCode()) } } func TestHandleSubmitLiveOrderUnavailable(t *testing.T) { deps := Deps{} resp, err := handleSubmitLiveOrder(deps, &altv1.SubmitLiveOrderRequest{ AccountId: "acct-1", Intent: &altv1.LiveOrderIntent{InstrumentId: "KRX:005930", Side: "buy", Type: "market"}, OperatorConfirmation: &altv1.OperatorConfirmation{Confirmed: true}, }) if err != nil { t.Fatalf("unexpected go error: %v", err) } if resp.GetError() == nil { t.Fatal("expected typed error, got nil") } if resp.GetError().GetCode() != backtestErrorUnavailable { t.Errorf("expected unavailable, got %q", resp.GetError().GetCode()) } } func TestHandleSubmitLiveOrderSuccess(t *testing.T) { svc := &fakeLiveService{ submitOrder: livetrading.LiveOrder{ ID: "lo-acct-1-1", AccountID: "acct-1", Status: "submitted", BrokerStatus: "ACCEPTED", BrokerOrderID: "broker-001", Intent: trading.OrderIntent{ InstrumentID: "KRX:005930", Type: "kis_best_limit", }, }, } deps := Deps{Live: svc} resp, err := handleSubmitLiveOrder(deps, &altv1.SubmitLiveOrderRequest{ AccountId: "acct-1", Intent: &altv1.LiveOrderIntent{InstrumentId: "KRX:005930", Side: "buy", Type: "kis_best_limit"}, OperatorConfirmation: &altv1.OperatorConfirmation{Confirmed: true, OperatorId: "op-1"}, }) if err != nil { t.Fatalf("unexpected go error: %v", err) } if resp.GetError() != nil { t.Fatalf("unexpected typed error: %+v", resp.GetError()) } if resp.GetOrder().GetId() != "lo-acct-1-1" { t.Errorf("unexpected order id: %q", resp.GetOrder().GetId()) } if resp.GetOrder().GetType() != "kis_best_limit" { t.Errorf("order type not preserved: %q", resp.GetOrder().GetType()) } } func TestHandleCancelLiveOrderNotFound(t *testing.T) { svc := &fakeLiveService{cancelErr: livetrading.ErrOrderNotFound} deps := Deps{Live: svc} resp, err := handleCancelLiveOrder(deps, &altv1.CancelLiveOrderRequest{ AccountId: "acct-1", LiveOrderId: "nonexistent", }) if err != nil { t.Fatalf("unexpected go error: %v", err) } if resp.GetError() == nil { t.Fatal("expected typed error, got nil") } if resp.GetError().GetCode() != backtestErrorNotFound { t.Errorf("expected not_found, got %q", resp.GetError().GetCode()) } } func TestHandleGetLiveOrderSuccess(t *testing.T) { svc := &fakeLiveService{ getOrder: livetrading.LiveOrder{ ID: "lo-acct-1-1", AccountID: "acct-1", Status: "filled", BrokerStatus: "FILLED", }, } deps := Deps{Live: svc} resp, err := handleGetLiveOrder(deps, &altv1.GetLiveOrderRequest{ AccountId: "acct-1", LiveOrderId: "lo-acct-1-1", }) if err != nil { t.Fatalf("unexpected go error: %v", err) } if resp.GetError() != nil { t.Fatalf("unexpected typed error: %+v", resp.GetError()) } if resp.GetOrder().GetStatus() != "filled" { t.Errorf("expected status filled, got %q", resp.GetOrder().GetStatus()) } if resp.GetOrder().GetBrokerStatus() != "FILLED" { t.Errorf("expected broker_status FILLED, got %q", resp.GetOrder().GetBrokerStatus()) } } func TestHandleSubmitLiveOrderRiskBlockedReturnsInvalidRequest(t *testing.T) { svc := &fakeLiveService{submitErr: livetrading.ErrRiskBlocked} deps := Deps{Live: svc} resp, err := handleSubmitLiveOrder(deps, &altv1.SubmitLiveOrderRequest{ AccountId: "acct-1", Intent: &altv1.LiveOrderIntent{InstrumentId: "KRX:005930", Side: "buy", Type: "market"}, OperatorConfirmation: &altv1.OperatorConfirmation{Confirmed: true}, }) if err != nil { t.Fatalf("unexpected go error: %v", err) } if resp.GetError() == nil { t.Fatal("expected typed error, got nil") } if resp.GetError().GetCode() != backtestErrorInvalidRequest { t.Errorf("expected invalid_request for risk-blocked, got %q", resp.GetError().GetCode()) } } func TestHandleSubmitLiveOrderLimitPriceCurrencyRoundTrip(t *testing.T) { svc := &fakeLiveService{ submitOrder: livetrading.LiveOrder{ ID: "lo-acct-1-1", AccountID: "acct-1", Status: "submitted", BrokerStatus: "ACCEPTED", BrokerOrderID: "broker-001", Intent: trading.OrderIntent{ InstrumentID: "KRX:005930", Type: "limit", LimitPrice: market.Price{Currency: market.CurrencyKRW, Amount: market.Decimal{Value: "75000"}}, }, }, } deps := Deps{Live: svc} resp, err := handleSubmitLiveOrder(deps, &altv1.SubmitLiveOrderRequest{ AccountId: "acct-1", Intent: &altv1.LiveOrderIntent{ InstrumentId: "KRX:005930", Side: "buy", Type: "limit", LimitPrice: &altv1.Price{Currency: altv1.Currency_CURRENCY_KRW, Amount: &altv1.Decimal{Value: "75000"}}, }, OperatorConfirmation: &altv1.OperatorConfirmation{Confirmed: true, OperatorId: "op-1"}, }) if err != nil { t.Fatalf("unexpected go error: %v", err) } if resp.GetError() != nil { t.Fatalf("unexpected typed error: %+v", resp.GetError()) } lp := resp.GetOrder().GetLimitPrice() if lp == nil { t.Fatal("expected limit_price in response") } if lp.GetCurrency() != altv1.Currency_CURRENCY_KRW { t.Errorf("expected CURRENCY_KRW in response, got %q", lp.GetCurrency()) } if lp.GetAmount().GetValue() != "75000" { t.Errorf("expected limit_price amount 75000, got %q", lp.GetAmount().GetValue()) } } func TestHandleSubmitLiveOrderRiskBlockedIncludesOrderAndDecision(t *testing.T) { rejOrder := livetrading.LiveOrder{ ID: "lo-acct-1-1", AccountID: "acct-1", Status: trading.OrderStatusRejected, RejectionReason: "kill switch is active", } svc := &fakeLiveService{ submitOrder: rejOrder, submitErr: fmt.Errorf("%w: kill switch is active", livetrading.ErrRiskBlocked), } deps := Deps{Live: svc} resp, err := handleSubmitLiveOrder(deps, &altv1.SubmitLiveOrderRequest{ AccountId: "acct-1", Intent: &altv1.LiveOrderIntent{InstrumentId: "KRX:005930", Side: "buy", Type: "market"}, OperatorConfirmation: &altv1.OperatorConfirmation{Confirmed: true}, }) if err != nil { t.Fatalf("unexpected go error: %v", err) } if resp.GetError() == nil { t.Fatal("expected typed error for risk-blocked, got nil") } if resp.GetError().GetCode() != backtestErrorInvalidRequest { t.Errorf("expected invalid_request, got %q", resp.GetError().GetCode()) } if resp.GetOrder() == nil { t.Fatal("expected rejected order in response, got nil") } if resp.GetOrder().GetId() != "lo-acct-1-1" { t.Errorf("unexpected order id: %q", resp.GetOrder().GetId()) } if resp.GetRiskDecision() == nil { t.Fatal("expected risk decision in response, got nil") } if resp.GetRiskDecision().GetAllowed() { t.Error("expected risk decision allowed=false") } if resp.GetRiskDecision().GetReason() != "kill switch is active" { t.Errorf("unexpected risk decision reason: %q", resp.GetRiskDecision().GetReason()) } } func TestHandleSubmitLiveOrderInvalidCurrencyReturnsInvalidRequest(t *testing.T) { svc := &fakeLiveService{} deps := Deps{Live: svc} resp, err := handleSubmitLiveOrder(deps, &altv1.SubmitLiveOrderRequest{ AccountId: "acct-1", Intent: &altv1.LiveOrderIntent{ InstrumentId: "KRX:005930", Side: "buy", Type: "limit", LimitPrice: &altv1.Price{Currency: altv1.Currency_CURRENCY_UNSPECIFIED, Amount: &altv1.Decimal{Value: "75000"}}, }, OperatorConfirmation: &altv1.OperatorConfirmation{Confirmed: true, OperatorId: "op-1"}, }) if err != nil { t.Fatalf("unexpected go error: %v", err) } if resp.GetError() == nil { t.Fatal("expected typed error for unsupported currency, got nil") } if resp.GetError().GetCode() != backtestErrorInvalidRequest { t.Errorf("expected invalid_request, got %q", resp.GetError().GetCode()) } } func TestSubmitLiveOrderSocketRoundTrip(t *testing.T) { svc := &fakeLiveService{ submitOrder: livetrading.LiveOrder{ ID: "lo-acct-1-1", AccountID: "acct-1", Status: "submitted", BrokerStatus: "ACCEPTED", BrokerOrderID: "broker-001", Intent: trading.OrderIntent{InstrumentID: "KRX:005930", Type: "market"}, }, } client := startBacktestWorkerTestClient(t, Deps{Live: svc}) resp, err := protoSocket.SendRequestTyped[*altv1.SubmitLiveOrderRequest, *altv1.SubmitLiveOrderResponse]( &client.Communicator, &altv1.SubmitLiveOrderRequest{ AccountId: "acct-1", Intent: &altv1.LiveOrderIntent{InstrumentId: "KRX:005930", Side: "buy", Type: "market"}, OperatorConfirmation: &altv1.OperatorConfirmation{Confirmed: true, OperatorId: "op-1"}, }, 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()) } if resp.GetOrder().GetId() != "lo-acct-1-1" { t.Errorf("unexpected order id: %q", resp.GetOrder().GetId()) } } func (f *fakeLiveService) SyncLiveAccount(ctx context.Context, accountID string) (trading.AccountSnapshot, error) { return f.syncSnap, f.syncSnapErr } func (f *fakeLiveService) GetLiveAccountSnapshot(ctx context.Context, accountID string) (trading.AccountSnapshot, error) { return f.getSnap, f.getSnapErr } func (f *fakeLiveService) ListLiveAuditEvents(_ context.Context, _ trading.AuditFilter) ([]trading.AuditEvent, error) { return f.listAuditEvents, f.listAuditErr } func sampleAccountSnapshotForSocket() trading.AccountSnapshot { return trading.AccountSnapshot{ Broker: trading.BrokerKIS, AccountID: "op-alias-001", Cash: []trading.CashBalance{ {Currency: market.CurrencyKRW, Amount: market.Decimal{Value: "5000000"}}, }, Positions: []trading.PositionSnapshot{ { InstrumentID: "KRX:005930", Side: trading.OrderSideBuy, Quantity: market.Quantity{Amount: market.Decimal{Value: "10"}}, }, }, Stale: false, } } func TestHandleSyncLiveAccountSuccess(t *testing.T) { snap := sampleAccountSnapshotForSocket() svc := &fakeLiveService{syncSnap: snap} deps := Deps{Live: svc} resp, err := handleSyncLiveAccount(deps, &altv1.SyncLiveAccountRequest{AccountId: "op-alias-001"}) if err != nil { t.Fatalf("unexpected error: %v", err) } if resp.GetError() != nil { t.Fatalf("unexpected typed error: %+v", resp.GetError()) } s := resp.GetSnapshot() if s == nil { t.Fatal("expected snapshot in response") } if s.GetAccountId() != "op-alias-001" { t.Errorf("account_id mismatch: %q", s.GetAccountId()) } if s.GetBroker() != "kis" { t.Errorf("broker mismatch: %q", s.GetBroker()) } if len(s.GetCash()) != 1 { t.Errorf("expected 1 cash entry, got %d", len(s.GetCash())) } if s.GetStale() { t.Error("expected stale=false") } } func TestHandleSyncLiveAccountEmptyAccountIDReturnsInvalidRequest(t *testing.T) { svc := &fakeLiveService{} deps := Deps{Live: svc} resp, err := handleSyncLiveAccount(deps, &altv1.SyncLiveAccountRequest{AccountId: ""}) if err != nil { t.Fatalf("unexpected error: %v", err) } if resp.GetError() == nil { t.Fatal("expected typed error, got nil") } if resp.GetError().GetCode() != backtestErrorInvalidRequest { t.Errorf("expected invalid_request, got %q", resp.GetError().GetCode()) } } func TestHandleSyncLiveAccountNilServiceReturnsUnavailable(t *testing.T) { resp, err := handleSyncLiveAccount(Deps{}, &altv1.SyncLiveAccountRequest{AccountId: "op-alias-001"}) if err != nil { t.Fatalf("unexpected error: %v", err) } if resp.GetError() == nil { t.Fatal("expected typed error, got nil") } if resp.GetError().GetCode() != backtestErrorUnavailable { t.Errorf("expected unavailable, got %q", resp.GetError().GetCode()) } } func TestHandleGetLiveAccountSnapshotNotFound(t *testing.T) { svc := &fakeLiveService{getSnapErr: livetrading.ErrAccountSnapshotNotFound} deps := Deps{Live: svc} resp, err := handleGetLiveAccountSnapshot(deps, &altv1.GetLiveAccountSnapshotRequest{AccountId: "op-alias-001"}) if err != nil { t.Fatalf("unexpected error: %v", err) } if resp.GetError() == nil { t.Fatal("expected typed error, got nil") } if resp.GetError().GetCode() != backtestErrorNotFound { t.Errorf("expected not_found, got %q", resp.GetError().GetCode()) } } func TestHandleGetLiveAccountSnapshotSuccess(t *testing.T) { snap := sampleAccountSnapshotForSocket() svc := &fakeLiveService{getSnap: snap} deps := Deps{Live: svc} resp, err := handleGetLiveAccountSnapshot(deps, &altv1.GetLiveAccountSnapshotRequest{AccountId: "op-alias-001"}) if err != nil { t.Fatalf("unexpected error: %v", err) } if resp.GetError() != nil { t.Fatalf("unexpected typed error: %+v", resp.GetError()) } if resp.GetSnapshot() == nil { t.Fatal("expected snapshot in response") } if resp.GetSnapshot().GetAccountId() != "op-alias-001" { t.Errorf("account_id mismatch: %q", resp.GetSnapshot().GetAccountId()) } } func TestLiveHandlersRegistrationIncludesAccountSync(t *testing.T) { required := map[string]bool{ "alt.v1.SyncLiveAccountRequest": false, "alt.v1.GetLiveAccountSnapshotRequest": false, } for _, h := range liveHandlers(Deps{}) { if _, ok := required[h.requestType]; ok { required[h.requestType] = true } } for name, seen := range required { if !seen { t.Errorf("missing live handler registration: %s", name) } } } func TestLiveHandlersRegistrationIncludesAuditQuery(t *testing.T) { found := false for _, h := range liveHandlers(Deps{}) { if h.requestType == "alt.v1.ListLiveAuditEventsRequest" { found = true break } } if !found { t.Error("missing live handler registration: alt.v1.ListLiveAuditEventsRequest") } } func TestHandleListLiveAuditEventsSuccess(t *testing.T) { events := []trading.AuditEvent{ { EventID: "aud-1", Type: trading.AuditEventTypeSubmitConfirmed, Timestamp: time.Now().UTC(), AccountID: "acct-1", OrderID: "lo-acct-1-1", Status: "submitted", Actor: "op-001", }, { EventID: "aud-2", Type: trading.AuditEventTypeRiskBlocked, Timestamp: time.Now().UTC(), AccountID: "acct-1", Status: "blocked", }, } svc := &fakeLiveService{listAuditEvents: events} deps := Deps{Live: svc} resp, err := handleListLiveAuditEvents(deps, &altv1.ListLiveAuditEventsRequest{AccountId: "acct-1"}) if err != nil { t.Fatalf("unexpected error: %v", err) } if resp.GetError() != nil { t.Fatalf("unexpected typed error: %+v", resp.GetError()) } if len(resp.GetEvents()) != 2 { t.Fatalf("expected 2 events, got %d", len(resp.GetEvents())) } if resp.GetEvents()[0].GetEventId() != "aud-1" { t.Errorf("event_id mismatch: %q", resp.GetEvents()[0].GetEventId()) } if resp.GetEvents()[0].GetType() != string(trading.AuditEventTypeSubmitConfirmed) { t.Errorf("type mismatch: %q", resp.GetEvents()[0].GetType()) } if resp.GetEvents()[0].GetActor() != "op-001" { t.Errorf("actor mismatch: %q", resp.GetEvents()[0].GetActor()) } } func TestHandleListLiveAuditEventsEmptyAccountIDReturnsInvalidRequest(t *testing.T) { svc := &fakeLiveService{} deps := Deps{Live: svc} resp, err := handleListLiveAuditEvents(deps, &altv1.ListLiveAuditEventsRequest{AccountId: ""}) if err != nil { t.Fatalf("unexpected error: %v", err) } if resp.GetError() == nil { t.Fatal("expected typed error, got nil") } if resp.GetError().GetCode() != backtestErrorInvalidRequest { t.Errorf("expected invalid_request, got %q", resp.GetError().GetCode()) } } func TestHandleListLiveAuditEventsNilServiceReturnsUnavailable(t *testing.T) { resp, err := handleListLiveAuditEvents(Deps{}, &altv1.ListLiveAuditEventsRequest{AccountId: "acct-1"}) if err != nil { t.Fatalf("unexpected error: %v", err) } if resp.GetError() == nil { t.Fatal("expected typed error, got nil") } if resp.GetError().GetCode() != backtestErrorUnavailable { t.Errorf("expected unavailable, got %q", resp.GetError().GetCode()) } } func TestHandleListLiveAuditEventsServiceErrorReturnsInternal(t *testing.T) { svc := &fakeLiveService{listAuditErr: errors.New("store unavailable")} deps := Deps{Live: svc} resp, err := handleListLiveAuditEvents(deps, &altv1.ListLiveAuditEventsRequest{AccountId: "acct-1"}) if err != nil { t.Fatalf("unexpected error: %v", err) } if resp.GetError() == nil { t.Fatal("expected typed error, got nil") } if resp.GetError().GetCode() != backtestErrorInternal { t.Errorf("expected internal, got %q", resp.GetError().GetCode()) } } func TestHandleListLiveAuditEventsEmptyResultIsOK(t *testing.T) { svc := &fakeLiveService{listAuditEvents: nil} deps := Deps{Live: svc} resp, err := handleListLiveAuditEvents(deps, &altv1.ListLiveAuditEventsRequest{AccountId: "acct-1"}) if err != nil { t.Fatalf("unexpected error: %v", err) } if resp.GetError() != nil { t.Fatalf("unexpected typed error: %+v", resp.GetError()) } if len(resp.GetEvents()) != 0 { t.Errorf("expected empty events, got %d", len(resp.GetEvents())) } }