예제 #1
0
        public void RefreshTests_SetDirtyForcesNextRefresh()
        {
            IConfigurationRefresher refresher = null;
            var mockClient = GetMockConfigurationClient();

            var config = new ConfigurationBuilder()
                         .AddAzureAppConfiguration(options =>
            {
                options.Client = mockClient.Object;
                options.Select("TestKey*");
                options.ConfigureRefresh(refreshOptions =>
                {
                    refreshOptions.Register("TestKey1", "label")
                    .SetCacheExpiration(TimeSpan.FromDays(1));
                });

                refresher = options.GetRefresher();
            })
                         .Build();

            Assert.Equal("TestValue1", config["TestKey1"]);
            FirstKeyValue.Value = "newValue";

            refresher.RefreshAsync().Wait();
            Assert.Equal("TestValue1", config["TestKey1"]);

            refresher.SetDirty(TimeSpan.FromSeconds(1));

            // Wait for the cache to expire based on the randomized delay in SetDirty()
            Thread.Sleep(1200);

            refresher.RefreshAsync().Wait();
            Assert.Equal("newValue", config["TestKey1"]);
        }
예제 #2
0
        public async Task RefreshTests_UpdatesAllSettingsIfInitialLoadFails()
        {
            var mockResponse = new Mock <Response>();
            var mockClient   = new Mock <ConfigurationClient>(MockBehavior.Strict, TestHelpers.CreateMockEndpointString());

            mockClient.SetupSequence(c => c.GetConfigurationSettingsAsync(It.IsAny <SettingSelector>(), It.IsAny <CancellationToken>()))
            .Throws(new RequestFailedException("Request failed"))
            .Throws(new RequestFailedException("Request failed"))
            .Returns(new MockAsyncPageable(_kvCollection));

            mockClient.SetupSequence(c => c.GetConfigurationSettingAsync("TestKey1", It.IsAny <string>(), It.IsAny <CancellationToken>()))
            .Returns(Task.FromResult(Response.FromValue(_kvCollection.FirstOrDefault(s => s.Key == "TestKey1" && s.Label == "label"), mockResponse.Object)));

            IConfigurationRefresher refresher     = null;
            IConfiguration          configuration = new ConfigurationBuilder()
                                                    .AddAzureAppConfiguration(options =>
            {
                options.Select("TestKey*");
                options.Client = mockClient.Object;
                options.ConfigureRefresh(refreshOptions =>
                {
                    refreshOptions.Register("TestKey1", "label")
                    .SetCacheExpiration(TimeSpan.FromSeconds(1));
                });

                refresher = options.GetRefresher();
            }, optional: true)
                                                    .Build();

            // Validate initial load failed to retrieve any setting
            Assert.Null(configuration["TestKey1"]);
            Assert.Null(configuration["TestKey2"]);
            Assert.Null(configuration["TestKey3"]);

            // Act
            await Assert.ThrowsAsync <RequestFailedException>(async() =>
            {
                await refresher.RefreshAsync();
            });

            await refresher.RefreshAsync();

            Assert.Null(configuration["TestKey1"]);
            Assert.Null(configuration["TestKey2"]);
            Assert.Null(configuration["TestKey3"]);

            // Wait for the cache to expire
            Thread.Sleep(1500);

            await refresher.RefreshAsync();

            // Validate all settings were loaded, including the ones not registered for refresh
            Assert.Equal("TestValue1", configuration["TestKey1"]);
            Assert.Equal("TestValue2", configuration["TestKey2"]);
            Assert.Equal("TestValue3", configuration["TestKey3"]);
        }
        public void RefreshAsyncUpdatesConfig()
        {
            // Arrange
            var mockResponse = new Mock <Response>();
            var mockClient   = GetMockConfigurationClient();

            IConfigurationRefresher refresher = null;

            var config = new ConfigurationBuilder()
                         .AddAzureAppConfiguration(options =>
            {
                options.Client = mockClient.Object;
                options.Select("*");
                options.ConfigureRefresh(refreshOptions =>
                {
                    refreshOptions.Register("TestKey1", "label")
                    .SetCacheExpiration(TimeSpan.FromDays(30));
                });
                refresher = options.GetRefresher();
            })
                         .Build();


            Assert.Equal("TestValue1", config["TestKey1"]);
            FirstKeyValue.Value = "newValue1";

            refresher.ProcessPushNotification(_pushNotificationList.First(), TimeSpan.FromSeconds(0));
            refresher.RefreshAsync().Wait();

            Assert.Equal("newValue1", config["TestKey1"]);
        }
예제 #4
0
        public void RefreshTests_RefreshAsyncThrowsOnRequestFailedException()
        {
            IConfigurationRefresher refresher = null;
            var mockClient = GetMockConfigurationClient();

            var config = new ConfigurationBuilder()
                         .AddAzureAppConfiguration(options =>
            {
                options.Client = mockClient.Object;
                options.Select("TestKey*");
                options.ConfigureRefresh(refreshOptions =>
                {
                    refreshOptions.Register("TestKey1")
                    .SetCacheExpiration(TimeSpan.FromSeconds(1));
                });

                refresher = options.GetRefresher();
            })
                         .Build();

            Assert.Equal("TestValue1", config["TestKey1"]);
            FirstKeyValue.Value = "newValue";

            mockClient.Setup(c => c.GetConfigurationSettingAsync(It.IsAny <string>(), It.IsAny <string>(), It.IsAny <CancellationToken>()))
            .Throws(new RequestFailedException("Request failed."));

            // Wait for the cache to expire
            Thread.Sleep(1500);

            Action action = () => refresher.RefreshAsync().Wait();

            Assert.Throws <AggregateException>(action);

            Assert.NotEqual("newValue", config["TestKey1"]);
        }
예제 #5
0
        public void RefreshTests_RefreshIsSkippedIfKvNotInSelectAndCacheIsNotExpired()
        {
            IConfigurationRefresher refresher = null;
            var mockClient = GetMockConfigurationClientSelectKeyLabel();

            var config = new ConfigurationBuilder()
                         .AddAzureAppConfiguration(options =>
            {
                options.Client = mockClient.Object;
                options.Select("TestKey2", "label");
                options.ConfigureRefresh(refreshOptions =>
                {
                    refreshOptions.Register("TestKey1", "label")
                    .SetCacheExpiration(TimeSpan.FromSeconds(10));
                });

                refresher = options.GetRefresher();
            })
                         .Build();

            Assert.Equal("TestValue1", config["TestKey1"]);
            FirstKeyValue.Value = "newValue1";

            refresher.RefreshAsync().Wait();

            Assert.Equal("TestValue1", config["TestKey1"]);
        }
예제 #6
0
        public async Task <IActionResult> Run([HttpTrigger(AuthorizationLevel.Function, "get", "post", Route = null)] HttpRequest req, ILogger log)
        {
            log.LogInformation("El trigger HTTP con C#, proceso un request.");
            string keyVaultEntry   = "proxymusk";
            string messageKeyVault = "keyvault es local";
            await _configurationRefresher.TryRefreshAsync();

            if (!isLocal)
            {
                await _configurationRefresher.RefreshAsync();

                messageKeyVault = _configuration[keyVaultEntry];
            }

            bool flag = await _featureManagerSnapshot.IsEnabledAsync("ActivacionMensaje");

            string keyName = "TestApp:Settings:Message02";
            string message = _configuration[keyName];

            if (flag)
            {
                GuardaenBD(message, log);
            }
            return(message != null
                ? (ActionResult) new OkObjectResult($"La cadena recuperada desde AppConfig fue '{message}', y el valor desde KeyVault era '{messageKeyVault}' {flag} :)")
                : new BadRequestObjectResult($"Please create a key-value with the key '{keyName}' in App Configuration, gracias."));
        }
예제 #7
0
        public void RefreshTests_RefreshIsSkippedIfCacheIsNotExpired()
        {
            IConfigurationRefresher refresher = null;
            var mockClient = GetMockConfigurationClient();

            var config = new ConfigurationBuilder()
                         .AddAzureAppConfiguration(options =>
            {
                options.Client = mockClient.Object;
                options.Select("TestKey*");
                options.ConfigureRefresh(refreshOptions =>
                {
                    refreshOptions.Register("TestKey1")
                    .SetCacheExpiration(TimeSpan.FromSeconds(10));
                });

                refresher = options.GetRefresher();
            })
                         .Build();

            Assert.Equal("TestValue1", config["TestKey1"]);
            FirstKeyValue.Value = "newValue1";

            // Wait for some time but not enough to let the cache expire
            Thread.Sleep(5000);

            refresher.RefreshAsync().Wait();

            Assert.Equal("TestValue1", config["TestKey1"]);
        }
        public void SyncTokenUpdatesCorrectNumberOfTimes()
        {
            // Arrange
            var mockResponse = new Mock <Response>();
            var mockClient   = GetMockConfigurationClient();

            IConfigurationRefresher refresher = null;

            var config = new ConfigurationBuilder()
                         .AddAzureAppConfiguration(options =>
            {
                options.Client = mockClient.Object;
                options.Select("*");
                options.ConfigureRefresh(refreshOptions =>
                {
                    refreshOptions.Register("TestKey1", "label")
                    .SetCacheExpiration(TimeSpan.FromDays(30));
                });
                refresher = options.GetRefresher();
            })
                         .Build();

            foreach (PushNotification pushNotification in _pushNotificationList)
            {
                refresher.ProcessPushNotification(pushNotification, TimeSpan.FromSeconds(0));
                refresher.RefreshAsync().Wait();
            }

            mockClient.Verify(c => c.GetConfigurationSettingAsync(It.IsAny <ConfigurationSetting>(), It.IsAny <bool>(), It.IsAny <CancellationToken>()), Times.Exactly(_pushNotificationList.Count));
            mockClient.Verify(c => c.UpdateSyncToken(It.IsAny <string>()), Times.Exactly(_pushNotificationList.Count));
        }
        public async Task <IActionResult> Run(
            [HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = null)] HttpRequest req,
            ILogger log)
        {
            if (bool.TryParse(req.Query["refresh"], out var result) && result)
            {
                await _refresher.RefreshAsync();
            }

            var responseMessage = $"Message: {_config.Message}{Environment.NewLine}Secret: {_config.Secret}";

            return(new OkObjectResult(responseMessage));
        }
        public void SecretsWithDifferentRefreshIntervals()
        {
            IConfigurationRefresher refresher = null;
            TimeSpan shortCacheExpirationTime = TimeSpan.FromSeconds(1);
            TimeSpan longCacheExpirationTime  = TimeSpan.FromDays(1);

            var mockResponse = new Mock <Response>();
            var mockClient   = new Mock <ConfigurationClient>(MockBehavior.Strict, TestHelpers.CreateMockEndpointString());

            mockClient.Setup(c => c.GetConfigurationSettingsAsync(It.IsAny <SettingSelector>(), It.IsAny <CancellationToken>()))
            .Returns(new MockAsyncPageable(_kvCollectionPageOne));

            var mockSecretClient = new Mock <SecretClient>(MockBehavior.Strict);

            mockSecretClient.SetupGet(client => client.VaultUri).Returns(new Uri("https://keyvault-theclassics.vault.azure.net"));
            mockSecretClient.Setup(client => client.GetSecretAsync(It.IsAny <string>(), It.IsAny <string>(), It.IsAny <CancellationToken>()))
            .Returns((string name, string version, CancellationToken cancellationToken) =>
                     Task.FromResult((Response <KeyVaultSecret>) new MockResponse <KeyVaultSecret>(new KeyVaultSecret(name, _secretValue))));

            var config = new ConfigurationBuilder()
                         .AddAzureAppConfiguration(options =>
            {
                options.Client = mockClient.Object;
                options.ConfigureKeyVault(kv =>
                {
                    kv.Register(mockSecretClient.Object);
                    kv.SetSecretRefreshInterval("TK1", shortCacheExpirationTime);
                    kv.SetSecretRefreshInterval(longCacheExpirationTime);
                });

                refresher = options.GetRefresher();
            })
                         .Build();

            Assert.Equal(_secretValue, config["TK1"]);
            Assert.Equal(_secretValue, config["TK2"]);

            // Sleep to let the secret cache expire for one secret
            Thread.Sleep(shortCacheExpirationTime);
            refresher.RefreshAsync().Wait();

            Assert.Equal(_secretValue, config["TK1"]);
            Assert.Equal(_secretValue, config["TK2"]);

            // Validate that 3 calls were made to fetch secrets from KeyVault because the secret cache had expired for only one secret.
            mockSecretClient.Verify(client => client.GetSecretAsync(It.IsAny <string>(), It.IsAny <string>(), It.IsAny <CancellationToken>()), Times.Exactly(3));
        }
예제 #11
0
        public static async Task Main(string[] args)
        {
            IConfiguration          _configuration = null;
            IConfigurationRefresher _refresher     = null;
            var RefreshInterval = TimeSpan.FromSeconds(Double.Parse(ConfigurationManager.AppSettings["RefreshInterval"]));
            var builder         = new ConfigurationBuilder()
                                  .AddAzureAppConfiguration(options =>
            {
                options
                .Connect(ConfigurationManager.AppSettings["AzureApplicationConnectionString"])
                .ConfigureRefresh(refresh =>
                {
                    refresh
                    .Register("TestApp:Settings:Message")
                    .SetCacheExpiration(RefreshInterval);
                })
                .UseFeatureFlags(refresh =>
                {
                    refresh.CacheExpirationTime = RefreshInterval;
                });
                _refresher = options.GetRefresher();
            });

            _configuration = builder.Build();
            IServiceCollection services = new ServiceCollection();

            services.AddSingleton <IConfiguration>(_configuration).AddFeatureManagement();
            using (ServiceProvider serviceProvider = services.BuildServiceProvider())
            {
                IFeatureManager featureManager = serviceProvider.GetRequiredService <IFeatureManager>();
                if (await featureManager.IsEnabledAsync("Beta"))
                {
                    Console.WriteLine("Welcome to the beta!");
                }
            }
            PrintConfig(_configuration);
            await _refresher.RefreshAsync();

            PrintConfig(_configuration);
        }
예제 #12
0
        public async Task RefreshTests_RefreshAsyncThrowsOnExceptionWhenOptionalIsTrueForInitialLoad()
        {
            IConfigurationRefresher refresher = null;
            var mockResponse = new Mock <Response>();
            var mockClient   = new Mock <ConfigurationClient>(MockBehavior.Strict, TestHelpers.CreateMockEndpointString());

            mockClient.SetupSequence(c => c.GetConfigurationSettingsAsync(It.IsAny <SettingSelector>(), It.IsAny <CancellationToken>()))
            .Returns(new MockAsyncPageable(_kvCollection))
            .Throws(new RequestFailedException("Request failed."));

            mockClient.SetupSequence(c => c.GetConfigurationSettingAsync("TestKey1", It.IsAny <string>(), It.IsAny <CancellationToken>()))
            .Returns(Task.FromResult(Response.FromValue(_kvCollection.FirstOrDefault(s => s.Key == "TestKey1"), mockResponse.Object)))
            .Returns(Task.FromResult(Response.FromValue(_kvCollection.FirstOrDefault(s => s.Key == "TestKey1"), mockResponse.Object)));

            var config = new ConfigurationBuilder()
                         .AddAzureAppConfiguration(options =>
            {
                options.Client = mockClient.Object;
                options.Select("TestKey*");
                options.ConfigureRefresh(refreshOptions =>
                {
                    refreshOptions.Register("TestKey1", refreshAll: true)
                    .SetCacheExpiration(TimeSpan.FromSeconds(1));
                });

                refresher = options.GetRefresher();
            }, optional: true)
                         .Build();

            Assert.Equal("TestValue1", config["TestKey1"]);
            FirstKeyValue.Value = "newValue";

            // Wait for the cache to expire
            Thread.Sleep(1500);

            await Assert.ThrowsAsync <RequestFailedException>(async() =>
                                                              await refresher.RefreshAsync()
                                                              );
        }
예제 #13
0
        public void RefreshTests_RefreshAllTrueUpdatesEntireConfiguration()
        {
            var serviceCollection             = new List <ConfigurationSetting>(_kvCollection);
            IConfigurationRefresher refresher = null;
            var mockClient = GetMockConfigurationClient();

            var config = new ConfigurationBuilder()
                         .AddAzureAppConfiguration(options =>
            {
                options.Client = mockClient.Object;
                options.Select("TestKey*");
                options.ConfigureRefresh(refreshOptions =>
                {
                    refreshOptions.Register("TestKey1", "label", refreshAll: true)
                    .SetCacheExpiration(TimeSpan.FromSeconds(1));
                });

                refresher = options.GetRefresher();
            })
                         .Build();

            Assert.Equal("TestValue1", config["TestKey1"]);
            Assert.Equal("TestValue2", config["TestKey2"]);
            Assert.Equal("TestValue3", config["TestKey3"]);

            serviceCollection.ForEach(kv => kv.Value = "newValue");

            // Wait for the cache to expire
            Thread.Sleep(1500);

            refresher.RefreshAsync().Wait();

            Assert.Equal("newValue", config["TestKey1"]);
            Assert.Equal("newValue", config["TestKey2"]);
            Assert.Equal("newValue", config["TestKey3"]);
        }
예제 #14
0
 private void WaitAndRefresh(IConfigurationRefresher refresher, int millisecondsDelay)
 {
     Task.Delay(millisecondsDelay).Wait();
     refresher.RefreshAsync().Wait();
 }
예제 #15
0
        public void RefreshTests_RefreshAllForNonExistentSentinelDoesNothing()
        {
            var serviceCollection = new List <ConfigurationSetting>(_kvCollection);

            var mockResponse = new Mock <Response>();
            var mockClient   = new Mock <ConfigurationClient>(MockBehavior.Strict, TestHelpers.CreateMockEndpointString());

            Response <ConfigurationSetting> GetSettingFromService(string k, string l, CancellationToken ct)
            {
                return(Response.FromValue(serviceCollection.FirstOrDefault(s => s.Key == k), mockResponse.Object));
            }

            Response <ConfigurationSetting> GetIfChanged(ConfigurationSetting setting, bool cond, CancellationToken ct)
            {
                var newSetting = serviceCollection.FirstOrDefault(s => s.Key == setting.Key);
                var unchanged  = (newSetting.Key == setting.Key && newSetting.Label == setting.Label && newSetting.Value == setting.Value);
                var response   = new MockResponse(unchanged ? 304 : 200);

                return(Response.FromValue(newSetting, response));
            }

            mockClient.Setup(c => c.GetConfigurationSettingsAsync(It.IsAny <SettingSelector>(), It.IsAny <CancellationToken>()))
            .Returns(() =>
            {
                var copy = new List <ConfigurationSetting>();
                foreach (var setting in serviceCollection)
                {
                    copy.Add(TestHelpers.CloneSetting(setting));
                }
                ;

                return(new MockAsyncPageable(copy));
            });

            mockClient.Setup(c => c.GetConfigurationSettingAsync(It.IsAny <string>(), It.IsAny <string>(), It.IsAny <CancellationToken>()))
            .ReturnsAsync((Func <string, string, CancellationToken, Response <ConfigurationSetting> >)GetSettingFromService);

            mockClient.Setup(c => c.GetConfigurationSettingAsync(It.IsAny <ConfigurationSetting>(), It.IsAny <bool>(), It.IsAny <CancellationToken>()))
            .ReturnsAsync((Func <ConfigurationSetting, bool, CancellationToken, Response <ConfigurationSetting> >)GetIfChanged);

            IConfigurationRefresher refresher = null;

            var config = new ConfigurationBuilder()
                         .AddAzureAppConfiguration(options =>
            {
                options.Client = mockClient.Object;
                options.Select("TestKey*");
                options.ConfigureRefresh(refreshOptions =>
                {
                    refreshOptions.Register("TestKey1")
                    .Register("NonExistentKey", refreshAll: true)
                    .SetCacheExpiration(TimeSpan.FromSeconds(1));
                });

                refresher = options.GetRefresher();
            })
                         .Build();

            Assert.Equal("TestValue1", config["TestKey1"]);
            Assert.Equal("TestValue2", config["TestKey2"]);
            Assert.Equal("TestValue3", config["TestKey3"]);

            serviceCollection.ElementAt(0).Value = "newValue1";
            serviceCollection.ElementAt(1).Value = "newValue2";
            serviceCollection.Remove(serviceCollection.Last());

            // Wait for the cache to expire
            Thread.Sleep(1500);

            refresher.RefreshAsync().Wait();

            // Validate that key-values registered for refresh were updated
            Assert.Equal("newValue1", config["TestKey1"]);

            // Validate that other key-values were not updated, which means refresh all wasn't triggered
            Assert.Equal("TestValue2", config["TestKey2"]);
            Assert.Equal("TestValue3", config["TestKey3"]);
        }
예제 #16
0
        private async Task RefreshAsync(CancellationToken cancellationToken = default)
        {
            await configurationClient.SetConfigurationSettingAsync(ConfigurationConstants.SentinelKey, DateTime.UtcNow.ToString("o"), cancellationToken : cancellationToken).ConfigureAwait(false);

            await configurationRefresher.RefreshAsync().ConfigureAwait(false);
        }
        public void CachedSecretIsInvalidatedWhenRefreshAllIsTrue()
        {
            IConfigurationRefresher refresher = null;
            TimeSpan cacheExpirationTime      = TimeSpan.FromSeconds(1);

            var mockResponse = new Mock <Response>();
            var mockClient   = new Mock <ConfigurationClient>(MockBehavior.Strict, TestHelpers.CreateMockEndpointString());

            Response <ConfigurationSetting> GetTestKey(string key, string label, CancellationToken cancellationToken)
            {
                return(Response.FromValue(TestHelpers.CloneSetting(sentinelKv), mockResponse.Object));
            }

            Response <ConfigurationSetting> GetIfChanged(ConfigurationSetting setting, bool onlyIfChanged, CancellationToken cancellationToken)
            {
                var unchanged = sentinelKv.Key == setting.Key && sentinelKv.Label == setting.Label && sentinelKv.Value == setting.Value;
                var response  = new MockResponse(unchanged ? 304 : 200);

                return(Response.FromValue(sentinelKv, response));
            }

            mockClient.Setup(c => c.GetConfigurationSettingsAsync(It.IsAny <SettingSelector>(), It.IsAny <CancellationToken>()))
            .Returns(new MockAsyncPageable(new List <ConfigurationSetting> {
                _kv
            }));

            mockClient.Setup(c => c.GetConfigurationSettingAsync(It.IsAny <string>(), It.IsAny <string>(), It.IsAny <CancellationToken>()))
            .ReturnsAsync((Func <string, string, CancellationToken, Response <ConfigurationSetting> >)GetTestKey);

            mockClient.Setup(c => c.GetConfigurationSettingAsync(It.IsAny <ConfigurationSetting>(), It.IsAny <bool>(), It.IsAny <CancellationToken>()))
            .ReturnsAsync((Func <ConfigurationSetting, bool, CancellationToken, Response <ConfigurationSetting> >)GetIfChanged);

            var mockSecretClient = new Mock <SecretClient>(MockBehavior.Strict);

            mockSecretClient.SetupGet(client => client.VaultUri).Returns(new Uri("https://keyvault-theclassics.vault.azure.net"));
            mockSecretClient.Setup(client => client.GetSecretAsync(It.IsAny <string>(), It.IsAny <string>(), It.IsAny <CancellationToken>()))
            .Returns((string name, string version, CancellationToken cancellationToken) =>
                     Task.FromResult((Response <KeyVaultSecret>) new MockResponse <KeyVaultSecret>(new KeyVaultSecret(name, _secretValue))));

            var config = new ConfigurationBuilder()
                         .AddAzureAppConfiguration(options =>
            {
                options.Client = mockClient.Object;
                options.ConfigureKeyVault(kv =>
                {
                    kv.Register(mockSecretClient.Object);
                    kv.SetSecretRefreshInterval(_kv.Key, TimeSpan.FromDays(1));
                });

                options.ConfigureRefresh(refreshOptions =>
                {
                    refreshOptions.Register("Sentinel", refreshAll: true)
                    .SetCacheExpiration(cacheExpirationTime);
                });

                refresher = options.GetRefresher();
            })
                         .Build();

            Assert.Equal("Value1", config["Sentinel"]);
            Assert.Equal(_secretValue, config[_kv.Key]);

            // Update sentinel key-value to trigger refresh operation
            sentinelKv.Value = "Value2";
            Thread.Sleep(cacheExpirationTime);
            refresher.RefreshAsync().Wait();

            Assert.Equal("Value2", config["Sentinel"]);
            Assert.Equal(_secretValue, config[_kv.Key]);

            // Validate that 2 calls were made to fetch secrets from KeyVault
            // Even though Key Vault refresh interval has not elapsed, refreshAll trigger should fetch secret from Key Vault again
            mockSecretClient.Verify(client => client.GetSecretAsync(It.IsAny <string>(), It.IsAny <string>(), It.IsAny <CancellationToken>()), Times.Exactly(2));
        }