public void GetConfig_SecondTime_CertificateLoadingThrewException_ErrorLogged()
        {
            var builder     = new ConfigurationBuilder();
            var proxyConfig = builder.AddInMemoryCollection(new Dictionary <string, string>
            {
                ["Clusters:cluster1:Destinations:destinationA:Address"] = "https://localhost:10001/destC",
                ["Routes:0:RouteId"]       = "routeA",
                ["Routes:0:ClusterId"]     = "cluster1",
                ["Routes:0:Order"]         = "1",
                ["Routes:0:Match:Hosts:0"] = "host-B",
            }).Build();
            var certLoader = new Mock <ICertificateConfigLoader>(MockBehavior.Strict);

            using var certificate = TestResources.GetTestCertificate();
            certLoader.Setup(l => l.LoadCertificate(It.IsAny <IConfigurationSection>())).Throws(new FileNotFoundException());
            var logger = new Mock <ILogger <ConfigurationConfigProvider> >();

            logger.Setup(l => l.IsEnabled(LogLevel.Error)).Returns(true);
            var provider = new ConfigurationConfigProvider(logger.Object, proxyConfig, certLoader.Object);

            var firstSnapshot = provider.GetConfig();

            logger.Verify(l => l.Log(LogLevel.Error, It.IsAny <EventId>(), It.IsAny <string>(), It.IsAny <Exception>(), It.IsAny <Func <string, Exception, string> >()), Times.Never);

            // Add configuration entry here and trigger a change
            proxyConfig["Clusters:cluster1:HttpClient:ClientCertificate:Path"] = "mycert.pfx";

            TriggerOnChange(proxyConfig);

            var secondSnapshot = provider.GetConfig();

            Assert.Same(firstSnapshot, secondSnapshot);
            logger.Verify(l => l.Log(LogLevel.Error, EventIds.ConfigurationDataConversionFailed, It.IsAny <It.IsAnyType>(), It.IsAny <Exception>(), (Func <It.IsAnyType, Exception, string>)It.IsAny <object>()), Times.Once);
        }
        public void CachedCertificateIsDisposed_RemoveItFromCache()
        {
            var builder     = new ConfigurationBuilder();
            var proxyConfig = builder.AddInMemoryCollection(new Dictionary <string, string>
            {
                ["Clusters:cluster1:Destinations:destinationA:Address"] = "https://localhost:10001/destC",
                ["Clusters:cluster1:HttpClient:ClientCertificate:Path"] = "testCert.pfx",
                ["Routes:0:RouteId"]       = "routeA",
                ["Routes:0:ClusterId"]     = "cluster1",
                ["Routes:0:Order"]         = "1",
                ["Routes:0:Match:Hosts:0"] = "host-B",
            }).Build();
            var certLoader = new Mock <ICertificateConfigLoader>(MockBehavior.Strict);

            using var certificate = TestResources.GetTestCertificate();
            certLoader.Setup(l => l.LoadCertificate(It.IsAny <IConfigurationSection>())).Returns(() => TestResources.GetTestCertificate());
            var logger = new Mock <ILogger <ConfigurationConfigProvider> >();

            logger.Setup(l => l.IsEnabled(LogLevel.Error)).Returns(true);
            var provider = new ConfigurationConfigProvider(logger.Object, proxyConfig, certLoader.Object);

            // Get several certificates.
            var certificateConfig = new List <X509Certificate2>();

            for (var i = 0; i < 5; i++)
            {
                certificateConfig.AddRange(provider.GetConfig().Clusters.Select(c => c.HttpClient.ClientCertificate));
                if (i < 4)
                {
                    TriggerOnChange(proxyConfig);
                }
            }

            // Verify cache contents match the configuration objects.
            var cachedCertificates = GetCachedCertificates(provider);

            Assert.Equal(certificateConfig.Count, cachedCertificates.Length);
            for (var i = 0; i < certificateConfig.Count; i++)
            {
                Assert.Same(certificateConfig[i], cachedCertificates[i]);
            }

            // Get several certificates.
            certificateConfig[1].Dispose();
            certificateConfig[3].Dispose();

            // Trigger cache compaction.
            TriggerOnChange(proxyConfig);

            // Verify disposed certificates were purged out.
            cachedCertificates = GetCachedCertificates(provider);
            Assert.Equal(4, cachedCertificates.Length);
            Assert.Same(certificateConfig[0], cachedCertificates[0]);
            Assert.Same(certificateConfig[2], cachedCertificates[1]);
            Assert.Same(certificateConfig[4], cachedCertificates[2]);
        }
        public void GetConfig_ValidSerializedConfiguration_ConvertToAbstractionsSuccessfully()
        {
            var builder = new ConfigurationBuilder();

            using var stream = new MemoryStream(Encoding.UTF8.GetBytes(_validJsonConfig));
            var proxyConfig = builder.AddJsonStream(stream).Build();
            var certLoader  = new Mock <ICertificateConfigLoader>(MockBehavior.Strict);

            using var certificate = TestResources.GetTestCertificate();
            certLoader.Setup(l => l.LoadCertificate(It.Is <IConfigurationSection>(o => o["Path"] == "mycert.pfx" && o["Password"] == "myPassword1234"))).Returns(certificate);
            var logger = new Mock <ILogger <ConfigurationConfigProvider> >();

            var provider = new ConfigurationConfigProvider(logger.Object, proxyConfig, certLoader.Object);

            Assert.NotNull(provider);
            var abstractConfig = provider.GetConfig();

            VerifyValidAbstractConfig(_validConfigurationData, certificate, abstractConfig);
        }
        public void GetConfig_FirstTime_CertificateLoadingThrewException_Throws()
        {
            var builder     = new ConfigurationBuilder();
            var proxyConfig = builder.AddInMemoryCollection(new Dictionary <string, string>
            {
                ["Clusters:cluster1:Destinations:destinationA:Address"] = "https://localhost:10001/destC",
                ["Clusters:cluster1:HttpClient:ClientCertificate:Path"] = "mycert.pfx",
                ["Routes:0:RouteId"]       = "routeA",
                ["Routes:0:ClusterId"]     = "cluster1",
                ["Routes:0:Order"]         = "1",
                ["Routes:0:Match:Hosts:0"] = "host-B",
            }).Build();
            var certLoader = new Mock <ICertificateConfigLoader>(MockBehavior.Strict);

            using var certificate = TestResources.GetTestCertificate();
            certLoader.Setup(l => l.LoadCertificate(It.IsAny <IConfigurationSection>())).Throws(new FileNotFoundException());
            var logger   = new Mock <ILogger <ConfigurationConfigProvider> >();
            var provider = new ConfigurationConfigProvider(logger.Object, proxyConfig, certLoader.Object);

            Assert.ThrowsAny <FileNotFoundException>(() => provider.GetConfig());
        }
        public void GetConfig_ValidConfiguration_AllAbstractionsPropertiesAreSet()
        {
            var builder = new ConfigurationBuilder();

            using var stream = new MemoryStream(Encoding.UTF8.GetBytes(_validJsonConfig));
            var proxyConfig = builder.AddJsonStream(stream).Build();
            var certLoader  = new Mock <ICertificateConfigLoader>(MockBehavior.Strict);

            using var certificate = TestResources.GetTestCertificate();
            certLoader.Setup(l => l.LoadCertificate(It.Is <IConfigurationSection>(o => o["Path"] == "mycert.pfx" && o["Password"] == "myPassword1234"))).Returns(certificate);
            var logger = new Mock <ILogger <ConfigurationConfigProvider> >();

            var provider       = new ConfigurationConfigProvider(logger.Object, proxyConfig, certLoader.Object);
            var abstractConfig = (ConfigurationSnapshot)provider.GetConfig();

            var abstractionsNamespace = typeof(Cluster).Namespace;

            // Removed incompletely filled out instances.
            abstractConfig.Clusters = abstractConfig.Clusters.Where(c => c.Id == "cluster1").ToList();
            abstractConfig.Routes   = abstractConfig.Routes.Where(r => r.RouteId == "routeA").ToList();

            VerifyAllPropertiesAreSet(abstractConfig);

            void VerifyFullyInitialized(object obj, string name)
            {
                switch (obj)
                {
                case null:
                    Assert.True(false, $"Property {name} is not initialized.");
                    break;

                case Enum m:
                    Assert.NotEqual(0, (int)(object)m);
                    break;

                case string str:
                    Assert.NotEmpty(str);
                    break;

                case ValueType v:
                    var equals = Equals(Activator.CreateInstance(v.GetType()), v);
                    Assert.False(equals, $"Property {name} is not initialized.");
                    if (v.GetType().Namespace == abstractionsNamespace)
                    {
                        VerifyAllPropertiesAreSet(v);
                    }
                    break;

                case IDictionary d:
                    Assert.NotEmpty(d);
                    foreach (var value in d.Values)
                    {
                        VerifyFullyInitialized(value, name);
                    }
                    break;

                case IEnumerable e:
                    Assert.NotEmpty(e);
                    foreach (var item in e)
                    {
                        VerifyFullyInitialized(item, name);
                    }

                    var type = e.GetType();
                    if (!type.IsArray && type.Namespace == abstractionsNamespace)
                    {
                        VerifyAllPropertiesAreSet(e);
                    }
                    break;

                case object o:
                    if (o.GetType().Namespace == abstractionsNamespace)
                    {
                        VerifyAllPropertiesAreSet(o);
                    }
                    break;
                }
            }

            void VerifyAllPropertiesAreSet(object obj)
            {
                var properties = obj.GetType().GetProperties(BindingFlags.Instance | BindingFlags.Public).Cast <PropertyInfo>();

                foreach (var property in properties)
                {
                    VerifyFullyInitialized(property.GetValue(obj), $"{property.DeclaringType.Name}.{property.Name}");
                }
            }
        }