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()
Console.WriteLine("š Discovering and Running Tests...\n");
int totalPassed = 0, totalFailed = 0;
var testClasses = Assembly.GetExecutingAssembly()
.Where(t => t.GetMethods(BindingFlags.Instance | BindingFlags.Public)
.Any(m => m.GetCustomAttribute<FactAttribute>() != null ||
m.GetCustomAttribute<TheoryAttribute>() != null))
foreach (var testClass in testClasses)
Console.WriteLine($"š Running tests in: {testClass.Name}");
(int passed, int failed) = await RunTests(testClass);
Console.WriteLine("\nš FINAL SUMMARY:");
Console.WriteLine($"ā
Total Passed: {totalPassed}");
Console.WriteLine($"ā Total Failed: {totalFailed}");
Console.WriteLine($"š Total Tests: {totalPassed + totalFailed}");
Console.WriteLine("\nā
Test execution completed.");
private static async Task<(int passed, int failed)> RunTests(Type testClassType)
object testInstance = Activator.CreateInstance(testClassType);
var testMethods = testClassType.GetMethods(BindingFlags.Instance | BindingFlags.Public)
.Where(m => m.GetCustomAttribute<FactAttribute>() != null ||
m.GetCustomAttribute<TheoryAttribute>() != null)
int passed = 0, failed = 0;
foreach (var method in testMethods)
var result = method.Invoke(testInstance, method.GetParameters().Length == 0 ? null : new object[method.GetParameters().Length]);
if (result is Task task) await task;
Console.WriteLine($" ā
{method.Name} PASSED");
catch (TargetInvocationException ex)
Console.WriteLine($" ā {method.Name} FAILED: {ex.InnerException?.Message ?? ex.Message}");
Console.WriteLine($" ā ļø {method.Name} ERROR: {ex.Message}");
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);