using LCU.Api.Internal.Dispatcher.Configurations; using LCU.Api.Internal.Dispatcher.Services; using LCU.Api.Internal.DispatcherLib.Repositories; using LCU.Api.Internal.DispatcherSharedLib.Utilities; using LCU.Library.Internal.DispatcherModels.Enums; using LCU.Library.Internal.DispatcherModels.Models.Requests; using LCU.Library.Internal.DispatcherModels.Models.Results; using NCrontab; using System.Collections.Generic; namespace LCU.Api.Internal.DispatcherTests.ServiceTests; public class JobServiceTest { private readonly JobService _jobService; private readonly Mock _mockUnitOfWork; private readonly Mock _mockJobRepository; private readonly Mock _mockWorkItemService; private readonly Mock _mockWorkItemExecutionService; private readonly ApiConfiguration _apiConfiguration; public JobServiceTest() { _mockJobRepository = new Mock(); _mockWorkItemService = new Mock(); _mockWorkItemExecutionService = new Mock(); _mockUnitOfWork = new Mock(); _apiConfiguration = new ApiConfiguration() { NumberOfWorkItemExecutions = 10, NumberOfExecutionMessages = 20, ProcessingLimitInMinutes = 30, JobStatusesToMonitorByTimeLimit = new List() { JobStatus.Processing } }; _jobService = new JobService(_mockUnitOfWork.Object, _mockJobRepository.Object, _mockWorkItemService.Object, _mockWorkItemExecutionService.Object, _apiConfiguration); } [Fact] public async Task GetListAsync_Success() { // Arrange _mockJobRepository.Setup(repo => repo.ListAsync(10, 20)).ReturnsAsync(new List() { new Job(), new Job() }); // Act var result = await _jobService.GetAsync(); // Assert Assert.NotNull(result); Assert.Equal(2, result.Count()); } [Fact] public async Task GetByIdAsync_Success() { // Arrange _mockJobRepository.Setup(repo => repo.FindByIdAsync(It.IsAny(), 10, 20)).ReturnsAsync(new Job()); // Act var result = await _jobService.GetByIdAsync(1); // Assert Assert.NotNull(result); } [Fact] public async Task SaveAsync_Success() { // Arrange _mockJobRepository.Setup(repo => repo.AddAsync(It.IsAny())).ReturnsAsync(new Job()); // Act var result = await _jobService.SaveAsync(new Job()); // Assert Assert.NotNull(result); _mockJobRepository.Verify(x => x.AddAsync(It.IsAny()), Times.Once); _mockUnitOfWork.Verify(x => x.CompleteAsync(), Times.Once); } [Fact] public async Task UpdateAsync_Success() { // Arrange var jobToUpdate = new Job() { JobId = 1, RequestingApplication = "App" }; _mockJobRepository.Setup(repo => repo.Update(It.IsAny())).ReturnsAsync(jobToUpdate).Verifiable(); // Act var result = await _jobService.UpdateAsync(jobToUpdate); // Assert Assert.NotNull(result); Assert.Equal(jobToUpdate.JobId, result.JobId); Assert.Equal(jobToUpdate.RequestingApplication, result.RequestingApplication); _mockJobRepository.Verify(x => x.FindByIdAsync(It.IsAny(), 10, 20), Times.Never); _mockJobRepository.Verify(x => x.Update(It.IsAny()), Times.Once); _mockUnitOfWork.Verify(x => x.CompleteAsync(), Times.Once); } [Fact] public async Task CompleteJobAndWorkItem_Success_NoReschedule() { // Arrange var jobToUpdate = new Job() { JobId = 1, RequestingApplication = "App", NextRunTime = Convert.ToDateTime("1/1/2022") }; _mockJobRepository.Setup(repo => repo.FindByIdExcludingWorkItemsAsync(1)).ReturnsAsync(jobToUpdate); var updatedJob = new Job() { JobId = 1, RequestingApplication = "App", NextRunTime = null }; _mockJobRepository.Setup(repo => repo.Update(It.IsAny())).ReturnsAsync(updatedJob).Verifiable(); var updatedWorkitemExecution = new WorkItemExecution() { WorkItemExecutionId = 1, Status = StatusType.Completed }; _mockWorkItemExecutionService.Setup(repo => repo.UpdateCompleteWorkItemExecutionWithoutSave(It.IsAny(), StatusType.Completed)).ReturnsAsync(updatedWorkitemExecution); _mockUnitOfWork.Setup(uow => uow.CompleteAsync()).Returns(Task.CompletedTask); // Act await _jobService.CompleteJobAndWorkItem(jobToUpdate, new WorkItemExecution(100), StatusType.Completed); // Assert _mockJobRepository.Verify(x => x.FindByIdExcludingWorkItemsAsync(1), Times.Never); _mockJobRepository.Verify(x => x.Update(It.IsAny()), Times.Once); _mockWorkItemExecutionService.Verify(x => x.UpdateCompleteWorkItemExecutionWithoutSave(It.IsAny(), It.IsAny()), Times.Once); _mockUnitOfWork.Verify(x => x.CompleteAsync(), Times.Once); } [Fact] public async Task CompleteJobAndWorkItem_Success_Reschedule() { // Arrange var jobToUpdate = new Job() { JobId = 1, RequestingApplication = "App", NextRunTime = Convert.ToDateTime("1/1/2022"), CronSchedule = "5 * * * *" }; _mockJobRepository.Setup(repo => repo.FindByIdExcludingWorkItemsAsync(1)).ReturnsAsync(jobToUpdate); var updatedJob = new Job() { JobId = 1, RequestingApplication = "App", NextRunTime = DateTime.Now.AddSeconds(5) }; _mockJobRepository.Setup(repo => repo.Update(It.IsAny())).ReturnsAsync(updatedJob).Verifiable(); var updatedWorkitemExecution = new WorkItemExecution() { WorkItemExecutionId = 1, Status = StatusType.Completed }; _mockWorkItemExecutionService.Setup(repo => repo.UpdateCompleteWorkItemExecutionWithoutSave(It.IsAny(), It.IsAny())).ReturnsAsync(updatedWorkitemExecution); _mockUnitOfWork.Setup(uow => uow.CompleteAsync()).Returns(Task.CompletedTask); // Act await _jobService.CompleteJobAndWorkItem(jobToUpdate, new WorkItemExecution(100), StatusType.Completed); // Assert _mockJobRepository.Verify(x => x.FindByIdExcludingWorkItemsAsync(1), Times.Never); _mockJobRepository.Verify(x => x.Update(It.IsAny()), Times.Once); _mockWorkItemExecutionService.Verify(x => x.UpdateCompleteWorkItemExecutionWithoutSave(It.IsAny(), It.IsAny()), Times.Once); _mockUnitOfWork.Verify(x => x.CompleteAsync(), Times.Once); } [Fact] public void HolidayHelper_Success_SkipMemorialDayHolidayNextOccurrence() { // Arrange var jobToUpdate = new Job() { JobId = 1, SkipHolidays = true, RequestingApplication = "App", CronSchedule = "0 0 1-30 5 *" }; CrontabSchedule schedule = CrontabSchedule.Parse(jobToUpdate.CronSchedule); var nextRunTime = schedule.GetNextOccurrence(DateTime.Now); var memorialDay = HolidayHelpers.GetMemorialDay(nextRunTime.Year); //stage next possible holiday date, reset cron jobToUpdate.NextRunTime = memorialDay; jobToUpdate.CronSchedule = $"0 0 {memorialDay.Day} {memorialDay.Month} *"; // Assert Assert.NotNull(jobToUpdate); Assert.Equal(memorialDay, jobToUpdate.NextRunTime); } [Fact] public void HolidayHelper_Success_SkipThanksGivingHolidayNextOccurrence() { // Arrange var jobToUpdate = new Job() { JobId = 1, SkipHolidays = true, RequestingApplication = "App", CronSchedule = "0 0 22-30 11 *" }; CrontabSchedule schedule = CrontabSchedule.Parse(jobToUpdate.CronSchedule); var nextRunTime = schedule.GetNextOccurrence(DateTime.Now); var thanksgivingDay = HolidayHelpers.GetThanksgivingDay(nextRunTime.Year); //stage next possible holiday date, reset cron jobToUpdate.NextRunTime = thanksgivingDay; jobToUpdate.CronSchedule = $"0 0 {thanksgivingDay.Day} {thanksgivingDay.Month} *"; // Assert Assert.NotNull(jobToUpdate); Assert.Equal(thanksgivingDay, jobToUpdate.NextRunTime); } [Fact] public void HolidayHelper_Success_SkipIndependenceDayHolidayNextOccurrence() { // Arrange var jobToUpdate = new Job() { JobId = 1, SkipHolidays = true, RequestingApplication = "App", CronSchedule = "0 0 4-31 7 *" }; CrontabSchedule schedule = CrontabSchedule.Parse(jobToUpdate.CronSchedule); var nextRunTime = schedule.GetNextOccurrence(DateTime.Now); CronHelpers.SetNextScheduledRunTime(ref jobToUpdate); var nextRunTimeAfterHoliday = HolidayHelpers.HandleHolidays(nextRunTime); // Assert Assert.NotNull(jobToUpdate); Assert.Equal(nextRunTimeAfterHoliday, jobToUpdate.NextRunTime); } [Fact] public void HolidayHelper_Success_SkipLaborDayHolidayNextOccurrence() { // Arrange var jobToUpdate = new Job() { JobId = 1, SkipHolidays = true, RequestingApplication = "App", CronSchedule = "0 0 1-30 9 *" }; CrontabSchedule schedule = CrontabSchedule.Parse(jobToUpdate.CronSchedule); var nextRunTime = schedule.GetNextOccurrence(DateTime.Now); var laborDay = HolidayHelpers.GetLaborDay(nextRunTime.Year); //stage next possible holiday date, reset cron jobToUpdate.NextRunTime = laborDay; jobToUpdate.CronSchedule = $"0 0 {laborDay.Day} {laborDay.Month} *"; var nextRunTimeAfterHoliday = HolidayHelpers.GetLaborDay(nextRunTime.Year); // Assert Assert.NotNull(jobToUpdate); Assert.Equal(nextRunTimeAfterHoliday, jobToUpdate.NextRunTime); } [Fact] public void GetJobsProcessingLongerThanTime_Return_OneProcessingJob_LongerThen_30minutes() { // Arrange var jobs = new List() { new Job() { Name = "Test Process Job", NextRunTime = DateTime.Now.AddHours(-1), WorkItems = new List() { new WorkItem() { Name = "Test Process WorkItem", WorkItemExecutions = new List(){ new WorkItemExecution() { Status = StatusType.Processing, StartedTimestamp = DateTime.Now.AddMinutes(-31) } } } }, JobStatus = JobStatus.Processing } }; // Act var result = _jobService.GetJobsInStatusLongerThanTimeLimit(jobs, 30); // Assert Assert.NotNull(result); Assert.Single(result); } [Fact] public void GetJobsProcessingLongerThanTime_Return_NoneProcessingJob_NotLongerThen_30minutes() { // Arrange var jobs = new List() { new Job() { Name = "Test Failed Job", NextRunTime = DateTime.Now.AddHours(-1), JobStatus = JobStatus.Failed, WorkItems = new List() { new WorkItem() { Name = "Test Failed WorkItem", WorkItemExecutions = new List(){ new WorkItemExecution() { Status = StatusType.Failed, StartedTimestamp = DateTime.Now.AddMinutes(-10) } } } } }, new Job() { Name = "Test Process Job", NextRunTime = DateTime.Now.AddHours(-1), JobStatus = JobStatus.Processing, WorkItems = new List() { new WorkItem() { Name = "Test Process WorkItem", WorkItemExecutions = new List(){ new WorkItemExecution() { Status = StatusType.Processing, StartedTimestamp = DateTime.Now.AddMinutes(-29) } } } } } }; // Act var result = _jobService.GetJobsInStatusLongerThanTimeLimit(jobs, 30); // Assert Assert.NotNull(result); Assert.Empty(result); } [Fact] public void GetJobsProcessingLongerThanTime_Return_NoneProcessingJob_NoneAreProcessing() { // Arrange var jobs = new List() { new Job() { Name = "Test Failed Job", NextRunTime = DateTime.Now.AddHours(-1), JobStatus = JobStatus.Failed, WorkItems = new List() { new WorkItem() { Name = "Test Failed WorkItem", WorkItemExecutions = new List(){ new WorkItemExecution() { Status = StatusType.Failed, StartedTimestamp = DateTime.Now.AddMinutes(-5) } } } } }, new Job() { Name = "Test Failed 2 Job", NextRunTime = DateTime.Now.AddHours(-1), JobStatus = JobStatus.Processing, WorkItems = new List() { new WorkItem() { Name = "Test Failed 2 WorkItem", WorkItemExecutions = new List(){ new WorkItemExecution() { Status = StatusType.Failed, StartedTimestamp = DateTime.Now.AddMinutes(-29) } } } } } }; // Act var result = _jobService.GetJobsInStatusLongerThanTimeLimit(jobs, 30); // Assert Assert.NotNull(result); Assert.Empty(result); } [Fact] public void GetJobsProcessingLongerThanTime_Return_TwoJobs_MultipleStatuses() { // Arrange var jobs = new List() { new Job() { Name = "Test Failed Job", NextRunTime = DateTime.Now.AddHours(-1), JobStatus = JobStatus.DeterminingOperation, WorkItems = new List() { new WorkItem() { Name = "Test Failed WorkItem", WorkItemExecutions = new List(){ new WorkItemExecution() { Status = StatusType.Failed, StartedTimestamp = Convert.ToDateTime("4/1/2021") } } } } }, new Job() { Name = "Test Failed 2 Job", NextRunTime = DateTime.Now.AddHours(-1), JobStatus = JobStatus.Processing, WorkItems = new List() { new WorkItem() { Name = "Test Failed 2 WorkItem", WorkItemExecutions = new List(){ new WorkItemExecution() { Status = StatusType.Failed, StartedTimestamp = DateTime.Now.AddMinutes(-31) } } } } } }; // Act var result = _jobService.GetJobsInStatusLongerThanTimeLimit(jobs, 30); // Assert Assert.NotNull(result); Assert.Equal(2, result.Count()); } [Fact] public async Task GetErroredAndProcessingJobs_Return_TwoJobs_OneFailed_OneProcessingLongerThen_30minutes() { var jobs = new List() { new Job() { Name = "Test Pending Job", NextRunTime = DateTime.Now.AddHours(-1), WorkItems = new List() { new WorkItem() { Name = "Test Pending WorkItem", WorkItemExecutions = new List(){ new WorkItemExecution() { Status = StatusType.Pending, StartedTimestamp = Convert.ToDateTime("4/1/2021") } } } }, JobStatus = JobStatus.Failed }, new Job() { Name = "Test Failed Job", NextRunTime = DateTime.Now.AddHours(-1), WorkItems = new List() { new WorkItem() { Name = "Test Failed WorkItem", WorkItemExecutions = new List(){ new WorkItemExecution() { Status = StatusType.Failed, StartedTimestamp = Convert.ToDateTime("4/1/2021") } } } }, JobStatus = JobStatus.Failed }, new Job() { Name = "Test Process Job", NextRunTime = DateTime.Now.AddHours(-1), WorkItems = new List() { new WorkItem() { Name = "Test Process WorkItem", WorkItemExecutions = new List(){ new WorkItemExecution() { Status = StatusType.Processing, StartedTimestamp = DateTime.Now.AddMinutes(-31) } } } }, JobStatus = JobStatus.Processing } }; _mockJobRepository.Setup(repo => repo.FindJobsPastDateByStatuses(It.IsAny(), 10, 20, It.IsAny>())).ReturnsAsync(jobs); // Act var result = await _jobService.GetErrorMonitoringJobs(); // Assert Assert.NotNull(result); Assert.Equal(3, result.ToList().Count); Assert.Single(result.Where(r => r.CurrentStatus == StatusType.Failed)); Assert.Single(result.Where(r => r.CurrentStatus == StatusType.Processing)); } [Fact] public async Task GetErroredAndProcessingJobs_Return_OneFailedJob() { var jobs = new List() { new Job() { Name = "Test Pending Job", NextRunTime = DateTime.Now.AddHours(-1), WorkItems = new List() { new WorkItem() { Name = "Test Pending WorkItem", WorkItemExecutions = new List(){ new WorkItemExecution() { Status = StatusType.Pending, StartedTimestamp = DateTime.Now.AddMinutes(-5) } } } }, JobStatus = JobStatus.PendingToRun }, new Job() { Name = "Test Failed Job", NextRunTime = DateTime.Now.AddHours(-1), WorkItems = new List() { new WorkItem() { Name = "Test Failed WorkItem", WorkItemExecutions = new List(){ new WorkItemExecution() { Status = StatusType.Failed, StartedTimestamp = Convert.ToDateTime("4/1/2021") } } } }, JobStatus = JobStatus.Failed }, new Job() { Name = "Test Process Job", NextRunTime = DateTime.Now.AddHours(-1), WorkItems = new List() { new WorkItem() { Name = "Test Process WorkItem", WorkItemExecutions = new List(){ new WorkItemExecution() { Status = StatusType.Processing, StartedTimestamp = DateTime.Now.AddMinutes(-5) } } } }, JobStatus = JobStatus.Inactive } }; _mockJobRepository.Setup(repo => repo.FindJobsPastDateByStatuses(It.IsAny(), 10, 20, It.IsAny>())).ReturnsAsync(jobs); // Act var result = await _jobService.GetErrorMonitoringJobs(); // Assert Assert.NotNull(result); Assert.Single(result); Assert.Single(result.Where(r => r.CurrentStatus == StatusType.Failed)); Assert.Empty(result.Where(r => r.CurrentStatus == StatusType.Processing)); } [Fact] public async Task GetErroredAndProcessingJobs_Return_Empty() { var jobs = new List() { new Job() { Name = "Test Pending Job", NextRunTime = DateTime.Now.AddHours(-1), WorkItems = new List() { new WorkItem() { Name = "Test Pending WorkItem", WorkItemExecutions = new List(){ new WorkItemExecution() { Status = StatusType.Pending, StartedTimestamp = DateTime.Now.AddMinutes(-12) } } } } }, new Job() { Name = "Test Process 1 Job", NextRunTime = DateTime.Now.AddHours(-1), WorkItems = new List() { new WorkItem() { Name = "Test Process 1 WorkItem", WorkItemExecutions = new List(){ new WorkItemExecution() { Status = StatusType.Processing, StartedTimestamp = DateTime.Now.AddMinutes(-2) } } } } }, new Job() { Name = "Test Process 2 Job", NextRunTime = DateTime.Now.AddHours(-1), WorkItems = new List() { new WorkItem() { Name = "Test Process 2 WorkItem", WorkItemExecutions = new List(){ new WorkItemExecution() { Status = StatusType.Processing, StartedTimestamp = DateTime.Now.AddMinutes(-5) } } } } } }; _mockJobRepository.Setup(repo => repo.FindJobsPastDateByStatuses(It.IsAny(), 10, 20, It.IsAny>())).ReturnsAsync(jobs); // Act var result = await _jobService.GetErrorMonitoringJobs(); // Assert Assert.NotNull(result); Assert.Empty(result); } [Fact] public async Task FilterPaged_Ok() { var pagedJobResult = new PagedResult(); var filter = new Filter() { Page = 1, Limit = 2, StatusFilters = null, SortableFields = null, TagIdFilters = new List { 1 } }; _mockJobRepository.Setup(repo => repo.FilteredListAsync(filter, _apiConfiguration.NumberOfWorkItemExecutions, _apiConfiguration.NumberOfExecutionMessages)) .ReturnsAsync(pagedJobResult).Verifiable(); // Act var result = await _jobService.GetFilteredJobs(filter); // Assert Assert.NotNull(result); Assert.Equal(pagedJobResult, result); } [Fact] public async Task DeleteAsync_Success() { var jobReturned = new Job { JobId = 1, WorkItems = new List { new WorkItem { WorkItemId = 1 }, new WorkItem { WorkItemId = 2 } } }; _mockJobRepository.Setup(repo => repo.FindByIdAsync(1, 10, 20)).ReturnsAsync(jobReturned); // Act var jobIdToDelete = 1; await _jobService.DeleteAsync(jobIdToDelete); // Assert _mockJobRepository.Verify(service => service.FindByIdAsync(It.Is(x => x == 1), It.Is(x => x == 10), It.Is(x => x == 20)), Times.Once); _mockWorkItemService.Verify(service => service.DeleteList(It.Is>(workItems => workItems.Count == 2)), Times.Never); } [Fact] public async Task GetJobsReadyToBeScheduled_Return_Empty() { var jobs = new List(); _mockJobRepository.Setup(repo => repo.FindJobsReadyToBeScheduled(It.IsAny())).ReturnsAsync(jobs); // Act var result = await _jobService.GetJobsReadyToBeScheduled(); // Assert Assert.NotNull(result); Assert.Empty(result); } [Fact] public async Task GetJobsReadyToBeScheduled_Returns_One() { var jobs = new List { new Job { JobId = 1, } }; _mockJobRepository.Setup(repo => repo.FindJobsReadyToBeScheduled(It.IsAny())).ReturnsAsync(jobs); // Act var result = await _jobService.GetJobsReadyToBeScheduled(); // Assert Assert.NotNull(result); Assert.Single(result); } [Fact] public async Task GetInactiveAsync_Success() { // Arrange _mockJobRepository.Setup(repo => repo.ListInactiveAsync(10, 20)).ReturnsAsync(new List() { new Job(), new Job() }); // Act var result = await _jobService.GetInactiveAsync(); // Assert Assert.NotNull(result); Assert.Equal(2, result.Count()); } [Fact] public async Task GetSoftDeletedAsync_Success() { // Arrange _mockJobRepository.Setup(repo => repo.ListSoftDeletedTrackedAsync(10, 20, It.IsAny())).ReturnsAsync(new List() { new Job(), new Job() }); // Act var result = await _jobService.GetSoftDeletedTrackedAsync(DateTime.Now.AddDays(-1)); // Assert Assert.NotNull(result); Assert.Equal(2, result.Count()); } [Fact] public async Task RemoveSoftDeleteById_Success() { // Arrange var existingJob = new Job() { JobId = 100, SoftDeleteDate = DateTime.Now, }; _mockJobRepository.Setup(repo => repo.FindByIdAsync(It.IsAny(), 10, 20)).ReturnsAsync(existingJob); _mockJobRepository.Setup(repo => repo.Update(It.IsAny())).Verifiable(); // Act var result = await _jobService.RemoveSoftDeleteById(100); // Assert Assert.NotNull(result); Assert.Equal(existingJob.JobId, result.JobId); Assert.Null(result.SoftDeleteDate); _mockJobRepository.Verify(x => x.FindByIdAsync(It.IsAny(), 10, 20), Times.Once); _mockJobRepository.Verify(x => x.Update(It.IsAny()), Times.Once); _mockUnitOfWork.Verify(x => x.CompleteAsync(), Times.Once); } [Fact] public void RemoveSoftDeleteById_Throws() { // Arrange _mockJobRepository.Setup(repo => repo.FindByIdAsync(It.IsAny(), 10, 20)).ReturnsAsync((Job)null); // Act/Assert var result = Assert.ThrowsAsync(async () => await _jobService.RemoveSoftDeleteById(100)); } [Fact] public async Task GetInactiveExcludeSoftDeletedAsync_Success() { // Arrange _mockJobRepository.Setup(repo => repo.ListInactiveExcludeSoftDeletedTrackedAsync(10, 20)).ReturnsAsync(new List() { new Job(), new Job() }); // Act var result = await _jobService.GetInactiveExcludeSoftDeletedTrackedAsync(); // Assert Assert.NotNull(result); Assert.Equal(2, result.Count()); } [Fact] public async void CopyJobAsync_Success_KeepEnvironment() { var job = new Job { JobId = DateTime.Now.Millisecond, CronSchedule = new Guid().ToString(), NextRunTime = DateTime.Now.AddHours(1) }; var request = new CopyJobRequest { JobId = job.JobId, ChangeEnvironment = false }; var newJob = new Job { JobId = DateTime.Now.Millisecond + 1 }; _mockJobRepository.Setup(x => x.FindByIdAsync(job.JobId, 10, 20)) .ReturnsAsync(job).Verifiable(); _mockJobRepository.Setup(x => x.AddAsync(It.IsAny())) .ReturnsAsync(newJob).Verifiable(); var result = await _jobService.CopyJobAsync(request); Assert.NotNull(result); Assert.Null(result.CronSchedule); Assert.Null(result.NextRunTime); Assert.NotEqual(request.JobId, result.JobId); Assert.Equal(newJob.JobId, result.JobId); _mockJobRepository.Verify(x => x.FindByIdAsync(It.IsAny(), It.IsAny(), It.IsAny()), Times.Once()); _mockJobRepository.Verify(x => x.AddAsync(It.IsAny()), Times.Once); _mockUnitOfWork.Verify(x => x.CompleteAsync(), Times.Once); } [Fact] public async void CopyJobAsync_Success_ChangeEnvironment() { var job = new Job { JobId = DateTime.Now.Millisecond, CronSchedule = new Guid().ToString(), NextRunTime = DateTime.Now.AddHours(1) }; var request = new CopyJobRequest { JobId = job.JobId, ChangeEnvironment = true }; _mockJobRepository.Setup(x => x.FindByIdAsync(job.JobId, 10, 20)) .ReturnsAsync(job).Verifiable(); var result = await _jobService.CopyJobAsync(request); Assert.NotNull(result); Assert.Null(result.CronSchedule); Assert.Null(result.NextRunTime); Assert.Equal(0, result.JobId); _mockJobRepository.Verify(x => x.FindByIdAsync(It.IsAny(), It.IsAny(), It.IsAny()), Times.Once()); _mockJobRepository.Verify(x => x.AddAsync(It.IsAny()), Times.Never); _mockUnitOfWork.Verify(x => x.CompleteAsync(), Times.Never); } [Fact] public void CopyJobAsync_Failure_NotFound() { var request = new CopyJobRequest { JobId = DateTime.Now.Millisecond }; Job? jobReturned = null; _mockJobRepository.Setup(x => x.FindByIdAsync(request.JobId, 10, 20)) .ReturnsAsync(jobReturned).Verifiable(); Assert.ThrowsAsync(async () => await _jobService.CopyJobAsync(request)); _mockJobRepository.Verify(x => x.FindByIdAsync(It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); _mockUnitOfWork.Verify(x => x.CompleteAsync(), Times.Never); } [Fact] public void CopyJobAsync_Failure_Exception() { _mockJobRepository.Setup(x => x.FindByIdAsync(It.IsAny(), It.IsAny(), It.IsAny())) .ThrowsAsync(new Exception("Test")).Verifiable(); Assert.ThrowsAsync(async () => await _jobService.CopyJobAsync(It.IsAny())); _mockJobRepository.Verify(x => x.FindByIdAsync(It.IsAny(), It.IsAny(), It.IsAny()), Times.Never); _mockUnitOfWork.Verify(x => x.CompleteAsync(), Times.Never); } }