public void DebugModeExpiresBasedOnServerTimeIfServerTimeIsLaterThanClientTime()
        {
            // Pick a server time that is somewhat ahead of the client time
            var serverTime = DateTime.Now.Add(TimeSpan.FromSeconds(20));

            var mockSender = MakeMockSender();
            var captured   = EventCapture.From(mockSender,
                                               new EventSenderResult(DeliveryStatus.Succeeded, serverTime));

            using (var ep = MakeProcessor(_config, mockSender))
            {
                // Send and flush an event we don't care about, just to set the last server time
                RecordIdentify(ep, _fixedTimestamp, User.WithKey("otherUser"));
                FlushAndWait(ep);
                captured.Events.Clear();

                // Now send an event with debug mode on, with a "debug until" time that is further in
                // the future than the client time, but in the past compared to the server.
                var flag = BasicFlag;
                flag.DebugEventsUntilDate = UnixMillisecondTime.FromDateTime(serverTime).PlusMillis(-1000);
                RecordEval(ep, flag, BasicEval);
                FlushAndWait(ep);

                // Should get a summary event only, not a full feature event
                Assert.Collection(captured.Events,
                                  item => CheckIndexEvent(item, BasicEval.Timestamp, _userJson),
                                  item => CheckSummaryEvent(item));
            }
        }
        public void FeatureEventCanHaveReason()
        {
            _config.InlineUsersInEvents = true;

            var mockSender = MakeMockSender();
            var captured   = EventCapture.From(mockSender);

            using (var ep = MakeProcessor(_config, mockSender))
            {
                var reasons = new EvaluationReason[]
                {
                    _irrelevantReason,
                    EvaluationReason.FallthroughReason,
                    EvaluationReason.TargetMatchReason,
                    EvaluationReason.RuleMatchReason(1, "id"),
                    EvaluationReason.PrerequisiteFailedReason("key"),
                    EvaluationReason.ErrorReason(EvaluationErrorKind.WrongType)
                };
                foreach (var reason in reasons)
                {
                    captured.Events.Clear();

                    var eval = BasicEval;
                    eval.Reason = reason;
                    RecordEval(ep, BasicFlagWithTracking, eval);
                    FlushAndWait(ep);

                    Assert.Collection(captured.Events,
                                      item => CheckFeatureEvent(item, BasicFlagWithTracking, eval, _userJson),
                                      item => CheckSummaryEvent(item));
                }
            }
        }
        public void EventsInBatchRecorded()
        {
            var expectedStats       = LdValue.BuildObject().Add("stats", "testValue").Build();
            var mockDiagnosticStore = MakeDiagnosticStore(null, null, new DiagnosticEvent(expectedStats));

            var            mockSender          = MakeMockSender();
            var            eventCapture        = EventCapture.From(mockSender);
            var            diagnosticCapture   = EventCapture.DiagnosticsFrom(mockSender);
            CountdownEvent diagnosticCountdown = new CountdownEvent(1);

            using (var ep = MakeProcessor(_config, mockSender, mockDiagnosticStore.Object, null, diagnosticCountdown))
            {
                RecordEval(ep, BasicFlagWithTracking, BasicEval);
                FlushAndWait(ep);

                mockDiagnosticStore.Verify(diagStore => diagStore.RecordEventsInBatch(2), Times.Once(),
                                           "Diagnostic store's RecordEventsInBatch should be called with the number of events in last flush");

                ep.DoDiagnosticSend(null);
                diagnosticCountdown.Wait();
                mockDiagnosticStore.Verify(diagStore => diagStore.CreateEventAndReset(), Times.Once());

                Assert.Equal(expectedStats, diagnosticCapture.EventsQueue.Take());
            }
        }
        public void TwoFeatureEventsForSameUserGenerateOnlyOneIndexEvent()
        {
            var mockSender = MakeMockSender();
            var captured   = EventCapture.From(mockSender);

            using (var ep = MakeProcessor(_config, mockSender))
            {
                var flag1 = new TestFlagProperties {
                    Key = "flagkey1", Version = 11, TrackEvents = true
                };
                var flag2 = new TestFlagProperties {
                    Key = "flagkey2", Version = 22, TrackEvents = true
                };
                var value = LdValue.Of("value");
                RecordEval(ep, flag1, BasicEval);
                RecordEval(ep, flag2, BasicEval);
                ep.Flush();
                ep.WaitUntilInactive();

                Assert.Collection(captured.Events,
                                  item => CheckIndexEvent(item, BasicEval.Timestamp, _userJson),
                                  item => CheckFeatureEvent(item, flag1, BasicEval, LdValue.Null),
                                  item => CheckFeatureEvent(item, flag2, BasicEval, LdValue.Null),
                                  item => CheckSummaryEvent(item));
            }
        }
        public void FlushDoesNothingIfThereAreNoEvents()
        {
            var mockSender = MakeMockSender();
            var captured   = EventCapture.From(mockSender);

            using (var ep = MakeProcessor(_config, mockSender))
            {
                ep.Flush();
                ep.WaitUntilInactive();

                Assert.Empty(captured.Events);
            }
        }
        public void IdentifyEventCanHaveNullUser()
        {
            var mockSender = MakeMockSender();
            var captured   = EventCapture.From(mockSender);

            using (var ep = MakeProcessor(_config, mockSender))
            {
                RecordIdentify(ep, _fixedTimestamp, null);
                FlushAndWait(ep);

                Assert.Collection(captured.Events,
                                  item => CheckIdentifyEvent(item, _fixedTimestamp, LdValue.Null));
            }
        }
        public void UserDetailsAreScrubbedInIdentifyEvent()
        {
            _config.AllAttributesPrivate = true;

            var mockSender = MakeMockSender();
            var captured   = EventCapture.From(mockSender);

            using (var ep = MakeProcessor(_config, mockSender))
            {
                RecordIdentify(ep, _fixedTimestamp, _user);
                FlushAndWait(ep);

                Assert.Collection(captured.Events,
                                  item => CheckIdentifyEvent(item, _fixedTimestamp, _scrubbedUserJson));
            }
        }
        public void FinalFlushIsDoneOnDispose()
        {
            var mockSender = MakeMockSender();
            var captured   = EventCapture.From(mockSender);

            using (var ep = MakeProcessor(_config, mockSender))
            {
                RecordIdentify(ep, _fixedTimestamp, _user);

                ep.Dispose();

                Assert.Collection(captured.Events,
                                  item => CheckIdentifyEvent(item, _fixedTimestamp, _userJson));
                mockSender.Verify(s => s.Dispose(), Times.Once());
            }
        }
        public void IndividualFeatureEventIsQueuedWithIndexEvent()
        {
            var mockSender = MakeMockSender();
            var captured   = EventCapture.From(mockSender);

            using (var ep = MakeProcessor(_config, mockSender))
            {
                RecordEval(ep, BasicFlagWithTracking, BasicEval);
                FlushAndWait(ep);

                Assert.Collection(captured.Events,
                                  item => CheckIndexEvent(item, BasicEval.Timestamp, _userJson),
                                  item => CheckFeatureEvent(item, BasicFlagWithTracking, BasicEval, LdValue.Null),
                                  item => CheckSummaryEvent(item));
            }
        }
        public void CustomEventCanHaveNullUser()
        {
            var mockSender = MakeMockSender();
            var captured   = EventCapture.From(mockSender);

            using (var ep = MakeProcessor(_config, mockSender))
            {
                var ce = BasicCustom;
                ce.User = null;
                RecordCustom(ep, ce);
                ep.Flush();
                ep.WaitUntilInactive();

                Assert.Collection(captured.Events,
                                  item => CheckCustomEvent(item, ce, LdValue.Null));
            }
        }
        public void FeatureEventCanHaveNullUser()
        {
            var mockSender = MakeMockSender();
            var captured   = EventCapture.From(mockSender);

            using (var ep = MakeProcessor(_config, mockSender))
            {
                var eval = BasicEval;
                eval.User = null;
                RecordEval(ep, BasicFlagWithTracking, eval);
                FlushAndWait(ep);

                Assert.Collection(captured.Events,
                                  item => CheckFeatureEvent(item, BasicFlagWithTracking, eval, LdValue.Null),
                                  item => CheckSummaryEvent(item));
            }
        }
        public void FeatureEventCanContainInlineUser()
        {
            _config.InlineUsersInEvents = true;

            var mockSender = MakeMockSender();
            var captured   = EventCapture.From(mockSender);

            using (var ep = MakeProcessor(_config, mockSender))
            {
                RecordEval(ep, BasicFlagWithTracking, BasicEval);
                FlushAndWait(ep);

                Assert.Collection(captured.Events,
                                  item => CheckFeatureEvent(item, BasicFlagWithTracking, BasicEval, _userJson),
                                  item => CheckSummaryEvent(item));
            }
        }
        public void IndexEventIsStillGeneratedIfInlineUsersIsTrueButFeatureEventIsNotTracked()
        {
            _config.InlineUsersInEvents = true;

            var mockSender = MakeMockSender();
            var captured   = EventCapture.From(mockSender);

            using (var ep = MakeProcessor(_config, mockSender))
            {
                RecordEval(ep, BasicFlag, BasicEval);
                FlushAndWait(ep);

                Assert.Collection(captured.Events,
                                  item => CheckIndexEvent(item, BasicEval.Timestamp, _userJson),
                                  item => CheckSummaryEvent(item));
            }
        }
        public void CustomEventCanContainInlineUser()
        {
            _config.InlineUsersInEvents = true;

            var mockSender = MakeMockSender();
            var captured   = EventCapture.From(mockSender);

            using (var ep = MakeProcessor(_config, mockSender))
            {
                RecordCustom(ep, BasicCustom);
                ep.Flush();
                ep.WaitUntilInactive();

                Assert.Collection(captured.Events,
                                  item => CheckCustomEvent(item, BasicCustom, _userJson));
            }
        }
        public void UserDetailsAreScrubbedInCustomEvent()
        {
            _config.AllAttributesPrivate = true;
            _config.InlineUsersInEvents  = true;

            var mockSender = MakeMockSender();
            var captured   = EventCapture.From(mockSender);

            using (var ep = MakeProcessor(_config, mockSender))
            {
                RecordCustom(ep, BasicCustom);
                ep.Flush();
                ep.WaitUntilInactive();

                Assert.Collection(captured.Events,
                                  item => CheckCustomEvent(item, BasicCustom, _scrubbedUserJson));
            }
        }
        public void UserDetailsAreScrubbedInFeatureEvent()
        {
            _config.AllAttributesPrivate = true;
            _config.InlineUsersInEvents  = true;

            var mockSender = MakeMockSender();
            var captured   = EventCapture.From(mockSender);

            using (var ep = MakeProcessor(_config, mockSender))
            {
                RecordEval(ep, BasicFlagWithTracking, BasicEval);
                FlushAndWait(ep);

                Assert.Collection(captured.Events,
                                  item => CheckFeatureEvent(item, BasicFlagWithTracking, BasicEval, _scrubbedUserJson),
                                  item => CheckSummaryEvent(item));
            }
        }
        public void EventKindIsDebugIfFlagIsTemporarilyInDebugMode()
        {
            var mockSender = MakeMockSender();
            var captured   = EventCapture.From(mockSender);

            using (var ep = MakeProcessor(_config, mockSender))
            {
                var flag = BasicFlag;
                flag.DebugEventsUntilDate = UnixMillisecondTime.Now.PlusMillis(1000000);
                RecordEval(ep, flag, BasicEval);
                FlushAndWait(ep);

                Assert.Collection(captured.Events,
                                  item => CheckIndexEvent(item, BasicEval.Timestamp, _userJson),
                                  item => CheckDebugEvent(item, flag, BasicEval, _userJson),
                                  item => CheckSummaryEvent(item));
            }
        }
        public void DiagnosticStoreInitEventSentToDiagnosticUri()
        {
            var expected            = LdValue.BuildObject().Add("testKey", "testValue").Build();
            var mockDiagnosticStore = MakeDiagnosticStore(null, new DiagnosticEvent(expected),
                                                          new DiagnosticEvent(LdValue.Null));

            var mockSender          = MakeMockSender();
            var eventCapture        = EventCapture.From(mockSender);
            var diagnosticCapture   = EventCapture.DiagnosticsFrom(mockSender);
            var diagnosticCountdown = new CountdownEvent(1);

            using (var ep = MakeProcessor(_config, mockSender, mockDiagnosticStore.Object, null, diagnosticCountdown))
            {
                diagnosticCountdown.Wait();

                Assert.Equal(expected, diagnosticCapture.EventsQueue.Take());
            }
        }
        public void EventCanBeBothTrackedAndDebugged()
        {
            var mockSender = MakeMockSender();
            var captured   = EventCapture.From(mockSender);

            using (var ep = MakeProcessor(_config, mockSender))
            {
                var flag = BasicFlagWithTracking;
                flag.DebugEventsUntilDate = UnixMillisecondTime.Now.PlusMillis(1000000);
                RecordEval(ep, flag, BasicEval);
                FlushAndWait(ep);

                Assert.Collection(captured.Events,
                                  item => CheckIndexEvent(item, BasicEval.Timestamp, _userJson),
                                  item => CheckFeatureEvent(item, flag, BasicEval, LdValue.Null),
                                  item => CheckDebugEvent(item, flag, BasicEval, _userJson),
                                  item => CheckSummaryEvent(item));
            }
        }
        public void EventsAreNotPostedAfterUnrecoverableFailure()
        {
            var mockSender = MakeMockSender();
            var captured   = EventCapture.From(mockSender,
                                               new EventSenderResult(DeliveryStatus.FailedAndMustShutDown, null));

            using (var ep = MakeProcessor(_config, mockSender))
            {
                RecordIdentify(ep, _fixedTimestamp, _user);
                ep.Flush();
                ep.WaitUntilInactive();

                RecordCustom(ep, BasicCustom);
                ep.Flush();
                ep.WaitUntilInactive();

                Assert.Collection(captured.Events,
                                  item => CheckIdentifyEvent(item, _fixedTimestamp, _userJson));
            }
        }
        internal static EventCapture From(Mock <IEventSender> mockSender, EventDataKind forKind, EventSenderResult result)
        {
            var ec = new EventCapture();

            mockSender.Setup(
                s => s.SendEventDataAsync(forKind, It.IsAny <string>(), It.IsAny <int>())
                ).Callback <EventDataKind, string, int>((kind, data, count) =>
            {
                var parsed = LdValue.Parse(data);
                var events = kind == EventDataKind.DiagnosticEvent ? new List <LdValue> {
                    parsed
                } :
                parsed.AsList(LdValue.Convert.Json);
                ec.Events.AddRange(events);
                foreach (var e in events)
                {
                    ec.EventsQueue.Add(e);
                }
            }).Returns(Task.FromResult(result));
            return(ec);
        }
        public void DiagnosticDisablerDisablesInitialDiagnostics()
        {
            var testDiagnostic      = LdValue.BuildObject().Add("testKey", "testValue").Build();
            var mockDiagnosticStore = MakeDiagnosticStore(new DiagnosticEvent(testDiagnostic),
                                                          new DiagnosticEvent(testDiagnostic), new DiagnosticEvent(LdValue.Null));

            var mockDiagnosticDisabler = new Mock <IDiagnosticDisabler>(MockBehavior.Strict);

            mockDiagnosticDisabler.Setup(diagDisabler => diagDisabler.Disabled).Returns(true);

            var mockSender        = MakeMockSender();
            var eventCapture      = EventCapture.From(mockSender);
            var diagnosticCapture = EventCapture.DiagnosticsFrom(mockSender);

            using (var ep = MakeProcessor(_config, mockSender, mockDiagnosticStore.Object, mockDiagnosticDisabler.Object, null))
            {
            }
            mockDiagnosticStore.Verify(diagStore => diagStore.InitEvent, Times.Never());
            mockDiagnosticStore.Verify(diagStore => diagStore.PersistedUnsentEvent, Times.Never());
            Assert.Empty(diagnosticCapture.Events);
        }
        public void AliasEventIsQueued()
        {
            var mockSender = MakeMockSender();
            var captured   = EventCapture.From(mockSender);

            using (var ep = MakeProcessor(_config, mockSender))
            {
                var ae = new AliasEvent
                {
                    Timestamp           = _fixedTimestamp,
                    Key                 = "newkey",
                    PreviousKey         = "oldkey",
                    ContextKind         = ContextKind.User,
                    PreviousContextKind = ContextKind.AnonymousUser
                };
                ep.RecordAliasEvent(ae);
                FlushAndWait(ep);

                Assert.Collection(captured.Events,
                                  item => CheckAliasEvent(item, ae));
            }
        }
        public void FlushDoesNothingWhenOffline()
        {
            var mockSender = MakeMockSender();
            var captured   = EventCapture.From(mockSender);

            using (var ep = MakeProcessor(_config, mockSender))
            {
                ep.SetOffline(true);
                RecordIdentify(ep, _fixedTimestamp, _user);
                ep.Flush();
                ep.WaitUntilInactive();

                Assert.Empty(captured.Events);

                // We should have still held on to that event, so if we go online again and flush, it is sent.
                ep.SetOffline(false);
                ep.Flush();
                ep.WaitUntilInactive();

                Assert.Collection(captured.Events,
                                  item => CheckIdentifyEvent(item, _fixedTimestamp, _userJson));
            };
        }
        public void CustomEventIsQueuedWithUser()
        {
            var mockSender = MakeMockSender();
            var captured   = EventCapture.From(mockSender);

            using (var ep = MakeProcessor(_config, mockSender))
            {
                var ce = new TestCustomEventProperties
                {
                    Timestamp   = _fixedTimestamp,
                    User        = _user,
                    Key         = "eventkey",
                    Data        = LdValue.Of(3),
                    MetricValue = 1.5
                };
                RecordCustom(ep, ce);
                ep.Flush();
                ep.WaitUntilInactive();

                Assert.Collection(captured.Events,
                                  item => CheckIndexEvent(item, ce.Timestamp, _userJson),
                                  item => CheckCustomEvent(item, ce, LdValue.Null));
            }
        }
        public void DiagnosticDisablerEnabledInitialDiagnostics()
        {
            var expectedStats       = LdValue.BuildObject().Add("stats", "testValue").Build();
            var expectedInit        = LdValue.BuildObject().Add("init", "testValue").Build();
            var mockDiagnosticStore = MakeDiagnosticStore(new DiagnosticEvent(expectedStats),
                                                          new DiagnosticEvent(expectedInit), new DiagnosticEvent(LdValue.Null));

            var mockDiagnosticDisabler = new Mock <IDiagnosticDisabler>(MockBehavior.Strict);

            mockDiagnosticDisabler.Setup(diagDisabler => diagDisabler.Disabled).Returns(false);

            var mockSender          = MakeMockSender();
            var eventCapture        = EventCapture.From(mockSender);
            var diagnosticCapture   = EventCapture.DiagnosticsFrom(mockSender);
            var diagnosticCountdown = new CountdownEvent(1);

            using (var ep = MakeProcessor(_config, mockSender, mockDiagnosticStore.Object, mockDiagnosticDisabler.Object, diagnosticCountdown))
            {
                diagnosticCountdown.Wait();

                Assert.Equal(expectedStats, diagnosticCapture.EventsQueue.Take());
                Assert.Equal(expectedInit, diagnosticCapture.EventsQueue.Take());
            }
        }
        public void NonTrackedEventsAreSummarized()
        {
            var mockSender = MakeMockSender();
            var captured   = EventCapture.From(mockSender);

            using (var ep = MakeProcessor(_config, mockSender))
            {
                var flag1 = new TestFlagProperties {
                    Key = "flagkey1", Version = 11
                };
                var flag2 = new TestFlagProperties {
                    Key = "flagkey2", Version = 22
                };
                var value1       = LdValue.Of("value1");
                var value2       = LdValue.Of("value2");
                var default1     = LdValue.Of("default1");
                var default2     = LdValue.Of("default2");
                var earliestTime = UnixMillisecondTime.OfMillis(10000);
                var latestTime   = UnixMillisecondTime.OfMillis(20000);
                RecordEval(ep, flag1, new TestEvalProperties
                {
                    Timestamp    = earliestTime,
                    User         = _user,
                    Variation    = 1,
                    Value        = value1,
                    DefaultValue = default1
                });
                RecordEval(ep, flag1, new TestEvalProperties
                {
                    Timestamp    = UnixMillisecondTime.OfMillis(earliestTime.Value + 10),
                    User         = _user,
                    Variation    = 1,
                    Value        = value1,
                    DefaultValue = default1
                });
                RecordEval(ep, flag1, new TestEvalProperties
                {
                    Timestamp    = UnixMillisecondTime.OfMillis(earliestTime.Value + 20),
                    User         = _user,
                    Variation    = 2,
                    Value        = value2,
                    DefaultValue = default1
                });
                RecordEval(ep, flag2, new TestEvalProperties
                {
                    Timestamp    = latestTime,
                    User         = _user,
                    Variation    = 2,
                    Value        = value2,
                    DefaultValue = default2
                });
                ep.Flush();
                ep.WaitUntilInactive();

                Assert.Collection(captured.Events,
                                  item => CheckIndexEvent(item, earliestTime, _userJson),
                                  item => CheckSummaryEventDetails(item,
                                                                   earliestTime,
                                                                   latestTime,
                                                                   MustHaveFlagSummary(flag1.Key, default1,
                                                                                       MustHaveFlagSummaryCounter(value1, 1, flag1.Version, 2),
                                                                                       MustHaveFlagSummaryCounter(value2, 2, flag1.Version, 1)
                                                                                       ),
                                                                   MustHaveFlagSummary(flag2.Key, default2,
                                                                                       MustHaveFlagSummaryCounter(value2, 2, flag2.Version, 1)
                                                                                       )
                                                                   )
                                  );
            }
        }