diff --git a/client/Lykke.Service.Assets.Client/AssetsServiceWithCache.cs b/client/Lykke.Service.Assets.Client/AssetsServiceWithCache.cs index f418eb41..8bcdcc41 100644 --- a/client/Lykke.Service.Assets.Client/AssetsServiceWithCache.cs +++ b/client/Lykke.Service.Assets.Client/AssetsServiceWithCache.cs @@ -1,6 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Reactive.Disposables; +using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; using Common.Log; @@ -10,87 +8,44 @@ namespace Lykke.Service.Assets.Client { /// - public class AssetsServiceWithCache : IAssetsServiceWithCache + internal class AssetsServiceWithCache : IAssetsServiceWithCache { - private readonly IAssetsService _assetsService; private readonly IDictionaryCache _assetsCache; private readonly IDictionaryCache _assetPairsCache; private readonly ILog _log; /// - public AssetsServiceWithCache(IAssetsService assetsService, IDictionaryCache assetsCache, IDictionaryCache assetPairsCache, ILog log) + public AssetsServiceWithCache(IDictionaryCache assetsCache, IDictionaryCache assetPairsCache) { - _assetsService = assetsService; _assetsCache = assetsCache; _assetPairsCache = assetPairsCache; - _log = log; } /// - public async Task> GetAllAssetPairsAsync(CancellationToken cancellationToken = new CancellationToken()) - { - await _assetPairsCache.EnsureCacheIsUpdatedAsync(() => GetUncachedAssetPairsAsync(cancellationToken)); - - return _assetPairsCache.GetAll(); - } + public Task> GetAllAssetPairsAsync(CancellationToken cancellationToken = new CancellationToken()) + => _assetPairsCache.GetAll(cancellationToken); - async Task> IAssetsServiceWithCache.GetAllAssetsAsync(CancellationToken cancellationToken) - => await GetAllAssetsAsync(false, cancellationToken); + Task> IAssetsServiceWithCache.GetAllAssetsAsync(CancellationToken cancellationToken = new CancellationToken()) + => GetAllAssetsAsync(false, cancellationToken); /// - public async Task> GetAllAssetsAsync(bool includeNonTradable, CancellationToken cancellationToken = new CancellationToken()) - { - await _assetsCache.EnsureCacheIsUpdatedAsync(() => GetUncachedAssetsAsync(cancellationToken)); - - return _assetsCache.GetAll(); - } + public Task> GetAllAssetsAsync(bool includeNonTradable, CancellationToken cancellationToken = new CancellationToken()) + => _assetsCache.GetAll(cancellationToken); /// - public async Task TryGetAssetAsync(string assetId, CancellationToken cancellationToken = new CancellationToken()) - { - await _assetsCache.EnsureCacheIsUpdatedAsync(() => GetUncachedAssetsAsync(cancellationToken)); - - return _assetsCache.TryGet(assetId); - } - - /// - public async Task TryGetAssetPairAsync(string assetPairId, CancellationToken cancellationToken = new CancellationToken()) - { - await _assetPairsCache.EnsureCacheIsUpdatedAsync(() => GetUncachedAssetPairsAsync(cancellationToken)); - - return _assetPairsCache.TryGet(assetPairId); - } + public Task TryGetAssetAsync(string assetId, CancellationToken cancellationToken = new CancellationToken()) + => _assetsCache.TryGet(assetId, cancellationToken); /// - public async Task UpdateAssetPairsCacheAsync(CancellationToken cancellationToken = new CancellationToken()) - { - _assetPairsCache.Update(await GetUncachedAssetPairsAsync(cancellationToken)); - } + public Task TryGetAssetPairAsync(string assetPairId, CancellationToken cancellationToken = new CancellationToken()) + => _assetPairsCache.TryGet(assetPairId, cancellationToken); /// - public async Task UpdateAssetsCacheAsync(CancellationToken cancellationToken = new CancellationToken()) - { - _assetsCache.Update(await GetUncachedAssetsAsync(cancellationToken)); - } + public Task UpdateAssetPairsCacheAsync(CancellationToken cancellationToken = new CancellationToken()) + => _assetPairsCache.Reset(cancellationToken); /// - public IDisposable StartAutoCacheUpdate() - { - return new CompositeDisposable - { - _assetPairsCache.StartAutoUpdate(nameof(AssetsServiceWithCache), _log, () => GetUncachedAssetPairsAsync(new CancellationToken())), - _assetsCache.StartAutoUpdate(nameof(AssetsServiceWithCache), _log, () => GetUncachedAssetsAsync(new CancellationToken())) - }; - } - - private async Task> GetUncachedAssetsAsync(CancellationToken cancellationToken) - { - return await _assetsService.AssetGetAllAsync(false, cancellationToken); - } - - private async Task> GetUncachedAssetPairsAsync(CancellationToken cancellationToken) - { - return await _assetsService.AssetPairGetAllAsync(cancellationToken); - } + public Task UpdateAssetsCacheAsync(CancellationToken cancellationToken = new CancellationToken()) + => _assetsCache.Reset(cancellationToken); } } diff --git a/client/Lykke.Service.Assets.Client/Cache/DateTimeProvider.cs b/client/Lykke.Service.Assets.Client/Cache/DateTimeProvider.cs deleted file mode 100644 index 0310a63d..00000000 --- a/client/Lykke.Service.Assets.Client/Cache/DateTimeProvider.cs +++ /dev/null @@ -1,9 +0,0 @@ -using System; - -namespace Lykke.Service.Assets.Client.Cache -{ - public class DateTimeProvider : IDateTimeProvider - { - public DateTime UtcNow => DateTime.UtcNow; - } -} diff --git a/client/Lykke.Service.Assets.Client/Cache/DictionaryCache.cs b/client/Lykke.Service.Assets.Client/Cache/DictionaryCache.cs index 1e47c44b..0246d0ee 100644 --- a/client/Lykke.Service.Assets.Client/Cache/DictionaryCache.cs +++ b/client/Lykke.Service.Assets.Client/Cache/DictionaryCache.cs @@ -3,96 +3,65 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Reactive.Disposables; using System.Threading; using System.Threading.Tasks; +using Lykke.Common.Cache; +using Lykke.Service.Assets.Client.Updaters; namespace Lykke.Service.Assets.Client.Cache { - /// - public class DictionaryCache : IDictionaryCache + /// + /// Base class for a dictionary cache. + /// + internal class DictionaryCache : IDictionaryCache where T : ICacheItem { - private readonly IDateTimeProvider _dateTimeProvider; - private readonly TimeSpan _cacheExpirationPeriod; + private const string AllItems = @"AllItems"; + private readonly OnDemandDataCache> _innerCache; + private readonly IUpdater _updater; + private readonly TimeSpan _expirationTime; - private Dictionary _items; - private DateTime _cacheExpirationMoment; - private bool _inAutoUpdate; - - /// - public DictionaryCache(IDateTimeProvider dateTimeProvider, TimeSpan cacheExpirationPeriod) + /// + /// Create new dictionary cache. + /// + protected DictionaryCache(IUpdater updater, TimeSpan expirationTime) { - _items = new Dictionary(); - _cacheExpirationMoment = DateTime.MinValue; - _dateTimeProvider = dateTimeProvider; - _cacheExpirationPeriod = cacheExpirationPeriod; + _innerCache = new OnDemandDataCache>(); + _updater = updater; + _expirationTime = expirationTime; } /// - public IDisposable StartAutoUpdate(string componentName, ILog log, Func>> getAllAsync) + public async Task Reset(CancellationToken token) { - if (_inAutoUpdate) - { - throw new InvalidOperationException("Dictionary is already in auto update mode."); - } - - _inAutoUpdate = true; - async Task UpdateCache(ITimerTrigger trigger, TimerTriggeredHandlerArgs args, CancellationToken token) - { - await Update(getAllAsync); - } - - var timer = new TimerTrigger(componentName, _cacheExpirationPeriod, log, UpdateCache); - timer.Start(); - - return Disposable.Create(() => - { - _inAutoUpdate = false; - timer.Dispose(); - }); + _innerCache.Remove(AllItems); + await GetItems(token); } /// - public async Task EnsureCacheIsUpdatedAsync(Func>> getAllItemsAsync) - { - if (_inAutoUpdate) - { - return; - } - - if (_cacheExpirationMoment < _dateTimeProvider.UtcNow) - { - await Update(getAllItemsAsync); - } - } - - private async Task Update(Func>> getAllItemsAsync) + public async Task TryGet(string id, CancellationToken token) { - var items = await getAllItemsAsync(); - Update(items); + var items = await GetItems(token); + items.TryGetValue(id, out var item); + return item; } /// - public void Update(IEnumerable items) + public async Task> GetAll(CancellationToken token) { - _items = items.ToDictionary(p => p.Id, p => p); - - _cacheExpirationMoment = _dateTimeProvider.UtcNow + _cacheExpirationPeriod; + var items = await GetItems(token); + return items.Values; } - /// - public T TryGet(string id) + private async Task> GetItems(CancellationToken token) { - _items.TryGetValue(id, out var pair); - - return pair; - } + async Task> Refresh() + { + var items = await _updater.GetItemsAsync(token); + return items.ToDictionary(x => x.Id); + } - /// - public IReadOnlyCollection GetAll() - { - return _items.Values; + return await _innerCache.GetOrAddAsync(AllItems, _ => Refresh(), _expirationTime); } } } diff --git a/client/Lykke.Service.Assets.Client/Cache/ExpiringDictionaryCache.cs b/client/Lykke.Service.Assets.Client/Cache/ExpiringDictionaryCache.cs new file mode 100644 index 00000000..2696f305 --- /dev/null +++ b/client/Lykke.Service.Assets.Client/Cache/ExpiringDictionaryCache.cs @@ -0,0 +1,23 @@ +using System; +using Lykke.Service.Assets.Client.Updaters; + +namespace Lykke.Service.Assets.Client.Cache +{ + /// + /// Expiring dictionary cache where the cache entry expires after given time. + /// + /// the type of cached item + internal sealed class ExpiringDictionaryCache : DictionaryCache + where T : ICacheItem + { + /// + /// Create a new expiring dictionary cache. + /// + /// expiration time + /// item updater + public ExpiringDictionaryCache(TimeSpan expirationTime, IUpdater updater) + : base(updater, expirationTime) + { + } + } +} diff --git a/client/Lykke.Service.Assets.Client/Cache/ICacheItem.cs b/client/Lykke.Service.Assets.Client/Cache/ICacheItem.cs index d071486d..4f7c944a 100644 --- a/client/Lykke.Service.Assets.Client/Cache/ICacheItem.cs +++ b/client/Lykke.Service.Assets.Client/Cache/ICacheItem.cs @@ -1,7 +1,13 @@ namespace Lykke.Service.Assets.Client.Cache { - public interface ICacheItem + /// + /// Cache item {T} in a . + /// + internal interface ICacheItem { + /// + /// The id of the entry + /// string Id { get; } } } diff --git a/client/Lykke.Service.Assets.Client/Cache/IDateTimeProvider .cs b/client/Lykke.Service.Assets.Client/Cache/IDateTimeProvider .cs deleted file mode 100644 index f84bf8f2..00000000 --- a/client/Lykke.Service.Assets.Client/Cache/IDateTimeProvider .cs +++ /dev/null @@ -1,9 +0,0 @@ -using System; - -namespace Lykke.Service.Assets.Client.Cache -{ - public interface IDateTimeProvider - { - DateTime UtcNow { get; } - } -} diff --git a/client/Lykke.Service.Assets.Client/Cache/IDictionaryCache.cs b/client/Lykke.Service.Assets.Client/Cache/IDictionaryCache.cs index a12d9a3a..fb58e463 100644 --- a/client/Lykke.Service.Assets.Client/Cache/IDictionaryCache.cs +++ b/client/Lykke.Service.Assets.Client/Cache/IDictionaryCache.cs @@ -1,5 +1,5 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; +using System.Threading; using System.Threading.Tasks; using Common.Log; @@ -8,32 +8,22 @@ namespace Lykke.Service.Assets.Client.Cache /// /// Simple in-memory client side cache. /// - public interface IDictionaryCache + internal interface IDictionaryCache where T : ICacheItem { /// - /// Starts an automatic updater that keeps the cache updated on a background thread. + /// Resets the cache. /// - IDisposable StartAutoUpdate(string componentName, ILog log, Func>> getAllAsync); - - /// - /// Update the cache when cache has expired. - /// - Task EnsureCacheIsUpdatedAsync(Func>> getAllAsync); - - /// - /// Update the cache with given data. - /// - void Update(IEnumerable items); + Task Reset(CancellationToken token); /// /// Try to get cached item with given id. /// - T TryGet(string id); + Task TryGet(string id, CancellationToken token); /// /// Get all cached items. /// - IReadOnlyCollection GetAll(); + Task> GetAll(CancellationToken token); } } diff --git a/client/Lykke.Service.Assets.Client/Cache/RefreshingDictionaryCache.cs b/client/Lykke.Service.Assets.Client/Cache/RefreshingDictionaryCache.cs new file mode 100644 index 00000000..8548d67f --- /dev/null +++ b/client/Lykke.Service.Assets.Client/Cache/RefreshingDictionaryCache.cs @@ -0,0 +1,37 @@ +using System; +using Common; +using Common.Log; +using Lykke.Service.Assets.Client.Updaters; + +namespace Lykke.Service.Assets.Client.Cache +{ + /// + /// A dictionary cache that refreshes/synchronizes in the background. + /// + /// the type of cached item + internal sealed class RefreshingDictionaryCache : DictionaryCache, IDisposable + where T : ICacheItem + { + private readonly TimerTrigger _trigger; + + /// + /// Creates a new refreshing dictionary cache. + /// + /// the refresh time + /// the item updater + /// the lykke log + public RefreshingDictionaryCache(TimeSpan refreshTime, IUpdater updater, ILog log) + : base(updater, refreshTime.Add(refreshTime)) + { + _trigger = new TimerTrigger(nameof(AssetsService), refreshTime, log, + async (x, y, token) => await Reset(token)); + _trigger.Start(); + } + + /// + public void Dispose() + { + _trigger?.Dispose(); + } + } +} diff --git a/client/Lykke.Service.Assets.Client/IAssetsServiceWithCache.cs b/client/Lykke.Service.Assets.Client/IAssetsServiceWithCache.cs index 02d7cff2..17ce6729 100644 --- a/client/Lykke.Service.Assets.Client/IAssetsServiceWithCache.cs +++ b/client/Lykke.Service.Assets.Client/IAssetsServiceWithCache.cs @@ -46,11 +46,5 @@ public interface IAssetsServiceWithCache /// Forcibly updates client-side assets cache /// Task UpdateAssetsCacheAsync(CancellationToken cancellationToken = new CancellationToken()); - - /// - /// Starts an automatic update process that will keep the caches updated the background. - /// - /// the update process, when disposed the auto update will stop - IDisposable StartAutoCacheUpdate(); } } diff --git a/client/Lykke.Service.Assets.Client/InternalsVisibleTo.cs b/client/Lykke.Service.Assets.Client/InternalsVisibleTo.cs new file mode 100644 index 00000000..5d280308 --- /dev/null +++ b/client/Lykke.Service.Assets.Client/InternalsVisibleTo.cs @@ -0,0 +1,4 @@ +using System.Runtime.CompilerServices; + +[assembly: InternalsVisibleTo("Lykke.Service.Assets.Tests")] +[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2")] diff --git a/client/Lykke.Service.Assets.Client/Lykke.Service.Assets.Client.csproj b/client/Lykke.Service.Assets.Client/Lykke.Service.Assets.Client.csproj index 63ff8fb8..4e588a27 100644 --- a/client/Lykke.Service.Assets.Client/Lykke.Service.Assets.Client.csproj +++ b/client/Lykke.Service.Assets.Client/Lykke.Service.Assets.Client.csproj @@ -26,10 +26,7 @@ - - - \ No newline at end of file diff --git a/client/Lykke.Service.Assets.Client/ServiceCollectionExtensions.cs b/client/Lykke.Service.Assets.Client/ServiceCollectionExtensions.cs index 7154ecd1..2703d97b 100644 --- a/client/Lykke.Service.Assets.Client/ServiceCollectionExtensions.cs +++ b/client/Lykke.Service.Assets.Client/ServiceCollectionExtensions.cs @@ -1,34 +1,58 @@ -using System.Net.Http; +using System; +using Common.Log; +using System.Net.Http; using Common.Log; using Lykke.Service.Assets.Client.Cache; using Lykke.Service.Assets.Client.Models; +using Lykke.Service.Assets.Client.Updaters; using Microsoft.Extensions.DependencyInjection; namespace Lykke.Service.Assets.Client { + /// + /// Service registration for client asset services. + /// public static class ServiceCollectionExtensions { - public static void RegisterAssetsClient(this IServiceCollection services, AssetServiceSettings settings, - ILog log) + /// + /// Register the asset services. + /// + /// the dependency injection service collection + /// the asset settings + /// the lykke log + /// use expiring caches or use a self refreshing cache for the assets and asset-pairs + public static void RegisterAssetsClient(this IServiceCollection services, AssetServiceSettings settings, ILog log, bool autoRefresh = true) { - services - .AddSingleton(x => new AssetsService(settings.BaseUri, new HttpClient())); + services.AddSingleton(x => new AssetsService(settings.BaseUri, new HttpClient())); - services - .AddSingleton>(x => - new DictionaryCache(new DateTimeProvider(), settings.AssetsCacheExpirationPeriod)); + services.AddTransient>(x => new AssetsUpdater(x.GetService())); + services.AddTransient(x => CreateDictionaryCache(x, settings.AssetsCacheExpirationPeriod, log, autoRefresh)); - services - .AddSingleton>(x => - new DictionaryCache(new DateTimeProvider(), settings.AssetPairsCacheExpirationPeriod)); + services.AddTransient>(x => new AssetPairsUpdater(x.GetService())); + services.AddTransient(x => CreateDictionaryCache(x, settings.AssetPairsCacheExpirationPeriod, log, autoRefresh)); - services - .AddSingleton(x => new AssetsServiceWithCache( - x.GetService(), + services.AddSingleton(x => new AssetsServiceWithCache( x.GetService>(), - x.GetService>(), + x.GetService>() + )); + } + + private static IDictionaryCache CreateDictionaryCache(IServiceProvider provider, TimeSpan period, ILog log, bool refreshing) + where T : ICacheItem + { + if (refreshing) + { + return new RefreshingDictionaryCache( + period, + provider.GetService>(), log - )); + ); + } + + return new ExpiringDictionaryCache( + period, + provider.GetService>() + ); } } } diff --git a/client/Lykke.Service.Assets.Client/Updaters/AssetPairsUpdater.cs b/client/Lykke.Service.Assets.Client/Updaters/AssetPairsUpdater.cs new file mode 100644 index 00000000..bed42a09 --- /dev/null +++ b/client/Lykke.Service.Assets.Client/Updaters/AssetPairsUpdater.cs @@ -0,0 +1,22 @@ +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using Lykke.Service.Assets.Client.Models; + +namespace Lykke.Service.Assets.Client.Updaters +{ + internal class AssetPairsUpdater : IUpdater + { + private readonly IAssetsService _service; + + public AssetPairsUpdater(IAssetsService service) + { + _service = service; + } + + public async Task> GetItemsAsync(CancellationToken token) + { + return await _service.AssetPairGetAllAsync(token); + } + } +} diff --git a/client/Lykke.Service.Assets.Client/Updaters/AssetsUpdater.cs b/client/Lykke.Service.Assets.Client/Updaters/AssetsUpdater.cs new file mode 100644 index 00000000..d62052d6 --- /dev/null +++ b/client/Lykke.Service.Assets.Client/Updaters/AssetsUpdater.cs @@ -0,0 +1,22 @@ +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using Lykke.Service.Assets.Client.Models; + +namespace Lykke.Service.Assets.Client.Updaters +{ + internal class AssetsUpdater : IUpdater + { + private readonly IAssetsService _service; + + public AssetsUpdater(IAssetsService service) + { + _service = service; + } + + public async Task> GetItemsAsync(CancellationToken token) + { + return await _service.AssetGetAllAsync(false, token); + } + } +} diff --git a/client/Lykke.Service.Assets.Client/Updaters/IUpdater.cs b/client/Lykke.Service.Assets.Client/Updaters/IUpdater.cs new file mode 100644 index 00000000..1b831f20 --- /dev/null +++ b/client/Lykke.Service.Assets.Client/Updaters/IUpdater.cs @@ -0,0 +1,20 @@ +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using Lykke.Service.Assets.Client.Cache; + +namespace Lykke.Service.Assets.Client.Updaters +{ + /// + /// Updater of for retrieving the contents of a dictionary cache. + /// + /// the type of cache entry to retrieve + internal interface IUpdater + where T : ICacheItem + { + /// + /// Retrieves the cache items. + /// + Task> GetItemsAsync(CancellationToken token); + } +} diff --git a/src/Lykke.Service.Assets.Repositories/Lykke.Service.Assets.Repositories.csproj b/src/Lykke.Service.Assets.Repositories/Lykke.Service.Assets.Repositories.csproj index 6ec47cf9..c64dcb66 100644 --- a/src/Lykke.Service.Assets.Repositories/Lykke.Service.Assets.Repositories.csproj +++ b/src/Lykke.Service.Assets.Repositories/Lykke.Service.Assets.Repositories.csproj @@ -5,7 +5,6 @@ - diff --git a/src/Lykke.Service.Assets.Services/Lykke.Service.Assets.Services.csproj b/src/Lykke.Service.Assets.Services/Lykke.Service.Assets.Services.csproj index 4348ca10..8aafb12b 100644 --- a/src/Lykke.Service.Assets.Services/Lykke.Service.Assets.Services.csproj +++ b/src/Lykke.Service.Assets.Services/Lykke.Service.Assets.Services.csproj @@ -5,7 +5,6 @@ - diff --git a/src/Lykke.Service.Assets/Lykke.Service.Assets.csproj b/src/Lykke.Service.Assets/Lykke.Service.Assets.csproj index 23b1dcb8..5872bb95 100644 --- a/src/Lykke.Service.Assets/Lykke.Service.Assets.csproj +++ b/src/Lykke.Service.Assets/Lykke.Service.Assets.csproj @@ -13,11 +13,6 @@ 1701;1702;1705;1591 - - - - - @@ -25,14 +20,8 @@ - - - - - - diff --git a/tests/Lykke.Service.Assets.Tests/Cache/DictionaryCacheTest.cs b/tests/Lykke.Service.Assets.Tests/Cache/DictionaryCacheTest.cs deleted file mode 100644 index a55326f8..00000000 --- a/tests/Lykke.Service.Assets.Tests/Cache/DictionaryCacheTest.cs +++ /dev/null @@ -1,88 +0,0 @@ -using Lykke.Service.Assets.Client.Cache; -using Microsoft.VisualStudio.TestTools.UnitTesting; -using Moq; -using System; -using System.Collections.Generic; -using System.Threading; -using System.Threading.Tasks; -using Common.Log; - -namespace Lykke.Service.Assets.Tests.Cache -{ - [TestClass] - public class DictionaryCacheTest - { - private int _getItemsCallCount; - - [TestInitialize] - public void Setup() - { - _getItemsCallCount = 0; - } - - [TestMethod] - public async Task TestExpiringCache() - { - var date = DateTime.UtcNow; - var provider = new Mock(); - provider.SetupGet(x => x.UtcNow).Returns(date); - - var sot = new DictionaryCache(provider.Object, TimeSpan.FromSeconds(30)); - - await sot.EnsureCacheIsUpdatedAsync(GetItems); - - Assert.IsTrue(sot.GetAll().Count > 0); - Assert.AreEqual(1, _getItemsCallCount); - - provider.SetupGet(x => x.UtcNow).Returns(date.AddMinutes(1)); - await sot.EnsureCacheIsUpdatedAsync(GetItems); - Assert.AreEqual(2, _getItemsCallCount); - - provider.SetupGet(x => x.UtcNow).Returns(date.AddSeconds(29)); - - await sot.EnsureCacheIsUpdatedAsync(GetItems); - Assert.AreEqual(2, _getItemsCallCount); - } - - [TestMethod] - public async Task TestRefreshingCache() - { - var date = DateTime.UtcNow; - var provider = new Mock(); - provider.SetupGet(x => x.UtcNow).Returns(date); - - var sot = new DictionaryCache(provider.Object, TimeSpan.FromMilliseconds(100)); - using (sot.StartAutoUpdate("TestRefreshingCache", Mock.Of(), GetItems)) - { - Assert.IsTrue(sot.GetAll().Count > 0, "Dictionary should be filled after update"); - Assert.AreEqual(1, _getItemsCallCount, "Initial update should be called at start"); - - await Task.Delay(150); - - Assert.AreEqual(2, _getItemsCallCount, "Update should have been called after timer tick"); - } - - await Task.Delay(150); - - Assert.AreEqual(2, _getItemsCallCount, "Auto update should have stopped after dispose"); - } - - private Task> GetItems() - { - _getItemsCallCount++; - - IEnumerable result = new List - { - new CacheItem {Id = "1"}, - new CacheItem {Id = "2"} - }; - - return Task.FromResult(result); - } - - private class CacheItem : ICacheItem - { - public string Id { get; set; } - } - } -} diff --git a/tests/Lykke.Service.Assets.Tests/Cache/ExpiringDictionaryCacheTest.cs b/tests/Lykke.Service.Assets.Tests/Cache/ExpiringDictionaryCacheTest.cs new file mode 100644 index 00000000..4eeaaef3 --- /dev/null +++ b/tests/Lykke.Service.Assets.Tests/Cache/ExpiringDictionaryCacheTest.cs @@ -0,0 +1,55 @@ +using System; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Lykke.Service.Assets.Client.Cache; +using Lykke.Service.Assets.Client.Updaters; +using Microsoft.Extensions.Caching.Memory; +using Microsoft.Extensions.Options; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Moq; + +namespace Lykke.Service.Assets.Tests.Cache +{ + [TestClass] + public class ExpiringDictionaryCacheTest + { + [TestMethod] + public async Task TestExpiringCache() + { + var items = Enumerable.Range(1, 5).Select(x => new CacheItem { Id = x.ToString() }); + + var updater = new Mock>(); + updater.Setup(x => x.GetItemsAsync(It.IsAny())).Returns(Task.FromResult(items)); + + var sot = new ExpiringDictionaryCache(TimeSpan.FromMilliseconds(100), updater.Object); + + var result1 = await sot.GetAll(new CancellationToken()); + Assert.IsNotNull(result1); + Assert.AreEqual(5, result1.Count); + + updater.Verify(x => x.GetItemsAsync(It.IsAny()), Times.Once); + + var result2 = await sot.GetAll(new CancellationToken()); + Assert.AreSame(result1, result2); + + updater.Verify(x => x.GetItemsAsync(It.IsAny()), Times.Once); + + await Task.Delay(150); + + updater.Verify(x => x.GetItemsAsync(It.IsAny()), Times.Once, @"Cache should expire not auto update"); + + var result3 = await sot.GetAll(new CancellationToken()); + Assert.IsNotNull(result3); + Assert.AreEqual(5, result1.Count); + Assert.AreNotSame(result1, result3); + + updater.Verify(x => x.GetItemsAsync(It.IsAny()), Times.Exactly(2)); + } + + public class CacheItem : ICacheItem + { + public string Id { get; set; } + } + } +} diff --git a/tests/Lykke.Service.Assets.Tests/Cache/RefreshingDictionaryCacheTest.cs b/tests/Lykke.Service.Assets.Tests/Cache/RefreshingDictionaryCacheTest.cs new file mode 100644 index 00000000..f57fa503 --- /dev/null +++ b/tests/Lykke.Service.Assets.Tests/Cache/RefreshingDictionaryCacheTest.cs @@ -0,0 +1,60 @@ +using Lykke.Service.Assets.Client.Cache; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Moq; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Common.Log; +using Lykke.Service.Assets.Client.Updaters; +using Microsoft.Extensions.Caching.Memory; +using Microsoft.Extensions.Options; + +namespace Lykke.Service.Assets.Tests.Cache +{ + [TestClass] + public class RefreshingDictionaryCacheTest + { + [TestMethod] + public async Task TestRefreshingCache() + { + var items = Enumerable.Range(1, 5).Select(x => new CacheItem { Id = x.ToString() }); + + var updater = new Mock>(); + updater.Setup(x => x.GetItemsAsync(It.IsAny())).Returns(Task.FromResult(items)); + + var sot = new RefreshingDictionaryCache(TimeSpan.FromMilliseconds(100), updater.Object, Mock.Of()); + + // Give system some time to update in background + await Task.Delay(10); + + updater.Verify(x => x.GetItemsAsync(It.IsAny()), Times.Once, @"Cache should auto initialize"); + + var result1 = await sot.GetAll(new CancellationToken()); + Assert.IsNotNull(result1); + Assert.AreEqual(5, result1.Count); + + var result2 = await sot.GetAll(new CancellationToken()); + Assert.AreSame(result1, result2); + + updater.Verify(x => x.GetItemsAsync(It.IsAny()), Times.Once, @"Cache should not update on GetItems"); + + await Task.Delay(125); + + updater.Verify(x => x.GetItemsAsync(It.IsAny()), Times.Exactly(2), @"Cache should update in background"); + + var result3 = await sot.GetAll(new CancellationToken()); + Assert.IsNotNull(result3); + Assert.AreEqual(5, result1.Count); + Assert.AreNotSame(result1, result3); + + updater.Verify(x => x.GetItemsAsync(It.IsAny()), Times.Exactly(2)); + } + + public class CacheItem : ICacheItem + { + public string Id { get; set; } + } + } +}