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();
            }
        }
Exemple #2
0
        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();
        }
Exemple #3
0
        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 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 NoEventsAreSentForUpsertIfNeverInited()
        {
            var events = new EventSink <FlagValueChangeEvent>();

            _flagTracker.FlagValueChanged += events.Add;

            _updateSink.Upsert(_basicUser, "key1", new FeatureFlagBuilder().Build().ToItemDescriptor());

            events.ExpectNoValue();
        }
Exemple #6
0
        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();
        }
Exemple #7
0
        public void UpdateStatusDoesNothingIfParametersHaveNoNewData()
        {
            var updates = MakeInstance();

            var statuses = new EventSink <DataSourceStatus>();

            updates.StatusChanged += statuses.Add;

            updates.UpdateStatus(DataSourceState.Initializing, null);

            statuses.ExpectNoValue();
        }
        public void NoEventsAreSentForFirstInit()
        {
            var events = new EventSink <FlagValueChangeEvent>();

            _flagTracker.FlagValueChanged += events.Add;

            var initData = new DataSetBuilder().Add("key1", new FeatureFlagBuilder().Build()).Build();

            _updateSink.Init(_basicUser, initData);

            events.ExpectNoValue();
        }
Exemple #9
0
        public void UpdateStatusDoesNothingIfNewStatusIsSame()
        {
            var statuses = new EventSink <DataStoreStatus>();

            updates.StatusChanged += statuses.Add;

            updates.UpdateStatus(new DataStoreStatus
            {
                Available     = true,
                RefreshNeeded = false
            });

            statuses.ExpectNoValue();
        }
Exemple #10
0
        public void DoesNotSendEventOnUpdateIfItemWasNotReallyUpdated()
        {
            var dataBuilder = new DataSetBuilder().Flags(flag1, flag2);

            var updates = MakeInstance();

            updates.Init(dataBuilder.Build());

            var eventSink = new EventSink <FlagChangeEvent>();

            updates.FlagChanged += eventSink.Add;

            updates.Upsert(DataModel.Features, flag2.Key, DescriptorOf(flag2));

            eventSink.ExpectNoValue();
        }
        public void EventIsNotSentIfUpsertFailsDueToLowerVersion()
        {
            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 FeatureFlagBuilder().Version(99).Value(LdValue.Of(false)).Variation(1).Build().ToItemDescriptor());

            events.ExpectNoValue();
        }
Exemple #12
0
        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();
        }
Exemple #13
0
        private void ExpectFlagChangeEvents(EventSink <FlagChangeEvent> eventSink, params string[] flagKeys)
        {
            var expectedChangedFlagKeys = new HashSet <string>(flagKeys);
            var actualChangedFlagKeys   = new HashSet <string>();

            for (var i = 0; i < flagKeys.Length; i++)
            {
                if (eventSink.TryTakeValue(out var e))
                {
                    actualChangedFlagKeys.Add(e.Key);
                }
                else
                {
                    Assert.True(false, string.Format("expected flag change events: [{0}] but only got: [{1}]",
                                                     string.Join(", ", expectedChangedFlagKeys), string.Join(", ", actualChangedFlagKeys)));
                }
            }
            Assert.Equal(expectedChangedFlagKeys, actualChangedFlagKeys);
            eventSink.ExpectNoValue();
        }
        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();
        }
Exemple #15
0
        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();
        }
Exemple #16
0
 public void ExpectNoMoreActions() => Actions.ExpectNoValue();
        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]);
            }
        }