Files
ARM64/MarketDataUnitTests/GBCacheUnitTestClass.cs
2026-02-23 21:25:49 -05:00

242 lines
7.7 KiB
C#

using Microsoft.VisualStudio.TestTools.UnitTesting;
using MarketData.Cache;
using MarketData.MarketDataModel;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Moq;
using MarketData.Utils;
using System.Reflection;
namespace MarketDataUnitTests;
[TestClass]
public class GBPriceCacheTests
{
private GBPriceCache cache = default!;
private Mock<IPricingDataAccess> pricingMock = default!;
private readonly string symbol = "AAPL";
private readonly DateTime today = DateTime.Today;
// ------------------------
// Helper to fully reset singleton
// ------------------------
private void ResetCacheSingleton()
{
if (null != cache) cache.Dispose();
FieldInfo field = typeof(GBPriceCache)
.GetField("priceCacheInstance", BindingFlags.Static | BindingFlags.NonPublic);
if (null != field) field.SetValue(null, null);
cache = GBPriceCache.GetInstance();
cache.Clear();
}
[TestInitialize]
public void Setup()
{
ResetCacheSingleton();
pricingMock = new Mock<IPricingDataAccess>();
cache.PricingDataAccess = pricingMock.Object;
}
[TestCleanup]
public void Cleanup()
{
cache.Dispose();
}
// ------------------------
// Singleton Behavior
// ------------------------
[TestMethod]
public void GetInstance_ReturnsSingleton()
{
GBPriceCache instance1 = GBPriceCache.GetInstance();
GBPriceCache instance2 = GBPriceCache.GetInstance();
Assert.AreSame(instance1, instance2);
}
// ------------------------
// Basic GetPrice
// ------------------------
[TestMethod]
public void GetPrice_ReturnsNull_WhenNoData()
{
Price nullPrice = default;
pricingMock.Setup(x => x.GetPrice(symbol, today)).Returns(nullPrice);
Price result = cache.GetPrice(symbol, today);
Assert.IsNull(result);
}
[TestMethod]
public void GetPrice_CachesResult_WhenDataExists()
{
Price price = new Price { Symbol = symbol, Date = today };
pricingMock.Setup(x => x.GetPrice(symbol, today)).Returns(price);
Price first = cache.GetPrice(symbol, today);
Price second = cache.GetPrice(symbol, today);
Assert.AreSame(first, second);
Assert.AreEqual(symbol, first.Symbol);
}
// ------------------------
// ContainsPrice
// ------------------------
[TestMethod]
public void ContainsPrice_BehavesCorrectly()
{
Price price = new Price { Symbol = symbol, Date = today };
pricingMock.Setup(x => x.GetPrice(symbol, today)).Returns(price);
Assert.IsFalse(cache.ContainsPrice(symbol, today));
cache.GetPrice(symbol, today);
Assert.IsTrue(cache.ContainsPrice(symbol, today));
}
// ------------------------
// GetPriceOrLatestAvailable
// ------------------------
[TestMethod]
public void GetPriceOrLatestAvailable_ReturnsLatestPrice()
{
DateTime earlier = today.AddDays(-1);
Price latestPrice = new Price { Symbol = symbol, Date = today };
pricingMock.Setup(x => x.GetLatestDateOnOrBefore(symbol, earlier)).Returns(today);
pricingMock.Setup(x => x.GetPrice(symbol, today)).Returns(latestPrice);
Price result = cache.GetPriceOrLatestAvailable(symbol, earlier);
Assert.IsNotNull(result);
Assert.AreEqual(today, result.Date);
}
// ------------------------
// GetPrices (DateTime overload)
// ------------------------
[TestMethod]
public void GetPrices_DateTimeOverload_ReturnsList()
{
DateGenerator generator = new DateGenerator();
DateTime later = generator.GetPrevBusinessDay(DateTime.Today);
DateTime earlier = later;
for (int i = 0; i < 2; i++) earlier = generator.GetPrevBusinessDay(earlier.AddDays(-1));
List<DateTime> allDates = generator.GenerateHistoricalDates(later, 1000);
List<Price> allPrices = allDates.Select(d => new Price { Symbol = symbol, Date = d }).ToList();
pricingMock.Setup(x => x.GetPrices(It.IsAny<string>(), It.IsAny<DateTime>(), It.IsAny<DateTime>()))
.Returns((string s, DateTime max, DateTime min) =>
{
List<Price> matched = allPrices.Where(p => p.Date >= min && p.Date <= max).ToList();
return new Prices(matched);
});
int dayCount = generator.GenerateHistoricalDates(later, 100)
.Where(d => d <= later && d >= earlier)
.Count();
Prices prices = cache.GetPrices(symbol, earlier, later);
Assert.IsNotNull(prices);
Assert.AreEqual(dayCount, prices.Count);
Assert.IsTrue(prices.SequenceEqual(prices.OrderByDescending(p => p.Date)));
}
// ------------------------
// GetPrices (startDate + dayCount)
// ------------------------
[TestMethod]
public void GetPrices_StartDateDayCount_Works()
{
int dayCount = 5;
List<Price> allPrices = new List<Price>();
for (int i = 0; i < 100; i++) allPrices.Add(new Price { Symbol = symbol, Date = today.AddDays(-i) });
pricingMock.Setup(x => x.GetPrices(It.IsAny<string>(), It.IsAny<DateTime>(), It.IsAny<DateTime>()))
.Returns((string s, DateTime max, DateTime min) =>
{
List<Price> matched = allPrices.Where(p => p.Date >= min && p.Date <= max).ToList();
return new Prices(matched);
});
Prices prices = cache.GetPrices(symbol, today, dayCount);
Assert.AreEqual(dayCount, prices.Count);
Assert.IsTrue(prices.SequenceEqual(prices.OrderByDescending(p => p.Date)));
}
// ------------------------
// Clear
// ------------------------
[TestMethod]
public void Clear_RemovesAllData()
{
Price price = new Price { Symbol = symbol, Date = today };
pricingMock.Setup(x => x.GetPrice(symbol, today)).Returns(price);
cache.GetPrice(symbol, today);
cache.Clear();
Assert.IsFalse(cache.ContainsPrice(symbol, today));
}
// ------------------------
// ClearCacheOnOrBefore
// ------------------------
[TestMethod]
public void ClearCacheOnOrBefore_FiltersOldPrices()
{
Price oldPrice = new Price { Symbol = symbol, Date = today.AddDays(-5) };
Price recentPrice = new Price { Symbol = symbol, Date = today };
pricingMock.Setup(x => x.GetPrice(symbol, oldPrice.Date)).Returns(oldPrice);
pricingMock.Setup(x => x.GetPrice(symbol, recentPrice.Date)).Returns(recentPrice);
cache.GetPrice(symbol, oldPrice.Date);
cache.GetPrice(symbol, recentPrice.Date);
cache.ClearCacheOnOrBefore(today.AddDays(-1));
Assert.IsFalse(cache.ContainsPrice(symbol, oldPrice.Date));
Assert.IsTrue(cache.ContainsPrice(symbol, recentPrice.Date));
}
// ------------------------
// Concurrency Tests
// ------------------------
[TestMethod]
public void Concurrent_ReadWrite_DoesNotThrow()
{
Price price = new Price { Symbol = symbol, Date = today };
pricingMock.Setup(x => x.GetPrice(symbol, It.IsAny<DateTime>())).Returns(price);
Parallel.For(0, 100, i =>
{
cache.GetPrice(symbol, today.AddDays(-i));
cache.ContainsPrice(symbol, today);
});
Assert.IsTrue(true);
}
[TestMethod]
public void Concurrent_MultipleSymbols_DoesNotThrow()
{
List<string> symbols = new List<string> { "AAPL", "MSFT", "GOOG" };
Price price = new Price { Symbol = "TEST", Date = today };
pricingMock.Setup(x => x.GetPrice(It.IsAny<string>(), It.IsAny<DateTime>())).Returns(price);
Parallel.ForEach(symbols, s =>
{
for (int i = 0; i < 50; i++)
{
cache.GetPrice(s, today.AddDays(-i));
}
});
Assert.IsTrue(true);
}
// ------------------------
// Dispose
// ------------------------
[TestMethod]
public void Dispose_CanBeCalledMultipleTimes()
{
cache.Dispose();
cache.Dispose();
Assert.IsTrue(true);
}
}