using System; using System.Runtime.CompilerServices; using System.Threading; using NSubstitute; using NUnit.Framework; using Plume.Core.Exceptions; using Plume.Core.Logging; using Plume.Interfaces.Services; using Plume.Tracking.Service.API; using Plume.Tracking.Service.API.Events; using Plume.Tracking.Service.API.Exceptions; using Plume.Tracking.Service.API.Statistics; using Plume.Tracking.Service.Implementation; namespace Plume.Tracking.Service.Tests { [TestFixture] public class TrackingServiceTests { public string GetCurrentMethodName([CallerMemberName] string callerMemberName = null) { return callerMemberName; } private const string DefaultMachineName = "default_machine_id"; private const string DefaultToolName = "default_tool_name"; private const string DefaultCommandLine = "default_command_line"; private const string DefaultCurrentDirectory = "default_current_directory"; private const string DefaultJobTitle = "default_job_title"; private const JobCategory DefaultJobCategory = JobCategory.ToolsProgrammer; private const string DefaultStudioName = "default_studio_name"; private const string DefaultId = "default_id"; private const int DefaultSessionId = 42; private static IClientDetails GenerateDefaultClientDetails() { return GenerateClientDetails(DefaultMachineName, DefaultToolName, DefaultCommandLine, DefaultCurrentDirectory, DefaultJobTitle, DefaultJobCategory, DefaultStudioName, DefaultId, DefaultSessionId); } private static TrackingService CreateTrackingServiceInitialized(int waitingDuration = TrackingService.DefaultWaitingDurationMs) { IServiceContext serviceContext = Substitute.For(); ILogger logger = Substitute.For(); serviceContext.Logger.Returns(logger); TrackingService instance = new TrackingService(GenerateDefaultClientDetails(), waitingDuration); instance.Initialize(serviceContext); return instance; } private static IClientDetails GenerateClientDetails(string machineId, string toolName, string commandLine, string currentDirectory, string jobTitle, JobCategory jobCategory, string studioName, string id, int sessionId) { IClientDetails result = Substitute.For(); result.MachineId.Returns(machineId); result.ToolName.Returns(toolName); result.CommandLine.Returns(commandLine); result.CurrentDirectory.Returns(currentDirectory); result.JobTitle.Returns(jobTitle); result.JobCategory.Returns(jobCategory); result.StudioName.Returns(studioName); result.Id.Returns(id); result.SessionId.Returns(sessionId); return result; } [Test] public void Constructor_ExpectedInstance() { IClientDetails clientDetails = GenerateDefaultClientDetails(); TrackingService instance = new TrackingService(clientDetails); Assert.AreEqual(new TimeSpan(0), instance.ActivityDuration); Assert.AreSame(clientDetails, instance.ClientDetails); Assert.AreEqual(new TimeSpan(0), instance.SessionDuration); Assert.AreEqual(TrackingService.DefaultWaitingDurationMs, instance.WaitingDuration); Assert.IsFalse(instance.IsSessionStarted); Assert.IsFalse(instance.IsInitialized); } [Test] public void Constructor_NullClientDetails_ShouldThrowArgumentNullException() { ArgumentNullException exception = Assert.Catch(delegate { new TrackingService(null); }); Assert.AreEqual(exception.ParamName, "clientDetails"); } [Test] public void Initialize_ValidServiceContext_LoggerIsSetted() { IServiceContext serviceContext = Substitute.For(); ILogger logger = Substitute.For(); serviceContext.Logger.Returns(logger); IClientDetails clientDetails = GenerateDefaultClientDetails(); TrackingService instance = new TrackingService(clientDetails); instance.Initialize(serviceContext); Assert.AreSame(logger, instance.Logger); } [Test] public void Initialize_NullServiceContext_ShouldThrowArgumentNullException() { IClientDetails clientDetails = GenerateDefaultClientDetails(); TrackingService instance = new TrackingService(clientDetails); ArgumentNullException exception = Assert.Catch(delegate { instance.Initialize(null); }); Assert.AreEqual("serviceContext", exception.ParamName); } [Test] public void Initialize_NullLoggerInServiceContext_ShouldThrowArgumentNullException() { IServiceContext serviceContext = Substitute.For(); ILogger logger = null; serviceContext.Logger.Returns(logger); IClientDetails clientDetails = GenerateDefaultClientDetails(); TrackingService instance = new TrackingService(clientDetails); ArgumentNullException exception = Assert.Catch(delegate { instance.Initialize(serviceContext); }); Assert.AreEqual("Logger", exception.ParamName); } [Test] public void Initialized_CallTwice_ShouldThrowAlreadyInitializedException() { TrackingService instance = CreateTrackingServiceInitialized(); IServiceContext serviceContext = Substitute.For(); AlreadyInitializedException exception = Assert.Throws>(delegate { instance.Initialize(serviceContext); }, $"No exception was thrown during {GetCurrentMethodName()}"); } [Test] public void Dispose_NothingHappened() { IClientDetails clientDetails = GenerateDefaultClientDetails(); TrackingService instance = new TrackingService(clientDetails); instance.Dispose(); } [Test] public void StartSession_EventTimerSessionCreatedTriggeredAndExpectedTimerSession() { TrackingService instance = CreateTrackingServiceInitialized(); bool eventTriggered = false; DateTime beforeStartSession = DateTime.UtcNow; instance.TimerSessionStarted += delegate(object sender, TimerSessionEventArgs e) { eventTriggered = true; CheckBaseSessionCreated(beforeStartSession, instance.ClientDetails, e.Data); Assert.AreEqual(0, e.Data.Duration); Assert.AreEqual(0, e.Data.ActiveDuration); }; instance.StartSession(); Assert.IsTrue(eventTriggered); } [Test] public void StartSession_EventActionCountSessionCreatedTriggeredAndExpectedActionCountSession() { TrackingService instance = CreateTrackingServiceInitialized(); bool eventTriggered = false; DateTime beforeStartSession = DateTime.UtcNow; instance.ActionCountSessionStarted += delegate (object sender, ActionCountSessionEventArgs e) { eventTriggered = true; CheckBaseSessionCreated(beforeStartSession, instance.ClientDetails, e.Data); Assert.AreEqual(0, e.Data.Data.Count); }; instance.StartSession(); Assert.IsTrue(eventTriggered); } private static void CheckBaseSessionCreated(DateTime beforeStartSession, IClientDetails clientDetails, BaseSession session) { Assert.IsNotNull(session); Assert.LessOrEqual(beforeStartSession, session.DateTime); Assert.AreEqual(clientDetails.ToolName, session.ToolName); Assert.AreEqual(clientDetails.SessionId, session.SessionId); Assert.AreEqual(clientDetails.JobTitle, session.JobTitle); Assert.AreEqual(clientDetails.JobCategory, session.JobCategory); Assert.AreEqual(clientDetails.StudioName, session.StudioName); Assert.AreEqual(clientDetails.CommandLine, session.CommandLine); Assert.AreEqual(clientDetails.CurrentDirectory, session.WorkingDirectory); Assert.AreEqual(clientDetails.MachineId, session.MachineId); Assert.IsNull(session.EndTime); } [Test] public void StartSession_InstanceNotInitialized_ShouldThrowNotInitializedException() { try { TrackingService instance = new TrackingService(GenerateDefaultClientDetails()); Assert.Throws>(delegate { instance.StartSession(); }, $"No exception was thrown during {GetCurrentMethodName()}"); } catch (Exception e) { Assert.Fail(e.Message); } } [Test] public void StartSession_CallTwice_ShouldThrowAlreadyStartedException() { try { TrackingService instance = CreateTrackingServiceInitialized(); instance.StartSession(); Assert.Throws(delegate { instance.StartSession(); }, $"No exception was thrown during {GetCurrentMethodName()}"); } catch (Exception e) { Assert.Fail(e.Message); } } [Test] public void StartSession_WaitMoreThanWaitingDuration_EventTimerSessionUpdatedNotTriggered() { try { int waitingDuration = 100; TrackingService instance = CreateTrackingServiceInitialized(waitingDuration); bool eventUpdateTriggered = false; instance.TimerSessionUpdated += delegate(object sender, TimerSessionEventArgs e) { eventUpdateTriggered = true; }; instance.StartSession(); Thread.Sleep(2 * waitingDuration); Assert.IsFalse(eventUpdateTriggered); } catch (Exception e) { Assert.Fail(e.Message); } } [Test] public void StopSession_EventTimerSessionEndedTriggered() { try { TrackingService instance = CreateTrackingServiceInitialized(); bool eventTriggered = false; instance.TimerSessionEnded += delegate (object sender, TimerSessionEventArgs e) { eventTriggered = true; Assert.IsNotNull(e.Data); Assert.IsNotNull(e.Data.EndTime); }; instance.StartSession(); Thread.Sleep(500); instance.StopSession(); Assert.IsTrue(eventTriggered); } catch (Exception e) { Assert.Fail(e.Message); } } [Test] public void StopSession_EventActionCountSessionEndedTriggered() { try { TrackingService instance = CreateTrackingServiceInitialized(); bool eventTriggered = false; instance.ActionCountSessionEnded += delegate (object sender, ActionCountSessionEventArgs e) { eventTriggered = true; Assert.IsNotNull(e.Data); Assert.IsNotNull(e.Data.EndTime); }; instance.StartSession(); Thread.Sleep(500); instance.StopSession(); Assert.IsTrue(eventTriggered); } catch (Exception e) { Assert.Fail(e.Message); } } [Test] public void StopSession_InstanceNotInitialized_ShouldThrowNotInitializedException() { try { TrackingService instance = new TrackingService(GenerateDefaultClientDetails()); Assert.Throws>(delegate { instance.StopSession(); }, $"No exception was thrown during {GetCurrentMethodName()}"); } catch (Exception e) { Assert.Fail(e.Message); } } [Test] public void StopSession_SessionNotStarted_ShouldThrowNotStartedSessionException() { try { TrackingService instance = CreateTrackingServiceInitialized(); Assert.Throws(delegate { instance.StopSession(); }, $"No exception was thrown during {GetCurrentMethodName()}"); } catch (Exception e) { Assert.Fail(e.Message); } } [Test] public void StopSession_CurrentlyInActivity_EventTimerSessionEndedTriggeredAndExpectedTimerSession() { try { TrackingService instance = CreateTrackingServiceInitialized(5000); DateTime dateTimeOnStarted = DateTime.MinValue; string toolNameOnStarted = string.Empty; int sessionIdOnStarted = 0; string jobTitleOnStarted = string.Empty; JobCategory jobCategoryOnStarted = DefaultJobCategory; string studioNameOnStarted = string.Empty; string commandLineOnStarted = string.Empty; string currentDirectoryOnStarted = string.Empty; string machineIdOnStarted = string.Empty; instance.TimerSessionStarted+= delegate(object sender, TimerSessionEventArgs e) { dateTimeOnStarted = e.Data.DateTime; toolNameOnStarted = e.Data.ToolName; sessionIdOnStarted = e.Data.SessionId; jobTitleOnStarted = e.Data.JobTitle; jobCategoryOnStarted = e.Data.JobCategory; studioNameOnStarted = e.Data.StudioName; commandLineOnStarted = e.Data.CommandLine; currentDirectoryOnStarted = e.Data.WorkingDirectory; machineIdOnStarted = e.Data.MachineId; }; bool eventUpdateTriggered = false; instance.TimerSessionUpdated += delegate(object sender, TimerSessionEventArgs e) { eventUpdateTriggered = true; }; double waitForTest = 1.5; bool eventEndTriggered = false; instance.TimerSessionEnded += delegate(object sender, TimerSessionEventArgs e) { Assert.AreEqual(dateTimeOnStarted, e.Data.DateTime); Assert.AreEqual(toolNameOnStarted, e.Data.ToolName); Assert.AreEqual(sessionIdOnStarted, e.Data.SessionId); Assert.AreEqual(jobTitleOnStarted, e.Data.JobTitle); Assert.AreEqual(jobCategoryOnStarted, e.Data.JobCategory); Assert.AreEqual(studioNameOnStarted, e.Data.StudioName); Assert.AreEqual(commandLineOnStarted, e.Data.CommandLine); Assert.AreEqual(currentDirectoryOnStarted, e.Data.WorkingDirectory); Assert.AreEqual(machineIdOnStarted, e.Data.MachineId); Assert.IsNotNull(e.Data.EndTime); double roundedActiveDuration = Math.Round(e.Data.ActiveDuration, 1, MidpointRounding.AwayFromZero); Assert.AreEqual(waitForTest, roundedActiveDuration); double roundedDuration = Math.Round(e.Data.Duration, 1, MidpointRounding.AwayFromZero); Assert.AreEqual(waitForTest, roundedDuration); eventEndTriggered = true; }; instance.StartSession(); instance.StartActivity(); Thread.Sleep((int) (waitForTest * 1000)); instance.StopSession(); Thread.Sleep(50); // Waiting a bit for the event TimerSessionEnded. Assert.IsTrue(eventEndTriggered); Assert.IsFalse(eventUpdateTriggered); } catch (Exception e) { Assert.Fail(e.Message); } } [Test] public void StopSession_NotInActivity_EventTimeSessionEndedTriggeredAndExpectedTimerSession() { try { TrackingService instance = CreateTrackingServiceInitialized(5000); double waitForTest = 1; bool eventTriggered = false; instance.TimerSessionEnded += delegate (object sender, TimerSessionEventArgs e) { Assert.AreEqual(0, e.Data.ActiveDuration); double roundedDuration = Math.Round(e.Data.Duration, 1, MidpointRounding.AwayFromZero); Assert.AreEqual(waitForTest, roundedDuration); eventTriggered = true; }; instance.StartSession(); Thread.Sleep((int)(waitForTest * 1000)); instance.StopSession(); Thread.Sleep(50); // Waiting a bit for the event TimerSessionEnded. Assert.IsTrue(eventTriggered); } catch (Exception e) { Assert.Fail(e.Message); } } [TestCase] public void StartActivity_SessionIsStartedAndWaitingDurationHasBeenReached_EventTimerSessionUpdatedTriggered() { try { int waitingDuration = 100; TrackingService instance = CreateTrackingServiceInitialized(waitingDuration); bool eventTriggered = false; instance.TimerSessionUpdated += delegate(object sender, TimerSessionEventArgs e) { eventTriggered = true; Assert.IsNotNull(e.Data); }; instance.StartSession(); instance.StartActivity(); Thread.Sleep(2 * waitingDuration - 10); Assert.IsTrue(eventTriggered); } catch (Exception e) { Assert.Fail(e.Message); } } [TestCase(100, 90, 0)] [TestCase(100, 190, 1)] [TestCase(100, 290, 2)] [TestCase(100, 390, 3)] public void StartActivity_SessionIsStartedAndWaitingDurationHasBeenReached_ExpectedEventTimerSessionUpdatedTriggered(int waitingDuration, int sleepDuration, int expectedTriggerCount) { try { TrackingService instance = CreateTrackingServiceInitialized(waitingDuration); int triggerCount = 0; instance.TimerSessionUpdated += delegate (object sender, TimerSessionEventArgs e) { triggerCount++; }; instance.StartSession(); instance.StartActivity(); Thread.Sleep(sleepDuration); Assert.AreEqual(expectedTriggerCount, triggerCount); } catch (Exception e) { Assert.Fail(e.Message); } } [Test] public void StartActivity_InstanceNotInitialized_ShouldThrowNotInitializedException() { try { TrackingService instance = new TrackingService(GenerateDefaultClientDetails()); Assert.Throws>(delegate { instance.StartActivity(); }, $"No exception was thrown during {GetCurrentMethodName()}"); } catch (Exception e) { Assert.Fail(e.Message); } } [Test] public void StartActivity_SessionIsNotStarted_ShouldThrowNotStartedSessionException() { try { TrackingService instance = CreateTrackingServiceInitialized(); Assert.Throws(delegate { instance.StartActivity(); }, $"No exception was thrown during {GetCurrentMethodName()}"); } catch (Exception e) { Assert.Fail(e.Message); } } [Test] public void StopActivity_SessionIsStartedAndMethodIsCalledBeforeWaitingDurationHasBeenReached_ExpectedEventTimerSessionEndedTriggered() { try { TrackingService instance = CreateTrackingServiceInitialized(2000); bool eventUpdateTriggered = false; instance.TimerSessionUpdated += delegate (object sender, TimerSessionEventArgs e) { eventUpdateTriggered = true; }; bool eventEndTriggered = false; double waitForTest = 1; double roundedActiveDuration = 0; double roundedDuration = 0; instance.TimerSessionEnded += delegate(object sender, TimerSessionEventArgs e) { eventEndTriggered = true; roundedActiveDuration = Math.Round(e.Data.ActiveDuration, 1, MidpointRounding.AwayFromZero); roundedDuration = Math.Round(e.Data.Duration, 1, MidpointRounding.AwayFromZero); }; instance.StartSession(); instance.StartActivity(); Thread.Sleep((int)(waitForTest * 1000)); instance.StopActivity(); instance.StopSession(); Assert.IsTrue(eventEndTriggered, nameof(eventEndTriggered)); Assert.IsFalse(eventUpdateTriggered, nameof(eventUpdateTriggered)); Assert.AreEqual(waitForTest, roundedActiveDuration, $"{nameof(roundedActiveDuration)} has not the expected value !"); Assert.AreEqual(waitForTest, roundedDuration, $"{nameof(roundedDuration)} has not the expected value !"); } catch (Exception e) { Assert.Fail(e.Message); } } [Test] public void StopActivity_SessionIsStartedAndMethodIsCalledFewTimesForReachingWaitingDuration_ExpectedEventTimerSessionUptadedAndEventTimerSessionEndedTriggered() { try { TrackingService instance = CreateTrackingServiceInitialized(1000); // /!\ value of waitingDuration is very important ! bool eventUpdateTriggered = false; instance.TimerSessionUpdated += delegate(object sender, TimerSessionEventArgs e) { eventUpdateTriggered = true; double roundedActiveDuration = Math.Round(e.Data.ActiveDuration, 1, MidpointRounding.AwayFromZero); Assert.AreEqual(0.4, roundedActiveDuration); double roundedDuration = Math.Round(e.Data.Duration, 1, MidpointRounding.AwayFromZero); Assert.AreEqual(1, roundedDuration); }; bool eventEndTriggered = false; double waitForTest = 1; instance.TimerSessionEnded += delegate (object sender, TimerSessionEventArgs e) { eventEndTriggered = true; double roundedActiveDuration = Math.Round(e.Data.ActiveDuration, 1, MidpointRounding.AwayFromZero); Assert.AreEqual(1.2, roundedActiveDuration); double roundedDuration = Math.Round(e.Data.Duration, 1, MidpointRounding.AwayFromZero); Assert.AreEqual(2, roundedDuration); }; instance.StartSession(); Thread.Sleep(500); instance.StartActivity(); Thread.Sleep(400); instance.StopActivity(); // SessionDuration = 900 & ActivityDuration = 400 Thread.Sleep(100); instance.StartActivity(); // SessionDuration = 1000 & ActivityDuration = 400 Thread.Sleep(400); instance.StopActivity(); // SessionDuration = 1400 & ActivityDuration = 800 Thread.Sleep(100); instance.StartActivity(); // SessionDuration = 1500 & ActivityDuration = 800 Thread.Sleep(400); // /!\ When WaitingDuration is reached => SessionDuration = 1700 instance.StopActivity(); // SessionDuration = 1900 & ActivityDuration = 1200 Thread.Sleep(100); instance.StopSession(); // SessionDuration = 2000 & ActivityDuration = 1200 Assert.IsTrue(eventUpdateTriggered); Assert.IsTrue(eventEndTriggered); } catch (Exception e) { Assert.Fail(e.Message); } } [Test] public void StopActivity_SessionIsNotStarted_ShouldThrowNotStartedSessionException() { try { TrackingService instance = CreateTrackingServiceInitialized(); Assert.Throws(delegate { instance.StopActivity(); }, $"No exception was thrown during {GetCurrentMethodName()}"); } catch (Exception e) { Assert.Fail(e.Message); } } [Test] public void StopActivity_InstanceNotInitialized_ShouldThrowNotInitializedException() { try { TrackingService instance = new TrackingService(GenerateDefaultClientDetails()); Assert.Throws>(delegate { instance.StopActivity(); }, $"No exception was thrown during {GetCurrentMethodName()}"); } catch (Exception e) { Assert.Fail(e.Message); } } [TestCase(1)] [TestCase(2)] [TestCase(3)] public void AddAction_SessionIsStartedAndWaitingDurationHasBeenReached_EventActionCountSessionUpdatedTriggeredAndExpectedValue(int actionCount) { try { int waitingDuration = 100; TrackingService instance = CreateTrackingServiceInitialized(waitingDuration); bool eventTriggered = false; instance.ActionCountSessionUpdated += delegate (object sender, ActionCountSessionEventArgs e) { eventTriggered = true; Assert.IsNotNull(e.Data); Assert.IsNotNull(e.Data.Data["my-id"]); Assert.AreEqual(actionCount, e.Data.Data["my-id"]); }; instance.StartSession(); for (int i = 0; i < actionCount; ++i) { instance.AddAction("my-id"); } Thread.Sleep(2 * waitingDuration - 10); Assert.IsTrue(eventTriggered); } catch (Exception e) { Assert.Fail(e.Message); } } [Test] public void AddAction_InstanceNotInitialized_ShouldThrowNotInitializedException() { try { TrackingService instance = new TrackingService(GenerateDefaultClientDetails()); Assert.Throws>(delegate { instance.AddAction("action"); }, $"No exception was thrown during {GetCurrentMethodName()}"); } catch (Exception e) { Assert.Fail(e.Message); } } [Test] public void AddAction_SessionIsNotStarted_ShouldThrowNotStartedSessionException() { try { TrackingService instance = CreateTrackingServiceInitialized(); Assert.Throws(delegate { instance.AddAction("action_id"); }, $"No exception was thrown during {GetCurrentMethodName()}"); } catch (Exception e) { Assert.Fail(e.Message); } } [Test] public void AddAction_SessionIsStartedAndWaitingDurationHasBeenReachedTwice_EventActionCountSessionUpdatedTriggeredOneTime() { try { int waitingDuration = 100; TrackingService instance = CreateTrackingServiceInitialized(waitingDuration); int eventTriggeredCount = 0; instance.ActionCountSessionUpdated += delegate (object sender, ActionCountSessionEventArgs e) { eventTriggeredCount++; }; instance.StartSession(); instance.AddAction("my-id"); Thread.Sleep(2 * waitingDuration - 10); Assert.AreEqual(1, eventTriggeredCount); Thread.Sleep(2 * waitingDuration - 10); Assert.AreEqual(1, eventTriggeredCount); } catch (Exception e) { Assert.Fail(e.Message); } } [Test] public void ReportMacroExecution_InstanceNotInitialized_ShouldThrowNotInitializedException() { try { TrackingService instance = new TrackingService(GenerateDefaultClientDetails()); Assert.Throws>(delegate { instance.ReportMacroExecution(string.Empty, DateTime.UtcNow, string.Empty, string.Empty, 0, string.Empty); }, $"No exception was thrown during {GetCurrentMethodName()}"); } catch (Exception e) { Assert.Fail(e.Message); } } [Test] public void ReportMacroExecution_ValidArguments_EventMacroExecutionEndedTriggered() { TrackingService instance = CreateTrackingServiceInitialized(); bool eventTriggered = false; string macroName = "my-macro"; DateTime launchDate = DateTime.UtcNow; string macroResult = "success"; string macroParameters = "my-parameters"; double duration = 42.42; string launchAction = "from-unit-tests"; DateTime beforeStartSession = DateTime.UtcNow; instance.MacroExecutionEnded += delegate (object sender, MacroExecutionSessionEventArgs e) { eventTriggered = true; CheckBaseSessionCreated(beforeStartSession, instance.ClientDetails, e.Data); Assert.AreEqual(macroName, e.Data.MacroName); Assert.AreEqual(launchDate, e.Data.LaunchDate); Assert.AreEqual(macroResult, e.Data.Result); Assert.AreEqual(macroParameters, e.Data.MacroParameters); Assert.AreEqual(duration, e.Data.Duration); Assert.AreEqual(launchAction, e.Data.LaunchAction); }; instance.ReportMacroExecution(macroName, launchDate, macroResult, macroParameters, duration, launchAction); Assert.IsTrue(eventTriggered); } } }