public async Task Cache_Dist_Test_InMemoryPerformance()
        {
            // Arrange
            var cacheAccessor1 = new CacheAccessor(new CacheConfig {
                IsAuditingEnabled = true
            });
            var cacheAccessor2 = new CacheAccessor(new CacheConfig {
                IsAuditingEnabled = true
            });
            await cacheAccessor1.InitialiseIfNecessaryAsync();

            await cacheAccessor2.InitialiseIfNecessaryAsync();

            var bigList = new List <int>(Enumerable.Range(0, 100000));
            await cacheAccessor1.SetAsync("biglist", bigList);

            var v1 = await cacheAccessor1.GetWithMetaDataAsync <List <int> >("biglist"); // should return instantly from the originating cache accessor (it's in-memory)

            var v2 = await cacheAccessor2.GetWithMetaDataAsync <List <int> >("biglist"); // should return with some delay from the new cache accessor (it's on the central cache)

            var v3 = await cacheAccessor2.GetWithMetaDataAsync <List <int> >("biglist"); // should return instantly, having put the object into in-memory cache the last time it was called

            Assert.IsNotNull(v1.Data);
            Assert.IsTrue(v1.IsFromInMemoryCache, "Shoulda oughta a bin from the in-memory cache");
            Assert.IsNotNull(v2.Data);
            Assert.IsTrue(v2.IsFromCentralCacheServer || v2.IsFromInMemoryCache, "Shoulda oughta a bin from the central / memory cache");
            Assert.IsNotNull(v3.Data);
            Assert.IsTrue(v3.IsFromInMemoryCache, "Shoulda oughta a bin from the in-memory cache");
        }
        public async Task CacheData_WhenCacheIsUnavailable_ReturnDefaultValueAndCheckConnectionTimeout()
        {
            // Arrange
            var tempCache = new CacheAccessor(new CacheConfig
            {
                ConnectionString = "invalid_connection_string"
            }); // configured with an incorrect server and will return null on error

            // Act
            var testValue = await tempCache.GetAsync <string>("lovely_non_existent_item");

            Thread.Sleep(50);
            var status1 = tempCache.Status;

            await tempCache.InitialiseIfNecessaryAsync();

            var status2 = tempCache.Status;

            // Assert
            Assert.IsNull(testValue);
            Assert.AreEqual(CacheAccessor.State.Connecting, status1);
            Assert.AreEqual(CacheAccessor.State.NotConnected, status2);
            Assert.IsTrue(tempCache.LastConnectionElapsedMilliseconds > 0 && tempCache.LastConnectionElapsedMilliseconds < 12000,
                          "The connection timeout should be more than 0 but less than 12 seconds. Actual timeouts are never inline with the stated max timeout, so for 10ms, the actual timeout is ~5s");
        }
コード例 #3
0
        /// <summary>
        /// Read and unprotect cache data
        /// </summary>
        /// <returns>Unprotected cache data</returns>
        public byte[] ReadData(bool ignoreExceptions = true)
        {
            bool cacheFileExists = File.Exists(CacheFilePath);

            _logger.LogInformation($"ReadData Cache file exists '{cacheFileExists}'");

            byte[] data = null;
            try
            {
                _logger.LogInformation($"Reading Data");
                data = CacheAccessor.Read();
                _logger.LogInformation($"Got '{data?.Length}' bytes from file storage");
            }
            catch (Exception e)
            {
                _logger.LogError($"An exception was encountered while reading data from the {nameof(MsalCacheStorage)} : {e}");

                // It's unlikely that Clear will work, but try it anyway
                Clear();

                if (!ignoreExceptions)
                {
                    throw;
                }
            }

            return(data ?? new byte[0]);
        }
        public async Task Cache_NonDist_Test_That_Cache_Delete_Key_Works_OK()
        {
            // ARRANGE
            var ca1 = new CacheAccessor(new CacheConfig {
                IsAuditingEnabled = true, IsCentralCacheEnabled = false
            });

            await ca1.InitialiseIfNecessaryAsync();

            var KEY   = CreateGuid();
            var VALUE = CreateGuid();

            // ACT
            await ca1.SetAsync(KEY, VALUE); // set the value

            await ca1.DeleteAsync(KEY);

            var evt1 = ca1.LastCachingEvent;
            var v2   = await ca1.GetWithMetaDataAsync <string>(KEY);

            // ASSERT
            Assert.IsNull(v2.Data);
            Assert.IsTrue(evt1 == eCacheEvent.KeyDeletedInMemory);
            Assert.IsTrue(ca1.LastCachingEvent == eCacheEvent.KeyValueGotFromMemoryAttempt);
        }
        public async Task Cache_TestImmutableRefTypeObjectDoesNOTCauseAnException()
        {
            var ca1 = new CacheAccessor(new CacheConfig {
                IsAuditingEnabled = true, IsCentralCacheEnabled = false
            });
            var ops = ca1;
            await ca1.InitialiseIfNecessaryAsync();

            await ops.SetAsync(Guid.NewGuid().ToString(), "this object is immutable", null);
        }
        public async Task CacheData_WhenCacheIsUnavailable_ReturnNull()
        {
            // Arrange
            var tempCache = new CacheAccessor(new CacheConfig
            {
                ConnectionString = "invalid_connection_string"
            }); // configured with an incorrect server and will return null on error

            // Act
            var testValue = await tempCache.GetAsync <string>("lovely_non_existent_item");

            Assert.IsNull(testValue);
        }
        /// <summary>
        /// Tries to write -> read -> clear a secret from the underlying persistence mechanism
        /// </summary>
        public void VerifyPersistence()
        {
            // do not use the _cacheAccessor for writing dummy data, as it might overwrite an actual token cache
            var persitenceValidatationAccessor = CacheAccessor.CreateForPersistenceValidation();

            try
            {
                _logger.LogInformation($"[Verify Persistence] Writing Data ");
                persitenceValidatationAccessor.Write(Encoding.UTF8.GetBytes(PersistenceValidationDummyData));

                _logger.LogInformation($"[Verify Persistence] Reading Data ");
                var data = persitenceValidatationAccessor.Read();

                if (data == null || data.Length == 0)
                {
                    throw new MsalCachePersistenceException(
                              "Persistence check failed. Data was written but it could not be read. " +
                              "Possible cause: on Linux, LibSecret is installed but D-Bus isn't running because it cannot be started over SSH.");
                }

                string dataRead = Encoding.UTF8.GetString(data);
                if (!string.Equals(PersistenceValidationDummyData, dataRead, StringComparison.Ordinal))
                {
                    throw new MsalCachePersistenceException(
                              $"Persistence check failed. Data written {PersistenceValidationDummyData} is different from data read {dataRead}");
                }
            }
            catch (InteropException e)
            {
                throw new MsalCachePersistenceException(
                          $"Persistence check failed. Reason: {e.Message}. OS error code {e.ErrorCode}.", e);
            }
            catch (Exception ex) when(!(ex is MsalCachePersistenceException))
            {
                throw new MsalCachePersistenceException("Persistence check failed. Inspect inner exception for details", ex);
            }
            finally
            {
                try
                {
                    _logger.LogInformation($"[Verify Persistence] Clearing data");
                    persitenceValidatationAccessor.Clear();
                }
                catch (Exception e)
                {
                    _logger.LogError($"[Verify Persistence] Could not clear the test data: " + e);
                }
            }
        }
        public async Task Cache_TestValueTypeObject_Roundtrips()
        {
            var cache = new CacheAccessor(new CacheConfig {
                IsAuditingEnabled = true, IsCentralCacheEnabled = false
            });
            var ops   = cache;
            var key   = Guid.NewGuid().ToString("N");
            var value = 4658792;
            await cache.InitialiseIfNecessaryAsync();

            await ops.SetAsync(key, value, null);

            var value2 = await cache.GetAsync <int>(key);

            Assert.AreEqual(value, value2);
        }
コード例 #9
0
 /// <summary>
 /// Delete cache file
 /// </summary>
 public void Clear(bool ignoreExceptions = true)
 {
     try
     {
         _logger.LogInformation("Clearing the cache file");
         CacheAccessor.Clear();
     }
     catch (Exception e)
     {
         _logger.LogError($"An exception was encountered while clearing data from {nameof(MsalCacheStorage)} : {e}");
         if (!ignoreExceptions)
         {
             throw;
         }
     }
 }
コード例 #10
0
        /// <summary>
        /// Read and unprotect cache data
        /// </summary>
        /// <returns>Unprotected cache data</returns>
        public byte[] ReadData()
        {
            byte[] data = null;
            try
            {
                _logger.LogInformation($"Reading Data");
                data = CacheAccessor.Read();
                _logger.LogInformation($"Got '{data?.Length ?? 0}' bytes from file storage");
            }
            catch (Exception e)
            {
                _logger.LogError($"An exception was encountered while reading data from the {nameof(Storage)} : {e}");
                throw;
            }

            return(data ?? new byte[0]);
        }
コード例 #11
0
        /// <summary>
        /// Protect and write cache data to file. It overrides existing data.
        /// </summary>
        /// <param name="data">Cache data</param>
        public void WriteData(byte[] data)
        {
            if (data == null)
            {
                throw new ArgumentNullException(nameof(data));
            }

            try
            {
                _logger.LogInformation($"Got '{data?.Length}' bytes to write to storage");
                CacheAccessor.Write(data);
            }
            catch (Exception e)
            {
                _logger.LogError($"An exception was encountered while writing data to {nameof(Storage)} : {e}");
                throw;
            }
        }
        public async Task Cache_TestCustomObjects_AreNotClones()
        {
            var cache = new CacheAccessor(new CacheConfig {
                IsAuditingEnabled = true, IsCentralCacheEnabled = false
            });
            var ops   = cache;
            var key   = Guid.NewGuid().ToString("N");
            var value = new Employee(3456, "test");
            await cache.InitialiseIfNecessaryAsync();

            await ops.SetAsync(key, value, null);

            var value2 = await cache.GetAsync <Employee>(key);

            Assert.AreEqual(value.Id, value2.Id);
            Assert.AreEqual(value.Name, value2.Name);
            Assert.AreNotEqual(value, value2);
        }
        public async Task Cache_TestWithoutRedis()
        {
            var cache = new CacheAccessor(new CacheConfig {
                IsAuditingEnabled = true, IsCentralCacheEnabled = false
            });
            var ops   = cache;
            var key   = Guid.NewGuid().ToString("N");
            var value = new Employee(3456, "test");
            await cache.InitialiseIfNecessaryAsync();

            await ops.SetAsync(key, value, null);

            var value2 = await cache.GetAsync <Employee>(key);

            Assert.AreEqual(cache.AuditLog[0].CachingEvent, eCacheEvent.KeySetInMemory);
            Assert.AreEqual(cache.AuditLog[1].CachingEvent, eCacheEvent.KeyValueGotFromMemoryAttempt);
            Assert.AreEqual(cache.AuditLog[2].CachingEvent, eCacheEvent.KeyValueGotFromMemory);
        }
        public async Task Cache_TestDictionaryObject_Roundtrips()
        {
            var cache = new CacheAccessor(new CacheConfig {
                IsAuditingEnabled = true, IsCentralCacheEnabled = false
            });
            var ops   = cache;
            var key   = Guid.NewGuid().ToString("N");
            var value = (IDictionary <string, string>) new Dictionary <string, string> {
                { "k", "v" }
            };
            await cache.InitialiseIfNecessaryAsync();

            await ops.SetAsync(key, value, null);

            var value2 = await cache.GetAsync <Dictionary <string, string> >(key);

            Assert.AreEqual(value, value2);
        }
        public async Task Cache_TestEnumerableObject_Roundtrips()
        {
            var cache = new CacheAccessor(new CacheConfig {
                IsAuditingEnabled = true, IsCentralCacheEnabled = false
            });
            var ops   = cache;
            var key   = Guid.NewGuid().ToString("N");
            var value = (IEnumerable) new List <string> {
                "this is a thing in the list"
            };
            await cache.InitialiseIfNecessaryAsync();

            await ops.SetAsync(key, value, null);

            var value2 = await cache.GetAsync <IEnumerable>(key);

            Assert.AreEqual(value, value2);
        }
        public void CacheData_TestThatMultipleInitialisationsResultsInOne()
        {
            var tempCache = CacheAccessor.Create();

            Parallel.For(0, 20, i =>
            {
                tempCache.InitialiseIfNecessaryAsync();
            });

            var connectingTask = tempCache.InitialiseIfNecessaryAsync();
            var status1        = tempCache.Status;

            connectingTask.Wait(); // wait for connection to complete
            var status2 = tempCache.Status;

            Assert.AreEqual(1, tempCache.ConnectionAttemptsCount, "Only 1 connection attempt should have been made in-spite of the bombardment of connection requests");
            Assert.IsTrue(status1 == CacheAccessor.State.NotConnected || status1 == CacheAccessor.State.Connecting);
            Assert.AreEqual(CacheAccessor.State.Connected, status2, "The cache should have connected by status2");
        }
        public async Task Cache_Dist_Test_That_Cache_Expiry_Works_OK()
        {
            // ARRANGE
            var ca1 = new CacheAccessor(new CacheConfig {
                IsAuditingEnabled = true
            });
            var ca2 = new CacheAccessor(new CacheConfig {
                IsAuditingEnabled = true
            });

            await ca1.InitialiseIfNecessaryAsync();

            await ca2.InitialiseIfNecessaryAsync();

            var KEY   = CreateGuid();
            var VALUE = CreateGuid();

            // ACT
            await ca1.SetAsync(KEY, VALUE, TimeSpan.FromSeconds(5));

            await ca2.WaitForEvent(eCacheEvent.KeySetInMemory);

            var v1 = await ca2.GetWithMetaDataAsync <string>(KEY);

            var v2 = await ca1.GetWithMetaDataAsync <string>(KEY);

            await Task.Delay(5000);

            var v3 = await ca2.GetWithMetaDataAsync <string>(KEY);

            var v4 = await ca1.GetWithMetaDataAsync <string>(KEY);

            Assert.IsNotNull(v1.Data);
            Assert.IsNotNull(v2.Data);
            Assert.IsTrue(v1.IsFromInMemoryCache);
            Assert.IsTrue(v2.IsFromInMemoryCache);

            Assert.IsNull(v3.Data);
            Assert.IsNull(v4.Data);
            Assert.IsTrue(v3.IsFromCentralCacheServer);
            Assert.IsTrue(v4.IsFromCentralCacheServer);
        }
        public async Task Cache_Dist_Test_Caching_Byte_Arrays()
        {
            // ARRANGE
            var ca1 = new CacheAccessor(new CacheConfig {
                IsAuditingEnabled = true
            });
            var ca2 = new CacheAccessor(new CacheConfig {
                IsAuditingEnabled = true
            });

            await ca1.InitialiseIfNecessaryAsync();

            await ca2.InitialiseIfNecessaryAsync();

            var KEY1   = CreateGuid();
            var VALUE1 = new byte[] { 32, 34, 55 };
            var KEY2   = CreateGuid();
            var VALUE2 = new byte[] { 1, 11, 33 };

            // ACT
            await ca1.SetAsync(KEY1, VALUE1); // set the value

            await ca2.WaitForEvent(eCacheEvent.KeySetInMemory);

            await ca2.SetAsync(KEY2, VALUE2); // set the value

            await ca1.WaitForEvent(eCacheEvent.KeySetInMemory);

            var data1 = await ca2.GetWithMetaDataAsync <byte[]>(KEY1);

            var data2 = await ca1.GetWithMetaDataAsync <byte[]>(KEY2);

            Assert.AreEqual(VALUE1.Length, data1.Data.Length);
            Assert.AreEqual(VALUE2.Length, data2.Data.Length);
            Assert.AreEqual(VALUE1[0], data1.Data[0]);
            Assert.AreEqual(VALUE1[1], data1.Data[1]);
            Assert.AreEqual(VALUE1[2], data1.Data[2]);

            Assert.AreEqual(VALUE2[0], data2.Data[0]);
            Assert.AreEqual(VALUE2[1], data2.Data[1]);
            Assert.AreEqual(VALUE2[2], data2.Data[2]);
        }
        public async Task Cache_Dist_Test_That_Cache_Delete_Key_Works_OK()
        {
            // ARRANGE
            var ca1 = new CacheAccessor(new CacheConfig {
                IsAuditingEnabled = true
            });
            var ca2 = new CacheAccessor(new CacheConfig {
                IsAuditingEnabled = true
            });

            await ca1.InitialiseIfNecessaryAsync();

            await ca2.InitialiseIfNecessaryAsync();

            var KEY   = CreateGuid();
            var VALUE = CreateGuid();

            // ACT
            await ca1.SetAsync(KEY, VALUE); // set the value

            // Wait for propagation to ca2
            await ca2.WaitForEvent(eCacheEvent.KeySetInMemory);

            var v1 = await ca2.GetWithMetaDataAsync <string>(KEY);

            await ca2.DeleteAsync(KEY);

            var propagationTimeInMS = await ca1.WaitForEvent(eCacheEvent.KeyDeletedInMemory);

            var v2 = await ca1.GetWithMetaDataAsync <string>(KEY);

            var v3 = await ca2.GetWithMetaDataAsync <string>(KEY);

            // ASSERT
            Assert.IsNotNull(v1.Data);
            Assert.IsTrue(v1.IsFromInMemoryCache);
            Assert.IsNull(v2.Data);
            Assert.IsTrue(v2.IsFromCentralCacheServer);
            Assert.IsNull(v3.Data);
            Assert.IsTrue(v3.IsFromCentralCacheServer);
        }
        public void Cache_SetIsReallyAsync()
        {
            var cache = CacheAccessor.Create();
            var key   = Key();
            var value = CreateGuid();

            Task settingTask = cache.SetAsync(key, value);

            List <bool> flags = new List <bool>();

            while (true)
            {
                flags.Add(settingTask.IsCompleted);
                if (settingTask.IsCompleted)
                {
                    break;
                }
                Thread.Sleep(10);
            }

            Assert.IsTrue(flags.Where(x => x == false).Count() > 0, "There should be at least a few points where the setting task was running");
            Assert.IsTrue(flags.Where(x => x).Count() > 0, "Some flags should indicate the task completed");
        }
        public async Task Cache_TestThatConnectIsAsync()
        {
            const long MAX_CONNECTION_TIME_MS = 60000;

            // Arrange
            var key   = Key();
            var value = CreateGuid();

            // Put something in the cache, the close the connection
            using (var cache = CacheAccessor.Create())
                await cache.SetAsync(key, value);

            // Re-create the cache
            using (var cache = CacheAccessor.Create())
            {
                // Act
                var cacheValue = await cache.GetAsync <string>(key);

                Assert.IsNull(cacheValue);

                var sw = Stopwatch.StartNew();
                while (cache.Status == CacheAccessor.State.NotConnected || cache.Status == CacheAccessor.State.Connecting)
                {
                    await Task.Delay(500);

                    Assert.IsFalse(sw.ElapsedMilliseconds > MAX_CONNECTION_TIME_MS, "Connecting took too long ( > {0})", sw.ElapsedMilliseconds);
                }
                sw.Stop();
                Assert.IsFalse(sw.ElapsedMilliseconds > MAX_CONNECTION_TIME_MS, "Connecting took too long ( > {0})", sw.ElapsedMilliseconds);

                cacheValue = await cache.GetAsync <string>(key);

                Assert.AreEqual(value, cacheValue);

                await cache.DeleteAsync(key);
            }
        }
コード例 #22
0
        protected IDirectoryManager FactoryManager(ConfigSettingsDto settings = null, IDbContextFactory <LogContext> logCtxFactory = null, IDbContextFactory <DirectoryContext> dirCtxFactory = null, IDbContextFactory <ExportContext> xprtCtxFactory = null)
        {
            if (settings == null)
            {
                settings = new ConfigSettingsDto
                {
                    StateCacheTtlSeconds       = 1200,
                    TranslationCacheTtlSeconds = 1200
                }
            }
            ;

            if (logCtxFactory == null)
            {
                logCtxFactory = new SqliteInMemoryContextFactory <LogContext>();
            }

            if (dirCtxFactory == null)
            {
                dirCtxFactory = new SqliteInMemoryContextFactory <DirectoryContext>();
            }

            if (xprtCtxFactory == null)
            {
                xprtCtxFactory = new SqliteInMemoryContextFactory <ExportContext>();
            }

            var cacheAccessor  = new CacheAccessor(new MemoryCache(), settings);
            var dirAccessor    = new DirectoryAccessor(dirCtxFactory);
            var logAccessor    = new LogAccessor(logCtxFactory);
            var exportAccessor = new ExportAccessor(xprtCtxFactory);

            var dirEngine = new DirectoryEngine(dirAccessor, cacheAccessor, exportAccessor);

            return(new DirectoryManager(dirEngine, logAccessor));
        }
    }
        public async Task Cache_Dist_TestKeyValuePairPropagation()
        {
            // Arrange
            var KEY            = Key();
            var VALUE          = CreateGuid();
            var cacheAccessor1 = new CacheAccessor(new CacheConfig {
                IsAuditingEnabled = true
            });
            var cacheAccessor2 = new CacheAccessor(new CacheConfig {
                IsAuditingEnabled = true
            });
            await cacheAccessor1.InitialiseIfNecessaryAsync();

            await cacheAccessor2.InitialiseIfNecessaryAsync();

            cacheAccessor1.AuditLog.Clear();
            cacheAccessor2.AuditLog.Clear();

            // Act
            var v1 = await cacheAccessor1.GetWithMetaDataAsync <string>(KEY);

            await cacheAccessor1.SetAsync(KEY, VALUE, TimeSpan.FromMinutes(2));

            await Task.Delay(300);

            var logOfCA2 = cacheAccessor2.AuditLog.ToArray();

            var v2 = await cacheAccessor2.GetWithMetaDataAsync <string>(KEY);

            var v3 = await cacheAccessor2.GetWithMetaDataAsync <string>(KEY);

            var cacheAccessor3 = new CacheAccessor(new CacheConfig());
            await cacheAccessor3.InitialiseIfNecessaryAsync();

            var v6 = await cacheAccessor3.GetWithMetaDataAsync <string>(KEY);

            var v7 = await cacheAccessor3.GetWithMetaDataAsync <string>(KEY);

            await cacheAccessor2.DeleteAsync(KEY);

            var v4_1 = await cacheAccessor1.GetWithMetaDataAsync <string>(KEY);

            var v5_1 = await cacheAccessor2.GetWithMetaDataAsync <string>(KEY);

            await Task.Delay(300);

            var v4 = await cacheAccessor1.GetWithMetaDataAsync <string>(KEY);

            var v5 = await cacheAccessor2.GetWithMetaDataAsync <string>(KEY);

            Assert.IsTrue(logOfCA2.First().CachingEvent == eCacheEvent.KeySetInMemory, "The next audit event should have been to receive the key update message");
            Assert.IsTrue(v1.IsFromCentralCacheServer, "Trying to get something that isn't set, should _AT_LEAST_ try the central cache");
            Assert.IsTrue(v2.IsFromInMemoryCache, "Getting an item after setting it should mean that the in-memory cache has had time to catch up");
            Assert.IsTrue(v3.IsFromInMemoryCache, "The value should have propagated to cache accessor");
            Assert.IsNull(v4.Data, "The value should be null");
            Assert.IsNull(v5.Data, "The value should be null");
            Assert.IsTrue(v4.IsFromCentralCacheServer, "If a value is null, it should have tried the central cache server");
            Assert.IsTrue(v5.IsFromCentralCacheServer, "If a value is null, it should have tried the central cache server");

            Assert.IsNotNull(v6.Data, "The data shouldn't be null");
            Assert.IsTrue(v6.IsFromCentralCacheServer, "The value should have been retrieved from the central server");

            Assert.IsNotNull(v7.Data, "The data shouldn't be null");
            Assert.AreEqual(VALUE, v7.Data);
            Assert.IsTrue(v7.IsFromInMemoryCache, "The value should have been retrieved from the memory cache");

            // This test is too subjective for the build server
            // Assert.IsTrue(v7.ElapsedMilliseconds < v6.ElapsedMilliseconds, "It should have been quicker to get the value the second time");
        }
        public async Task Cache_Dist_Test_LargeVolumeOfCacheAccessors()
        {
            // Arrange
            var       inits      = new List <Task>();
            var       caches     = new List <CacheAccessor>();
            const int MAX_CACHES = 20;

            // Create MAX_CACHES cache accessors and therefore MAX_CACHES connections to redis
            for (int i = 0; i < MAX_CACHES; i++)
            {
                var c = new CacheAccessor(new CacheConfig {
                    IsAuditingEnabled = true
                });
                inits.Add(c.InitialiseIfNecessaryAsync());
                caches.Add(c);
            }

            await Task.WhenAll(inits.ToArray()); // wait for initialisation

            var cacheOne = caches.First();

            var KEY   = Key();
            var VALUE = new List <int>(Enumerable.Range(0, 1000));

            // Clear audit logs of all caches
            caches.ForEach(x => x.AuditLog.Clear());

            // ACT
            await cacheOne.SetAsync(KEY, VALUE);

            var valueFromOriginator = await cacheOne.GetWithMetaDataAsync <List <int> >(KEY);

            // Wait until the value has propagated
            var propagationTasks = new List <Task>();

            foreach (var cacher in caches)
            {
                if (cacher == cacheOne)
                {
                    continue;                     // ignore the originating cacher
                }
                propagationTasks.Add(cacher.WaitForEvent(eCacheEvent.KeySetInMemory, 60000));
            }
            await Task.WhenAll(propagationTasks.ToArray());

            var values = new List <CacheResponseDto <List <int> > >();

            for (int i = 0; i < MAX_CACHES; i++)
            {
                var currentCache = caches[i];
                values.Add(await currentCache.GetWithMetaDataAsync <List <int> >(KEY));
            }

            // ASSERT
            Assert.AreEqual(VALUE, valueFromOriginator.Data);
            Assert.IsTrue(valueFromOriginator.IsFromInMemoryCache);
            foreach (var item in values)
            {
                Assert.AreEqual(VALUE, item.Data);
                Assert.IsTrue(item.IsFromInMemoryCache);
            }

            for (int i = 1; i < MAX_CACHES; i++)
            {
                Assert.IsTrue(caches[i].AuditLog.First().CachingEvent == eCacheEvent.KeySetInMemory);
            }
        }
 public void Setup()
 {
     // Create cache that is actually connected
     _cacheThatsConnected = CacheAccessor.Create();
     _cacheThatsConnected.InitialiseIfNecessaryAsync().Wait();
 }