648 lines
20 KiB
Go
648 lines
20 KiB
Go
package fix
|
|
|
|
import (
|
|
"errors"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
|
|
"quantex.com/qfixdpl/quickfix"
|
|
"quantex.com/qfixdpl/src/app"
|
|
"quantex.com/qfixdpl/src/domain"
|
|
)
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Helpers
|
|
// ---------------------------------------------------------------------------
|
|
|
|
// newTestManager builds a Manager with the given store without calling Start().
|
|
func newTestManager(store domain.PersistenceStore) *Manager {
|
|
notify := &MockNotifier{}
|
|
return NewManager(app.FIXConfig{}, store, notify)
|
|
}
|
|
|
|
// makeMsg builds a TradeMessage with the given quoteReqID, msgType, and body.
|
|
func makeMsg(quoteReqID, msgType string, body map[string]interface{}) domain.TradeMessage {
|
|
return domain.TradeMessage{
|
|
ID: "test-id-" + quoteReqID + "-" + msgType,
|
|
QuoteReqID: quoteReqID,
|
|
JMessage: domain.FixMessageJSON{
|
|
MsgType: msgType,
|
|
QuoteReqID: quoteReqID,
|
|
Body: body,
|
|
ReceiveTime: time.Now(),
|
|
},
|
|
CreatedAt: time.Now(),
|
|
}
|
|
}
|
|
|
|
// makeQuoteRequest builds a "R" (QuoteRequest) TradeMessage.
|
|
func makeQuoteRequest(quoteReqID, listID, nt string, extras map[string]interface{}) domain.TradeMessage {
|
|
body := map[string]interface{}{
|
|
"NegotiationType": nt,
|
|
"ListID": listID,
|
|
}
|
|
for k, v := range extras {
|
|
body[k] = v
|
|
}
|
|
return makeMsg(quoteReqID, "R", body)
|
|
}
|
|
|
|
// makeQuoteAck builds a "CW" (QuoteAck) TradeMessage.
|
|
func makeQuoteAck(quoteReqID, status string) domain.TradeMessage {
|
|
return makeMsg(quoteReqID, "CW", map[string]interface{}{
|
|
"QuoteAckStatus": status,
|
|
})
|
|
}
|
|
|
|
// makeQuoteResponse builds an "AJ" (QuoteResponse) TradeMessage.
|
|
func makeQuoteResponse(quoteReqID, quoteRespID string) domain.TradeMessage {
|
|
return makeMsg(quoteReqID, "AJ", map[string]interface{}{
|
|
"QuoteRespID": quoteRespID,
|
|
})
|
|
}
|
|
|
|
// makeExecutionReport builds an "8" (ExecutionReport) TradeMessage.
|
|
// clOrdID maps to body["ClOrdID"]; it is also set as TradeMessage.QuoteReqID
|
|
// to mirror how persistMessage works in handleExecutionReport.
|
|
func makeExecutionReport(clOrdID, execID string) domain.TradeMessage {
|
|
return makeMsg(clOrdID, "8", map[string]interface{}{
|
|
"ClOrdID": clOrdID,
|
|
"ExecID": execID,
|
|
})
|
|
}
|
|
|
|
// makeOutgoing builds an outgoing message (AI, S, BN) for a given quoteReqID.
|
|
func makeOutgoing(quoteReqID, msgType string) domain.TradeMessage {
|
|
return makeMsg(quoteReqID, msgType, map[string]interface{}{})
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Group 1 — DB vacia
|
|
// ---------------------------------------------------------------------------
|
|
|
|
func TestLoadTrades_EmptyDB(t *testing.T) {
|
|
store := &MockPersistenceStore{}
|
|
store.On("GetTodayMessages").Return([]domain.TradeMessage{}, nil)
|
|
|
|
m := newTestManager(store)
|
|
err := m.loadTrades()
|
|
|
|
require.NoError(t, err)
|
|
assert.NotNil(t, m.trades)
|
|
assert.Len(t, m.trades, 0)
|
|
store.AssertExpectations(t)
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Group 2 — Interrupcion despues de QuoteRequest
|
|
// ---------------------------------------------------------------------------
|
|
|
|
func TestLoadTrades_SingleR_CreatesOneTrade(t *testing.T) {
|
|
r := makeQuoteRequest("LST_ABC123", "LIST_1", "RFQ", map[string]interface{}{
|
|
"SecurityID": "US1234567890",
|
|
"Currency": "USD",
|
|
"Side": "1",
|
|
"OrderQty": "1000000",
|
|
"SettlDate": "20260320",
|
|
"OwnerTraderID": "trader1",
|
|
})
|
|
|
|
store := &MockPersistenceStore{}
|
|
store.On("GetTodayMessages").Return([]domain.TradeMessage{r}, nil)
|
|
|
|
m := newTestManager(store)
|
|
err := m.loadTrades()
|
|
|
|
require.NoError(t, err)
|
|
require.Len(t, m.trades, 1)
|
|
|
|
trade := m.trades["LST_ABC123"]
|
|
require.NotNil(t, trade)
|
|
assert.Equal(t, "LST_ABC123", trade.QuoteReqID)
|
|
assert.Equal(t, "LIST_1", trade.ListID)
|
|
assert.Equal(t, "US1234567890", trade.Symbol)
|
|
assert.Equal(t, "USD", trade.Currency)
|
|
assert.Equal(t, "1", string(trade.Side))
|
|
assert.Equal(t, "20260320", trade.SettlDate)
|
|
assert.Equal(t, "trader1", trade.OwnerTraderID)
|
|
assert.Equal(t, domain.TradeStatusActive, trade.Status)
|
|
store.AssertExpectations(t)
|
|
}
|
|
|
|
func TestLoadTrades_R_WithOutgoingMsgs_IgnoresAI_S(t *testing.T) {
|
|
r := makeQuoteRequest("LST_ABC123", "LIST_1", "RFQ", nil)
|
|
ai := makeOutgoing("LST_ABC123", "AI")
|
|
s := makeOutgoing("LST_ABC123", "S")
|
|
|
|
store := &MockPersistenceStore{}
|
|
store.On("GetTodayMessages").Return([]domain.TradeMessage{r, ai, s}, nil)
|
|
|
|
m := newTestManager(store)
|
|
err := m.loadTrades()
|
|
|
|
require.NoError(t, err)
|
|
assert.Len(t, m.trades, 1)
|
|
assert.Equal(t, domain.TradeStatusActive, m.trades["LST_ABC123"].Status)
|
|
store.AssertExpectations(t)
|
|
}
|
|
|
|
func TestLoadTrades_R_NonLSTPrefix_Ignored(t *testing.T) {
|
|
r := makeQuoteRequest("TRD_ABC123", "LIST_1", "RFQ", nil)
|
|
|
|
store := &MockPersistenceStore{}
|
|
store.On("GetTodayMessages").Return([]domain.TradeMessage{r}, nil)
|
|
|
|
m := newTestManager(store)
|
|
err := m.loadTrades()
|
|
|
|
require.NoError(t, err)
|
|
assert.Len(t, m.trades, 0)
|
|
store.AssertExpectations(t)
|
|
}
|
|
|
|
func TestLoadTrades_R_NonRFQ_NegotiationType_Ignored(t *testing.T) {
|
|
r := makeQuoteRequest("LST_X", "LIST_1", "DEALER", nil)
|
|
|
|
store := &MockPersistenceStore{}
|
|
store.On("GetTodayMessages").Return([]domain.TradeMessage{r}, nil)
|
|
|
|
m := newTestManager(store)
|
|
err := m.loadTrades()
|
|
|
|
require.NoError(t, err)
|
|
assert.Len(t, m.trades, 0)
|
|
store.AssertExpectations(t)
|
|
}
|
|
|
|
func TestLoadTrades_R_EmptyListID_Ignored(t *testing.T) {
|
|
r := makeQuoteRequest("LST_X", "", "RFQ", nil)
|
|
|
|
store := &MockPersistenceStore{}
|
|
store.On("GetTodayMessages").Return([]domain.TradeMessage{r}, nil)
|
|
|
|
m := newTestManager(store)
|
|
err := m.loadTrades()
|
|
|
|
require.NoError(t, err)
|
|
assert.Len(t, m.trades, 0)
|
|
store.AssertExpectations(t)
|
|
}
|
|
|
|
func TestLoadTrades_R_MissingOptionalFields_TradeCreated(t *testing.T) {
|
|
r := makeQuoteRequest("LST_MIN", "LIST_MIN", "RFQ", nil)
|
|
|
|
store := &MockPersistenceStore{}
|
|
store.On("GetTodayMessages").Return([]domain.TradeMessage{r}, nil)
|
|
|
|
m := newTestManager(store)
|
|
err := m.loadTrades()
|
|
|
|
require.NoError(t, err)
|
|
require.Len(t, m.trades, 1)
|
|
|
|
trade := m.trades["LST_MIN"]
|
|
require.NotNil(t, trade)
|
|
assert.Equal(t, "LST_MIN", trade.QuoteReqID)
|
|
assert.Equal(t, "LIST_MIN", trade.ListID)
|
|
assert.Equal(t, "", trade.Symbol)
|
|
assert.Equal(t, "", trade.Currency)
|
|
assert.Equal(t, "", trade.SettlDate)
|
|
assert.Equal(t, "", trade.OwnerTraderID)
|
|
assert.Equal(t, domain.TradeStatusActive, trade.Status)
|
|
store.AssertExpectations(t)
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Group 3 — Interrupcion despues de QuoteAck
|
|
// ---------------------------------------------------------------------------
|
|
|
|
func TestLoadTrades_CW_Accepted_TradeRemains(t *testing.T) {
|
|
r := makeQuoteRequest("LST_Q1", "LIST_1", "RFQ", nil)
|
|
cw := makeQuoteAck("LST_Q1", "1") // ACCEPTED
|
|
|
|
store := &MockPersistenceStore{}
|
|
store.On("GetTodayMessages").Return([]domain.TradeMessage{r, cw}, nil)
|
|
|
|
m := newTestManager(store)
|
|
err := m.loadTrades()
|
|
|
|
require.NoError(t, err)
|
|
assert.Len(t, m.trades, 1)
|
|
assert.Equal(t, domain.TradeStatusActive, m.trades["LST_Q1"].Status)
|
|
store.AssertExpectations(t)
|
|
}
|
|
|
|
func TestLoadTrades_CW_Rejected_TradeMarkedRejected(t *testing.T) {
|
|
r := makeQuoteRequest("LST_Q1", "LIST_1", "RFQ", nil)
|
|
cw := makeQuoteAck("LST_Q1", "2") // REJECTED
|
|
|
|
store := &MockPersistenceStore{}
|
|
store.On("GetTodayMessages").Return([]domain.TradeMessage{r, cw}, nil)
|
|
|
|
m := newTestManager(store)
|
|
err := m.loadTrades()
|
|
|
|
require.NoError(t, err)
|
|
assert.Len(t, m.trades, 1)
|
|
assert.Equal(t, domain.TradeStatusRejected, m.trades["LST_Q1"].Status)
|
|
store.AssertExpectations(t)
|
|
}
|
|
|
|
// QuoteAckStatus "0" is RECEIVED_NOT_YET_PROCESSED — not a rejection.
|
|
// Only status "2" (Rejected) should mark the trade as rejected.
|
|
func TestLoadTrades_CW_ReceivedNotYetProcessed_TradeStaysActive(t *testing.T) {
|
|
r := makeQuoteRequest("LST_Q1", "LIST_1", "RFQ", nil)
|
|
cw := makeQuoteAck("LST_Q1", "0") // RECEIVED_NOT_YET_PROCESSED
|
|
|
|
store := &MockPersistenceStore{}
|
|
store.On("GetTodayMessages").Return([]domain.TradeMessage{r, cw}, nil)
|
|
|
|
m := newTestManager(store)
|
|
err := m.loadTrades()
|
|
|
|
require.NoError(t, err)
|
|
assert.Len(t, m.trades, 1)
|
|
assert.Equal(t, domain.TradeStatusActive, m.trades["LST_Q1"].Status)
|
|
store.AssertExpectations(t)
|
|
}
|
|
|
|
func TestLoadTrades_CW_WithoutPriorR_NoCrash(t *testing.T) {
|
|
cw := makeQuoteAck("LST_ORPHAN", "2")
|
|
|
|
store := &MockPersistenceStore{}
|
|
store.On("GetTodayMessages").Return([]domain.TradeMessage{cw}, nil)
|
|
|
|
m := newTestManager(store)
|
|
err := m.loadTrades()
|
|
|
|
require.NoError(t, err)
|
|
assert.Len(t, m.trades, 0)
|
|
store.AssertExpectations(t)
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Group 4 — Interrupcion despues de QuoteResponse
|
|
// ---------------------------------------------------------------------------
|
|
|
|
func TestLoadTrades_AJ_TRDREQ_TradeRemains(t *testing.T) {
|
|
r := makeQuoteRequest("LST_Q1", "LIST_1", "RFQ", nil)
|
|
cw := makeQuoteAck("LST_Q1", "1")
|
|
aj := makeQuoteResponse("LST_Q1", "LST_Q1_TRDREQ")
|
|
|
|
store := &MockPersistenceStore{}
|
|
store.On("GetTodayMessages").Return([]domain.TradeMessage{r, cw, aj}, nil)
|
|
|
|
m := newTestManager(store)
|
|
err := m.loadTrades()
|
|
|
|
require.NoError(t, err)
|
|
assert.Len(t, m.trades, 1)
|
|
assert.Equal(t, domain.TradeStatusActive, m.trades["LST_Q1"].Status)
|
|
store.AssertExpectations(t)
|
|
}
|
|
|
|
func TestLoadTrades_AJ_TRDEND_TradeRemains(t *testing.T) {
|
|
r := makeQuoteRequest("LST_Q1", "LIST_1", "RFQ", nil)
|
|
cw := makeQuoteAck("LST_Q1", "1")
|
|
aj := makeQuoteResponse("LST_Q1", "LST_Q1_TRDEND")
|
|
|
|
store := &MockPersistenceStore{}
|
|
store.On("GetTodayMessages").Return([]domain.TradeMessage{r, cw, aj}, nil)
|
|
|
|
m := newTestManager(store)
|
|
err := m.loadTrades()
|
|
|
|
require.NoError(t, err)
|
|
assert.Len(t, m.trades, 1)
|
|
assert.Equal(t, domain.TradeStatusActive, m.trades["LST_Q1"].Status)
|
|
store.AssertExpectations(t)
|
|
}
|
|
|
|
func TestLoadTrades_AJ_LISTEND_TradeRemains(t *testing.T) {
|
|
r := makeQuoteRequest("LST_Q1", "LIST_1", "RFQ", nil)
|
|
cw := makeQuoteAck("LST_Q1", "1")
|
|
aj := makeQuoteResponse("LST_Q1", "LST_Q1_LISTEND")
|
|
|
|
store := &MockPersistenceStore{}
|
|
store.On("GetTodayMessages").Return([]domain.TradeMessage{r, cw, aj}, nil)
|
|
|
|
m := newTestManager(store)
|
|
err := m.loadTrades()
|
|
|
|
require.NoError(t, err)
|
|
assert.Len(t, m.trades, 1)
|
|
assert.Equal(t, domain.TradeStatusActive, m.trades["LST_Q1"].Status)
|
|
store.AssertExpectations(t)
|
|
}
|
|
|
|
func TestLoadTrades_AJ_TRDSUMM_TradeMarkedCompleted(t *testing.T) {
|
|
r := makeQuoteRequest("LST_Q1", "LIST_1", "RFQ", nil)
|
|
cw := makeQuoteAck("LST_Q1", "1")
|
|
aj := makeQuoteResponse("LST_Q1", "LST_Q1_TRDSUMM")
|
|
|
|
store := &MockPersistenceStore{}
|
|
store.On("GetTodayMessages").Return([]domain.TradeMessage{r, cw, aj}, nil)
|
|
|
|
m := newTestManager(store)
|
|
err := m.loadTrades()
|
|
|
|
require.NoError(t, err)
|
|
assert.Len(t, m.trades, 1)
|
|
assert.Equal(t, domain.TradeStatusCompleted, m.trades["LST_Q1"].Status)
|
|
store.AssertExpectations(t)
|
|
}
|
|
|
|
func TestLoadTrades_AJ_TRDSUMM_WithoutPriorR_NoCrash(t *testing.T) {
|
|
aj := makeQuoteResponse("LST_ORPHAN", "LST_ORPHAN_TRDSUMM")
|
|
|
|
store := &MockPersistenceStore{}
|
|
store.On("GetTodayMessages").Return([]domain.TradeMessage{aj}, nil)
|
|
|
|
m := newTestManager(store)
|
|
err := m.loadTrades()
|
|
|
|
require.NoError(t, err)
|
|
assert.Len(t, m.trades, 0)
|
|
store.AssertExpectations(t)
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Group 5 — Interrupcion despues de ExecutionReport
|
|
// ---------------------------------------------------------------------------
|
|
|
|
func TestLoadTrades_ExecReport_LISTEND_TradeRemains(t *testing.T) {
|
|
r := makeQuoteRequest("LST_ABC123", "LIST_1", "RFQ", nil)
|
|
cw := makeQuoteAck("LST_ABC123", "1")
|
|
aj := makeQuoteResponse("LST_ABC123", "LST_ABC123_TRDREQ")
|
|
exec := makeExecutionReport("LST_ABC123", "LST_ABC123_LISTEND")
|
|
|
|
store := &MockPersistenceStore{}
|
|
store.On("GetTodayMessages").Return([]domain.TradeMessage{r, cw, aj, exec}, nil)
|
|
|
|
m := newTestManager(store)
|
|
err := m.loadTrades()
|
|
|
|
require.NoError(t, err)
|
|
assert.Len(t, m.trades, 1)
|
|
assert.Equal(t, domain.TradeStatusActive, m.trades["LST_ABC123"].Status)
|
|
store.AssertExpectations(t)
|
|
}
|
|
|
|
func TestLoadTrades_ExecReport_TRDEND_TradeRemains(t *testing.T) {
|
|
r := makeQuoteRequest("LST_ABC123", "LIST_1", "RFQ", nil)
|
|
cw := makeQuoteAck("LST_ABC123", "1")
|
|
aj := makeQuoteResponse("LST_ABC123", "LST_ABC123_TRDREQ")
|
|
execListEnd := makeExecutionReport("LST_ABC123", "LST_ABC123_LISTEND")
|
|
execTrdEnd := makeExecutionReport("LST_ABC123", "LST_ABC123_TRDEND")
|
|
|
|
store := &MockPersistenceStore{}
|
|
store.On("GetTodayMessages").Return([]domain.TradeMessage{r, cw, aj, execListEnd, execTrdEnd}, nil)
|
|
|
|
m := newTestManager(store)
|
|
err := m.loadTrades()
|
|
|
|
require.NoError(t, err)
|
|
assert.Len(t, m.trades, 1)
|
|
assert.Equal(t, domain.TradeStatusActive, m.trades["LST_ABC123"].Status)
|
|
store.AssertExpectations(t)
|
|
}
|
|
|
|
func TestLoadTrades_ExecReport_TRDSUMM_TradeMarkedCompleted(t *testing.T) {
|
|
r := makeQuoteRequest("LST_ABC123", "LIST_1", "RFQ", nil)
|
|
cw := makeQuoteAck("LST_ABC123", "1")
|
|
aj := makeQuoteResponse("LST_ABC123", "LST_ABC123_TRDREQ")
|
|
exec := makeExecutionReport("LST_ABC123", "LST_ABC123_TRDSUMM")
|
|
|
|
store := &MockPersistenceStore{}
|
|
store.On("GetTodayMessages").Return([]domain.TradeMessage{r, cw, aj, exec}, nil)
|
|
|
|
m := newTestManager(store)
|
|
err := m.loadTrades()
|
|
|
|
require.NoError(t, err)
|
|
assert.Len(t, m.trades, 1)
|
|
assert.Equal(t, domain.TradeStatusCompleted, m.trades["LST_ABC123"].Status)
|
|
store.AssertExpectations(t)
|
|
}
|
|
|
|
func TestLoadTrades_ExecReport_TRDSUMM_WithoutPriorR_NoCrash(t *testing.T) {
|
|
exec := makeExecutionReport("LST_ORPHAN", "LST_ORPHAN_TRDSUMM")
|
|
|
|
store := &MockPersistenceStore{}
|
|
store.On("GetTodayMessages").Return([]domain.TradeMessage{exec}, nil)
|
|
|
|
m := newTestManager(store)
|
|
err := m.loadTrades()
|
|
|
|
require.NoError(t, err)
|
|
assert.Len(t, m.trades, 0)
|
|
store.AssertExpectations(t)
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Group 6 — Multiples trades en paralelo
|
|
// ---------------------------------------------------------------------------
|
|
|
|
func TestLoadTrades_TwoTrades_BothActive(t *testing.T) {
|
|
r1 := makeQuoteRequest("LST_TRADE1", "LIST_1", "RFQ", nil)
|
|
r2 := makeQuoteRequest("LST_TRADE2", "LIST_2", "RFQ", nil)
|
|
cw1 := makeQuoteAck("LST_TRADE1", "1")
|
|
cw2 := makeQuoteAck("LST_TRADE2", "1")
|
|
|
|
store := &MockPersistenceStore{}
|
|
store.On("GetTodayMessages").Return([]domain.TradeMessage{r1, r2, cw1, cw2}, nil)
|
|
|
|
m := newTestManager(store)
|
|
err := m.loadTrades()
|
|
|
|
require.NoError(t, err)
|
|
assert.Len(t, m.trades, 2)
|
|
assert.Equal(t, domain.TradeStatusActive, m.trades["LST_TRADE1"].Status)
|
|
assert.Equal(t, domain.TradeStatusActive, m.trades["LST_TRADE2"].Status)
|
|
store.AssertExpectations(t)
|
|
}
|
|
|
|
func TestLoadTrades_TwoTrades_OneCompleted_OneActive(t *testing.T) {
|
|
// TRADE1: fully completed via flow 8.4 (_TRDSUMM execution report)
|
|
r1 := makeQuoteRequest("LST_TRADE1", "LIST_1", "RFQ", nil)
|
|
cw1 := makeQuoteAck("LST_TRADE1", "1")
|
|
aj1 := makeQuoteResponse("LST_TRADE1", "LST_TRADE1_TRDREQ")
|
|
exec1 := makeExecutionReport("LST_TRADE1", "LST_TRADE1_TRDSUMM")
|
|
|
|
// TRADE2: still active
|
|
r2 := makeQuoteRequest("LST_TRADE2", "LIST_2", "RFQ", nil)
|
|
cw2 := makeQuoteAck("LST_TRADE2", "1")
|
|
|
|
store := &MockPersistenceStore{}
|
|
store.On("GetTodayMessages").Return(
|
|
[]domain.TradeMessage{r1, cw1, aj1, exec1, r2, cw2}, nil,
|
|
)
|
|
|
|
m := newTestManager(store)
|
|
err := m.loadTrades()
|
|
|
|
require.NoError(t, err)
|
|
assert.Len(t, m.trades, 2)
|
|
assert.Equal(t, domain.TradeStatusCompleted, m.trades["LST_TRADE1"].Status)
|
|
assert.Equal(t, domain.TradeStatusActive, m.trades["LST_TRADE2"].Status)
|
|
store.AssertExpectations(t)
|
|
}
|
|
|
|
func TestLoadTrades_TwoTrades_BothCompleted(t *testing.T) {
|
|
r1 := makeQuoteRequest("LST_TRADE1", "LIST_1", "RFQ", nil)
|
|
cw1 := makeQuoteAck("LST_TRADE1", "1")
|
|
exec1 := makeExecutionReport("LST_TRADE1", "LST_TRADE1_TRDSUMM")
|
|
|
|
r2 := makeQuoteRequest("LST_TRADE2", "LIST_2", "RFQ", nil)
|
|
cw2 := makeQuoteAck("LST_TRADE2", "1")
|
|
exec2 := makeExecutionReport("LST_TRADE2", "LST_TRADE2_TRDSUMM")
|
|
|
|
store := &MockPersistenceStore{}
|
|
store.On("GetTodayMessages").Return(
|
|
[]domain.TradeMessage{r1, cw1, exec1, r2, cw2, exec2}, nil,
|
|
)
|
|
|
|
m := newTestManager(store)
|
|
err := m.loadTrades()
|
|
|
|
require.NoError(t, err)
|
|
assert.Len(t, m.trades, 2)
|
|
assert.Equal(t, domain.TradeStatusCompleted, m.trades["LST_TRADE1"].Status)
|
|
assert.Equal(t, domain.TradeStatusCompleted, m.trades["LST_TRADE2"].Status)
|
|
store.AssertExpectations(t)
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Group 7 — SessionID se asigna en onLogon (solo a trades activos)
|
|
// ---------------------------------------------------------------------------
|
|
|
|
func TestLoadTrades_SessionID_IsZeroAfterRecovery(t *testing.T) {
|
|
r := makeQuoteRequest("LST_X", "LIST_1", "RFQ", nil)
|
|
|
|
store := &MockPersistenceStore{}
|
|
store.On("GetTodayMessages").Return([]domain.TradeMessage{r}, nil)
|
|
|
|
m := newTestManager(store)
|
|
err := m.loadTrades()
|
|
|
|
require.NoError(t, err)
|
|
require.Len(t, m.trades, 1)
|
|
|
|
trade := m.trades["LST_X"]
|
|
require.NotNil(t, trade)
|
|
assert.Equal(t, quickfix.SessionID{}, trade.SessionID)
|
|
store.AssertExpectations(t)
|
|
}
|
|
|
|
func TestOnLogon_AssignsSessionToRecoveredActiveTrades(t *testing.T) {
|
|
r := makeQuoteRequest("LST_X", "LIST_1", "RFQ", nil)
|
|
|
|
store := &MockPersistenceStore{}
|
|
store.On("GetTodayMessages").Return([]domain.TradeMessage{r}, nil)
|
|
|
|
m := newTestManager(store)
|
|
require.NoError(t, m.loadTrades())
|
|
|
|
sessionID := quickfix.SessionID{
|
|
BeginString: "FIXT.1.1",
|
|
SenderCompID: "QFIXDPL",
|
|
TargetCompID: "TRADEWEB",
|
|
}
|
|
m.onLogon(sessionID)
|
|
|
|
trade := m.trades["LST_X"]
|
|
require.NotNil(t, trade)
|
|
assert.Equal(t, sessionID, trade.SessionID)
|
|
}
|
|
|
|
func TestOnLogon_DoesNotAssignSessionToCompletedTrades(t *testing.T) {
|
|
r := makeQuoteRequest("LST_X", "LIST_1", "RFQ", nil)
|
|
exec := makeExecutionReport("LST_X", "LST_X_TRDSUMM")
|
|
|
|
store := &MockPersistenceStore{}
|
|
store.On("GetTodayMessages").Return([]domain.TradeMessage{r, exec}, nil)
|
|
|
|
m := newTestManager(store)
|
|
require.NoError(t, m.loadTrades())
|
|
|
|
sessionID := quickfix.SessionID{
|
|
BeginString: "FIXT.1.1",
|
|
SenderCompID: "QFIXDPL",
|
|
TargetCompID: "TRADEWEB",
|
|
}
|
|
m.onLogon(sessionID)
|
|
|
|
trade := m.trades["LST_X"]
|
|
require.NotNil(t, trade)
|
|
assert.Equal(t, quickfix.SessionID{}, trade.SessionID, "completed trades should not get session assigned")
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Group 8 — GetTrades filtra por active, GetAllTrades devuelve todos
|
|
// ---------------------------------------------------------------------------
|
|
|
|
func TestGetTrades_ReturnsOnlyActive(t *testing.T) {
|
|
r1 := makeQuoteRequest("LST_ACTIVE", "LIST_1", "RFQ", nil)
|
|
r2 := makeQuoteRequest("LST_DONE", "LIST_2", "RFQ", nil)
|
|
exec := makeExecutionReport("LST_DONE", "LST_DONE_TRDSUMM")
|
|
r3 := makeQuoteRequest("LST_REJ", "LIST_3", "RFQ", nil)
|
|
cw := makeQuoteAck("LST_REJ", "2") // REJECTED
|
|
|
|
store := &MockPersistenceStore{}
|
|
store.On("GetTodayMessages").Return([]domain.TradeMessage{r1, r2, exec, r3, cw}, nil)
|
|
|
|
m := newTestManager(store)
|
|
require.NoError(t, m.loadTrades())
|
|
|
|
trades := m.GetTrades()
|
|
assert.Len(t, trades, 1)
|
|
assert.Equal(t, "LST_ACTIVE", trades[0].QuoteReqID)
|
|
assert.Equal(t, domain.TradeStatusActive, trades[0].Status)
|
|
}
|
|
|
|
func TestGetAllTrades_ReturnsAll(t *testing.T) {
|
|
r1 := makeQuoteRequest("LST_ACTIVE", "LIST_1", "RFQ", nil)
|
|
r2 := makeQuoteRequest("LST_DONE", "LIST_2", "RFQ", nil)
|
|
exec := makeExecutionReport("LST_DONE", "LST_DONE_TRDSUMM")
|
|
r3 := makeQuoteRequest("LST_REJ", "LIST_3", "RFQ", nil)
|
|
cw := makeQuoteAck("LST_REJ", "2") // REJECTED
|
|
|
|
store := &MockPersistenceStore{}
|
|
store.On("GetTodayMessages").Return([]domain.TradeMessage{r1, r2, exec, r3, cw}, nil)
|
|
|
|
m := newTestManager(store)
|
|
require.NoError(t, m.loadTrades())
|
|
|
|
trades := m.GetAllTrades()
|
|
assert.Len(t, trades, 3)
|
|
|
|
statusMap := map[string]domain.TradeStatus{}
|
|
for _, tr := range trades {
|
|
statusMap[tr.QuoteReqID] = tr.Status
|
|
}
|
|
|
|
assert.Equal(t, domain.TradeStatusActive, statusMap["LST_ACTIVE"])
|
|
assert.Equal(t, domain.TradeStatusCompleted, statusMap["LST_DONE"])
|
|
assert.Equal(t, domain.TradeStatusRejected, statusMap["LST_REJ"])
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Group 9 — Error en store
|
|
// ---------------------------------------------------------------------------
|
|
|
|
func TestLoadTrades_StoreError_ReturnsError_MapEmpty(t *testing.T) {
|
|
store := &MockPersistenceStore{}
|
|
store.On("GetTodayMessages").Return([]domain.TradeMessage(nil), errors.New("db connection failed"))
|
|
|
|
m := newTestManager(store)
|
|
err := m.loadTrades()
|
|
|
|
assert.Error(t, err)
|
|
assert.NotNil(t, m.trades)
|
|
assert.Len(t, m.trades, 0)
|
|
store.AssertExpectations(t)
|
|
}
|