실거래 주문이 브로커에 도달하기 전에 kill switch와 계정별 주문 한도를 검증해야 한다. API, CLI, client parser surface와 완료된 review archive를 함께 정리한다.
557 lines
18 KiB
Go
557 lines
18 KiB
Go
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
|
|
}
|
|
|
|
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())
|
|
}
|
|
}
|