public void CheckSourceForExistingCalculationAggregates_GivenCalcContainsCalcReferenceThatIncludesAvgInTheNameButNoOtherAggregateRefrences_ReturnsFalse() { //Arrange Dictionary <string, string> functions = new Dictionary <string, string> { { "Calc1", "Return Calc2() + Min( Calc6)" }, { "Calc2", "Return 1" }, { "Calc3", "Return Sum(Calc2) + 5" }, { "Calc4", "Return Sum(Calc2) + Avg(Calc2) + Min( Calc3)" }, { "Calc5", "Return CalcAvg1()" }, { "Calc6", "Return Calc5()" }, { "Calc7", "Return Calc6()" } }; string calcToCheck = "Return CalcAvg1() = Calc6()"; //Act bool result = SourceCodeHelpers.CheckSourceForExistingCalculationAggregates(functions, calcToCheck); //Assert result .Should() .BeFalse(); }
public void CheckSourceForExistingCalculationAggregates_GivenCalcIsReferencedInAnAggregateWithDeepNestingAndFurtherLevelsAndIncludeWhitespaceInAggregateCall_ReturnsTrue() { //Arrange Dictionary <string, string> functions = new Dictionary <string, string> { { "Calc1", "Return Calc2() + Min( Calc6)" }, { "Calc2", "Return 1" }, { "Calc3", "Return Sum(Calc2) + 5" }, { "Calc4", "Return Sum(Calc2) + Avg(Calc2) + Min( Calc3)" }, { "Calc5", "Return Calc1()" }, { "Calc6", "Return Calc5()" }, { "Calc7", "Return Calc6()" } }; string calcToCheck = "Return Sum(Calc2) + Avg(Calc2) + Sum(Calc3)"; //Act bool result = SourceCodeHelpers.CheckSourceForExistingCalculationAggregates(functions, calcToCheck); //Assert result .Should() .BeTrue(); }
private async Task <IActionResult> GenerateAndCompile(BuildProject buildProject, Calculation calculationToPreview, IEnumerable <Calculation> calculations, CompilerOptions compilerOptions, PreviewRequest previewRequest) { PreviewProviderCalculationResponseModel previewProviderCalculation = null; Build compilerOutput = _sourceCodeService.Compile(buildProject, calculations, compilerOptions); if (compilerOutput.SourceFiles != null) { await _sourceCodeService.SaveSourceFiles(compilerOutput.SourceFiles, buildProject.SpecificationId, SourceCodeType.Preview); } if (compilerOutput.Success) { _logger.Information($"Build compiled successfully for calculation id {calculationToPreview.Id}"); string calculationIdentifier = $"{_typeIdentifierGenerator.GenerateIdentifier(calculationToPreview.Namespace)}.{_typeIdentifierGenerator.GenerateIdentifier(calculationToPreview.Name)}"; IDictionary <string, string> functions = _sourceCodeService.GetCalculationFunctions(compilerOutput.SourceFiles); IDictionary <string, string> calculationIdentifierMap = calculations .Select(_ => new { Identifier = $"{_typeIdentifierGenerator.GenerateIdentifier(_.Namespace)}.{_typeIdentifierGenerator.GenerateIdentifier(_.Name)}", CalcName = _.Name }) .ToDictionary(d => d.Identifier, d => d.CalcName); if (!functions.ContainsKey(calculationIdentifier)) { compilerOutput.Success = false; compilerOutput.CompilerMessages.Add(new CompilerMessage { Message = $"{calculationIdentifier} is not an aggregable field", Severity = Severity.Error }); } else { if (previewRequest != null) { if (!SourceCodeHelpers.HasReturn(previewRequest.SourceCode)) { compilerOutput.Success = false; compilerOutput.CompilerMessages.Add(new CompilerMessage { Message = $"{calculationIdentifier} must have a return statement so that a calculation result will be returned", Severity = Severity.Error }); } else { IEnumerable <string> aggregateParameters = SourceCodeHelpers.GetCalculationAggregateFunctionParameters(previewRequest.SourceCode); bool continueChecking = true; if (!aggregateParameters.IsNullOrEmpty()) { foreach (string aggregateParameter in aggregateParameters) { if (!functions.ContainsKey(aggregateParameter)) { compilerOutput.Success = false; compilerOutput.CompilerMessages.Add(new CompilerMessage { Message = $"{aggregateParameter} is not an aggregable field", Severity = Severity.Error }); continueChecking = false; break; } if (calculationIdentifierMap.ContainsKey(aggregateParameter)) { Calculation calculation = calculations.SingleOrDefault(_ => _.Name == calculationIdentifierMap[aggregateParameter]); if (calculation.Current.DataType != CalculationDataType.Decimal) { compilerOutput.Success = false; compilerOutput.CompilerMessages.Add(new CompilerMessage { Message = $"Only decimal fields can be used on aggregation. {aggregateParameter} has data type of {calculation.Current.DataType}", Severity = Severity.Error }); continueChecking = false; break; } } } if (continueChecking) { if (SourceCodeHelpers.IsCalcReferencedInAnAggregate(functions, calculationIdentifier)) { compilerOutput.Success = false; compilerOutput.CompilerMessages.Add(new CompilerMessage { Message = $"{calculationIdentifier} is already referenced in an aggregation that would cause nesting", Severity = Severity.Error }); } else if (SourceCodeHelpers.CheckSourceForExistingCalculationAggregates(functions, previewRequest.SourceCode)) { compilerOutput.Success = false; compilerOutput.CompilerMessages.Add(new CompilerMessage { Message = $"{calculationIdentifier} cannot reference another calc that is being aggregated", Severity = Severity.Error }); } } } } } } } else { _logger.Information($"Build did not compile successfully for calculation id {calculationToPreview.Id}"); } LogMessages(compilerOutput, buildProject, calculationToPreview); if (!string.IsNullOrEmpty(previewRequest.ProviderId)) { CalculationSummaryModel calculationSummaryModel = calculationToPreview.ToSummaryModel(); CalcEngineModels.CalculationSummaryModel model = _mapper.Map <CalcEngineModels.CalculationSummaryModel>(calculationSummaryModel); CalcEngineModels.PreviewCalculationRequest previewCalculationRequest = new CalcEngineModels.PreviewCalculationRequest { AssemblyContent = compilerOutput.Assembly, PreviewCalculationSummaryModel = model }; ApiResponse <CalcEngineProviderResult> previewCalcResultApiResponse = await _calcEngineApiClientPolicy.ExecuteAsync( () => _calcEngineApiClient.PreviewCalculationResults( previewRequest.SpecificationId, previewRequest.ProviderId, previewCalculationRequest)); if (previewCalcResultApiResponse.StatusCode.IsSuccess()) { CalcEngineProviderResult calcEngineProviderResult = previewCalcResultApiResponse.Content; previewProviderCalculation = new PreviewProviderCalculationResponseModel { ProviderName = calcEngineProviderResult.Provider.Name, CalculationResult = _mapper.Map <CalculationResult>( calcEngineProviderResult.CalculationResults.SingleOrDefault(_ => _.Calculation?.Id == calculationToPreview.Id)), }; } } return(new OkObjectResult(new PreviewResponse { Calculation = calculationToPreview.ToResponseModel(), CompilerOutput = compilerOutput, PreviewProviderCalculation = previewProviderCalculation })); }
private async Task <IActionResult> GenerateAndCompile(BuildProject buildProject, Calculation calculationToPreview, IEnumerable <Calculation> calculations, CompilerOptions compilerOptions, PreviewRequest previewRequest) { Build compilerOutput = _sourceCodeService.Compile(buildProject, calculations, compilerOptions); compilerOutput = FilterDoubleToDecimalErrors(compilerOutput); await _sourceCodeService.SaveSourceFiles(compilerOutput.SourceFiles, buildProject.SpecificationId, SourceCodeType.Preview); if (compilerOutput.Success) { _logger.Information($"Build compiled successfully for calculation id {calculationToPreview.Id}"); string calculationIdentifier = VisualBasicTypeGenerator.GenerateIdentifier(calculationToPreview.Name); IDictionary <string, string> functions = _sourceCodeService.GetCalculationFunctions(compilerOutput.SourceFiles); if (!functions.ContainsKey(calculationIdentifier)) { compilerOutput.Success = false; compilerOutput.CompilerMessages.Add(new CompilerMessage { Message = $"{calculationIdentifier} is not an aggregable field", Severity = Severity.Error }); } else { if (previewRequest != null) { IEnumerable <string> aggregateParameters = SourceCodeHelpers.GetCalculationAggregateFunctionParameters(previewRequest.SourceCode); bool continueChecking = true; if (!aggregateParameters.IsNullOrEmpty()) { foreach (string aggregateParameter in aggregateParameters) { if (!functions.ContainsKey(aggregateParameter)) { compilerOutput.Success = false; compilerOutput.CompilerMessages.Add(new CompilerMessage { Message = $"{aggregateParameter} is not an aggregable field", Severity = Severity.Error }); continueChecking = false; break; } } if (continueChecking) { if (SourceCodeHelpers.IsCalcReferencedInAnAggregate(functions, calculationIdentifier)) { compilerOutput.Success = false; compilerOutput.CompilerMessages.Add(new CompilerMessage { Message = $"{calculationIdentifier} is already referenced in an aggregation that would cause nesting", Severity = Severity.Error }); } else if (SourceCodeHelpers.CheckSourceForExistingCalculationAggregates(functions, previewRequest.SourceCode)) { compilerOutput.Success = false; compilerOutput.CompilerMessages.Add(new CompilerMessage { Message = $"{calculationIdentifier} cannot reference another calc that is being aggregated", Severity = Severity.Error }); } } } } } //Forcing to compile for calc runs only compilerOptions.OptionStrictEnabled = false; Build nonPreviewCompilerOutput = _sourceCodeService.Compile(buildProject, calculations, compilerOptions); if (nonPreviewCompilerOutput.Success) { await _sourceCodeService.SaveSourceFiles(nonPreviewCompilerOutput.SourceFiles, buildProject.SpecificationId, SourceCodeType.Release); } } else { _logger.Information($"Build did not compile successfully for calculation id {calculationToPreview.Id}"); } CheckCircularReference(calculationToPreview, compilerOutput); LogMessages(compilerOutput, buildProject, calculationToPreview); return(new OkObjectResult(new PreviewResponse { Calculation = calculationToPreview, CompilerOutput = compilerOutput })); }