public async Task GetJobDetails_ReturnsJobViewModel()
        {
            IJobsApiClient jobsApiClient             = Substitute.For <IJobsApiClient>();
            JobManagementResiliencePolicies policies = new JobManagementResiliencePolicies
            {
                JobsApiClient = Policy.NoOpAsync()
            };
            IMessengerService messengerService = Substitute.For <IMessengerService>();
            ILogger           logger           = Substitute.For <ILogger>();

            JobViewModel jvm = new JobViewModel
            {
                CompletionStatus = null
            };

            ApiResponse <JobViewModel> jobApiResponse = new ApiResponse <JobViewModel>(HttpStatusCode.OK, jvm);

            jobsApiClient
            .GetJobById(Arg.Any <string>())
            .Returns(jobApiResponse);

            JobManagement jobManagement = new JobManagement(jobsApiClient, logger, policies, messengerService);

            string jobId = "3456";

            //Act
            JobViewModel viewModel = await jobManagement.GetJobById(jobId);

            //Assert
            viewModel
            .Should()
            .Be(jvm);
        }
        public async Task CreateAllocationLineResultStatusUpdateJobs_GivenJobIdButJobResponseIsNotFound_LogsAndDoesNotProcess()
        {
            //Arrange
            Message message = new Message();

            message.UserProperties.Add("jobId", jobId);

            ILogger logger = CreateLogger();

            ApiResponse <JobViewModel> apiResponse = new ApiResponse <JobViewModel>(HttpStatusCode.NotFound);

            IJobsApiClient jobsApiClient = CreateJobsApiClient();

            jobsApiClient
            .GetJobById(jobId)
            .Returns(apiResponse);

            PublishedResultsService publishedResultsService = CreateResultsService(logger, jobsApiClient: jobsApiClient);

            //Act
            await publishedResultsService.CreateAllocationLineResultStatusUpdateJobs(message);

            //Assert
            logger
            .Received(1)
            .Error(Arg.Is($"Could not find the job with id: '{jobId}'"));

            await jobsApiClient
            .DidNotReceive()
            .AddJobLog(Arg.Is(jobId), Arg.Any <JobLogUpdateModel>());
        }
        public void CreateAllocationLineResultStatusUpdateJobs_GivenJobFoundButModelNotFoundInCache_LogsAndThrowsException()
        {
            //Arrange
            const string cacheKey = "cache-key";

            Message message = new Message();

            message.UserProperties.Add("jobId", jobId);

            ILogger logger = CreateLogger();

            JobViewModel job = new JobViewModel
            {
                Id         = jobId,
                Properties = new Dictionary <string, string>
                {
                    { "cache-key", cacheKey }
                }
            };

            ApiResponse <JobViewModel> apiResponse = new ApiResponse <JobViewModel>(HttpStatusCode.OK, job);

            IJobsApiClient jobsApiClient = CreateJobsApiClient();

            jobsApiClient
            .GetJobById(jobId)
            .Returns(apiResponse);

            ICacheProvider cacheProvider = CreateCacheProvider();

            cacheProvider
            .GetAsync <UpdatePublishedAllocationLineResultStatusModel>(Arg.Is(cacheKey))
            .Returns((UpdatePublishedAllocationLineResultStatusModel)null);

            PublishedResultsService publishedResultsService = CreateResultsService(logger, jobsApiClient: jobsApiClient, cacheProvider: cacheProvider);

            //Act
            Func <Task> test = async() => await publishedResultsService.CreateAllocationLineResultStatusUpdateJobs(message);

            //Assert
            test
            .Should()
            .ThrowExactly <Exception>()
            .Which
            .Message
            .Should()
            .Be($"Could not find the update model in cache with cache key: '{cacheKey}'");

            logger
            .Received(1)
            .Error(Arg.Is($"Could not find the update model in cache with cache key: '{cacheKey}'"));
        }
        public async Task RetrieveJobAndCheckCanBeProcessed_ApiReturnsIncomplete_ReturnsCorrectly()
        {
            //Arrange
            IJobsApiClient jobsApiClient             = Substitute.For <IJobsApiClient>();
            JobManagementResiliencePolicies policies = new JobManagementResiliencePolicies
            {
                JobsApiClient = Policy.NoOpAsync()
            };
            IMessengerService messengerService = Substitute.For <IMessengerService>();
            ILogger           logger           = Substitute.For <ILogger>();

            JobViewModel jvm = new JobViewModel
            {
                CompletionStatus = null
            };

            ApiResponse <JobViewModel> jobApiResponse = new ApiResponse <JobViewModel>(HttpStatusCode.OK, jvm);

            jobsApiClient
            .GetJobById(Arg.Any <string>())
            .Returns(jobApiResponse);

            JobManagement jobManagement = new JobManagement(jobsApiClient, logger, policies, messengerService);

            string jobId = "3456";

            //Act
            JobViewModel viewModel = await jobManagement.RetrieveJobAndCheckCanBeProcessed(jobId);

            //Assert
            await jobsApiClient
            .Received(1)
            .GetJobById(jobId);

            viewModel
            .Should()
            .Be(jvm);
        }
        public async Task CreateAllocationLineResultStatusUpdateJobs_GivenJobFoundButAlreadyCompleted_LogsAndReturnsDoesNotAddJobLog()
        {
            //Arrange
            Message message = new Message();

            message.UserProperties.Add("jobId", jobId);

            ILogger logger = CreateLogger();

            JobViewModel job = new JobViewModel
            {
                Id = jobId,
                CompletionStatus = CompletionStatus.Cancelled
            };

            ApiResponse <JobViewModel> apiResponse = new ApiResponse <JobViewModel>(HttpStatusCode.OK, job);

            IJobsApiClient jobsApiClient = CreateJobsApiClient();

            jobsApiClient
            .GetJobById(jobId)
            .Returns(apiResponse);

            PublishedResultsService publishedResultsService = CreateResultsService(logger, jobsApiClient: jobsApiClient);

            //Act
            await publishedResultsService.CreateAllocationLineResultStatusUpdateJobs(message);

            //Assert
            logger
            .Received(1)
            .Information(Arg.Is($"Received job with id: '{job.Id}' is already in a completed state with status {job.CompletionStatus.ToString()}"));

            await
            jobsApiClient
            .DidNotReceive()
            .AddJobLog(Arg.Any <string>(), Arg.Any <JobLogUpdateModel>());
        }
        public async Task RetrieveJobAndCheckCanBeProcessed_FailsWithJobAlreadyCompleted_LogsAndErrors(ApiResponse <JobViewModel> jobApiResponse,
                                                                                                       string jobId,
                                                                                                       string errorMessage,
                                                                                                       LogEventLevel logEventLevel)
        {
            //Arrange
            IJobsApiClient    jobsApiClient          = Substitute.For <IJobsApiClient>();
            IMessengerService messengerService       = Substitute.For <IMessengerService>();
            JobManagementResiliencePolicies policies = new JobManagementResiliencePolicies
            {
                JobsApiClient = Policy.NoOpAsync()
            };
            ILogger logger = Substitute.For <ILogger>();

            jobsApiClient
            .GetJobById(Arg.Any <string>())
            .Returns(jobApiResponse);

            JobManagement jobManagement = new JobManagement(jobsApiClient, logger, policies, messengerService);

            Func <Task> test = async() => await jobManagement.RetrieveJobAndCheckCanBeProcessed(jobId);

            test
            .Should().Throw <JobAlreadyCompletedException>()
            .Which
            .Message
            .Should().Be(errorMessage);

            await jobsApiClient
            .Received(1)
            .GetJobById(jobId);

            logger
            .Received(1)
            .Write(logEventLevel, errorMessage);
        }
示例#7
0
        public async Task <JobViewModel> RetrieveJobAndCheckCanBeProcessed(string jobId)
        {
            ApiResponse <JobViewModel> response = await _jobsApiClientPolicy.ExecuteAsync(() => _jobsApiClient.GetJobById(jobId));

            if (response?.Content == null)
            {
                string error = $"Could not find the job with id: '{jobId}'";

                _logger.Write(LogEventLevel.Error, error);

                throw new JobNotFoundException(error, jobId);
            }

            JobViewModel job = response.Content;

            if (job.CompletionStatus.HasValue)
            {
                string error = $"Received job with id: '{jobId}' is already in a completed state with status {job.CompletionStatus}";

                _logger.Write(LogEventLevel.Information, error);

                throw new JobAlreadyCompletedException(error, job);
            }

            return(job);
        }
        public void CreateAllocationLineResultStatusUpdateJobs_GivenUpateModelWith10ProvidersAndMaxPartitionSizeOf2ButOnlyThreeJobsCreatedFromFive_ThrowsException()
        {
            //Arrange
            const string cacheKey = "cache-key";

            Message message = new Message();

            message.UserProperties.Add("jobId", jobId);

            ILogger logger = CreateLogger();

            JobViewModel job = new JobViewModel
            {
                Id = jobId,
                SpecificationId        = specificationId,
                InvokerUserDisplayName = "user-name",
                InvokerUserId          = "user-id",
                CorrelationId          = "coorelation-id",
                Properties             = new Dictionary <string, string>
                {
                    { "cache-key", cacheKey }
                },
                JobDefinitionId = JobConstants.DefinitionNames.CreateInstructAllocationLineResultStatusUpdateJob
            };

            UpdatePublishedAllocationLineResultStatusModel updateModel = new UpdatePublishedAllocationLineResultStatusModel
            {
                Status    = AllocationLineStatus.Approved,
                Providers = CreateProviderAllocationLineResults()
            };

            ApiResponse <JobViewModel> apiResponse = new ApiResponse <JobViewModel>(HttpStatusCode.OK, job);

            IJobsApiClient jobsApiClient = CreateJobsApiClient();

            jobsApiClient
            .GetJobById(jobId)
            .Returns(apiResponse);

            IEnumerable <Job> newJobs = new[]
            {
                new Job(),
                new Job(),
                new Job()
            };

            jobsApiClient
            .CreateJobs(Arg.Any <IEnumerable <JobCreateModel> >())
            .Returns(newJobs);

            ICacheProvider cacheProvider = CreateCacheProvider();

            cacheProvider
            .GetAsync <UpdatePublishedAllocationLineResultStatusModel>(Arg.Is(cacheKey))
            .Returns(updateModel);

            IPublishedProviderResultsSettings settings = CreatePublishedProviderResultsSettings();

            settings
            .UpdateAllocationLineResultStatusBatchCount
            .Returns(2);

            PublishedResultsService publishedResultsService = CreateResultsService(logger, jobsApiClient: jobsApiClient, cacheProvider: cacheProvider, publishedProviderResultsSettings: settings);

            //Act
            Func <Task> test = async() => await publishedResultsService.CreateAllocationLineResultStatusUpdateJobs(message);

            //Assert
            test
            .Should()
            .ThrowExactly <Exception>()
            .Which
            .Message
            .Should()
            .Be($"Only 3 jobs were created from 5 childJobs for parent job: '{job.Id}'");

            logger
            .Received(1)
            .Error(Arg.Is($"Only 3 jobs were created from 5 childJobs for parent job: '{job.Id}'"));
        }
        public async Task CreateAllocationLineResultStatusUpdateJobs_GivenUpateModelWith10ProvidersAndMaxPartitionSizeOf2_CreatesFiveChildJobs()
        {
            //Arrange
            const string cacheKey = "cache-key";

            Message message = new Message();

            message.UserProperties.Add("jobId", jobId);

            ILogger logger = CreateLogger();

            JobViewModel job = new JobViewModel
            {
                Id = jobId,
                SpecificationId        = specificationId,
                InvokerUserDisplayName = "user-name",
                InvokerUserId          = "user-id",
                CorrelationId          = "coorelation-id",
                Properties             = new Dictionary <string, string>
                {
                    { "cache-key", cacheKey }
                },
                JobDefinitionId = JobConstants.DefinitionNames.CreateInstructAllocationLineResultStatusUpdateJob
            };

            UpdatePublishedAllocationLineResultStatusModel updateModel = new UpdatePublishedAllocationLineResultStatusModel
            {
                Status    = AllocationLineStatus.Approved,
                Providers = CreateProviderAllocationLineResults()
            };

            ApiResponse <JobViewModel> apiResponse = new ApiResponse <JobViewModel>(HttpStatusCode.OK, job);

            IJobsApiClient jobsApiClient = CreateJobsApiClient();

            jobsApiClient
            .GetJobById(jobId)
            .Returns(apiResponse);

            IEnumerable <Job> newJobs = new[]
            {
                new Job(),
                new Job(),
                new Job(),
                new Job(),
                new Job(),
            };

            jobsApiClient
            .CreateJobs(Arg.Any <IEnumerable <JobCreateModel> >())
            .Returns(newJobs);

            ICacheProvider cacheProvider = CreateCacheProvider();

            cacheProvider
            .GetAsync <UpdatePublishedAllocationLineResultStatusModel>(Arg.Is(cacheKey))
            .Returns(updateModel);

            IPublishedProviderResultsSettings settings = CreatePublishedProviderResultsSettings();

            settings
            .UpdateAllocationLineResultStatusBatchCount
            .Returns(2);

            PublishedResultsService publishedResultsService = CreateResultsService(logger, jobsApiClient: jobsApiClient, cacheProvider: cacheProvider, publishedProviderResultsSettings: settings);

            //Act
            await publishedResultsService.CreateAllocationLineResultStatusUpdateJobs(message);

            //Assert
            await
            jobsApiClient
            .Received(1)
            .CreateJobs(Arg.Is <IEnumerable <JobCreateModel> >(m => m.Count() == 5));

            await
            cacheProvider
            .Received(1)
            .RemoveAsync <UpdatePublishedAllocationLineResultStatusModel>(Arg.Is(cacheKey));
        }
        public async Task CreateAllocationLineResultStatusUpdateJobs_GivenUpateModelWith10ProvidersAndDefaultMaxPartitionSize_CreatesOneChildJob()
        {
            //Arrange
            const string cacheKey = "cache-key";

            Message message = new Message();

            message.UserProperties.Add("jobId", jobId);

            ILogger logger = CreateLogger();

            JobViewModel job = new JobViewModel
            {
                Id = jobId,
                SpecificationId        = specificationId,
                InvokerUserDisplayName = "user-name",
                InvokerUserId          = "user-id",
                CorrelationId          = "coorelation-id",
                Properties             = new Dictionary <string, string>
                {
                    { "cache-key", cacheKey }
                },
                JobDefinitionId = JobConstants.DefinitionNames.CreateInstructAllocationLineResultStatusUpdateJob
            };

            UpdatePublishedAllocationLineResultStatusModel updateModel = new UpdatePublishedAllocationLineResultStatusModel
            {
                Status    = AllocationLineStatus.Approved,
                Providers = CreateProviderAllocationLineResults()
            };

            ApiResponse <JobViewModel> apiResponse = new ApiResponse <JobViewModel>(HttpStatusCode.OK, job);

            IJobsApiClient jobsApiClient = CreateJobsApiClient();

            jobsApiClient
            .GetJobById(jobId)
            .Returns(apiResponse);

            IEnumerable <Job> newJobs = new[]
            {
                new Job()
            };

            jobsApiClient
            .CreateJobs(Arg.Any <IEnumerable <JobCreateModel> >())
            .Returns(newJobs);

            ICacheProvider cacheProvider = CreateCacheProvider();

            cacheProvider
            .GetAsync <UpdatePublishedAllocationLineResultStatusModel>(Arg.Is(cacheKey))
            .Returns(updateModel);

            PublishedResultsService publishedResultsService = CreateResultsService(logger, jobsApiClient: jobsApiClient, cacheProvider: cacheProvider);

            //Act
            await publishedResultsService.CreateAllocationLineResultStatusUpdateJobs(message);

            //Assert
            await
            jobsApiClient
            .Received(1)
            .CreateJobs(Arg.Is <IEnumerable <JobCreateModel> >(
                            m => m.First().Trigger.EntityId == jobId &&
                            m.First().Trigger.EntityType == "Job" &&
                            m.First().Trigger.Message == $"Triggered by parent job" &&
                            m.First().SpecificationId == specificationId &&
                            m.First().ParentJobId == jobId &&
                            m.First().InvokerUserId == job.InvokerUserId &&
                            m.First().InvokerUserDisplayName == job.InvokerUserDisplayName &&
                            m.First().CorrelationId == job.CorrelationId &&
                            !string.IsNullOrWhiteSpace(m.First().MessageBody)));

            await
            cacheProvider
            .Received(1)
            .RemoveAsync <UpdatePublishedAllocationLineResultStatusModel>(Arg.Is(cacheKey));
        }
        public async Task UpdateAllocations(Message message)
        {
            Guard.ArgumentNotNull(message, nameof(message));

            JobViewModel job = null;

            if (!message.UserProperties.ContainsKey("jobId"))
            {
                _logger.Error("Missing parent job id to instruct generating allocations");

                return;
            }

            string jobId = message.UserProperties["jobId"].ToString();

            ApiResponse <JobViewModel> response = await _jobsApiClientPolicy.ExecuteAsync(() => _jobsApiClient.GetJobById(jobId));

            if (response == null || response.Content == null)
            {
                _logger.Error($"Could not find the parent job with job id: '{jobId}'");

                throw new Exception($"Could not find the parent job with job id: '{jobId}'");
            }

            job = response.Content;

            if (job.CompletionStatus.HasValue)
            {
                _logger.Information($"Received job with id: '{job.Id}' is already in a completed state with status {job.CompletionStatus.ToString()}");

                return;
            }

            await _jobsApiClientPolicy.ExecuteAsync(() => _jobsApiClient.AddJobLog(jobId, new Common.ApiClient.Jobs.Models.JobLogUpdateModel()));

            IDictionary <string, string> properties = message.BuildMessageProperties();

            string specificationId = message.UserProperties["specification-id"].ToString();

            BuildProject buildProject = await GetBuildProjectForSpecificationId(specificationId);

            if (message.UserProperties.ContainsKey("ignore-save-provider-results"))
            {
                properties.Add("ignore-save-provider-results", "true");
            }

            string cacheKey = "";

            if (message.UserProperties.ContainsKey("provider-cache-key"))
            {
                cacheKey = message.UserProperties["provider-cache-key"].ToString();
            }
            else
            {
                cacheKey = $"{CacheKeys.ScopedProviderSummariesPrefix}{specificationId}";
            }

            bool summariesExist = await _cacheProvider.KeyExists <ProviderSummary>(cacheKey);

            long totalCount = await _cacheProvider.ListLengthAsync <ProviderSummary>(cacheKey);

            bool refreshCachedScopedProviders = false;

            if (summariesExist)
            {
                IEnumerable <string> scopedProviderIds = await _providerResultsRepository.GetScopedProviderIds(specificationId);

                if (scopedProviderIds.Count() != totalCount)
                {
                    refreshCachedScopedProviders = true;
                }
                else
                {
                    IEnumerable <ProviderSummary> cachedScopedSummaries = await _cacheProvider.ListRangeAsync <ProviderSummary>(cacheKey, 0, (int)totalCount);

                    IEnumerable <string> differences = scopedProviderIds.Except(cachedScopedSummaries.Select(m => m.Id));

                    refreshCachedScopedProviders = differences.AnyWithNullCheck();
                }
            }

            if (!summariesExist || refreshCachedScopedProviders)
            {
                totalCount = await _providerResultsRepository.PopulateProviderSummariesForSpecification(specificationId);
            }

            const string providerSummariesPartitionIndex = "provider-summaries-partition-index";

            const string providerSummariesPartitionSize = "provider-summaries-partition-size";

            properties.Add(providerSummariesPartitionSize, _engineSettings.MaxPartitionSize.ToString());

            properties.Add("provider-cache-key", cacheKey);

            properties.Add("specification-id", specificationId);

            IList <IDictionary <string, string> > allJobProperties = new List <IDictionary <string, string> >();

            for (int partitionIndex = 0; partitionIndex < totalCount; partitionIndex += _engineSettings.MaxPartitionSize)
            {
                if (properties.ContainsKey(providerSummariesPartitionIndex))
                {
                    properties[providerSummariesPartitionIndex] = partitionIndex.ToString();
                }
                else
                {
                    properties.Add(providerSummariesPartitionIndex, partitionIndex.ToString());
                }

                IDictionary <string, string> jobProperties = new Dictionary <string, string>();

                foreach (KeyValuePair <string, string> item in properties)
                {
                    jobProperties.Add(item.Key, item.Value);
                }
                allJobProperties.Add(jobProperties);
            }

            await _specificationsRepository.UpdateCalculationLastUpdatedDate(specificationId);

            try
            {
                if (!allJobProperties.Any())
                {
                    _logger.Information($"No scoped providers set for specification '{specificationId}'");

                    JobLogUpdateModel jobCompletedLog = new JobLogUpdateModel
                    {
                        CompletedSuccessfully = true,
                        Outcome = "Calculations not run as no scoped providers set for specification"
                    };
                    await _jobsApiClientPolicy.ExecuteAsync(() => _jobsApiClient.AddJobLog(job.Id, jobCompletedLog));

                    return;
                }

                IEnumerable <Job> newJobs = await CreateGenerateAllocationJobs(job, allJobProperties);

                int newJobsCount = newJobs.Count();
                int batchCount   = allJobProperties.Count();

                if (newJobsCount != batchCount)
                {
                    throw new Exception($"Only {newJobsCount} child jobs from {batchCount} were created with parent id: '{job.Id}'");
                }
                else
                {
                    _logger.Information($"{newJobsCount} child jobs were created for parent id: '{job.Id}'");
                }
            }
            catch (Exception ex)
            {
                _logger.Error(ex.Message);

                throw new Exception($"Failed to create child jobs for parent job: '{job.Id}'");
            }
        }
        private async Task <JobViewModel> AddStartingProcessJobLog(string jobId)
        {
            if (string.IsNullOrWhiteSpace(jobId))
            {
                _logger.Error($"No jobId given.");
                throw new NonRetriableException("No Job Id given");
            }

            ApiResponse <JobViewModel> jobResponse = await _jobsApiClientPolicy.ExecuteAsync(() => _jobsApiClient.GetJobById(jobId));

            if (jobResponse == null || jobResponse.Content == null)
            {
                _logger.Error($"Could not find the parent job with job id: '{jobId}'");

                throw new NonRetriableException($"Could not find the parent job with job id: '{jobId}'");
            }

            JobViewModel job = jobResponse.Content;

            if (job.CompletionStatus.HasValue)
            {
                _logger.Information($"Received job with id: '{job.Id}' is already in a completed state with status {job.CompletionStatus.ToString()}");

                return(null);
            }

            await _jobsApiClientPolicy.ExecuteAsync(() => _jobsApiClient.AddJobLog(jobId, new JobLogUpdateModel()));

            return(job);
        }