public void BackgroundDisabledState() { var testData = TestData.DataSource(); var backgrounder = new MockBackgroundModeManager(); var config = BasicConfig() .BackgroundModeManager(backgrounder) .DataSource(testData) .EnableBackgroundUpdating(false) .Build(); using (var client = TestUtil.CreateClient(config, BasicUser)) { Assert.True(client.DataSourceStatusProvider.WaitFor(DataSourceState.Valid, TimeSpan.FromSeconds(5))); var statuses = new EventSink <DataSourceStatus>(); client.DataSourceStatusProvider.StatusChanged += statuses.Add; backgrounder.UpdateBackgroundMode(true); var newStatus1 = statuses.ExpectValue(); Assert.Equal(DataSourceState.BackgroundDisabled, newStatus1.State); backgrounder.UpdateBackgroundMode(false); var newStatus2 = statuses.ExpectValue(); Assert.Equal(DataSourceState.Initializing, newStatus2.State); var newStatus3 = statuses.ExpectValue(); Assert.Equal(DataSourceState.Valid, newStatus3.State); } }
public void DataSourceStatusIsInterruptedAfterErrorIfAlreadyInitialized() { var dataSourceFactory = new CapturingDataSourceFactory(); var config = BasicConfig() .DataSource(dataSourceFactory) .Build(); using (var client = TestUtil.CreateClient(config, BasicUser)) { var statuses = new EventSink <DataSourceStatus>(); client.DataSourceStatusProvider.StatusChanged += statuses.Add; dataSourceFactory.UpdateSink.UpdateStatus(DataSourceState.Valid, null); var newStatus1 = statuses.ExpectValue(); Assert.Equal(DataSourceState.Valid, newStatus1.State); var errorInfo = DataSourceStatus.ErrorInfo.FromHttpError(503); dataSourceFactory.UpdateSink.UpdateStatus(DataSourceState.Interrupted, errorInfo); var newStatus2 = statuses.ExpectValue(); Assert.Equal(DataSourceState.Interrupted, newStatus2.State); Assert.Equal(errorInfo, newStatus2.LastError); } }
public void SetOfflineStatusOverridesNetworkUnavailableStatus() { var testData = TestData.DataSource(); var connectivity = new MockConnectivityStateManager(false); var config = BasicConfig() .DataSource(testData) .ConnectivityStateManager(connectivity) .Offline(true) .Build(); using (var client = TestUtil.CreateClient(config, BasicUser)) { Assert.True(client.DataSourceStatusProvider.WaitFor(DataSourceState.SetOffline, TimeSpan.FromSeconds(5))); var statuses = new EventSink <DataSourceStatus>(); client.DataSourceStatusProvider.StatusChanged += statuses.Add; client.SetOffline(false, TimeSpan.FromSeconds(1)); var newStatus1 = statuses.ExpectValue(); Assert.Equal(DataSourceState.NetworkUnavailable, newStatus1.State); connectivity.Connect(true); var newStatus2 = statuses.ExpectValue(); Assert.Equal(DataSourceState.Initializing, newStatus2.State); var newStatus3 = statuses.ExpectValue(); Assert.Equal(DataSourceState.Valid, newStatus3.State); } }
public void PollingDetectsStaleStatus() { SetStoreTimestamp(UnixMillisecondTime.Now.PlusMillis(5000)); // future time, definitely not stale var bsConfig = Components.BigSegments(_storeFactory) .StatusPollInterval(TimeSpan.FromMilliseconds(10)) .StaleAfter(TimeSpan.FromMilliseconds(200)) .CreateBigSegmentsConfiguration(BasicContext); using (var sw = new BigSegmentStoreWrapper(bsConfig, BasicTaskExecutor, TestLogger)) { var status1 = sw.GetStatus(); Assert.False(status1.Stale); var statuses = new EventSink <BigSegmentStoreStatus>(); sw.StatusChanged += statuses.Add; SetStoreTimestamp(UnixMillisecondTime.Now.PlusMillis(-200)); var status2 = statuses.ExpectValue(); Assert.True(status2.Stale); SetStoreTimestamp(UnixMillisecondTime.Now.PlusMillis(5000)); var status3 = statuses.ExpectValue(); Assert.False(status3.Stale); } }
public void StatusListenerIsNotifiedOnFailureAndRecovery(TestParams testParams) { using (var wrapper = MakeWrapper(testParams)) { var dataStoreStatusProvider = new DataStoreStatusProviderImpl(wrapper, _dataStoreUpdates); var statuses = new EventSink <DataStoreStatus>(); dataStoreStatusProvider.StatusChanged += statuses.Add; CauseStoreError(_core, wrapper); var status1 = statuses.ExpectValue(); Assert.False(status1.Available); Assert.False(status1.RefreshNeeded); Assert.True(logCapture.HasMessageWithRegex(LogLevel.Warn, "Detected persistent store unavailability")); MakeStoreAvailable(_core); var status2 = statuses.ExpectValue(); Assert.True(status2.Available); Assert.Equal(!testParams.CacheMode.IsCachedIndefinitely, status2.RefreshNeeded); Assert.True(logCapture.HasMessageWithRegex(LogLevel.Warn, "Persistent store is available again")); } }
public void CanUseCustomEventDispatcher() { var actions = new EventSink <Action>(); var customExecutor = new TaskExecutor(MyEventSender, actions.Enqueue, testLogger); var values1 = new EventSink <string>(); var values2 = new EventSink <string>(); myEvent += values1.Add; myEvent += values2.Add; customExecutor.ScheduleEvent("hello", myEvent); values1.ExpectNoValue(); values2.ExpectNoValue(); var action1 = actions.ExpectValue(); var action2 = actions.ExpectValue(); actions.ExpectNoValue(); action1(); action2(); Assert.Equal("hello", values1.ExpectValue()); Assert.Equal("hello", values2.ExpectValue()); }
public FullDataSet ExpectInit(User user) { var action = Assert.IsType <ReceivedInit>(Actions.ExpectValue(TimeSpan.FromSeconds(5))); AssertHelpers.UsersEqual(user, action.User); return(action.Data); }
public void CacheIsWrittenToStoreAfterRecoveryIfTtlIsInfinite() { using (var wrapper = MakeWrapper(CachedIndefinitely)) { var dataStoreStatusProvider = new DataStoreStatusProviderImpl(wrapper, _dataStoreUpdates); var statuses = new EventSink <DataStoreStatus>(); dataStoreStatusProvider.StatusChanged += statuses.Add; string key1 = "key1", key2 = "key2"; var item1 = new TestItem("name1"); var item2 = new TestItem("name2"); wrapper.Init(new TestDataBuilder() .Add(TestDataKind, key1, 1, item1) .Build()); // In infinite cache mode, we do *not* expect exceptions thrown by the store to be propagated; it will // swallow the error, but also go into polling/recovery mode. Note that even though the store rejects // this update, it'll still be cached. CauseStoreError(_core, wrapper); Assert.Equal(FakeError, Assert.Throws(FakeError.GetType(), () => wrapper.Upsert(TestDataKind, key1, item1.WithVersion(2)))); Assert.Equal(item1.WithVersion(2), wrapper.Get(TestDataKind, key1)); var status1 = statuses.ExpectValue(); Assert.False(status1.Available); Assert.False(status1.RefreshNeeded); // While the store is still down, try to update it again - the update goes into the cache Assert.Equal(FakeError, Assert.Throws(FakeError.GetType(), () => wrapper.Upsert(TestDataKind, key2, item2.WithVersion(1)))); Assert.Equal(item2.WithVersion(1), wrapper.Get(TestDataKind, key2)); // Verify that this update did not go into the underlying data yet Assert.False(_core.Data[TestDataKind].TryGetValue(key2, out _)); // Now simulate the store coming back up MakeStoreAvailable(_core); // Wait for the poller to notice this and publish a new status var status2 = statuses.ExpectValue(); Assert.True(status2.Available); Assert.False(status2.RefreshNeeded); // Once that has happened, the cache should have been written to the store Assert.Equal(item1.SerializedWithVersion(2), _core.Data[TestDataKind][key1]); Assert.Equal(item2.SerializedWithVersion(1), _core.Data[TestDataKind][key2]); } }
public void ClientSendsFlagValueChangeEvents() { var testData = TestData.DataSource(); var config = BasicConfig().DataSource(testData).Build(); var flagKey = "flagkey"; testData.Update(testData.Flag(flagKey).Variation(true)); using (var client = TestUtil.CreateClient(config, BasicUser)) { var eventSink1 = new EventSink <FlagValueChangeEvent>(); var eventSink2 = new EventSink <FlagValueChangeEvent>(); EventHandler <FlagValueChangeEvent> listener1 = eventSink1.Add; EventHandler <FlagValueChangeEvent> listener2 = eventSink2.Add; client.FlagTracker.FlagValueChanged += listener1; client.FlagTracker.FlagValueChanged += listener2; eventSink1.ExpectNoValue(); eventSink2.ExpectNoValue(); testData.Update(testData.Flag(flagKey).Variation(false)); var event1 = eventSink1.ExpectValue(); var event2 = eventSink2.ExpectValue(); Assert.Equal(flagKey, event1.Key); Assert.Equal(LdValue.Of(true), event1.OldValue); Assert.Equal(LdValue.Of(false), event1.NewValue); Assert.Equal(event1, event2); eventSink1.ExpectNoValue(); eventSink2.ExpectNoValue(); } }
public void EventIsSentForChangedFlagOnInit() { var events = new EventSink <FlagValueChangeEvent>(); _flagTracker.FlagValueChanged += events.Add; var initData1 = new DataSetBuilder() .Add("key1", new FeatureFlagBuilder().Value(LdValue.Of(true)).Variation(0).Build()) .Add("key2", new FeatureFlagBuilder().Value(LdValue.Of(true)).Variation(0).Build()) .Build(); _updateSink.Init(_basicUser, initData1); var initData2 = new DataSetBuilder() .Add("key1", new FeatureFlagBuilder().Value(LdValue.Of(false)).Variation(1).Build()) .Add("key2", new FeatureFlagBuilder().Value(LdValue.Of(true)).Variation(0).Build()) .Build(); _updateSink.Init(_basicUser, initData2); var e = events.ExpectValue(); Assert.Equal("key1", e.Key); Assert.Equal(LdValue.Of(true), e.OldValue); Assert.Equal(LdValue.Of(false), e.NewValue); Assert.False(e.Deleted); }
public void ValueChangesAreTrackedSeparatelyForEachUser() { var events = new EventSink <FlagValueChangeEvent>(); _flagTracker.FlagValueChanged += events.Add; var initDataForBasicUser = new DataSetBuilder() .Add("key1", new FeatureFlagBuilder().Version(100).Value(LdValue.Of("a")).Variation(1).Build()) .Add("key2", new FeatureFlagBuilder().Version(200).Value(LdValue.Of("b")).Variation(2).Build()) .Build(); _updateSink.Init(_basicUser, initDataForBasicUser); var initDataForOtherUser = new DataSetBuilder() .Add("key1", new FeatureFlagBuilder().Version(100).Value(LdValue.Of("c")).Variation(3).Build()) .Add("key2", new FeatureFlagBuilder().Version(200).Value(LdValue.Of("d")).Variation(4).Build()) .Build(); _updateSink.Init(_otherUser, initDataForOtherUser); events.ExpectNoValue(); _updateSink.Upsert(_basicUser, "key1", new FeatureFlagBuilder().Version(101).Value(LdValue.Of("c")).Variation(3).Build().ToItemDescriptor()); var e = events.ExpectValue(); Assert.Equal("key1", e.Key); Assert.Equal(LdValue.Of("a"), e.OldValue); Assert.Equal(LdValue.Of("c"), e.NewValue); }
public void PassesConfiguredEventSenderToEventHandler() { var gotSender = new EventSink <object>(); myEvent += (sender, args) => gotSender.Enqueue(sender); executor.ScheduleEvent("hello", myEvent); Assert.Equal(MyEventSender, gotSender.ExpectValue()); }
public void FlagChangeListeners() { var flagKey = "flagKey"; var store = new InMemoryDataStore(); var dataSourceUpdates = TestUtils.BasicDataSourceUpdates(store, testLogger); var tracker = new FlagTrackerImpl(dataSourceUpdates, null); var eventSink1 = new EventSink <FlagChangeEvent>(); var eventSink2 = new EventSink <FlagChangeEvent>(); EventHandler <FlagChangeEvent> listener1 = eventSink1.Add; EventHandler <FlagChangeEvent> listener2 = eventSink2.Add; tracker.FlagChanged += listener1; tracker.FlagChanged += listener2; eventSink1.ExpectNoValue(); eventSink2.ExpectNoValue(); var flagV1 = new FeatureFlagBuilder(flagKey).Version(1).Build(); dataSourceUpdates.Upsert(DataModel.Features, flagKey, DescriptorOf(flagV1)); var event1 = eventSink1.ExpectValue(); var event2 = eventSink2.ExpectValue(); Assert.Equal(flagKey, event1.Key); Assert.Equal(flagKey, event2.Key); eventSink1.ExpectNoValue(); eventSink2.ExpectNoValue(); tracker.FlagChanged -= listener2; var flagV2 = new FeatureFlagBuilder(flagKey).Version(2).Build(); dataSourceUpdates.Upsert(DataModel.Features, flagKey, DescriptorOf(flagV2)); var event3 = eventSink1.ExpectValue(); Assert.Equal(flagKey, event3.Key); eventSink2.ExpectNoValue(); }
public void DataSourceStatusIsRestoredWhenNoLongerSetOffline() { var testData = TestData.DataSource(); var config = BasicConfig().DataSource(testData).Offline(true).Build(); using (var client = TestUtil.CreateClient(config, BasicUser)) { Assert.True(client.DataSourceStatusProvider.WaitFor(DataSourceState.SetOffline, TimeSpan.FromSeconds(5))); var statuses = new EventSink <DataSourceStatus>(); client.DataSourceStatusProvider.StatusChanged += statuses.Add; client.SetOffline(false, TimeSpan.FromSeconds(1)); var newStatus1 = statuses.ExpectValue(); Assert.Equal(DataSourceState.Initializing, newStatus1.State); var newStatus2 = statuses.ExpectValue(); Assert.Equal(DataSourceState.Valid, newStatus2.State); } }
public void FlagValueChangeListener() { var flagKey = "important-flag"; var user = User.WithKey("important-user"); var otherUser = User.WithKey("unimportant-user"); var store = new InMemoryDataStore(); var dataSourceUpdates = TestUtils.BasicDataSourceUpdates(store, testLogger); var resultMap = new Dictionary <KeyValuePair <string, User>, LdValue>(); var tracker = new FlagTrackerImpl(dataSourceUpdates, (key, u) => resultMap[new KeyValuePair <string, User>(key, u)]); resultMap[new KeyValuePair <string, User>(flagKey, user)] = LdValue.Of(false); resultMap[new KeyValuePair <string, User>(flagKey, otherUser)] = LdValue.Of(false); var eventSink1 = new EventSink <FlagValueChangeEvent>(); var eventSink2 = new EventSink <FlagValueChangeEvent>(); var eventSink3 = new EventSink <FlagValueChangeEvent>(); var listener1 = tracker.FlagValueChangeHandler(flagKey, user, eventSink1.Add); var listener2 = tracker.FlagValueChangeHandler(flagKey, user, eventSink2.Add); var listener3 = tracker.FlagValueChangeHandler(flagKey, otherUser, eventSink3.Add); tracker.FlagChanged += listener1; tracker.FlagChanged += listener2; tracker.FlagChanged -= listener2; // just verifying that removing a listener works tracker.FlagChanged += listener3; eventSink1.ExpectNoValue(); eventSink2.ExpectNoValue(); eventSink3.ExpectNoValue(); // make the flag true for the first user only, and broadcast a flag change event resultMap[new KeyValuePair <string, User>(flagKey, user)] = LdValue.Of(true); var flagV1 = new FeatureFlagBuilder(flagKey).Version(1).Build(); dataSourceUpdates.Upsert(DataModel.Features, flagKey, DescriptorOf(flagV1)); // eventSink1 receives a value change event var event1 = eventSink1.ExpectValue(); Assert.Equal(flagKey, event1.Key); Assert.Equal(LdValue.Of(false), event1.OldValue); Assert.Equal(LdValue.Of(true), event1.NewValue); eventSink1.ExpectNoValue(); // eventSink2 doesn't receive one, because it was unregistered eventSink2.ExpectNoValue(); // eventSink3 doesn't receive one, because the flag's value hasn't changed for otherUser eventSink3.ExpectNoValue(); }
public void SendsEvent() { var values1 = new EventSink <string>(); var values2 = new EventSink <string>(); myEvent += values1.Add; myEvent += values2.Add; executor.ScheduleEvent("hello", myEvent); Assert.Equal("hello", values1.ExpectValue()); Assert.Equal("hello", values2.ExpectValue()); }
public void PollingDetectsStoreUnavailability() { SetStoreTimestamp(UnixMillisecondTime.Now); var bsConfig = Components.BigSegments(_storeFactory) .StatusPollInterval(TimeSpan.FromMilliseconds(10)) .StaleAfter(TimeSpan.FromDays(1)) .CreateBigSegmentsConfiguration(BasicContext); using (var sw = new BigSegmentStoreWrapper(bsConfig, BasicTaskExecutor, TestLogger)) { var status1 = sw.GetStatus(); Assert.True(status1.Available); var statuses = new EventSink <BigSegmentStoreStatus>(); sw.StatusChanged += statuses.Add; SetStoreStatusError(new Exception("sorry")); var status2 = statuses.ExpectValue(); if (status2.Available) { // depending on timing, we might or might not receive an initial update of Available = true status2 = statuses.ExpectValue(); Assert.False(status2.Available); } Assert.Equal(status2, sw.GetStatus()); SetStoreTimestamp(UnixMillisecondTime.Now); var status3 = statuses.ExpectValue(); Assert.True(status3.Available); Assert.Equal(status3, sw.GetStatus()); } AssertLogMessageRegex(true, Logging.LogLevel.Error, "Big Segment store status.*Exception.*sorry"); }
private void VerifyInvalidDataEvent(string eventName, string eventData) { var statuses = new EventSink <DataSourceStatus>(); _dataSourceStatusProvider.StatusChanged += statuses.Add; VerifyEventCausesStreamRestartWithInMemoryStore(eventName, eventData); // We did not allow the stream to successfully process an event before causing the error, so the // state will still be Initializing, but we should be able to see that an error happened. var status = statuses.ExpectValue(); Assert.NotNull(status.LastError); Assert.Equal(DataSourceStatus.ErrorKind.InvalidData, status.LastError.Value.Kind); }
public void ExceptionFromEventHandlerIsLoggedAndDoesNotStopOtherHandlers() { var values1 = new EventSink <string>(); myEvent += (sender, args) => throw new Exception("sorry"); myEvent += values1.Add; executor.ScheduleEvent("hello", myEvent); Assert.Equal("hello", values1.ExpectValue()); AssertEventually(TimeSpan.FromSeconds(10), TimeSpan.FromMilliseconds(20), () => logCapture.HasMessageWithText(LogLevel.Error, "Unexpected exception from event handler for String: System.Exception: sorry") && logCapture.HasMessageWithRegex(LogLevel.Debug, "at LaunchDarkly.Sdk.Internal.Concurrent.TaskExecutorTest")); }
public void UpdateStatusBroadcastsNewStatus() { var updates = MakeInstance(); var statuses = new EventSink <DataSourceStatus>(); updates.StatusChanged += statuses.Add; var timeBeforeUpdate = DateTime.Now; var errorInfo = DataSourceStatus.ErrorInfo.FromHttpError(401); updates.UpdateStatus(DataSourceState.Off, errorInfo); var status = statuses.ExpectValue(); Assert.Equal(DataSourceState.Off, status.State); Assert.InRange(status.StateSince, timeBeforeUpdate, timeBeforeUpdate.AddSeconds(1)); Assert.Equal(errorInfo, status.LastError); }
public void UpdateStatusBroadcastsNewStatus() { var statuses = new EventSink <DataStoreStatus>(); updates.StatusChanged += statuses.Add; var expectedStatus = new DataStoreStatus { Available = false, RefreshNeeded = true }; updates.UpdateStatus(expectedStatus); var newStatus = statuses.ExpectValue(); Assert.Equal(expectedStatus, newStatus); statuses.ExpectNoValue(); }
public void DataSourceStatusProviderSendsStatusUpdates() { var testData = TestData.DataSource(); var config = BasicConfig().DataSource(testData).Build(); using (var client = TestUtil.CreateClient(config, BasicUser)) { var statuses = new EventSink <DataSourceStatus>(); client.DataSourceStatusProvider.StatusChanged += statuses.Add; var errorInfo = DataSourceStatus.ErrorInfo.FromHttpError(401); testData.UpdateStatus(DataSourceState.Shutdown, errorInfo); var newStatus = statuses.ExpectValue(); Assert.Equal(DataSourceState.Shutdown, newStatus.State); Assert.True(newStatus.StateSince >= errorInfo.Time); Assert.Equal(errorInfo, newStatus.LastError); } }
public void Listeners() { var statuses = new EventSink <DataStoreStatus>(); _dataStoreStatusProvider.StatusChanged += statuses.Add; var unwantedStatuses = new EventSink <DataStoreStatus>(); _dataStoreStatusProvider.StatusChanged += unwantedStatuses.Add; _dataStoreStatusProvider.StatusChanged -= unwantedStatuses.Add; // testing that a listener can be removed var status = new DataStoreStatus { Available = false, RefreshNeeded = false }; _dataStoreUpdates.UpdateStatus(status); Assert.Equal(status, statuses.ExpectValue()); unwantedStatuses.ExpectNoValue(); }
public void StatusListeners() { var statuses = new EventSink <DataSourceStatus>(); statusProvider.StatusChanged += statuses.Add; var unwantedStatuses = new EventSink <DataSourceStatus>(); EventHandler <DataSourceStatus> listener2 = unwantedStatuses.Add; statusProvider.StatusChanged += listener2; statusProvider.StatusChanged -= listener2; // testing that a listener can be unregistered updates.UpdateStatus(DataSourceState.Valid, null); var newStatus = statuses.ExpectValue(); Assert.Equal(DataSourceState.Valid, newStatus.State); statuses.ExpectNoValue(); }
public void UpdateStatusKeepsStateUnchangedIfStateWasInitializingAndNewStateIsInterrupted() { var updates = MakeInstance(); Assert.Equal(DataSourceState.Initializing, updates.LastStatus.State); var originalTime = updates.LastStatus.StateSince; var statuses = new EventSink <DataSourceStatus>(); updates.StatusChanged += statuses.Add; var errorInfo = DataSourceStatus.ErrorInfo.FromHttpError(401); updates.UpdateStatus(DataSourceState.Interrupted, errorInfo); var status = statuses.ExpectValue(); Assert.Equal(DataSourceState.Initializing, status.State); Assert.Equal(originalTime, status.StateSince); Assert.Equal(errorInfo, status.LastError); }
public void EventHandlerIsCalledOnUIThread() { var td = TestData.DataSource(); var config = BasicConfig().DataSource(td).Build(); var captureMainThread = new EventSink <Thread>(); NSRunLoop.Main.BeginInvokeOnMainThread(() => captureMainThread.Enqueue(Thread.CurrentThread)); var mainThread = captureMainThread.ExpectValue(); using (var client = TestUtil.CreateClient(config, BasicUser)) { var receivedOnThread = new EventSink <Thread>(); client.FlagTracker.FlagValueChanged += (sender, args) => receivedOnThread.Enqueue(Thread.CurrentThread); td.Update(td.Flag("flagkey").Variation(true)); var t = receivedOnThread.ExpectValue(); Assert.Equal(mainThread, t); } }
public void EventIsSentForDeletedFlagOnUpsert() { var events = new EventSink <FlagValueChangeEvent>(); _flagTracker.FlagValueChanged += events.Add; var initData = new DataSetBuilder() .Add("key1", new FeatureFlagBuilder().Version(100).Value(LdValue.Of(true)).Variation(0).Build()) .Add("key2", new FeatureFlagBuilder().Version(200).Value(LdValue.Of(true)).Variation(0).Build()) .Build(); _updateSink.Init(_basicUser, initData); _updateSink.Upsert(_basicUser, "key1", new ItemDescriptor(101, null)); var e = events.ExpectValue(); Assert.Equal("key1", e.Key); Assert.Equal(LdValue.Of(true), e.OldValue); Assert.Equal(LdValue.Null, e.NewValue); Assert.True(e.Deleted); }
public void FlagChangeEventIsGeneratedWhenModifiedFileIsReloaded() { using (var file = TempFile.Create()) { file.SetContent(@"{""flagValues"":{""flag1"":""a""}}"); var config = BasicConfig() .DataSource(FileData.DataSource().FilePaths(file.Path).AutoUpdate(true)) .Build(); using (var client = new LdClient(config)) { var events = new EventSink <FlagChangeEvent>(); client.FlagTracker.FlagChanged += events.Add; file.SetContent(@"{""flagValues"":{""flag1"":""b""}}"); var e = events.ExpectValue(TimeSpan.FromSeconds(5)); Assert.Equal("flag1", e.Key); Assert.Equal("b", client.StringVariation("flag1", user, "")); } } }
public void EventSenderIsClientInstance() { // We're only checking one kind of events here (FlagValueChanged), but since the SDK uses the // same TaskExecutor instance for all event dispatches and the sender is configured in // that object, the sender should be the same for all events. var flagKey = "flagKey"; var testData = TestData.DataSource(); testData.Update(testData.Flag(flagKey).Variation(true)); var config = BasicConfig().DataSource(testData).Build(); using (var client = TestUtil.CreateClient(config, BasicUser)) { var receivedSender = new EventSink <object>(); client.FlagTracker.FlagValueChanged += (s, e) => receivedSender.Enqueue(s); testData.Update(testData.Flag(flagKey).Variation(false)); var sender = receivedSender.ExpectValue(); Assert.Same(client, sender); } }
public void StatusRemainsUnavailableIfStoreSaysItIsAvailableButInitFails() { // Most of this test is identical to CacheIsWrittenToStoreAfterRecoveryIfTtlIsInfinite() except as noted below. using (var wrapper = MakeWrapper(CachedIndefinitely)) { var dataStoreStatusProvider = new DataStoreStatusProviderImpl(wrapper, _dataStoreUpdates); var statuses = new EventSink <DataStoreStatus>(); dataStoreStatusProvider.StatusChanged += statuses.Add; string key1 = "key1", key2 = "key2"; var item1 = new TestItem("name1"); var item2 = new TestItem("name2"); wrapper.Init(new TestDataBuilder() .Add(TestDataKind, key1, 1, item1) .Build()); // In infinite cache mode, we do *not* expect exceptions thrown by the store to be propagated; it will // swallow the error, but also go into polling/recovery mode. Note that even though the store rejects // this update, it'll still be cached. CauseStoreError(_core, wrapper); Assert.Equal(FakeError, Assert.Throws(FakeError.GetType(), () => wrapper.Upsert(TestDataKind, key1, item1.WithVersion(2)))); Assert.Equal(item1.WithVersion(2), wrapper.Get(TestDataKind, key1)); var status1 = statuses.ExpectValue(); Assert.False(status1.Available); Assert.False(status1.RefreshNeeded); // While the store is still down, try to update it again - the update goes into the cache Assert.Equal(FakeError, Assert.Throws(FakeError.GetType(), () => wrapper.Upsert(TestDataKind, key2, item2.WithVersion(1)))); Assert.Equal(item2.WithVersion(1), wrapper.Get(TestDataKind, key2)); // Verify that this update did not go into the underlying data yet Assert.False(_core.Data[TestDataKind].TryGetValue(key2, out _)); // Here's what is unique to this test: we are telling the store to report its status as "available", // but *not* clearing the fake exception, so when the poller tries to write the cached data with // init() it should fail. _core.Available = true; // We can't prove that an unwanted status transition will never happen, but we can verify that it // does not happen within two status poll intervals. Thread.Sleep(PersistentDataStoreStatusManager.PollInterval + PersistentDataStoreStatusManager.PollInterval); statuses.ExpectNoValue(); Assert.InRange(_core.InitCalledCount, 2, 100); // that is, it *tried* to do at least one more init // Now simulate the store coming back up and actually working _core.Error = null; // Wait for the poller to notice this and publish a new status var status2 = statuses.ExpectValue(); Assert.True(status2.Available); Assert.False(status2.RefreshNeeded); // Once that has happened, the cache should have been written to the store Assert.Equal(item1.SerializedWithVersion(2), _core.Data[TestDataKind][key1]); Assert.Equal(item2.SerializedWithVersion(1), _core.Data[TestDataKind][key2]); } }