package parsers import ( "context" "math/big" "testing" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" mevtypes "github.com/your-org/mev-bot/pkg/types" ) // mockParser is a mock implementation of Parser for testing type mockParser struct { protocol mevtypes.ProtocolType supportsLog func(types.Log) bool parseLog func(context.Context, types.Log, *types.Transaction) (*mevtypes.SwapEvent, error) parseReceipt func(context.Context, *types.Receipt, *types.Transaction) ([]*mevtypes.SwapEvent, error) } func (m *mockParser) ParseLog(ctx context.Context, log types.Log, tx *types.Transaction) (*mevtypes.SwapEvent, error) { if m.parseLog != nil { return m.parseLog(ctx, log, tx) } return nil, nil } func (m *mockParser) ParseReceipt(ctx context.Context, receipt *types.Receipt, tx *types.Transaction) ([]*mevtypes.SwapEvent, error) { if m.parseReceipt != nil { return m.parseReceipt(ctx, receipt, tx) } return nil, nil } func (m *mockParser) SupportsLog(log types.Log) bool { if m.supportsLog != nil { return m.supportsLog(log) } return false } func (m *mockParser) Protocol() mevtypes.ProtocolType { return m.protocol } func TestNewFactory(t *testing.T) { factory := NewFactory() if factory == nil { t.Fatal("NewFactory returned nil") } } func TestFactory_RegisterParser(t *testing.T) { tests := []struct { name string protocol mevtypes.ProtocolType parser Parser wantErr bool errString string }{ { name: "valid registration", protocol: mevtypes.ProtocolUniswapV2, parser: &mockParser{protocol: mevtypes.ProtocolUniswapV2}, wantErr: false, }, { name: "unknown protocol", protocol: mevtypes.ProtocolUnknown, parser: &mockParser{protocol: mevtypes.ProtocolUnknown}, wantErr: true, errString: "cannot register parser for unknown protocol", }, { name: "nil parser", protocol: mevtypes.ProtocolUniswapV2, parser: nil, wantErr: true, errString: "parser cannot be nil", }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { factory := NewFactory() err := factory.RegisterParser(tt.protocol, tt.parser) if tt.wantErr { if err == nil { t.Errorf("RegisterParser() expected error, got nil") return } if tt.errString != "" && err.Error() != tt.errString { t.Errorf("RegisterParser() error = %v, want %v", err.Error(), tt.errString) } } else { if err != nil { t.Errorf("RegisterParser() unexpected error: %v", err) } } }) } } func TestFactory_RegisterParser_Duplicate(t *testing.T) { factory := NewFactory() parser := &mockParser{protocol: mevtypes.ProtocolUniswapV2} // First registration should succeed err := factory.RegisterParser(mevtypes.ProtocolUniswapV2, parser) if err != nil { t.Fatalf("First RegisterParser() failed: %v", err) } // Second registration should fail err = factory.RegisterParser(mevtypes.ProtocolUniswapV2, parser) if err == nil { t.Error("RegisterParser() expected error for duplicate registration, got nil") } } func TestFactory_GetParser(t *testing.T) { factory := NewFactory() parser := &mockParser{protocol: mevtypes.ProtocolUniswapV2} // Register parser err := factory.RegisterParser(mevtypes.ProtocolUniswapV2, parser) if err != nil { t.Fatalf("RegisterParser() failed: %v", err) } tests := []struct { name string protocol mevtypes.ProtocolType wantErr bool }{ { name: "get registered parser", protocol: mevtypes.ProtocolUniswapV2, wantErr: false, }, { name: "get unregistered parser", protocol: mevtypes.ProtocolUniswapV3, wantErr: true, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got, err := factory.GetParser(tt.protocol) if tt.wantErr { if err == nil { t.Error("GetParser() expected error, got nil") } if got != nil { t.Error("GetParser() expected nil parser on error") } } else { if err != nil { t.Errorf("GetParser() unexpected error: %v", err) } if got == nil { t.Error("GetParser() returned nil parser") } } }) } } func TestFactory_ParseLog(t *testing.T) { ctx := context.Background() // Create test log testLog := types.Log{ Address: common.HexToAddress("0x1234"), Topics: []common.Hash{common.HexToHash("0xabcd")}, Data: []byte{}, } testTx := types.NewTransaction( 0, common.HexToAddress("0x1234"), big.NewInt(0), 21000, big.NewInt(1000000000), nil, ) tests := []struct { name string setupFactory func() Factory log types.Log tx *types.Transaction wantErr bool wantEvent bool }{ { name: "parser supports log", setupFactory: func() Factory { f := NewFactory() parser := &mockParser{ protocol: mevtypes.ProtocolUniswapV2, supportsLog: func(log types.Log) bool { return true }, parseLog: func(ctx context.Context, log types.Log, tx *types.Transaction) (*mevtypes.SwapEvent, error) { return &mevtypes.SwapEvent{ Protocol: mevtypes.ProtocolUniswapV2, }, nil }, } f.RegisterParser(mevtypes.ProtocolUniswapV2, parser) return f }, log: testLog, tx: testTx, wantErr: false, wantEvent: true, }, { name: "no parser supports log", setupFactory: func() Factory { f := NewFactory() parser := &mockParser{ protocol: mevtypes.ProtocolUniswapV2, supportsLog: func(log types.Log) bool { return false }, } f.RegisterParser(mevtypes.ProtocolUniswapV2, parser) return f }, log: testLog, tx: testTx, wantErr: true, wantEvent: false, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { factory := tt.setupFactory() event, err := factory.ParseLog(ctx, tt.log, tt.tx) if tt.wantErr { if err == nil { t.Error("ParseLog() expected error, got nil") } } else { if err != nil { t.Errorf("ParseLog() unexpected error: %v", err) } } if tt.wantEvent { if event == nil { t.Error("ParseLog() expected event, got nil") } } else { if event != nil && !tt.wantErr { t.Error("ParseLog() expected nil event") } } }) } } func TestFactory_ParseTransaction(t *testing.T) { ctx := context.Background() testTx := types.NewTransaction( 0, common.HexToAddress("0x1234"), big.NewInt(0), 21000, big.NewInt(1000000000), nil, ) testLog := &types.Log{ Address: common.HexToAddress("0x1234"), Topics: []common.Hash{common.HexToHash("0xabcd")}, Data: []byte{}, } testReceipt := &types.Receipt{ Logs: []*types.Log{testLog}, } tests := []struct { name string setupFactory func() Factory tx *types.Transaction receipt *types.Receipt wantErr bool wantEvents int }{ { name: "parse transaction with events", setupFactory: func() Factory { f := NewFactory() parser := &mockParser{ protocol: mevtypes.ProtocolUniswapV2, supportsLog: func(log types.Log) bool { return true }, parseLog: func(ctx context.Context, log types.Log, tx *types.Transaction) (*mevtypes.SwapEvent, error) { return &mevtypes.SwapEvent{ Protocol: mevtypes.ProtocolUniswapV2, }, nil }, } f.RegisterParser(mevtypes.ProtocolUniswapV2, parser) return f }, tx: testTx, receipt: testReceipt, wantErr: false, wantEvents: 1, }, { name: "parse transaction with no matching parsers", setupFactory: func() Factory { f := NewFactory() parser := &mockParser{ protocol: mevtypes.ProtocolUniswapV2, supportsLog: func(log types.Log) bool { return false }, } f.RegisterParser(mevtypes.ProtocolUniswapV2, parser) return f }, tx: testTx, receipt: testReceipt, wantErr: false, wantEvents: 0, }, { name: "nil receipt", setupFactory: func() Factory { return NewFactory() }, tx: testTx, receipt: nil, wantErr: true, wantEvents: 0, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { factory := tt.setupFactory() events, err := factory.ParseTransaction(ctx, tt.tx, tt.receipt) if tt.wantErr { if err == nil { t.Error("ParseTransaction() expected error, got nil") } } else { if err != nil { t.Errorf("ParseTransaction() unexpected error: %v", err) } } if len(events) != tt.wantEvents { t.Errorf("ParseTransaction() got %d events, want %d", len(events), tt.wantEvents) } }) } } func TestFactory_ConcurrentAccess(t *testing.T) { factory := NewFactory() // Test concurrent registration done := make(chan bool) for i := 0; i < 10; i++ { go func(n int) { protocol := mevtypes.ProtocolType(fmt.Sprintf("protocol-%d", n)) parser := &mockParser{protocol: protocol} factory.RegisterParser(protocol, parser) done <- true }(i) } for i := 0; i < 10; i++ { <-done } // Test concurrent reads for i := 0; i < 10; i++ { go func(n int) { protocol := mevtypes.ProtocolType(fmt.Sprintf("protocol-%d", n)) factory.GetParser(protocol) done <- true }(i) } for i := 0; i < 10; i++ { <-done } }