using System.Collections.Generic;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
public string Name { get; set; }
public string ShortName { get; set; }
public MarketType MarketType { get; set; }
public List<SpecifierValue> SpecifierValues { get; set; } = new List<SpecifierValue>();
public string ShortNamePattern { get; set; }
public string NamePattern { get; set; }
public class SpecifierValue
public string Name { get; set; }
public string Value { get; set; }
public class SignalRMessageSetting
public bool GenerateShortNamesInMultiLive { get; set; }
public bool EnableMarketsSyncStateSlim { get; set; }
public interface ISettingService
Task<SignalRMessageSetting> GetSignalRMessageSettingAsync();
public interface IEventNotificationService
Task SendMarketsAsync(List<Market> markets, Guid eventId, bool enableMarketsSyncStateSlim);
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 static class NameUtils
public static string GenerateShortNameFromNamePattern(string pattern, List<string> teams, List<string> players, List<string> roster, List<SpecifierValue> specifierValues)
if (string.IsNullOrWhiteSpace(pattern))
if(teams != null) shortName += string.Join("", teams);
if(players != null) shortName += string.Join("", players);
if(roster != null) shortName += string.Join("", roster);
if(specifierValues != null) shortName += string.Join("", specifierValues.Select(sv => sv.Name + sv.Value));
return shortName.GetHashCode().ToString();
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(List<Market> markets)
_logger.LogWarning("SendSignalRMarketsInternalAsync called with null markets list.");
SignalRMessageSetting setting = await _settingService.GetSignalRMessageSettingAsync();
if (_isInMultiLive && setting.GenerateShortNamesInMultiLive)
foreach (var market in markets.Where(m => string.IsNullOrWhiteSpace(m.ShortName)))
string pattern = !string.IsNullOrWhiteSpace(market.MarketType?.ShortNamePattern)
? market.MarketType.ShortNamePattern
: market.MarketType?.NamePattern;
if (string.IsNullOrWhiteSpace(pattern))
_logger.LogWarning("Market {MarketName} has no ShortNamePattern or NamePattern.", market.Name);
market.ShortName = market.Name;
market.ShortName = NameUtils.GenerateShortNameFromNamePattern(
catch (ArgumentNullException ex)
_logger.LogError(ex, "ArgumentNullException during short name generation for market {MarketName}.", market.Name);
market.ShortName = market.Name;
catch (InvalidOperationException ex)
_logger.LogError(ex, "InvalidOperationException during short name generation for market {MarketName}.", market.Name);
market.ShortName = market.Name;
_logger.LogError(ex, "Unexpected error during short name generation for market {MarketName}.", market.Name);
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 MockSettingService : ISettingService
public Task<SignalRMessageSetting> GetSignalRMessageSettingAsync()
return Task.FromResult(new SignalRMessageSetting { GenerateShortNamesInMultiLive = true, EnableMarketsSyncStateSlim = false });
public class MockEventNotificationService : IEventNotificationService
public List<Market> SentMarkets { get; private set; } = new List<Market>();
public Guid LastEventId { get; private set; }
public bool LastEnableMarketsSyncStateSlim { get; private set; }
public Task SendMarketsAsync(List<Market> markets, Guid eventId, bool enableMarketsSyncStateSlim)
SentMarkets.AddRange(markets);
LastEnableMarketsSyncStateSlim = enableMarketsSyncStateSlim;
return Task.CompletedTask;
public class MockLogger : ILogger
public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter)
Console.WriteLine($"[{logLevel}] {formatter(state, exception)}");
public bool IsEnabled(LogLevel logLevel)
public IDisposable BeginScope<TState>(TState state)
public void LogError(Exception exception, string v, string name)
Console.WriteLine($"[ERROR] {v} {name} Exception details: {exception.Message}");
public void LogWarning(string v, string name)
Console.WriteLine($"[WARNING] {v} {name} Exception details");
public void LogInformation(string message)
Console.WriteLine($"[INFO] {message}");
public class MarketDataProcessorTests
public async Task SendSignalRMarketsInternalAsync_NullMarkets_LogsWarning()
var mockSettingService = new Mock<ISettingService>();
var mockEventNotificationService = new Mock<IEventNotificationService>();
var mockLogger = new Mock<ILogger>();
var processor = new MarketDataProcessor(mockSettingService.Object, mockEventNotificationService.Object, mockLogger.Object, false, Guid.NewGuid(), new EventData());
await processor.SendSignalRMarketsInternalAsync(null);
It.Is<It.IsAnyType>((v, t) => v.ToString().Contains("called with null markets list")),
It.IsAny<Func<It.IsAnyType, Exception, string>>()
public async Task SendSignalRMarketsInternalAsync_NotInMultiLive_DoesNotGenerateShortNames()
var mockSettingService = new Mock<ISettingService>();
mockSettingService.Setup(s => s.GetSignalRMessageSettingAsync()).ReturnsAsync(new SignalRMessageSetting { GenerateShortNamesInMultiLive = true });
var mockEventNotificationService = new Mock<IEventNotificationService>();
var mockLogger = new Mock<ILogger>();
var eventData = new EventData();
var processor = new MarketDataProcessor(mockSettingService.Object, mockEventNotificationService.Object, mockLogger.Object, false, Guid.NewGuid(), eventData);
var markets = new List<Market> { new Market { Name = "Market 1", MarketType = new MarketType { ShortNamePattern = "Pattern1" } } };
await processor.SendSignalRMarketsInternalAsync(markets);
Assert.Null(markets[0].ShortName);
mockEventNotificationService.Verify(s => s.SendMarketsAsync(markets, It.IsAny<Guid>(), It.IsAny<bool>()), Times.Once);
public async Task SendSignalRMarketsInternalAsync_GenerateShortNamesDisabled_DoesNotGenerateShortNames()
var mockSettingService = new Mock<ISettingService>();
mockSettingService.Setup(s => s.GetSignalRMessageSettingAsync()).ReturnsAsync(new SignalRMessageSetting { GenerateShortNamesInMultiLive = false });
var mockEventNotificationService = new Mock<IEventNotificationService>();
var mockLogger = new Mock<ILogger>();
var eventData = new EventData();
var processor = new MarketDataProcessor(mockSettingService.Object, mockEventNotificationService.Object, mockLogger.Object, true, Guid.NewGuid(), eventData);
var markets = new List<Market> { new Market { Name = "Market 1", MarketType = new MarketType { ShortNamePattern = "Pattern1" } } };
await processor.SendSignalRMarketsInternalAsync(markets);
Assert.Null(markets[0].ShortName);
mockEventNotificationService.Verify(s => s.SendMarketsAsync(markets, It.IsAny<Guid>(), It.IsAny<bool>()), Times.Once);
public async Task SendSignalRMarketsInternalAsync_GeneratesShortNames_WhenEnabledAndInMultiLive()
var mockSettingService = new Mock<ISettingService>();
mockSettingService.Setup(s => s.GetSignalRMessageSettingAsync()).ReturnsAsync(new SignalRMessageSetting { GenerateShortNamesInMultiLive = true });
var mockEventNotificationService = new Mock<IEventNotificationService>();
var mockLogger = new Mock<ILogger>();
var eventData = new EventData();
var processor = new MarketDataProcessor(mockSettingService.Object, mockEventNotificationService.Object, mockLogger.Object, true, Guid.NewGuid(), eventData);
var markets = new List<Market> { new Market { Name = "Market 1", MarketType = new MarketType { ShortNamePattern = "Pattern1" } } };
await processor.SendSignalRMarketsInternalAsync(markets);
Assert.NotNull(markets[0].ShortName);
Assert.NotEqual(string.Empty, markets[0].ShortName);
mockEventNotificationService.Verify(s => s.SendMarketsAsync(markets, It.IsAny<Guid>(), It.IsAny<bool>()), Times.Once);
public async Task SendSignalRMarketsInternalAsync_UsesNamePattern_WhenShortNamePatternIsNull()
var mockSettingService = new Mock<ISettingService>();
mockSettingService.Setup(s => s.GetSignalRMessageSettingAsync()).ReturnsAsync(new SignalRMessageSetting { GenerateShortNamesInMultiLive = true });
var mockEventNotificationService = new Mock<IEventNotificationService>();
var mockLogger = new Mock<ILogger>();
var eventData = new EventData();
var processor = new MarketDataProcessor(mockSettingService.Object, mockEventNotificationService.Object, mockLogger.Object, true, Guid.NewGuid(), eventData);
var markets = new List<Market> { new Market { Name = "Market 1", MarketType = new MarketType { ShortNamePattern = null, NamePattern = "NamePattern1" } } };
await processor.SendSignalRMarketsInternalAsync(markets);
Assert.NotNull(markets[0].ShortName);
Assert.NotEqual(string.Empty, markets[0].ShortName);
mockEventNotificationService.Verify(s => s.SendMarketsAsync(markets, It.IsAny<Guid>(), It.IsAny<bool>()), Times.Once);
public async Task SendSignalRMarketsInternalAsync_UsesName_WhenBothPatternsAreNullOrWhitespace()
var mockSettingService = new Mock<ISettingService>();
mockSettingService.Setup(s => s.GetSignalRMessageSettingAsync()).ReturnsAsync(new SignalRMessageSetting { GenerateShortNamesInMultiLive = true });
var mockEventNotificationService = new Mock<IEventNotificationService>();
var mockLogger = new Mock<ILogger>();
var eventData = new EventData();
var processor = new MarketDataProcessor(mockSettingService.Object, mockEventNotificationService.Object, mockLogger.Object, true, Guid.NewGuid(), eventData);
var markets = new List<Market> { new Market { Name = "Market 1", MarketType = new MarketType { ShortNamePattern = "", NamePattern = null } } };
await processor.SendSignalRMarketsInternalAsync(markets);
Assert.Equal("Market 1", markets[0].ShortName);
It.Is<It.IsAnyType>((v, t) => v.ToString().Contains("has no ShortNamePattern or NamePattern")),
It.IsAny<Func<It.IsAnyType, Exception, string>>()
mockEventNotificationService.Verify(s => s.SendMarketsAsync(markets, It.IsAny<Guid>(), It.IsAny<bool>()), Times.Once);
public async Task SendSignalRMarketsInternalAsync_HandlesException_DuringShortNameGeneration()
var mockSettingService = new Mock<ISettingService>();
mockSettingService.Setup(s => s.GetSignalRMessageSettingAsync()).ReturnsAsync(new SignalRMessageSetting { GenerateShortNamesInMultiLive = true });
var mockEventNotificationService = new Mock<IEventNotificationService>();
var mockLogger = new Mock<ILogger>();
var throwingMarket = new Market { Name = "MarketWillThrow", MarketType = new MarketType { ShortNamePattern = "ThrowExceptionPattern" } };
var markets = new List<Market> { throwingMarket };
var eventData = new EventData();
var processor = new MarketDataProcessor(mockSettingService.Object, mockEventNotificationService.Object, mockLogger.Object, true, Guid.NewGuid(), eventData);
await processor.SendSignalRMarketsInternalAsync(markets);
Assert.Equal("MarketWillThrow", throwingMarket.ShortName);
It.Is<LogLevel>(l => l == LogLevel.Error),
It.Is<It.IsAnyType>((v, t) => v.ToString().Contains("Unexpected error during short name generation for market")),
It.IsAny<Func<It.IsAnyType, Exception, string>>()
mockEventNotificationService.Verify(s => s.SendMarketsAsync(markets, It.IsAny<Guid>(), It.IsAny<bool>()), Times.Once);
public async Task SendSignalRMarketsInternalAsync_SendsCorrectData_ToEventNotificationService()
var mockSettingService = new Mock<ISettingService>();
var expectedSetting = new SignalRMessageSetting { GenerateShortNamesInMultiLive = false, EnableMarketsSyncStateSlim = true };
mockSettingService.Setup(s => s.GetSignalRMessageSettingAsync()).ReturnsAsync(expectedSetting);
var mockEventNotificationService = new Mock<IEventNotificationService>();
var mockLogger = new Mock<ILogger>();
var eventId = Guid.NewGuid();
var eventData = new EventData();
var processor = new MarketDataProcessor(mockSettingService.Object, mockEventNotificationService.Object, mockLogger.Object, true, eventId, eventData);
var markets = new List<Market> { new Market { Name = "Market 1" } };
await processor.SendSignalRMarketsInternalAsync(markets);
mockEventNotificationService.Verify(s => s.SendMarketsAsync(markets, eventId, expectedSetting.EnableMarketsSyncStateSlim), Times.Once);
public async Task SendSignalRMarketsInternalAsync_HandlesEmptyMarketList_DoesNotThrow()
var mockSettingService = new Mock<ISettingService>();
var mockEventNotificationService = new Mock<IEventNotificationService>();
var mockLogger = new Mock<ILogger>();
var processor = new MarketDataProcessor(mockSettingService.Object, mockEventNotificationService.Object, mockLogger.Object, true, Guid.NewGuid(), new EventData());
await processor.SendSignalRMarketsInternalAsync(new List<Market>());