using Microsoft.Extensions.Logging;
using System.Collections.Generic;
using System.Threading.Tasks;
public string Name { get; set; }
public string ShortName { get; set; }
public MarketType MarketType { get; set; }
public Dictionary<string, string> SpecifierValues { get; set; }
public string ShortNamePattern { get; set; }
public string NamePattern { get; set; }
public class SignalRMessageSetting
public bool EnableShortNameInMultiLive { get; set; }
public bool EnableMarketsSyncStateSlim { get; set; }
public interface ISettingService
Task<SignalRMessageSetting> GetSignalRMessageSettingAsync();
public interface IEventNotificationService
Task SendMarketsAsync(IEnumerable<Market> markets, Guid eventId, bool enableMarketsSyncStateSlim);
public static class NameUtils
public static string GenerateShortNameFromNamePattern(string pattern, List<string> teams, List<string> players, List<string> roster, Dictionary<string, string> specifierValues)
if (string.IsNullOrWhiteSpace(pattern))
throw new ArgumentException("Pattern cannot be null or whitespace.", nameof(pattern));
return pattern + "_short";
public List<string> Teams { get; set; } = new List<string>();
public List<string> Players { get; set; } = new List<string>();
public List<string> Roster { get; set; } = new List<string>();
public class MarketDataProcessor
private readonly ISettingService _settingService;
private readonly IEventNotificationService _eventNotificationService;
private readonly ILogger _logger;
private readonly bool _isInMultiLive;
private readonly Guid _eventId;
private readonly EventData _event;
public MarketDataProcessor(ISettingService settingService, IEventNotificationService eventNotificationService, ILogger logger, bool isInMultiLive, Guid eventId, EventData @event)
_settingService = settingService ?? throw new ArgumentNullException(nameof(settingService));
_eventNotificationService = eventNotificationService ?? throw new ArgumentNullException(nameof(eventNotificationService));
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
_isInMultiLive = isInMultiLive;
_event = @event ?? throw new ArgumentNullException(nameof(@event));
public async Task SendSignalRMarketsInternalAsync(IEnumerable<Market> markets)
throw new ArgumentNullException(nameof(markets));
var setting = await _settingService.GetSignalRMessageSettingAsync();
if (_isInMultiLive && setting.EnableShortNameInMultiLive)
foreach (var market in markets)
if (string.IsNullOrWhiteSpace(market.ShortName))
string pattern = string.IsNullOrWhiteSpace(market.MarketType?.ShortNamePattern) ? market.MarketType?.NamePattern : market.MarketType?.ShortNamePattern;
if (string.IsNullOrWhiteSpace(pattern))
throw new InvalidOperationException($"Both ShortNamePattern and NamePattern are null or whitespace for market type: {market.MarketType}");
market.ShortName = NameUtils.GenerateShortNameFromNamePattern(pattern, _event.Teams, _event.Players, _event.Roster, market.SpecifierValues);
catch (ArgumentException ex)
_logger.LogError(ex, "Error generating short name for market {MarketName}: {ErrorMessage}", market.Name, ex.Message);
market.ShortName = market.Name;
catch (InvalidOperationException ex)
_logger.LogError(ex, "Error generating short name for market {MarketName}: {ErrorMessage}", market.Name, ex.Message);
market.ShortName = market.Name;
_logger.LogError(ex, "Unexpected error generating short name for market {MarketName}: {ErrorMessage}", market.Name, ex.Message);
market.ShortName = market.Name;
await _eventNotificationService.SendMarketsAsync(markets, _eventId, setting.EnableMarketsSyncStateSlim);
public static async Task Main(string[] args)
var markets = new List<Market>
new Market { Name = "Market 1", MarketType = new MarketType { ShortNamePattern = "Pattern1" }, SpecifierValues = new Dictionary<string, string>() },
new Market { Name = "Market 2", ShortName = "SME", MarketType = new MarketType { ShortNamePattern = "Pattern2" } , SpecifierValues = new Dictionary<string, string>()}
var setting = new SignalRMessageSetting { EnableShortNameInMultiLive = true, EnableMarketsSyncStateSlim = false };
var settingServiceMock = new Mock<ISettingService>();
settingServiceMock.Setup(s => s.GetSignalRMessageSettingAsync()).ReturnsAsync(setting);
var eventNotificationServiceMock = new MockEventNotificationService();
var loggerMock = new Mock<ILogger>();
var eventData = new EventData();
var processor = new MarketDataProcessor(settingServiceMock.Object, eventNotificationServiceMock, loggerMock.Object, true, Guid.NewGuid(), eventData);
await processor.SendSignalRMarketsInternalAsync(markets);
Console.WriteLine("Markets processed.");
public class MockEventNotificationService : IEventNotificationService
public async Task SendMarketsAsync(IEnumerable<Market> markets, Guid eventId, bool enableMarketsSyncStateSlim)
Console.WriteLine($"Sending {markets.Count()} markets for event {eventId}. Slim state: {enableMarketsSyncStateSlim}");
foreach(var m in markets)
Console.WriteLine($"Market: Name = {m.Name}, ShortName = {m.ShortName}");
await Task.CompletedTask;
public class MarketDataProcessorTests
public async Task SendSignalRMarketsInternalAsync_NullMarkets_ThrowsArgumentNullException()
var settingServiceMock = new Mock<ISettingService>();
var eventNotificationServiceMock = new Mock<IEventNotificationService>();
var loggerMock = new Mock<ILogger>();
var processor = new MarketDataProcessor(settingServiceMock.Object, eventNotificationServiceMock.Object, loggerMock.Object, false, Guid.NewGuid(), new EventData());
await Assert.ThrowsAsync<ArgumentNullException>(() => processor.SendSignalRMarketsInternalAsync(null));
public async Task SendSignalRMarketsInternalAsync_NotInMultiLive_DoesNotGenerateShortNames()
var markets = new List<Market> { new Market { Name = "Market 1" } };
var setting = new SignalRMessageSetting { EnableShortNameInMultiLive = false, EnableMarketsSyncStateSlim = true };
var settingServiceMock = new Mock<ISettingService>();
settingServiceMock.Setup(s => s.GetSignalRMessageSettingAsync()).ReturnsAsync(setting);
var eventNotificationServiceMock = new Mock<IEventNotificationService>();
var loggerMock = new Mock<ILogger>();
var processor = new MarketDataProcessor(settingServiceMock.Object, eventNotificationServiceMock.Object, loggerMock.Object, false, Guid.NewGuid(), new EventData());
await processor.SendSignalRMarketsInternalAsync(markets);
Assert.Equal("Market 1", markets[0].Name);
Assert.Null(markets[0].ShortName);
eventNotificationServiceMock.Verify(e => e.SendMarketsAsync(markets, It.IsAny<Guid>(), true), Times.Once);
public async Task SendSignalRMarketsInternalAsync_ShortNamesDisabled_DoesNotGenerateShortNames()
var markets = new List<Market> { new Market { Name = "Market 1" } };
var setting = new SignalRMessageSetting { EnableShortNameInMultiLive = false, EnableMarketsSyncStateSlim = true };
var settingServiceMock = new Mock<ISettingService>();
settingServiceMock.Setup(s => s.GetSignalRMessageSettingAsync()).ReturnsAsync(setting);
var eventNotificationServiceMock = new Mock<IEventNotificationService>();
var loggerMock = new Mock<ILogger>();
var processor = new MarketDataProcessor(settingServiceMock.Object, eventNotificationServiceMock.Object, loggerMock.Object, true, Guid.NewGuid(), new EventData());
await processor.SendSignalRMarketsInternalAsync(markets);
Assert.Equal("Market 1", markets[0].Name);
Assert.Null(markets[0].ShortName);
eventNotificationServiceMock.Verify(e => e.SendMarketsAsync(markets, It.IsAny<Guid>(), true), Times.Once);
public async Task SendSignalRMarketsInternalAsync_GeneratesShortNames_WhenEnabledAndInMultiLive()
var markets = new List<Market>
new Market { Name = "Market 1", MarketType = new MarketType { ShortNamePattern = "Pattern1" }, SpecifierValues = new Dictionary<string, string>() },
new Market { Name = "Market 2", ShortName = "ExistingShortName", MarketType = new MarketType { ShortNamePattern = "Pattern2" } , SpecifierValues = new Dictionary<string, string>()}
var setting = new SignalRMessageSetting { EnableShortNameInMultiLive = true, EnableMarketsSyncStateSlim = false };
var settingServiceMock = new Mock<ISettingService>();
settingServiceMock.Setup(s => s.GetSignalRMessageSettingAsync()).ReturnsAsync(setting);
var eventNotificationServiceMock = new Mock<IEventNotificationService>();
var loggerMock = new Mock<ILogger>();
var eventData = new EventData();
var processor = new MarketDataProcessor(settingServiceMock.Object, eventNotificationServiceMock.Object, loggerMock.Object, true, Guid.NewGuid(), eventData);
await processor.SendSignalRMarketsInternalAsync(markets);
Assert.Equal("Pattern1_short", markets[0].ShortName);
Assert.Equal("ExistingShortName", markets[1].ShortName);
eventNotificationServiceMock.Verify(e => e.SendMarketsAsync(markets, It.IsAny<Guid>(), false), Times.Once);
public async Task SendSignalRMarketsInternalAsync_UsesNamePattern_WhenShortNamePatternIsNull()
var markets = new List<Market>
new Market { Name = "Market 1", MarketType = new MarketType { ShortNamePattern = null, NamePattern = "NamePattern1" }, SpecifierValues = new Dictionary<string, string>() },
var setting = new SignalRMessageSetting { EnableShortNameInMultiLive = true, EnableMarketsSyncStateSlim = false };
var settingServiceMock = new Mock<ISettingService>();
settingServiceMock.Setup(s => s.GetSignalRMessageSettingAsync()).ReturnsAsync(setting);
var eventNotificationServiceMock = new Mock<IEventNotificationService>();
var loggerMock = new Mock<ILogger>();
var eventData = new EventData();
var processor = new MarketDataProcessor(settingServiceMock.Object, eventNotificationServiceMock.Object, loggerMock.Object, true, Guid.NewGuid(), eventData);
await processor.SendSignalRMarketsInternalAsync(markets);
Assert.Equal("NamePattern1_short", markets[0].ShortName);
eventNotificationServiceMock.Verify(e => e.SendMarketsAsync(markets, It.IsAny<Guid>(), false), Times.Once);
public async Task SendSignalRMarketsInternalAsync_LogsErrorAndUsesFullName_WhenExceptionOccurs()
var markets = new List<Market>
new Market { Name = "Market 1", MarketType = new MarketType { ShortNamePattern = "" }, SpecifierValues = new Dictionary<string, string>() },
var setting = new SignalRMessageSetting { EnableShortNameInMultiLive = true, EnableMarketsSyncStateSlim = true };
var settingServiceMock = new Mock<ISettingService>();
settingServiceMock.Setup(s => s.GetSignalRMessageSettingAsync()).ReturnsAsync(setting);
var eventNotificationServiceMock = new Mock<IEventNotificationService>();
var loggerMock = new Mock<ILogger>();
var eventData = new EventData();
var processor = new MarketDataProcessor(settingServiceMock.Object, eventNotificationServiceMock.Object, loggerMock.Object, true, Guid.NewGuid(), eventData);
await processor.SendSignalRMarketsInternalAsync(markets);
Assert.Equal("Market 1", markets[0].ShortName);
loggerMock.Verify(l => l.Log(
It.Is<It.IsAnyType>((v, t) => v.ToString().Contains("Error generating short name for market Market 1")),
It.IsAny<Func<It.IsAnyType, Exception, string>>()),
eventNotificationServiceMock.Verify(e => e.SendMarketsAsync(markets, It.IsAny<Guid>(), true), Times.Once);