public async Task ValidateFundingTemplate_GivenTemplateWIthValidSchemaVersionButTemplateDoesNotValidateAgainstTheSchema_ReturnsValidationResultWithErrors1()
        {
            //Arrange
            const string schemaVersion = "1.0";

            string fundingTemplate = CreateJsonFile("CalculateFunding.Services.Policy.Resources.LogicalModelTemplate.json");
            string fundingSchema   = CreateJsonFile("CalculateFunding.Services.Policy.Resources.LogicalModel.json");

            string blobName = $"{fundingSchemaFolder}/{schemaVersion}.json";

            IFundingSchemaRepository fundingSchemaRepository = CreateFundingSchemaRepository();

            fundingSchemaRepository
            .SchemaVersionExists(Arg.Is(blobName))
            .Returns(true);
            fundingSchemaRepository
            .GetFundingSchemaVersion(Arg.Is(blobName))
            .Returns(fundingSchema);

            FundingTemplateValidationService fundingTemplateValidationService = CreateFundingTemplateValidationService(fundingSchemaRepository: fundingSchemaRepository);

            //Act
            FundingTemplateValidationResult result = await fundingTemplateValidationService.ValidateFundingTemplate(fundingTemplate, "fsid", "fpid", "1.0");

            //Assert
            result
            .Errors
            .Should()
            .HaveCount(8);

            result
            .IsValid
            .Should()
            .BeFalse();
        }
        public async Task ValidateFundingTemplate_GivenTemplateThatDoesNotContainASchemaVersionValue_ReturnsValidationResultWithErrors()
        {
            //Arrange
            string fundingTemplate = "{ \"schemaVersion\" : \"\"}";

            FundingTemplateValidationService fundingTemplateValidationService = CreateFundingTemplateValidationService();

            //Act
            FundingTemplateValidationResult result = await fundingTemplateValidationService.ValidateFundingTemplate(fundingTemplate, "fsid", "fpid", "1.0");

            //Assert
            result
            .Errors
            .Should()
            .HaveCount(1);

            result
            .Errors[0]
            .ErrorMessage
            .Should()
            .Be("Missing schema version from funding template.");

            result
            .IsValid
            .Should()
            .BeFalse();
        }
        public async Task <FundingTemplateValidationResult> ValidateFundingTemplate(string fundingTemplate, string fundingStreamId, string fundingPeriodId, string templateVersion = null)
        {
            Guard.IsNullOrWhiteSpace(fundingTemplate, nameof(fundingTemplate));

            FundingTemplateValidationResult fundingTemplateValidationResult = new FundingTemplateValidationResult()
            {
                FundingStreamId = fundingStreamId,
                FundingPeriodId = fundingPeriodId,
                TemplateVersion = templateVersion
            };

            JObject parsedFundingTemplate;

            try
            {
                parsedFundingTemplate = JObject.Parse(fundingTemplate);
            }
            catch (JsonReaderException jre)
            {
                fundingTemplateValidationResult.Errors.Add(new ValidationFailure("", jre.Message));

                return(fundingTemplateValidationResult);
            }

            if (parsedFundingTemplate["schemaVersion"] == null ||
                string.IsNullOrWhiteSpace(parsedFundingTemplate["schemaVersion"].Value <string>()))
            {
                fundingTemplateValidationResult.Errors.Add(new ValidationFailure("", "Missing schema version from funding template."));

                return(fundingTemplateValidationResult);
            }

            string schemaVersion = parsedFundingTemplate["schemaVersion"].Value <string>();

            fundingTemplateValidationResult.SchemaVersion = schemaVersion;

            string blobName = $"{fundingSchemaFolder}/{schemaVersion}.json";

            bool schemaExists = await _fundingSchemaRepositoryPolicy.ExecuteAsync(() => _fundingSchemaRepository.SchemaVersionExists(blobName));

            if (!schemaExists)
            {
                fundingTemplateValidationResult.Errors.Add(new ValidationFailure("", $"A valid schema could not be found for schema version '{schemaVersion}'."));

                return(fundingTemplateValidationResult);
            }

            await ValidateAgainstSchema(blobName, parsedFundingTemplate, fundingTemplateValidationResult);

            if (!fundingTemplateValidationResult.IsValid)
            {
                return(fundingTemplateValidationResult);
            }

            await ValidateFundingStream(fundingTemplateValidationResult);
            await ValidateFundingPeriod(fundingTemplateValidationResult);

            return(fundingTemplateValidationResult);
        }
        public async Task SaveFundingTemplate_GivenInvalidTemplateDueToProfilePeriods_BadRequest()
        {
            //Arrange
            string template        = CreateJsonFile("CalculateFunding.Services.Policy.Resources.LogicalModelTemplate.json");
            string fundingStreamId = "PES";
            string templateVersion = "1.5";
            string fundingPeriodId = "AY-2020";

            FundingTemplateValidationResult validationResult = new FundingTemplateValidationResult
            {
                TemplateVersion = templateVersion,
                FundingStreamId = fundingStreamId,
                SchemaVersion   = "1.0",
            };

            ITemplateMetadataResolver templateMetadataResolver = CreateMetadataResolver("1.0");

            IFundingTemplateValidationService fundingTemplateValidationService = CreateFundingTemplateValidationService();

            fundingTemplateValidationService
            .ValidateFundingTemplate(Arg.Is(template), Arg.Is(fundingStreamId), Arg.Is(fundingPeriodId), Arg.Is(templateVersion))
            .Returns(validationResult);

            IFundingTemplateRepository fundingTemplateRepository = CreateFundingTemplateRepository();

            ILogger logger = CreateLogger();

            ICacheProvider cacheProvider = CreateCacheProvider();

            FundingTemplateService fundingTemplateService = CreateFundingTemplateService(
                logger,
                fundingTemplateValidationService: fundingTemplateValidationService,
                fundingTemplateRepository: fundingTemplateRepository,
                cacheProvider: cacheProvider,
                templateMetadataResolver: templateMetadataResolver);

            //Act
            IActionResult result = await fundingTemplateService.SaveFundingTemplate(createdAtActionName, createdAtControllerName, template, fundingStreamId, fundingPeriodId, templateVersion);

            //Assert
            result
            .Should()
            .BeAssignableTo <BadRequestObjectResult>();

            BadRequestObjectResult badRequestObjectResult = result as BadRequestObjectResult;

            SerializableError validationResults = badRequestObjectResult.Value as SerializableError;

            validationResults
            .Count()
            .Should()
            .Be(1);

            ((string[])validationResults["DistributionPeriods"])[0]
            .Should()
            .Be("Funding line : 'Total funding line' has values for the distribution periods");
        }
        public async Task <IActionResult> SaveFundingTemplate(string actionName, string controllerName, string template, string fundingStreamId, string fundingPeriodId, string templateVersion)
        {
            Guard.IsNullOrWhiteSpace(actionName, nameof(actionName));
            Guard.IsNullOrWhiteSpace(controllerName, nameof(controllerName));
            Guard.IsNullOrWhiteSpace(fundingStreamId, nameof(fundingStreamId));
            Guard.IsNullOrWhiteSpace(fundingPeriodId, nameof(fundingPeriodId));
            Guard.IsNullOrWhiteSpace(templateVersion, nameof(templateVersion));

            //Already checked for null above when getting body
            if (template.Trim() == string.Empty)
            {
                return(new BadRequestObjectResult("Null or empty funding template was provided."));
            }

            FundingTemplateValidationResult validationResult = await _fundingTemplateValidationService.ValidateFundingTemplate(template, fundingStreamId, fundingPeriodId, templateVersion);

            if (!validationResult.IsValid)
            {
                return(validationResult.AsBadRequest());
            }

            ITemplateMetadataGenerator templateMetadataGenerator = _templateMetadataResolver.GetService(validationResult.SchemaVersion);

            ValidationResult validationGeneratorResult = templateMetadataGenerator.Validate(template);

            if (!validationGeneratorResult.IsValid)
            {
                return(validationGeneratorResult.PopulateModelState());
            }

            string blobName = GetBlobNameFor(validationResult.FundingStreamId, validationResult.FundingPeriodId, validationResult.TemplateVersion);

            try
            {
                byte[] templateFileBytes = Encoding.UTF8.GetBytes(template);

                await SaveFundingTemplateVersion(blobName, templateFileBytes);

                string cacheKey = $"{CacheKeys.FundingTemplatePrefix}{validationResult.FundingStreamId}-{validationResult.FundingPeriodId}-{validationResult.TemplateVersion}".ToLowerInvariant();
                await _cacheProviderPolicy.ExecuteAsync(() => _cacheProvider.RemoveAsync <string>(cacheKey));

                await _cacheProviderPolicy.ExecuteAsync(() => _cacheProvider.RemoveAsync <FundingTemplateContents>($"{CacheKeys.FundingTemplateContents}{validationResult.FundingStreamId}:{validationResult.FundingPeriodId}:{validationResult.TemplateVersion}".ToLowerInvariant()));

                await _cacheProviderPolicy.ExecuteAsync(() => _cacheProvider.RemoveAsync <TemplateMetadataContents>($"{CacheKeys.FundingTemplateContentMetadata}{validationResult.FundingStreamId}:{validationResult.FundingPeriodId}:{validationResult.TemplateVersion}".ToLowerInvariant()));

                await _cacheProviderPolicy.ExecuteAsync(() => _cacheProvider.RemoveAsync <TemplateMetadataDistinctContents>($"{CacheKeys.FundingTemplateContentMetadataDistinct}{validationResult.FundingStreamId}:{validationResult.FundingPeriodId}:{validationResult.TemplateVersion}".ToLowerInvariant()));

                return(new CreatedAtActionResult(actionName, controllerName, new { fundingStreamId = validationResult.FundingStreamId, fundingPeriodId = validationResult.FundingPeriodId, templateVersion = validationResult.TemplateVersion }, string.Empty));
            }
            catch (Exception ex)
            {
                _logger.Error(ex, $"Failed to save funding template '{blobName}' to blob storage");

                return(new InternalServerErrorResult("Error occurred uploading funding template"));
            }
        }
        public async Task ValidateFundingTemplate_GivenTemplateWithValidFundingPeriodIdButNoFundingPeriodExists_ReturnsValidationResultWithErrors()
        {
            //Arrange
            const string schemaVersion = "1.0";

            FundingStream fundingStream = new FundingStream();

            JsonSchema schema = JsonSchema.FromType <TestTemplate_schema_1_0>();

            TestTemplate_schema_1_0 testClassWithFunding = new TestTemplate_schema_1_0
            {
                SchemaVersion = schemaVersion,
                Funding       = new { templateVersion = "2.1", fundingStream = new { code = "PES" }, fundingPeriod = new { id = "AY-2020" } }
            };

            string fundingTemplate = JsonConvert.SerializeObject(testClassWithFunding);

            string fundingSchema = schema.AsJson();

            string blobName = $"{fundingSchemaFolder}/{schemaVersion}.json";

            IFundingSchemaRepository fundingSchemaRepository = CreateFundingSchemaRepository();

            fundingSchemaRepository
            .SchemaVersionExists(Arg.Is(blobName))
            .Returns(true);
            fundingSchemaRepository
            .GetFundingSchemaVersion(Arg.Is(blobName))
            .Returns(fundingSchema);

            IPolicyRepository policyRepository = CreatePolicyRepository();

            policyRepository
            .GetFundingStreamById(Arg.Is("PES"))
            .Returns(fundingStream);

            FundingTemplateValidationService fundingTemplateValidationService = CreateFundingTemplateValidationService(
                fundingSchemaRepository: fundingSchemaRepository,
                policyRepository: policyRepository);

            //Act
            FundingTemplateValidationResult result = await fundingTemplateValidationService.ValidateFundingTemplate(fundingTemplate, "PES", "AY-2020", "2.1");

            //Assert
            result
            .IsValid
            .Should()
            .BeFalse();

            result
            .Errors[0]
            .ErrorMessage
            .Should()
            .Be("A funding period could not be found for funding period id 'AY-2020'");
        }
        private async Task ValidateFundingPeriod(FundingTemplateValidationResult fundingTemplateValidationResult)
        {
            if (!string.IsNullOrWhiteSpace(fundingTemplateValidationResult.FundingPeriodId))
            {
                FundingPeriod fundingPeriod = await _policyRepositoryPolicy.ExecuteAsync(() => _policyRepository.GetFundingPeriodById(fundingTemplateValidationResult.FundingPeriodId));

                if (fundingPeriod == null)
                {
                    fundingTemplateValidationResult.Errors
                    .Add(new ValidationFailure("", $"A funding period could not be found for funding period id '{fundingTemplateValidationResult.FundingPeriodId}'"));
                }
            }
        }
        private async Task ValidateFundingStream(FundingTemplateValidationResult fundingTemplateValidationResult)
        {
            if (!fundingTemplateValidationResult.FundingStreamId.IsNullOrEmpty())
            {
                FundingStream fundingStream = await _policyRepositoryPolicy.ExecuteAsync(() =>
                                                                                         _policyRepository.GetFundingStreamById(fundingTemplateValidationResult.FundingStreamId));

                if (fundingStream == null)
                {
                    fundingTemplateValidationResult.Errors
                    .Add(new ValidationFailure("", $"A funding stream could not be found for funding stream id '{fundingTemplateValidationResult.FundingStreamId}'"));
                }
            }
        }
        public async Task SaveFundingTemplate_GivenTemplateButDidNotValidate_ReturnsBadRequest()
        {
            //Arrange
            const string template        = "a template";
            string       fundingStreamId = NewRandomString();
            string       templateVersion = NewRandomString();
            string       fundingPeriodId = NewRandomString();

            FundingTemplateValidationResult validationResult = new FundingTemplateValidationResult();

            validationResult.Errors.Add(new ValidationFailure("prop1", "an error"));
            validationResult.Errors.Add(new ValidationFailure("prop2", "another error"));

            IFundingTemplateValidationService fundingTemplateValidationService = CreateFundingTemplateValidationService();

            fundingTemplateValidationService
            .ValidateFundingTemplate(Arg.Is(template), Arg.Is(fundingStreamId), Arg.Is(fundingPeriodId), Arg.Is(templateVersion))
            .Returns(validationResult);

            FundingTemplateService fundingTemplateService = CreateFundingTemplateService(fundingTemplateValidationService: fundingTemplateValidationService);

            //Act
            IActionResult result = await fundingTemplateService.SaveFundingTemplate(createdAtActionName, createdAtControllerName, template, fundingStreamId, fundingPeriodId, templateVersion);

            //Assert
            result
            .Should()
            .BeAssignableTo <BadRequestObjectResult>();

            BadRequestObjectResult badRequestObjectResult = result as BadRequestObjectResult;

            badRequestObjectResult
            .Value
            .Should()
            .BeOfType <SerializableError>();

            SerializableError modelState = badRequestObjectResult.Value as SerializableError;

            modelState
            .Should()
            .NotBeNull();

            modelState
            .Values
            .Should()
            .HaveCount(2);
        }
        private async Task ValidateAgainstSchema(string blobName, JObject parsedFundingTemplate,
                                                 FundingTemplateValidationResult fundingTemplateValidationResult)
        {
            string fundingSchemaJson =
                await _fundingSchemaRepositoryPolicy.ExecuteAsync(() => _fundingSchemaRepository.GetFundingSchemaVersion(blobName));

            JsonSchema fundingSchema = await JsonSchema.FromJsonAsync(fundingSchemaJson);

            ICollection <ValidationError> validationMessages = fundingSchema.Validate(parsedFundingTemplate);

            if (validationMessages.AnyWithNullCheck())
            {
                foreach (ValidationError message in validationMessages)
                {
                    fundingTemplateValidationResult.Errors.Add(new ValidationFailure(message.Property, message.ToString()));
                }
            }
        }
        public async Task ValidateFundingTemplate_GivenTemplateWithInvalidJson_ReturnsValidationResultWithErrors()
        {
            //Arrange
            string fundingTemplate = "invalid json schema";

            FundingTemplateValidationService fundingTemplateValidationService = CreateFundingTemplateValidationService();

            //Act
            FundingTemplateValidationResult result = await fundingTemplateValidationService.ValidateFundingTemplate(fundingTemplate, "fsid", "fpid", "1.0");

            //Assert
            result
            .Errors
            .Should()
            .HaveCount(1);

            result
            .IsValid
            .Should()
            .BeFalse();
        }
        public async Task ValidateFundingTemplate_GivenTemplateWIthValidSchemaVersionButSchemaDoesNotExist_ReturnsValidationResultWithErrors()
        {
            //Arrange
            const string schemaVersion   = "1.0";
            string       fundingTemplate = $"{{ \"schemaVersion\" : \"{schemaVersion}\"}}";

            string blobName = $"{fundingSchemaFolder}/{schemaVersion}.json";

            IFundingSchemaRepository fundingSchemaRepository = CreateFundingSchemaRepository();

            fundingSchemaRepository
            .SchemaVersionExists(Arg.Is(blobName))
            .Returns(false);

            FundingTemplateValidationService fundingTemplateValidationService = CreateFundingTemplateValidationService(fundingSchemaRepository: fundingSchemaRepository);

            //Act
            FundingTemplateValidationResult result = await fundingTemplateValidationService.ValidateFundingTemplate(fundingTemplate, "fsid", "fpid", "1.0");

            //Assert
            result
            .Errors
            .Should()
            .HaveCount(1);

            result
            .Errors[0]
            .ErrorMessage
            .Should()
            .Be($"A valid schema could not be found for schema version '{schemaVersion}'.");

            result
            .IsValid
            .Should()
            .BeFalse();
        }
        public async Task ValidateFundingTemplate_Schema_1_1_GivenTemplateIsValidAndValuesExtracted_ReturnsValidationResultWithNoErrors()
        {
            //Arrange
            const string schemaVersion = "1.1";

            FundingStream fundingStream = new FundingStream();

            JsonSchema schema = JsonSchema.FromType <TestTemplate_schema_1_1>();

            var template = new TestTemplate_schema_1_1
            {
                SchemaVersion         = schemaVersion,
                FundingStreamTemplate = new { templateVersion = "56.4", fundingStream = new { code = "XXX" }, fundingPeriod = new { id = "AY-2020" } }
            };

            string fundingTemplate = JsonConvert.SerializeObject(template);

            string fundingSchema = schema.AsJson();

            string blobName = $"{fundingSchemaFolder}/{schemaVersion}.json";

            IFundingSchemaRepository fundingSchemaRepository = CreateFundingSchemaRepository();

            fundingSchemaRepository
            .SchemaVersionExists(Arg.Is(blobName))
            .Returns(true);
            fundingSchemaRepository
            .GetFundingSchemaVersion(Arg.Is(blobName))
            .Returns(fundingSchema);

            IPolicyRepository policyRepository = CreatePolicyRepository();

            policyRepository
            .GetFundingStreamById(Arg.Is("XXX"))
            .Returns(fundingStream);
            policyRepository
            .GetFundingPeriodById(Arg.Is("AY-2020"))
            .Returns(new FundingPeriod());

            FundingTemplateValidationService fundingTemplateValidationService = CreateFundingTemplateValidationService(
                fundingSchemaRepository: fundingSchemaRepository,
                policyRepository: policyRepository);

            //Act
            FundingTemplateValidationResult result = await fundingTemplateValidationService.ValidateFundingTemplate(fundingTemplate, "XXX", "AY-2020", "56.4");

            //Assert
            result
            .Errors
            .Should()
            .BeEmpty();

            result
            .TemplateVersion
            .Should()
            .Be("56.4");

            result
            .SchemaVersion
            .Should()
            .Be("1.1");

            result
            .FundingStreamId
            .Should()
            .Be("XXX");

            result
            .FundingPeriodId
            .Should()
            .Be("AY-2020");

            result
            .IsValid
            .Should()
            .BeTrue();
        }
        public async Task SaveFundingTemplate_GivenValidTemplateAndSaves_InvalidatesCacheReturnsCreatedAtActionResult()
        {
            //Arrange
            string template        = CreateJsonFile("CalculateFunding.Services.Policy.Resources.LogicalModelTemplateNoProfilePeriods.json");
            string fundingStreamId = "PES";
            string templateVersion = "1.5";
            string fundingPeriodId = "AY-2020";

            FundingTemplateValidationResult validationResult = new FundingTemplateValidationResult
            {
                TemplateVersion = templateVersion,
                FundingStreamId = fundingStreamId,
                SchemaVersion   = "1.0",
                FundingPeriodId = fundingPeriodId
            };

            string cacheKey = $"{CacheKeys.FundingTemplatePrefix}{validationResult.FundingStreamId}-{validationResult.FundingPeriodId}-{validationResult.TemplateVersion}".ToLowerInvariant();

            ITemplateMetadataResolver templateMetadataResolver = CreateMetadataResolver("1.0");

            IFundingTemplateValidationService fundingTemplateValidationService = CreateFundingTemplateValidationService();

            fundingTemplateValidationService
            .ValidateFundingTemplate(Arg.Is(template), Arg.Is(fundingStreamId), Arg.Is(fundingPeriodId), Arg.Is(templateVersion))
            .Returns(validationResult);

            IFundingTemplateRepository fundingTemplateRepository = CreateFundingTemplateRepository();

            ILogger logger = CreateLogger();

            ICacheProvider cacheProvider = CreateCacheProvider();

            FundingTemplateService fundingTemplateService = CreateFundingTemplateService(
                logger,
                fundingTemplateValidationService: fundingTemplateValidationService,
                fundingTemplateRepository: fundingTemplateRepository,
                cacheProvider: cacheProvider,
                templateMetadataResolver: templateMetadataResolver);

            //Act
            IActionResult result = await fundingTemplateService.SaveFundingTemplate(createdAtActionName, createdAtControllerName, template, fundingStreamId, fundingPeriodId, templateVersion);

            //Assert
            result
            .Should()
            .BeAssignableTo <CreatedAtActionResult>();

            CreatedAtActionResult actionResult = result as CreatedAtActionResult;

            actionResult
            .ActionName
            .Should()
            .Be(createdAtActionName);

            actionResult
            .ControllerName
            .Should()
            .Be(createdAtControllerName);

            actionResult
            .RouteValues["fundingStreamId"].ToString()
            .Should()
            .Be("PES");

            actionResult
            .RouteValues["fundingPeriodId"].ToString()
            .Should()
            .Be("AY-2020");

            actionResult
            .RouteValues["templateVersion"].ToString()
            .Should()
            .Be("1.5");

            await
            cacheProvider
            .Received(1)
            .RemoveAsync <string>(Arg.Is(cacheKey));

            await
            cacheProvider
            .Received(1)
            .RemoveAsync <FundingTemplateContents>(Arg.Is($"{CacheKeys.FundingTemplateContents}pes:ay-2020:1.5"));

            await cacheProvider
            .Received(1)
            .RemoveAsync <TemplateMetadataContents>($"{CacheKeys.FundingTemplateContentMetadata}pes:ay-2020:1.5");

            await cacheProvider
            .Received(1)
            .RemoveAsync <TemplateMetadataDistinctContents>($"{CacheKeys.FundingTemplateContentMetadataDistinct}pes:ay-2020:1.5");
        }
        public async Task SaveFundingTemplate_GivenValidTemplateButFailedToSaveToBlobStorage_ReturnsInternalServerError()
        {
            //Arrange
            const string template        = "a template";
            string       fundingStreamId = NewRandomString();
            string       templateVersion = NewRandomString();
            string       fundingPeriodId = NewRandomString();

            FundingTemplateValidationResult validationResult = new FundingTemplateValidationResult
            {
                TemplateVersion = "1.8",
                FundingStreamId = "PES",
                SchemaVersion   = "1.0",
                FundingPeriodId = "AY-2020"
            };

            string blobName = $"{validationResult.FundingStreamId}/{validationResult.FundingPeriodId}/{validationResult.TemplateVersion}.json";

            IFundingTemplateValidationService fundingTemplateValidationService = CreateFundingTemplateValidationService();

            fundingTemplateValidationService
            .ValidateFundingTemplate(Arg.Is(template), Arg.Is(fundingStreamId), Arg.Is(fundingPeriodId), Arg.Is(templateVersion))
            .Returns(validationResult);

            IFundingTemplateRepository fundingTemplateRepository = CreateFundingTemplateRepository();

            fundingTemplateRepository
            .When(x => x.SaveFundingTemplateVersion(Arg.Is(blobName), Arg.Any <byte[]>()))
            .Do(x => { throw new Exception(); });

            ILogger logger = CreateLogger();

            ITemplateMetadataGenerator templateMetadataGenerator = CreateMetadataGenerator();

            templateMetadataGenerator.Validate(Arg.Is <string>(template))
            .Returns(new FluentValidation.Results.ValidationResult());

            ITemplateMetadataResolver templateMetadataResolver = CreateMetadataResolver("1.0", templateMetadataGenerator);

            FundingTemplateService fundingTemplateService = CreateFundingTemplateService(
                logger,
                fundingTemplateValidationService: fundingTemplateValidationService,
                fundingTemplateRepository: fundingTemplateRepository,
                templateMetadataResolver: templateMetadataResolver);

            //Act
            IActionResult result = await fundingTemplateService.SaveFundingTemplate(createdAtActionName, createdAtControllerName, template, fundingStreamId, fundingPeriodId, templateVersion);

            //Assert
            result
            .Should()
            .BeAssignableTo <InternalServerErrorResult>()
            .Which
            .Value
            .Should()
            .Be("Error occurred uploading funding template");

            logger
            .Received(1)
            .Error(Arg.Any <NonRetriableException>(), Arg.Is($"Failed to save funding template '{blobName}' to blob storage"));
        }