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 Dictionary<string, string> SpecifierValues { get; set; }
public string ShortNamePattern { get; set; }
public string NamePattern { get; set; }
public class SignalRMessageSetting
public bool EnableMarketsShortNameInMultiLive { 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 + "_" + string.Join("_", specifierValues.Values);
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));
SignalRMessageSetting setting = await _settingService.GetSignalRMessageSettingAsync();
if (_isInMultiLive && setting.EnableMarketsShortNameInMultiLive)
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: {market.Name}");
market.ShortName = NameUtils.GenerateShortNameFromNamePattern(
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, "Invalid Operation 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 MockSettingService : ISettingService
public async Task<SignalRMessageSetting> GetSignalRMessageSettingAsync()
return new SignalRMessageSetting { EnableMarketsShortNameInMultiLive = true, EnableMarketsSyncStateSlim = false };
public class MockEventNotificationService : IEventNotificationService
public async Task SendMarketsAsync(IEnumerable<Market> markets, Guid eventId, bool enableMarketsSyncStateSlim)
Console.WriteLine($"Sending markets for event {eventId}, EnableMarketsSyncStateSlim: {enableMarketsSyncStateSlim}");
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 settingServiceMock = new Mock<ISettingService>();
settingServiceMock.Setup(s => s.GetSignalRMessageSettingAsync()).ReturnsAsync(new SignalRMessageSetting { EnableMarketsShortNameInMultiLive = true });
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());
var markets = new List<Market> { new Market { Name = "Market 1" } };
await processor.SendSignalRMarketsInternalAsync(markets);
Assert.Null(markets[0].ShortName);
eventNotificationServiceMock.Verify(e => e.SendMarketsAsync(markets, It.IsAny<Guid>(), It.IsAny<bool>()), Times.Once);
public async Task SendSignalRMarketsInternalAsync_ShortNamesDisabled_DoesNotGenerateShortNames()
var settingServiceMock = new Mock<ISettingService>();
settingServiceMock.Setup(s => s.GetSignalRMessageSettingAsync()).ReturnsAsync(new SignalRMessageSetting { EnableMarketsShortNameInMultiLive = false });
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());
var markets = new List<Market> { new Market { Name = "Market 1" } };
await processor.SendSignalRMarketsInternalAsync(markets);
Assert.Null(markets[0].ShortName);
eventNotificationServiceMock.Verify(e => e.SendMarketsAsync(markets, It.IsAny<Guid>(), It.IsAny<bool>()), Times.Once);
public async Task SendSignalRMarketsInternalAsync_GeneratesShortNames_Success()
var settingServiceMock = new Mock<ISettingService>();
settingServiceMock.Setup(s => s.GetSignalRMessageSettingAsync()).ReturnsAsync(new SignalRMessageSetting { EnableMarketsShortNameInMultiLive = true });
var eventNotificationServiceMock = new Mock<IEventNotificationService>();
var loggerMock = new Mock<ILogger>();
var eventData = new EventData
Teams = new List<string> { "TeamA", "TeamB" },
Players = new List<string> { "PlayerX", "PlayerY" },
Roster = new List<string> {"RosterAlpha", "RosterBeta"}
var processor = new MarketDataProcessor(settingServiceMock.Object, eventNotificationServiceMock.Object, loggerMock.Object, true, Guid.NewGuid(), eventData);
var markets = new List<Market>
new Market { Name = "Market 1", MarketType = new MarketType { ShortNamePattern = "Pattern1" }, SpecifierValues = new Dictionary<string, string>{{"key1", "Value1"}}},
new Market { Name = "Market 2", ShortName = "ExistingShortName", MarketType = new MarketType { ShortNamePattern = "Pattern2" }, SpecifierValues = new Dictionary<string, string>{{"key2", "Value2"}}}
await processor.SendSignalRMarketsInternalAsync(markets);
Assert.Equal("Pattern1_Value1", markets[0].ShortName);
Assert.Equal("ExistingShortName", markets[1].ShortName);
eventNotificationServiceMock.Verify(e => e.SendMarketsAsync(markets, It.IsAny<Guid>(), It.IsAny<bool>()), Times.Once);
public async Task SendSignalRMarketsInternalAsync_NullMarketType_LogsErrorAndUsesFullName()
var settingServiceMock = new Mock<ISettingService>();
settingServiceMock.Setup(s => s.GetSignalRMessageSettingAsync()).ReturnsAsync(new SignalRMessageSetting { EnableMarketsShortNameInMultiLive = true, EnableMarketsSyncStateSlim = true });
var eventNotificationServiceMock = new Mock<IEventNotificationService>();
var loggerMock = new Mock<ILogger>();
var eventData = new EventData
Teams = new List<string> { "TeamA", "TeamB" },
Players = new List<string> { "PlayerX", "PlayerY" },
Roster = new List<string> {"RosterAlpha", "RosterBeta"}
var processor = new MarketDataProcessor(settingServiceMock.Object, eventNotificationServiceMock.Object, loggerMock.Object, true, Guid.NewGuid(), eventData);
var markets = new List<Market> { new Market { Name = "Market 1", MarketType = null, SpecifierValues = new () } };
await processor.SendSignalRMarketsInternalAsync(markets);
Assert.Equal("Market 1", markets[0].ShortName);
loggerMock.Verify(l => l.Log(
It.Is<LogLevel>(logLevel => logLevel == LogLevel.Error),
It.Is<It.IsAnyType>((v, t) => v.ToString().StartsWith("Invalid Operation 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);
public async Task SendSignalRMarketsInternalAsync_EmptyShortAndNamePattern_LogsErrorAndUsesFullName()
var settingServiceMock = new Mock<ISettingService>();
settingServiceMock.Setup(s => s.GetSignalRMessageSettingAsync()).ReturnsAsync(new SignalRMessageSetting { EnableMarketsShortNameInMultiLive = true, EnableMarketsSyncStateSlim = true });
var eventNotificationServiceMock = new Mock<IEventNotificationService>();
var loggerMock = new Mock<ILogger>();
var eventData = new EventData
Teams = new List<string> { "TeamA", "TeamB" },
Players = new List<string> { "PlayerX", "PlayerY" },
Roster = new List<string> {"RosterAlpha", "RosterBeta"}
var processor = new MarketDataProcessor(settingServiceMock.Object, eventNotificationServiceMock.Object, loggerMock.Object, true, Guid.NewGuid(), eventData);
var markets = new List<Market> { new Market { Name = "Market 1", MarketType = new MarketType{ ShortNamePattern = "", NamePattern = null}, SpecifierValues = new () } };
await processor.SendSignalRMarketsInternalAsync(markets);
Assert.Equal("Market 1", markets[0].ShortName);
loggerMock.Verify(l => l.Log(
It.Is<LogLevel>(logLevel => logLevel == LogLevel.Error),
It.Is<It.IsAnyType>((v, t) => v.ToString().StartsWith("Invalid Operation 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);