242 lines
7.7 KiB
C#
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);
|
|
}
|
|
}
|