alt/services/api/internal/socket/live_test.go
toki 5488c4bc8b feat(live-trading): 브로커 포트 경계를 추가한다
실거래 경계를 열기 위한 domain, contract, API, worker, client parser 등록을 함께 추가한다.

브로커 capability 조회와 live trading capability 광고가 각 런타임에서 일관되게 검증되도록 테스트와 review artifact를 포함한다.
2026-06-07 10:24:28 +09:00

163 lines
6 KiB
Go

package socket
import (
"testing"
"google.golang.org/protobuf/proto"
altv1 "git.toki-labs.com/toki/alt/packages/contracts/gen/go/alt/v1"
apiContracts "git.toki-labs.com/toki/alt/services/api/internal/contracts"
"git.toki-labs.com/toki/alt/services/api/internal/workerclient"
protoSocket "git.toki-labs.com/toki/proto-socket/go"
)
func TestHandleGetLiveBrokerCapabilitiesWorkerForward(t *testing.T) {
expectedCap := &altv1.LiveBrokerCapability{Broker: "kis"}
worker := &fakeWorkerClient{
liveCapRes: &altv1.GetLiveBrokerCapabilitiesResponse{BrokerCapabilities: expectedCap},
isConnected: true,
}
resp, err := handleGetLiveBrokerCapabilities(worker, &altv1.GetLiveBrokerCapabilitiesRequest{AccountId: "test"})
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if resp.GetError() != nil {
t.Fatalf("unexpected error: %+v", resp.GetError())
}
if resp.GetBrokerCapabilities() == nil {
t.Fatal("expected broker capabilities, got nil")
}
if worker.GetLiveBrokerCapabilitiesCallCount() != 1 {
t.Errorf("expected worker call count 1, got %d", worker.GetLiveBrokerCapabilitiesCallCount())
}
if resp.GetBrokerCapabilities().GetBroker() != "kis" {
t.Errorf("broker mismatch: %q", resp.GetBrokerCapabilities().GetBroker())
}
}
func TestHandleGetLiveBrokerCapabilitiesNilWorker(t *testing.T) {
resp, err := handleGetLiveBrokerCapabilities(nil, &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 error code, got %q", resp.GetError().GetCode())
}
}
func TestHandleGetLiveBrokerCapabilitiesEmptyAccountID(t *testing.T) {
worker := &fakeWorkerClient{isConnected: true}
resp, err := handleGetLiveBrokerCapabilities(worker, &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 error 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())
}
if worker.GetLiveBrokerCapabilitiesCallCount() != 0 {
t.Errorf("worker should not be called when account_id is empty, got %d calls", worker.GetLiveBrokerCapabilitiesCallCount())
}
}
func TestHandleGetLiveBrokerCapabilitiesWorkerUnavailable(t *testing.T) {
worker := &fakeWorkerClient{connectErr: workerclient.ErrUnavailable, isConnected: false}
resp, err := handleGetLiveBrokerCapabilities(worker, &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 error code, got %q", resp.GetError().GetCode())
}
}
func TestHandleGetLiveBrokerCapabilitiesWorkerTimeout(t *testing.T) {
worker := &fakeWorkerClient{connectErr: workerclient.ErrTimeout, isConnected: false}
resp, err := handleGetLiveBrokerCapabilities(worker, &altv1.GetLiveBrokerCapabilitiesRequest{AccountId: "test"})
if err != nil {
t.Fatalf("expected typed timeout response, got error: %v", err)
}
if resp.GetError() == nil {
t.Fatal("expected error, got nil")
}
if resp.GetError().GetCode() != backtestErrorTimeout {
t.Errorf("expected timeout error code, got %q", resp.GetError().GetCode())
}
}
// TestHandleGetLiveBrokerCapabilitiesWorkerCallTimeout verifies the live handler
// maps a worker-level call timeout (GetLiveBrokerCapabilities returning
// workerclient.ErrTimeout) to backtestErrorTimeout. This is separate from the
// Connect failure test above because Connect failure path is tested with
// connectErr: workerclient.ErrTimeout above.
func TestHandleGetLiveBrokerCapabilitiesWorkerCallTimeout(t *testing.T) {
worker := &fakeWorkerClient{err: workerclient.ErrTimeout, isConnected: true}
resp, err := handleGetLiveBrokerCapabilities(worker, &altv1.GetLiveBrokerCapabilitiesRequest{AccountId: "test"})
if err != nil {
t.Fatalf("expected typed timeout response, got error: %v", err)
}
if resp.GetError() == nil {
t.Fatal("expected error, got nil")
}
if resp.GetError().GetCode() != backtestErrorTimeout {
t.Errorf("expected timeout error code, got %q", resp.GetError().GetCode())
}
if worker.GetLiveBrokerCapabilitiesCallCount() != 1 {
t.Errorf("expected worker call count 1, got %d", worker.GetLiveBrokerCapabilitiesCallCount())
}
}
func TestHandleGetLiveBrokerCapabilitiesNilWorkerResponse(t *testing.T) {
worker := &fakeWorkerClient{liveCapRes: nil, isConnected: true}
resp, err := handleGetLiveBrokerCapabilities(worker, &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 error code, got %q", resp.GetError().GetCode())
}
}
func TestSessionHandlersIncludeLive(t *testing.T) {
worker := &fakeWorkerClient{isConnected: true}
handlers := sessionHandlers(worker)
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 TestLiveParserMapContainsLiveMessages(t *testing.T) {
pm := apiContracts.ParserMap()
for _, m := range []proto.Message{
&altv1.GetLiveBrokerCapabilitiesRequest{},
&altv1.GetLiveBrokerCapabilitiesResponse{},
&altv1.LiveBrokerCapability{},
} {
name := protoSocket.TypeNameOf(m)
if _, ok := pm[name]; !ok {
t.Errorf("API parser map missing %q", name)
}
}
}