private void AndTheTemplateContentsCalculation(TemplateMappingItem mappingItem,
                                                       TemplateMetadataContents templateMetadataContents,
                                                       TemplateCalculation calculation)
        {
            calculation.TemplateCalculationId = mappingItem.TemplateId;

            FundingLine fundingLine = templateMetadataContents.RootFundingLines.First();

            fundingLine.Calculations = fundingLine.Calculations.Concat(new[]
            {
                calculation
            });
        }
        public void ThrowsExceptionIfCreateCallFailsWhenCreatingMissingCalculations()
        {
            TemplateMappingItem      mappingWithMissingCalculation1 = NewTemplateMappingItem();
            TemplateMapping          templateMapping          = NewTemplateMapping(_ => _.WithItems(mappingWithMissingCalculation1));
            TemplateMetadataContents templateMetadataContents = NewTemplateMetadataContents(_ => _.WithFundingLines(NewFundingLine(fl => fl.WithCalculations(NewTemplateMappingCalculation()))));
            TemplateCalculation      templateCalculationOne   = NewTemplateMappingCalculation(_ => _.WithName("template calculation 1"));

            GivenAValidMessage();
            AndTheJobCanBeRun();
            AndTheTemplateMapping(templateMapping);
            AndTheTemplateMetaDataContents(templateMetadataContents);
            AndTheTemplateContentsCalculation(mappingWithMissingCalculation1, templateMetadataContents, templateCalculationOne);
            AndTheSpecificationIsReturned();

            ThenAnExceptionShouldBeThrownWithMessage("Unable to create new default template calculation for template mapping");
        }
        public override async Task Process(Message message)
        {
            string specificationId   = message.GetUserProperty <string>("specification-id");
            string specificationName = message.GetUserProperty <string>("specification-name");

            if (specificationId == null)
            {
                string error = "Specification id missing";

                _logger.Error(error);

                throw new NonRetriableException(error);
            }

            IEnumerable <string> jobDefinitions = new List <string>
            {
                JobConstants.DefinitionNames.CreateInstructAllocationJob
            };

            IEnumerable <string> jobTypesRunning = await GetJobTypes(specificationId, jobDefinitions);

            if (!jobTypesRunning.IsNullOrEmpty())
            {
                string errorMessage = string.Join(Environment.NewLine, jobTypesRunning.Select(_ => $"{_} is still running"));
                _logger.Error(errorMessage);

                throw new NonRetriableException(errorMessage);
            }

            string temporaryFilePath = new CsvFilePath(_fileSystemCacheSettings.Path, specificationId);

            EnsureFileIsNew(temporaryFilePath);

            bool outputHeaders = true;

            ApiResponse <SpecificationSummary> specificationSummaryResponse = await _specificationsApiClientPolicy.ExecuteAsync(() => _specificationsApiClient.GetSpecificationSummaryById(specificationId));

            if (specificationSummaryResponse?.Content == null)
            {
                string errorMessage = $"Specification: {specificationId} not found";
                _logger.Error(errorMessage);

                throw new NonRetriableException(errorMessage);
            }

            SpecificationSummary specificationSummary = specificationSummaryResponse.Content;

            IEnumerable <TemplateMappingItem> allMappings = new TemplateMappingItem[0];

            foreach (Reference reference in specificationSummary.FundingStreams)
            {
                ApiResponse <TemplateMapping> templateMapping = await _calculationsApiClientPolicy.ExecuteAsync(() => _calculationsApiClient.GetTemplateMapping(specificationId, reference.Id));

                if (templateMapping?.Content == null)
                {
                    continue;
                }

                allMappings = allMappings.Concat(templateMapping.Content.TemplateMappingItems);
            }

            if (allMappings.Any(_ => _.CalculationId == null))
            {
                string errorMessage = $"Specification: {specificationId} has missing calculations in template mapping";
                _logger.Error(errorMessage);

                throw new NonRetriableException(errorMessage);
            }

            await _resultsRepositoryPolicy.ExecuteAsync(() => _resultsRepository.ProviderResultsBatchProcessing(specificationId,
                                                                                                                providerResults =>
            {
                IEnumerable <ExpandoObject> csvRows = _resultsToCsvRowsTransformation.TransformProviderResultsIntoCsvRows(providerResults, allMappings.ToDictionary(_ => _.CalculationId));

                string csv = _csvUtils.AsCsv(csvRows, outputHeaders);

                _fileSystemAccess.Append(temporaryFilePath, csv)
                .GetAwaiter()
                .GetResult();

                outputHeaders = false;
                return(Task.CompletedTask);
            }, BatchSize)
                                                        );

            ICloudBlob blob = _blobClient.GetBlockBlobReference($"calculation-results-{specificationId}.csv");

            blob.Properties.ContentDisposition = $"attachment; filename={GetPrettyFileName(specificationName)}";

            await using Stream csvFileStream = _fileSystemAccess.OpenRead(temporaryFilePath);

            await _blobClientPolicy.ExecuteAsync(() => UploadBlob(blob, csvFileStream, GetMetadata(specificationId, specificationName)));
        }
        public async Task DoesNotModifiesCalculationsIfOnTemplateExistsAndHasSameValues()
        {
            uint templateCalculationId1 = (uint)new RandomNumberBetween(1, int.MaxValue);
            uint templateCalculationId2 = (uint)new RandomNumberBetween(1, int.MaxValue);
            uint templateCalculationId3 = (uint)new RandomNumberBetween(1, int.MaxValue);
            uint templateCalculationId4 = (uint)new RandomNumberBetween(1, int.MaxValue);

            string calculationId1 = NewRandomString();
            string calculationId2 = NewRandomString();

            string calculationName1 = NewRandomString();
            string calculationName2 = NewRandomString();

            string newCalculationId1 = NewRandomString();
            string newCalculationId2 = NewRandomString();

            CalculationValueFormat calculationValueFormat1 = CalculationValueFormat.Currency;
            CalculationValueFormat calculationValueFormat2 = CalculationValueFormat.Number;

            TemplateMappingItem mappingWithMissingCalculation1 = NewTemplateMappingItem();
            TemplateMappingItem mappingWithMissingCalculation2 = NewTemplateMappingItem();

            TemplateMapping templateMapping = NewTemplateMapping(_ => _.WithItems(mappingWithMissingCalculation1,
                                                                                  mappingWithMissingCalculation2));

            TemplateMetadataContents templateMetadataContents = NewTemplateMetadataContents(_ => _.WithFundingLines(NewFundingLine(fl =>
                                                                                                                                   fl.WithCalculations(
                                                                                                                                       NewTemplateMappingCalculation(x => x.WithTemplateCalculationId(templateCalculationId1).WithName(calculationName1).WithValueFormat(calculationValueFormat1)),
                                                                                                                                       NewTemplateMappingCalculation(x => x.WithTemplateCalculationId(templateCalculationId2).WithName(calculationName2).WithValueFormat(calculationValueFormat2)),
                                                                                                                                       NewTemplateMappingCalculation(x => x.WithTemplateCalculationId(templateCalculationId3)),
                                                                                                                                       NewTemplateMappingCalculation(x => x.WithTemplateCalculationId(templateCalculationId4))))));
            TemplateCalculation templateCalculationOne = NewTemplateMappingCalculation(_ => _.WithName("template calculation 1"));
            TemplateCalculation templateCalculationTwo = NewTemplateMappingCalculation(_ => _.WithName("template calculation 2"));

            List <Calculation> calculations = new List <Calculation>
            {
                NewCalculation(_ => _.WithId(calculationId1)
                               .WithCurrentVersion(
                                   NewCalculationVersion(x =>
                                                         x.WithCalculationId(calculationId1).WithName(calculationName1).WithValueType(calculationValueFormat1.AsMatchingEnum <CalculationValueType>())))),
                NewCalculation(_ => _.WithId(calculationId2)
                               .WithCurrentVersion(
                                   NewCalculationVersion(x => x.WithCalculationId(calculationId2).WithName(calculationName2).WithValueType(calculationValueFormat2.AsMatchingEnum <CalculationValueType>())))),
            };

            GivenAValidMessage();
            AndTheJobCanBeRun();
            AndTheTemplateMapping(templateMapping);
            AndTheTemplateMetaDataContents(templateMetadataContents);

            CalculationValueType calculationValueTypeOne = templateCalculationOne.ValueFormat.AsMatchingEnum <CalculationValueType>();
            CalculationValueType calculationValueTypeTwo = templateCalculationTwo.ValueFormat.AsMatchingEnum <CalculationValueType>();

            AndTheCalculationIsCreatedForRequestMatching(_ => _.Name == templateCalculationOne.Name &&
                                                         _.SourceCode == calculationValueTypeOne.GetDefaultSourceCode() &&
                                                         _.SpecificationId == _specificationId &&
                                                         _.FundingStreamId == _fundingStreamId &&
                                                         _.ValueType.GetValueOrDefault() == calculationValueTypeOne,
                                                         NewCalculation(_ => _.WithId(newCalculationId1)));
            AndTheCalculationIsCreatedForRequestMatching(_ => _.Name == templateCalculationTwo.Name &&
                                                         _.SourceCode == calculationValueTypeTwo.GetDefaultSourceCode() &&
                                                         _.SpecificationId == _specificationId &&
                                                         _.FundingStreamId == _fundingStreamId &&
                                                         _.ValueType.GetValueOrDefault() == calculationValueTypeTwo,
                                                         NewCalculation(_ => _.WithId(newCalculationId2)));
            AndTheTemplateContentsCalculation(mappingWithMissingCalculation1, templateMetadataContents, templateCalculationOne);
            AndTheTemplateContentsCalculation(mappingWithMissingCalculation2, templateMetadataContents, templateCalculationTwo);

            AndTheCalculations(calculations);
            AndTheSpecificationIsReturned();

            await WhenTheTemplateCalculationsAreApplied();

            mappingWithMissingCalculation1
            .CalculationId
            .Should().Be(newCalculationId1);

            mappingWithMissingCalculation2
            .CalculationId
            .Should().Be(newCalculationId2);

            AndTheTemplateMappingWasUpdated(templateMapping, 1);
            AndTheJobsStartWasLogged();
            AndTheJobCompletionWasLogged();
            AndACalculationRunWasInitialised();

            AndCalculationEdited(_ => _.Name == calculationName1 &&
                                 _.ValueType.GetValueOrDefault() == calculationValueFormat1.AsMatchingEnum <CalculationValueType>() &&
                                 _.Description == null &&
                                 _.SourceCode == null,
                                 calculationId1, 0);

            AndCalculationEdited(_ => _.Name == calculationName2 &&
                                 _.ValueType.GetValueOrDefault() == calculationValueFormat2.AsMatchingEnum <CalculationValueType>() &&
                                 _.Description == null &&
                                 _.SourceCode == null,
                                 calculationId2, 0);
        }
        public async Task ModifiesCalculationsIfOnTemplateExists()
        {
            uint templateCalculationId1 = (uint)new RandomNumberBetween(1, int.MaxValue);
            uint templateCalculationId2 = (uint)new RandomNumberBetween(1, int.MaxValue);

            string calculationId1 = "calculationId1";
            string calculationId2 = "calculationId2";
            string calculationId3 = "calculationId3";

            string calculationName1 = "calculationName1";
            string calculationName2 = "calculationName2";
            string calculationName3 = "calculationName3";

            string newCalculationName1 = "newCalculationName1";
            string newCalculationName2 = "newCalculationName2";

            string newCalculationId4 = "newCalculationId4";
            string newCalculationId5 = "newCalculationId5";

            CalculationValueFormat calculationValueFormat1 = CalculationValueFormat.Currency;
            CalculationValueFormat calculationValueFormat2 = CalculationValueFormat.Number;
            CalculationValueType   calculationValueType3   = CalculationValueType.Percentage;

            TemplateMappingItem mappingWithMissingCalculation1 = NewTemplateMappingItem();
            TemplateMappingItem mappingWithMissingCalculation2 = NewTemplateMappingItem();
            TemplateMappingItem mappingWithMissingCalculation3 = NewTemplateMappingItem(_ =>
            {
                _.WithCalculationId(calculationId3);
                _.WithName(calculationName3);
            });

            TemplateMapping templateMapping = NewTemplateMapping(_ => _.WithItems(mappingWithMissingCalculation1,
                                                                                  NewTemplateMappingItem(mi => mi.WithCalculationId(calculationId1).WithTemplateId(templateCalculationId1)),
                                                                                  mappingWithMissingCalculation2,
                                                                                  NewTemplateMappingItem(mi => mi.WithCalculationId(calculationId2).WithTemplateId(templateCalculationId2)),
                                                                                  mappingWithMissingCalculation3));

            TemplateMetadataContents templateMetadataContents = NewTemplateMetadataContents(_ => _.WithFundingLines(NewFundingLine(fl =>
                                                                                                                                   fl.WithCalculations(
                                                                                                                                       NewTemplateMappingCalculation(),
                                                                                                                                       NewTemplateMappingCalculation(),
                                                                                                                                       NewTemplateMappingCalculation(x => x.WithTemplateCalculationId(templateCalculationId1).WithName(newCalculationName1).WithValueFormat(calculationValueFormat1)),
                                                                                                                                       NewTemplateMappingCalculation(x => x.WithTemplateCalculationId(templateCalculationId2).WithName(newCalculationName2).WithValueFormat(calculationValueFormat2))))));
            TemplateCalculation templateCalculationOne = NewTemplateMappingCalculation(_ => _.WithName("template calculation 1"));
            TemplateCalculation templateCalculationTwo = NewTemplateMappingCalculation(_ => _.WithName("template calculation 2"));

            List <Calculation> calculations = new List <Calculation>
            {
                NewCalculation(_ => _.WithId(calculationId1)
                               .WithCurrentVersion(
                                   NewCalculationVersion(x => x.WithCalculationId(calculationId1).WithName(calculationName1)))),
                NewCalculation(_ => _.WithId(calculationId2)
                               .WithCurrentVersion(
                                   NewCalculationVersion(x => x.WithCalculationId(calculationId2).WithName(calculationName2)))),
            };

            Calculation missingCalculation = NewCalculation(_ => _.WithId(calculationId3)
                                                            .WithCurrentVersion(
                                                                NewCalculationVersion(x =>
            {
                x.WithName(calculationName3);
                x.WithValueType(calculationValueType3);
            })));

            GivenAValidMessage();
            AndTheJobCanBeRun();
            AndTheTemplateMapping(templateMapping);
            AndTheTemplateMetaDataContents(templateMetadataContents);

            CalculationValueType calculationValueTypeOne = templateCalculationOne.ValueFormat.AsMatchingEnum <CalculationValueType>();
            CalculationValueType calculationValueTypeTwo = templateCalculationTwo.ValueFormat.AsMatchingEnum <CalculationValueType>();

            AndTheCalculationIsCreatedForRequestMatching(_ => _.Name == templateCalculationOne.Name &&
                                                         _.SourceCode == calculationValueTypeOne.GetDefaultSourceCode() &&
                                                         _.SpecificationId == _specificationId &&
                                                         _.FundingStreamId == _fundingStreamId &&
                                                         _.ValueType.GetValueOrDefault() == calculationValueTypeOne,
                                                         NewCalculation(_ => _.WithId(newCalculationId4)));
            AndTheCalculationIsEditedForRequestMatching(_ => _.Name == newCalculationName1 &&
                                                        _.ValueType.GetValueOrDefault() == calculationValueFormat1.AsMatchingEnum <CalculationValueType>() &&
                                                        _.Description == null &&
                                                        _.SourceCode == null,
                                                        calculationId1);
            AndTheCalculationIsCreatedForRequestMatching(_ => _.Name == templateCalculationTwo.Name &&
                                                         _.SourceCode == calculationValueTypeTwo.GetDefaultSourceCode() &&
                                                         _.SpecificationId == _specificationId &&
                                                         _.FundingStreamId == _fundingStreamId &&
                                                         _.ValueType.GetValueOrDefault() == calculationValueTypeTwo,
                                                         NewCalculation(_ => _.WithId(newCalculationId5)));
            AndTheCalculationIsEditedForRequestMatching(_ => _.Name == newCalculationName2 &&
                                                        _.ValueType.GetValueOrDefault() == calculationValueFormat2.AsMatchingEnum <CalculationValueType>() &&
                                                        _.Description == null &&
                                                        _.SourceCode == null,
                                                        calculationId2);

            AndTheTemplateContentsCalculation(mappingWithMissingCalculation1, templateMetadataContents, templateCalculationOne);
            AndTheTemplateContentsCalculation(mappingWithMissingCalculation2, templateMetadataContents, templateCalculationTwo);

            AndMissingCalculation(calculationId3, missingCalculation);
            AndTheCalculations(calculations);
            AndTheSpecificationIsReturned();
            AndTheCalculationCodeOnCalculationChangeReturned(calculationId1, newCalculationName1, calculationName1, _specificationId, calculations.Where(_ => _.Id == calculationId1));

            await WhenTheTemplateCalculationsAreApplied();

            mappingWithMissingCalculation1
            .CalculationId
            .Should().Be(newCalculationId4);

            mappingWithMissingCalculation2
            .CalculationId
            .Should().Be(newCalculationId5);

            AndTheCalculationCodeOnCalculationChangeUpdated(calculationId1, newCalculationName1, calculationName1, _specificationId, 1);
            AndTheCalculationCodeOnCalculationChangeUpdated(calculationId2, newCalculationName2, calculationName2, _specificationId, 1);
            AndUpdateBuildProjectCalled(_specificationId, 2);
            AndTheTemplateMappingWasUpdated(templateMapping, 1);
            AndTheJobsStartWasLogged();
            AndTheJobCompletionWasLogged();
            AndACalculationRunWasInitialised();
            AndACodeContextUpdateJobWasQueued();
        }
        public async Task CreatesCalculationsIfOnTemplateMappingButDontExistYet()
        {
            TemplateMappingItem mappingWithMissingCalculation1 = NewTemplateMappingItem();
            TemplateMappingItem mappingWithMissingCalculation2 = NewTemplateMappingItem();
            TemplateMappingItem mappingWithMissingCalculation3 = NewTemplateMappingItem();
            TemplateMappingItem mappingWithMissingCalculation4 = NewTemplateMappingItem();

            TemplateMapping templateMapping = NewTemplateMapping(_ => _.WithItems(mappingWithMissingCalculation1,
                                                                                  NewTemplateMappingItem(mi => mi.WithCalculationId(NewRandomString())),
                                                                                  mappingWithMissingCalculation2,
                                                                                  NewTemplateMappingItem(mi => mi.WithCalculationId(NewRandomString())),

                                                                                  mappingWithMissingCalculation3,
                                                                                  NewTemplateMappingItem(mi => mi.WithCalculationId(NewRandomString())),
                                                                                  mappingWithMissingCalculation4));

            TemplateMetadataContents templateMetadataContents = NewTemplateMetadataContents(_ => _.WithFundingLines(NewFundingLine(fl =>
                                                                                                                                   fl.WithCalculations(
                                                                                                                                       NewTemplateMappingCalculation(c1 =>
            {
                c1.WithCalculations(NewTemplateMappingCalculation(c4 => c4.WithTemplateCalculationId(4)));
                c1.WithTemplateCalculationId(1);
            }),
                                                                                                                                       NewTemplateMappingCalculation(c2 => c2.WithTemplateCalculationId(2)),
                                                                                                                                       NewTemplateMappingCalculation(c3 => c3.WithTemplateCalculationId(3)),
                                                                                                                                       NewTemplateMappingCalculation(c4 => c4.WithTemplateCalculationId(4)
                                                                                                                                                                     .WithType(Common.TemplateMetadata.Enums.CalculationType.Enum)
                                                                                                                                                                     .WithAllowedEnumTypeValues(new List <string>()
            {
                "Type1", "Type2", "Type3"
            })
                                                                                                                                                                     .WithValueFormat(CalculationValueFormat.String))
                                                                                                                                       ))));
            TemplateCalculation templateCalculationOne   = NewTemplateMappingCalculation(_ => _.WithName("template calculation 1"));
            TemplateCalculation templateCalculationTwo   = NewTemplateMappingCalculation(_ => _.WithName("template calculation 2"));
            TemplateCalculation templateCalculationThree = NewTemplateMappingCalculation(_ => _.WithName("template calculation 3"));
            TemplateCalculation templateCalculationFour  = NewTemplateMappingCalculation(_ => _.WithName("template calculation 4"));

            string newCalculationId1 = NewRandomString();
            string newCalculationId2 = NewRandomString();
            string newCalculationId3 = NewRandomString();
            string newCalculationId4 = NewRandomString();

            GivenAValidMessage();
            AndTheJobCanBeRun();
            AndTheTemplateMapping(templateMapping);
            AndTheTemplateMetaDataContents(templateMetadataContents);

            CalculationValueType calculationValueTypeOne   = templateCalculationOne.ValueFormat.AsMatchingEnum <CalculationValueType>();
            CalculationValueType calculationValueTypeTwo   = templateCalculationTwo.ValueFormat.AsMatchingEnum <CalculationValueType>();
            CalculationValueType calculationValueTypeThree = templateCalculationThree.ValueFormat.AsMatchingEnum <CalculationValueType>();
            CalculationValueType calculationValueTypeFour  = templateCalculationFour.ValueFormat.AsMatchingEnum <CalculationValueType>();

            AndTheCalculationIsCreatedForRequestMatching(_ => _.Name == templateCalculationOne.Name &&
                                                         _.SourceCode == calculationValueTypeOne.GetDefaultSourceCode() &&
                                                         _.SpecificationId == _specificationId &&
                                                         _.FundingStreamId == _fundingStreamId &&
                                                         _.ValueType.GetValueOrDefault() == calculationValueTypeOne,
                                                         NewCalculation(_ => _.WithId(newCalculationId1)));
            AndTheCalculationIsCreatedForRequestMatching(_ => _.Name == templateCalculationTwo.Name &&
                                                         _.SourceCode == calculationValueTypeTwo.GetDefaultSourceCode() &&
                                                         _.SpecificationId == _specificationId &&
                                                         _.FundingStreamId == _fundingStreamId &&
                                                         _.ValueType.GetValueOrDefault() == calculationValueTypeTwo,
                                                         NewCalculation(_ => _.WithId(newCalculationId2)));
            AndTheCalculationIsCreatedForRequestMatching(_ => _.Name == templateCalculationThree.Name &&
                                                         _.SourceCode == calculationValueTypeThree.GetDefaultSourceCode() &&
                                                         _.SpecificationId == _specificationId &&
                                                         _.FundingStreamId == _fundingStreamId &&
                                                         _.ValueType.GetValueOrDefault() == calculationValueTypeThree,
                                                         NewCalculation(_ => _.WithId(newCalculationId3)));
            AndTheCalculationIsCreatedForRequestMatching(_ => _.Name == templateCalculationFour.Name &&
                                                         _.SourceCode == calculationValueTypeFour.GetDefaultSourceCode() &&
                                                         _.SpecificationId == _specificationId &&
                                                         _.FundingStreamId == _fundingStreamId &&
                                                         _.ValueType.GetValueOrDefault() == calculationValueTypeFour,
                                                         NewCalculation(_ => _.WithId(newCalculationId4)));
            AndTheTemplateContentsCalculation(mappingWithMissingCalculation1, templateMetadataContents, templateCalculationOne);
            AndTheTemplateContentsCalculation(mappingWithMissingCalculation2, templateMetadataContents, templateCalculationTwo);
            AndTheTemplateContentsCalculation(mappingWithMissingCalculation3, templateMetadataContents, templateCalculationThree);
            AndTheTemplateContentsCalculation(mappingWithMissingCalculation4, templateMetadataContents, templateCalculationFour);
            AndTheSpecificationIsReturned();

            await WhenTheTemplateCalculationsAreApplied();

            mappingWithMissingCalculation1
            .CalculationId
            .Should().Be(newCalculationId1);

            mappingWithMissingCalculation2
            .CalculationId
            .Should().Be(newCalculationId2);

            mappingWithMissingCalculation3
            .CalculationId
            .Should().Be(newCalculationId3);

            mappingWithMissingCalculation4
            .CalculationId
            .Should().Be(newCalculationId4);

            AndTheTemplateMappingWasUpdated(templateMapping, 1);
            AndTheJobsStartWasLogged();
            AndTheJobCompletionWasLogged();
            AndACalculationRunWasInitialised();
        }