private void TestDiagnosticConfig(
            Func <ConfigurationBuilder, ConfigurationBuilder> modConfig,
            Func <EventProcessorBuilder, EventProcessorBuilder> modEvents,
            LdValue.ObjectBuilder expected
            )
        {
            var eventsBuilder = Components.SendEvents()
                                .EventSender(testEventSender);

            modEvents?.Invoke(eventsBuilder);
            var configBuilder = BasicConfig()
                                .DataSource(null)
                                .Events(eventsBuilder)
                                .Http(Components.HttpConfiguration().MessageHandler(new StubMessageHandler(HttpStatusCode.Unauthorized)))
                                .StartWaitTime(testStartWaitTime);

            configBuilder = modConfig is null ? configBuilder : modConfig(configBuilder);
            using (var client = new LdClient(configBuilder.Build()))
            {
                var payload = testEventSender.RequirePayload();

                Assert.Equal(EventDataKind.DiagnosticEvent, payload.Kind);
                Assert.Equal(1, payload.EventCount);

                var data = JsonOf(payload.Data);
                AssertJsonEqual(JsonFromValue("diagnostic-init"), data.Property("kind"));

                AssertJsonEqual(JsonOf(expected.Build().ToJsonString()), data.Property("configuration"));
            }
        }
        public void DiagnosticInitEventIsSent()
        {
            var config = BasicConfig()
                         .Events(Components.SendEvents().EventSender(testEventSender))
                         .Http(
                Components.HttpConfiguration().Wrapper(testWrapperName, testWrapperVersion)
                )
                         .Build();

            using (var client = new LdClient(config))
            {
                var payload = testEventSender.RequirePayload();

                Assert.Equal(EventDataKind.DiagnosticEvent, payload.Kind);
                Assert.Equal(1, payload.EventCount);

                var data = JsonOf(payload.Data);
                AssertJsonEqual(JsonFromValue("diagnostic-init"), data.Property("kind"));
                AssertJsonEqual(JsonFromValue("dotnet"), data.RequiredProperty("platform").Property("name"));
                AssertJsonEqual(JsonFromValue(ServerDiagnosticStore.GetDotNetTargetFramework()),
                                data.RequiredProperty("platform").Property("dotNetTargetFramework"));
                AssertJsonEqual(expectedSdk, data.Property("sdk"));
                AssertJsonEqual(JsonFromValue("dk-key"), data.Property("id").Property("sdkKeySuffix"));

                data.RequiredProperty("creationDate");
            }
        }
示例#3
0
        public void DiagnosticInitEventIsSent()
        {
            var config = Configuration.Builder(sdkKey)
                         .DataSource(Components.ExternalUpdatesOnly)
                         .Events(Components.SendEvents().EventSender(testEventSender))
                         .Http(
                Components.HttpConfiguration().Wrapper(testWrapperName, testWrapperVersion)
                )
                         .Logging(Components.Logging(testLogging))
                         .Build();

            using (var client = new LdClient(config))
            {
                var payload = testEventSender.RequirePayload();

                Assert.Equal(EventDataKind.DiagnosticEvent, payload.Kind);
                Assert.Equal(1, payload.EventCount);

                var data = LdValue.Parse(payload.Data);
                Assert.Equal("diagnostic-init", data.Get("kind").AsString);
                AssertHelpers.JsonEqual(ExpectedPlatform(), data.Get("platform"));
                AssertHelpers.JsonEqual(expectedSdk, data.Get("sdk"));
                Assert.Equal("DK_KEY", data.Get("id").Get("sdkKeySuffix").AsString);

                var timestamp = data.Get("creationDate").AsLong;
                Assert.NotEqual(0, timestamp);
            }
        }
示例#4
0
        private void TestDiagnosticConfig(
            Func <ConfigurationBuilder, ConfigurationBuilder> modConfig,
            Func <EventProcessorBuilder, EventProcessorBuilder> modEvents,
            LdValue.ObjectBuilder expected
            )
        {
            var eventsBuilder = Components.SendEvents()
                                .EventSender(testEventSender);

            modEvents?.Invoke(eventsBuilder);
            var configBuilder = Configuration.Builder(sdkKey)
                                .Events(eventsBuilder)
                                .Http(Components.HttpConfiguration().MessageHandler(new StubMessageHandler(HttpStatusCode.Unauthorized)))
                                .Logging(Components.Logging(testLogging))
                                .StartWaitTime(testStartWaitTime);

            modConfig?.Invoke(configBuilder);
            using (var client = new LdClient(configBuilder.Build()))
            {
                var payload = testEventSender.RequirePayload();

                Assert.Equal(EventDataKind.DiagnosticEvent, payload.Kind);
                Assert.Equal(1, payload.EventCount);

                var data = LdValue.Parse(payload.Data);
                Assert.Equal("diagnostic-init", data.Get("kind").AsString);

                AssertHelpers.JsonEqual(expected.Build(), data.Get("configuration"));
            }
        }
示例#5
0
 public void CustomConfigForEvents()
 {
     TestDiagnosticConfig(
         c => c.Http(Components.HttpConfiguration().MessageHandler(StubMessageHandler.EmptyStreamingResponse())),
         e => e.AllAttributesPrivate(true)
         .BaseUri(new Uri("http://custom"))
         .Capacity(333)
         .DiagnosticRecordingInterval(TimeSpan.FromMinutes(32))
         .FlushInterval(TimeSpan.FromMilliseconds(555))
         .InlineUsersInEvents(true)
         .UserKeysCapacity(444)
         .UserKeysFlushInterval(TimeSpan.FromMinutes(23)),
         ExpectedConfigProps.Base()
         .WithStoreDefaults()
         .WithStreamingDefaults()
         .Add("allAttributesPrivate", true)
         .Add("customEventsURI", true)
         .Add("diagnosticRecordingIntervalMillis", TimeSpan.FromMinutes(32).TotalMilliseconds)
         .Add("eventsCapacity", 333)
         .Add("eventsFlushIntervalMillis", 555)
         .Add("inlineUsersInEvents", true)
         .Add("samplingInterval", 0)     // obsolete, no way to set this
         .Add("userKeysCapacity", 444)
         .Add("userKeysFlushIntervalMillis", TimeSpan.FromMinutes(23).TotalMilliseconds)
         );
 }
 public void ConfigDefaults()
 {
     // Note that in all of the test configurations where the streaming or polling data source
     // is enabled, we're setting a fake HTTP message handler so it doesn't try to do any real
     // HTTP requests that would fail and (depending on timing) disrupt the test.
     TestDiagnosticConfig(
         c => c.Http(Components.HttpConfiguration().MessageHandler(StubMessageHandler.EmptyStreamingResponse())),
         null,
         ExpectedConfigProps.Base()
         );
 }
示例#7
0
 public void DefaultEventsBaseUri()
 {
     using (var client = new LdClient(
                BasicConfig()
                .Events(Components.SendEvents())
                .Http(Components.HttpConfiguration().MessageHandler(_stubHandler))
                .Build()))
     {
         var req = _stubHandler.Requests.ExpectValue();
         Assert.Equal(StandardEndpoints.DefaultEventsBaseUri, BaseUriOf(req.RequestUri));
     }
 }
 public void CustomConfigForStreaming()
 {
     TestDiagnosticConfig(
         c => c.DataSource(
             Components.StreamingDataSource()
             .InitialReconnectDelay(TimeSpan.FromSeconds(2))
             )
         .Http(Components.HttpConfiguration().MessageHandler(StubMessageHandler.EmptyStreamingResponse())),
         null,
         ExpectedConfigProps.Base()
         .Set("reconnectTimeMillis", 2000)
         );
 }
        public void CustomConfigForHTTP()
        {
            TestDiagnosticConfig(
                c => c.Http(
                    Components.HttpConfiguration()
                    .ConnectTimeout(TimeSpan.FromMilliseconds(8888))
                    .ReadTimeout(TimeSpan.FromMilliseconds(9999))
                    .MessageHandler(StubMessageHandler.EmptyStreamingResponse())
                    ),
                null,
                ExpectedConfigProps.Base()
                .Set("connectTimeoutMillis", 8888)
                .Set("socketTimeoutMillis", 9999)
                .Set("usingProxy", false)
                .Set("usingProxyAuthenticator", false)
                );

            var proxyUri = new Uri("http://fake");
            var proxy    = new WebProxy(proxyUri);

            TestDiagnosticConfig(
                c => c.Http(
                    Components.HttpConfiguration()
                    .Proxy(proxy)
                    .MessageHandler(StubMessageHandler.EmptyStreamingResponse())
                    ),
                null,
                ExpectedConfigProps.Base()
                .Set("usingProxy", true)
                );

            var credentials = new CredentialCache();

            credentials.Add(proxyUri, "Basic", new NetworkCredential("user", "pass"));
            var proxyWithAuth = new WebProxy(proxyUri);

            proxyWithAuth.Credentials = credentials;
            TestDiagnosticConfig(
                c => c.Http(
                    Components.HttpConfiguration()
                    .Proxy(proxyWithAuth)
                    .MessageHandler(StubMessageHandler.EmptyStreamingResponse())
                    ),
                null,
                ExpectedConfigProps.Base()
                .Set("usingProxy", true)
                .Set("usingProxyAuthenticator", true)
                );
        }
示例#10
0
        public void CustomEventsBaseUriWithDeprecatedMethod()
        {
            using (var client = new LdClient(
                       BasicConfig()
                       .Events(Components.SendEvents().BaseUri(CustomUri))
                       .Http(Components.HttpConfiguration().MessageHandler(_stubHandler))
                       .Build()))
            {
                var req = _stubHandler.Requests.ExpectValue();
                Assert.Equal(CustomUri, BaseUriOf(req.RequestUri));

                AssertLogMessageRegex(false, LogLevel.Error,
                                      "You have set custom ServiceEndpoints without specifying");
            }
        }
示例#11
0
        public void CustomPollingDataSourceBaseUri()
        {
            using (var client = new LdClient(
                       BasicConfig()
                       .DataSource(Components.PollingDataSource())
                       .Http(Components.HttpConfiguration().MessageHandler(_stubHandler))
                       .ServiceEndpoints(Components.ServiceEndpoints().Polling(CustomUri))
                       .Build()))
            {
                var req = _stubHandler.Requests.ExpectValue();
                Assert.Equal(CustomUri, BaseUriOf(req.RequestUri));

                Assert.False(LogCapture.HasMessageWithRegex(LogLevel.Error,
                                                            "You have set custom ServiceEndpoints without specifying"));
            }
        }
        static void TestHttpClientCanUseProxy(Handler responseHandler,
                                              HttpConfigurationTestAction testActionShouldSucceed)
        {
            // To verify that a web proxy will really be used if provided, we set up a proxy
            // configuration pointing to our test server. It's not really a proxy server,
            // but if it receives a request that was intended for some other URI (instead of
            // the SDK trying to access that other URI directly), then that's a success.

            using (var server = HttpServer.Start(responseHandler))
            {
                var proxy       = new WebProxy(server.Uri);
                var httpConfig  = Components.HttpConfiguration().Proxy(proxy);
                var fakeBaseUri = new Uri("http://not-a-real-host");

                testActionShouldSucceed(fakeBaseUri, httpConfig, server);
            }
        }
示例#13
0
        public void ErrorIsLoggedIfANecessaryUriIsNotSetWhenOtherCustomUrisAreSet()
        {
            var logCapture1 = Logs.Capture();

            using (var client = new LdClient(
                       BasicConfig()
                       .DataSource(Components.StreamingDataSource())
                       .Http(Components.HttpConfiguration().MessageHandler(_stubHandler))
                       .Logging(logCapture1)
                       .ServiceEndpoints(Components.ServiceEndpoints().Polling(CustomUri))
                       .Build()))
            {
                Assert.True(logCapture1.HasMessageWithRegex(LogLevel.Error,
                                                            "You have set custom ServiceEndpoints without specifying the Streaming base URI"));
            }

            var logCapture2 = Logs.Capture();

            using (var client = new LdClient(
                       BasicConfig()
                       .DataSource(Components.PollingDataSource())
                       .Http(Components.HttpConfiguration().MessageHandler(_stubHandler))
                       .Logging(logCapture2)
                       .ServiceEndpoints(Components.ServiceEndpoints().Events(CustomUri))
                       .Build()))
            {
                Assert.True(logCapture2.HasMessageWithRegex(LogLevel.Error,
                                                            "You have set custom ServiceEndpoints without specifying the Polling base URI"));
            }

            var logCapture3 = Logs.Capture();

            using (var client = new LdClient(
                       BasicConfig()
                       .Events(Components.SendEvents())
                       .Http(Components.HttpConfiguration().MessageHandler(_stubHandler))
                       .Logging(logCapture3)
                       .ServiceEndpoints(Components.ServiceEndpoints().Streaming(CustomUri))
                       .Build()))
            {
                Assert.True(logCapture3.HasMessageWithRegex(LogLevel.Error,
                                                            "You have set custom ServiceEndpoints without specifying the Events base URI"));
            }
        }
        public void CustomConfigForPolling()
        {
            TestDiagnosticConfig(
                c => c.DataSource(Components.PollingDataSource())
                .Http(Components.HttpConfiguration().MessageHandler(StubMessageHandler.EmptyPollingResponse())),
                null,
                ExpectedConfigProps.Base()
                .WithPollingDefaults()
                );

            TestDiagnosticConfig(
                c => c.DataSource(
                    Components.PollingDataSource()
                    .PollInterval(TimeSpan.FromSeconds(45))
                    )
                .Http(Components.HttpConfiguration().MessageHandler(StubMessageHandler.EmptyPollingResponse())),
                null,
                ExpectedConfigProps.Base()
                .WithPollingDefaults()
                .Set("pollingIntervalMillis", 45000)
                );
        }
        static void TestHttpClientCanUseCustomMessageHandler(Handler responseHandler,
                                                             HttpConfigurationTestAction testActionShouldSucceed)
        {
            // To verify that a custom HttpMessageHandler will really be used if provided, we
            // create one that behaves normally except that it modifies the request path.
            // Then we verify that the server received a request with a modified path.

            var recordAndDelegate = Handlers.Record(out var recorder).Then(responseHandler);

            using (var server = HttpServer.Start(recordAndDelegate))
            {
                var suffix         = "/modified-by-test";
                var messageHandler = new MessageHandlerThatAddsPathSuffix(suffix);
                var httpConfig     = Components.HttpConfiguration().MessageHandler(messageHandler);

                testActionShouldSucceed(server.Uri, httpConfig, server);

                var request = recorder.RequireRequest();
                Assert.EndsWith(suffix, request.Path);
                recorder.RequireNoRequests(TimeSpan.FromMilliseconds(100));
            }
        }
示例#16
0
 public void CustomConfigGeneralProperties()
 {
     TestDiagnosticConfig(
         c => c.Http(
             Components.HttpConfiguration()
             .ConnectTimeout(TimeSpan.FromMilliseconds(1001))
             .ReadTimeout(TimeSpan.FromMilliseconds(1003))
             .MessageHandler(StubMessageHandler.EmptyStreamingResponse())
             )
         .StartWaitTime(TimeSpan.FromMilliseconds(2)),
         null,
         LdValue.BuildObject()
         .WithStoreDefaults()
         .WithEventsDefaults()
         .WithStreamingDefaults()
         .Add("connectTimeoutMillis", 1001)
         .Add("socketTimeoutMillis", 1003)
         .Add("startWaitMillis", 2)
         .Add("usingProxy", false)
         .Add("usingProxyAuthenticator", false)
         );
 }
示例#17
0
        public void CustomConfigForPolling()
        {
            TestDiagnosticConfig(
                c => c.DataSource(
                    Components.PollingDataSource()
                    .PollInterval(TimeSpan.FromSeconds(45))
                    )
                .Http(Components.HttpConfiguration().MessageHandler(StubMessageHandler.EmptyPollingResponse())),
                null,
                ExpectedConfigProps.Base()
                .WithStoreDefaults()
                .WithEventsDefaults()
                .Add("customBaseURI", false)
                .Add("customStreamURI", false)
                .Add("pollingIntervalMillis", 45000)
                .Add("streamingDisabled", true)
                .Add("usingRelayDaemon", false)
                );

            TestDiagnosticConfig(
                c => c.DataSource(
                    Components.PollingDataSource()
                    .BaseUri(new Uri("http://custom"))
                    .PollInterval(TimeSpan.FromSeconds(45))
                    )
                .Http(Components.HttpConfiguration().MessageHandler(StubMessageHandler.EmptyPollingResponse())),
                null,
                ExpectedConfigProps.Base()
                .WithStoreDefaults()
                .WithEventsDefaults()
                .Add("customBaseURI", true)
                .Add("customStreamURI", false)
                .Add("pollingIntervalMillis", 45000)
                .Add("streamingDisabled", true)
                .Add("usingRelayDaemon", false)
                );
        }
示例#18
0
        public void CustomConfigForHTTP()
        {
            TestDiagnosticConfig(
                c => c.Http(
                    Components.HttpConfiguration()
                    .ConnectTimeout(TimeSpan.FromMilliseconds(8888))
                    .ReadTimeout(TimeSpan.FromMilliseconds(9999))
                    .MessageHandler(StubMessageHandler.EmptyStreamingResponse())
                    ),
                null,
                LdValue.BuildObject()
                .Add("connectTimeoutMillis", 8888)
                .Add("socketTimeoutMillis", 9999)
                .Add("startWaitMillis", LdClientDiagnosticEventTest.testStartWaitTime.TotalMilliseconds)
                .Add("usingProxy", false)
                .Add("usingProxyAuthenticator", false)
                .WithStoreDefaults()
                .WithStreamingDefaults()
                .WithEventsDefaults()
                );

            var proxyUri = new Uri("http://fake");
            var proxy    = new WebProxy(proxyUri);

            TestDiagnosticConfig(
                c => c.Http(
                    Components.HttpConfiguration()
                    .Proxy(proxy)
                    .MessageHandler(StubMessageHandler.EmptyStreamingResponse())
                    ),
                null,
                LdValue.BuildObject()
                .Add("connectTimeoutMillis", HttpConfigurationBuilder.DefaultConnectTimeout.TotalMilliseconds)
                .Add("socketTimeoutMillis", HttpConfigurationBuilder.DefaultReadTimeout.TotalMilliseconds)
                .Add("startWaitMillis", LdClientDiagnosticEventTest.testStartWaitTime.TotalMilliseconds)
                .Add("usingProxy", true)
                .Add("usingProxyAuthenticator", false)
                .WithStoreDefaults()
                .WithStreamingDefaults()
                .WithEventsDefaults()
                );

            var credentials = new CredentialCache();

            credentials.Add(proxyUri, "Basic", new NetworkCredential("user", "pass"));
            var proxyWithAuth = new WebProxy(proxyUri);

            proxyWithAuth.Credentials = credentials;
            TestDiagnosticConfig(
                c => c.Http(
                    Components.HttpConfiguration()
                    .Proxy(proxyWithAuth)
                    .MessageHandler(StubMessageHandler.EmptyStreamingResponse())
                    ),
                null,
                LdValue.BuildObject()
                .Add("connectTimeoutMillis", HttpConfigurationBuilder.DefaultConnectTimeout.TotalMilliseconds)
                .Add("socketTimeoutMillis", HttpConfigurationBuilder.DefaultReadTimeout.TotalMilliseconds)
                .Add("startWaitMillis", LdClientDiagnosticEventTest.testStartWaitTime.TotalMilliseconds)
                .Add("usingProxy", true)
                .Add("usingProxyAuthenticator", true)
                .WithStoreDefaults()
                .WithStreamingDefaults()
                .WithEventsDefaults()
                );
        }
示例#19
0
        /// <summary>
        /// Creates a new client to connect to LaunchDarkly with a custom configuration.
        /// </summary>
        /// <remarks>
        /// <para>
        /// Applications should instantiate a single instance for the lifetime of the application. In
        /// unusual cases where an application needs to evaluate feature flags from different LaunchDarkly
        /// projects or environments, you may create multiple clients, but they should still be retained
        /// for the lifetime of the application rather than created per request or per thread.
        /// </para>
        /// <para>
        /// Normally, the client will begin attempting to connect to LaunchDarkly as soon as you call the
        /// constructor. The constructor returns as soon as any of the following things has happened:
        /// </para>
        /// <list type="number">
        /// <item><description> It has successfully connected to LaunchDarkly and received feature flag data. In this
        /// case, <see cref="Initialized"/> will be true, and the <see cref="DataSourceStatusProvider"/>
        /// will return a state of <see cref="DataSourceState.Valid"/>. </description></item>
        /// <item><description> It has not succeeded in connecting within the <see cref="ConfigurationBuilder.StartWaitTime(TimeSpan)"/>
        /// timeout (the default for this is 5 seconds). This could happen due to a network problem or a
        /// temporary service outage. In this case, <see cref="Initialized"/> will be false, and the
        /// <see cref="DataSourceStatusProvider"/> will return a state of <see cref="DataSourceState.Initializing"/>,
        /// indicating that the SDK will still continue trying to connect in the background. </description></item>
        /// <item><description> It has encountered an unrecoverable error: for instance, LaunchDarkly has rejected the
        /// SDK key. Since an invalid key will not become valid, the SDK will not retry in this case.
        /// <see cref="Initialized"/> will be false, and the <see cref="DataSourceStatusProvider"/> will
        /// return a state of <see cref="DataSourceState.Off"/>. </description></item>
        /// </list>
        /// <para>
        /// If you have specified <see cref="ConfigurationBuilder.Offline"/> mode or
        /// <see cref="Components.ExternalUpdatesOnly"/>, the constructor returns immediately without
        /// trying to connect to LaunchDarkly.
        /// </para>
        /// <para>
        /// Failure to connect to LaunchDarkly will never cause the constructor to throw an exception.
        /// Under any circumstance where it is not able to get feature flag data from LaunchDarkly (and
        /// therefore <see cref="Initialized"/> is false), if it does not have any other source of data
        /// (such as a persistent data store) then feature flag evaluations will behave the same as if
        /// the flags were not found: that is, they will return whatever default value is specified in
        /// your code.
        /// </para>
        /// </remarks>
        /// <param name="config">a client configuration object (which includes an SDK key)</param>
        /// <example>
        /// <code>
        ///     var config = Configuration.Builder("my-sdk-key")
        ///         .AllAttributesPrivate(true)
        ///         .EventCapacity(1000)
        ///         .Build();
        ///     var client = new LDClient(config);
        /// </code>
        /// </example>
        /// <seealso cref="LdClient.LdClient(string)"/>
        public LdClient(Configuration config)
        {
            _configuration = config;

            var logConfig = (config.LoggingConfigurationFactory ?? Components.Logging())
                            .CreateLoggingConfiguration();

            _log = logConfig.LogAdapter.Logger(logConfig.BaseLoggerName ?? LogNames.DefaultBase);
            _log.Info("Starting LaunchDarkly client {0}",
                      AssemblyVersions.GetAssemblyVersionStringForType(typeof(LdClient)));
            _evalLog = _log.SubLogger(LogNames.EvaluationSubLog);

            var basicConfig = new BasicConfiguration(config, _log);
            var httpConfig  = (config.HttpConfigurationFactory ?? Components.HttpConfiguration())
                              .CreateHttpConfiguration(basicConfig);
            ServerDiagnosticStore diagnosticStore = _configuration.DiagnosticOptOut ? null :
                                                    new ServerDiagnosticStore(_configuration, basicConfig, httpConfig);

            var taskExecutor = new TaskExecutor(this, _log);

            var clientContext = new LdClientContext(basicConfig, httpConfig, diagnosticStore, taskExecutor);

            var dataStoreUpdates = new DataStoreUpdatesImpl(taskExecutor, _log.SubLogger(LogNames.DataStoreSubLog));

            _dataStore = (_configuration.DataStoreFactory ?? Components.InMemoryDataStore)
                         .CreateDataStore(clientContext, dataStoreUpdates);
            _dataStoreStatusProvider = new DataStoreStatusProviderImpl(_dataStore, dataStoreUpdates);

            var bigSegmentsConfig = (_configuration.BigSegmentsConfigurationFactory ?? Components.BigSegments(null))
                                    .CreateBigSegmentsConfiguration(clientContext);

            _bigSegmentStoreWrapper = bigSegmentsConfig.Store is null ? null :
                                      new BigSegmentStoreWrapper(
                bigSegmentsConfig,
                taskExecutor,
                _log.SubLogger(LogNames.BigSegmentsSubLog)
                );
            _bigSegmentStoreStatusProvider = new BigSegmentStoreStatusProviderImpl(_bigSegmentStoreWrapper);

            _evaluator = new Evaluator(
                GetFlag,
                GetSegment,
                _bigSegmentStoreWrapper == null ? (Func <string, BigSegmentsInternalTypes.BigSegmentsQueryResult>)null :
                _bigSegmentStoreWrapper.GetUserMembership,
                _log
                );

            var eventProcessorFactory =
                config.Offline ? Components.NoEvents :
                (_configuration.EventProcessorFactory ?? Components.SendEvents());

            _eventProcessor = eventProcessorFactory.CreateEventProcessor(clientContext);

            var dataSourceUpdates = new DataSourceUpdatesImpl(_dataStore, _dataStoreStatusProvider,
                                                              taskExecutor, _log, logConfig.LogDataSourceOutageAsErrorAfter);
            IDataSourceFactory dataSourceFactory =
                config.Offline ? Components.ExternalUpdatesOnly :
                (_configuration.DataSourceFactory ?? Components.StreamingDataSource());

            _dataSource = dataSourceFactory.CreateDataSource(clientContext, dataSourceUpdates);
            _dataSourceStatusProvider = new DataSourceStatusProviderImpl(dataSourceUpdates);
            _flagTracker = new FlagTrackerImpl(dataSourceUpdates,
                                               (string key, User user) => JsonVariation(key, user, LdValue.Null));

            var initTask = _dataSource.Start();

            if (!(_dataSource is ComponentsImpl.NullDataSource))
            {
                _log.Info("Waiting up to {0} milliseconds for LaunchDarkly client to start...",
                          _configuration.StartWaitTime.TotalMilliseconds);
            }

            try
            {
                var success = initTask.Wait(_configuration.StartWaitTime);
                if (!success)
                {
                    _log.Warn("Timeout encountered waiting for LaunchDarkly client initialization");
                }
            }
            catch (AggregateException)
            {
                // StreamProcessor may throw an exception if initialization fails, because we want that behavior
                // in the Xamarin client. However, for backward compatibility we do not want to throw exceptions
                // from the LdClient constructor in the .NET client, so we'll just swallow this.
            }
        }
        public void TestConfigForServiceEndpoints()
        {
            TestDiagnosticConfig(
                c => c.ServiceEndpoints(Components.ServiceEndpoints().RelayProxy("http://custom"))
                .Http(Components.HttpConfiguration().MessageHandler(StubMessageHandler.EmptyStreamingResponse())),
                null,
                ExpectedConfigProps.Base()
                .Set("customBaseURI", false)     // this is the polling base URI, not relevant in streaming mode
                .Set("customStreamURI", true)
                .Set("customEventsURI", true)
                );

            TestDiagnosticConfig(
                c => c.ServiceEndpoints(Components.ServiceEndpoints().RelayProxy("http://custom"))
                .DataSource(Components.PollingDataSource())
                .Http(Components.HttpConfiguration().MessageHandler(StubMessageHandler.EmptyPollingResponse())),
                null,
                ExpectedConfigProps.Base()
                .WithPollingDefaults()
                .Set("customBaseURI", true)
                .Set("customEventsURI", true)
                );

            TestDiagnosticConfig(
                c => c.ServiceEndpoints(Components.ServiceEndpoints()
                                        .Streaming("http://custom-streaming")
                                        .Polling("http://custom-polling")
                                        .Events("http://custom-events"))
                .Http(Components.HttpConfiguration().MessageHandler(StubMessageHandler.EmptyStreamingResponse())),
                null,
                ExpectedConfigProps.Base()
                .Set("customBaseURI", false)     // this is the polling base URI, not relevant in streaming mode
                .Set("customStreamURI", true)
                .Set("customEventsURI", true)
                );

            TestDiagnosticConfig(
                c => c.DataSource(
#pragma warning disable CS0618  // using deprecated symbol
                    Components.StreamingDataSource()
                    .BaseUri(new Uri("http://custom"))
#pragma warning restore CS0618
                    )
                .Http(Components.HttpConfiguration().MessageHandler(StubMessageHandler.EmptyStreamingResponse())),
                null,
                ExpectedConfigProps.Base()
                .Set("customStreamURI", true)
                );

            TestDiagnosticConfig(
                c => c.DataSource(
#pragma warning disable CS0618  // using deprecated symbol
                    Components.PollingDataSource().BaseUri(new Uri("http://custom"))
#pragma warning restore CS0618
                    )
                .Http(Components.HttpConfiguration().MessageHandler(StubMessageHandler.EmptyPollingResponse())),
                null,
                ExpectedConfigProps.Base()
                .WithPollingDefaults()
                .Set("customBaseURI", true)
                );
        }