diff --git a/src/EPR.Calculator.API.UnitTests/CalResultsTests.cs b/src/EPR.Calculator.API.UnitTests/CalResultsTests.cs new file mode 100644 index 0000000..1ed4bc6 --- /dev/null +++ b/src/EPR.Calculator.API.UnitTests/CalResultsTests.cs @@ -0,0 +1,82 @@ +using EPR.Calculator.API.Builder; +using EPR.Calculator.API.Controllers; +using EPR.Calculator.API.Data; +using EPR.Calculator.API.Dtos; +using EPR.Calculator.API.Exporter; +using EPR.Calculator.API.Models; +using EPR.Calculator.API.Validators; +using EPR.Calculator.API.Wrapper; +using Microsoft.AspNetCore.Mvc; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Moq; + +namespace EPR.Calculator.API.UnitTests +{ + [TestClass] + public class CalcResultServiceTests + { + private Mock mockCalcResultBuilder; + private Mock> mockExporter; + private Mock mockDetailBuilder; + private Mock mockLapcapBuilder; + + private Mock mockContext; + private CalculatorInternalController controller; + private CalcResultBuilder calcResultBuilder; + private CalcResultDetailBuilder detailBuilder; + private CalcResultsExporter exporter; + protected ApplicationDBContext? dbContext; + protected IOrgAndPomWrapper? wrapper; + + [TestInitialize] + public void Setup() + { + mockCalcResultBuilder = new Mock(); + mockExporter = new Mock>(); + wrapper = new Mock().Object; + controller = new CalculatorInternalController( + dbContext, + new RpdStatusDataValidator(wrapper), + wrapper, + mockCalcResultBuilder.Object, + mockExporter.Object + ); + + mockDetailBuilder = new Mock(); + mockLapcapBuilder = new Mock(); + calcResultBuilder = new CalcResultBuilder(mockDetailBuilder.Object, mockLapcapBuilder.Object); + + mockContext = new Mock(); + detailBuilder = new CalcResultDetailBuilder(mockContext.Object); + + } + + [TestMethod] + public void PrepareCalcResults_ShouldReturnCreatedStatus() + { + var requestDto = new CalcResultsRequestDto(); + var calcResult = new CalcResult(); + mockCalcResultBuilder.Setup(b => b.Build(requestDto)).Returns(calcResult); + + var result = controller.PrepareCalcResults(requestDto) as ObjectResult; + + Assert.IsNotNull(result); + Assert.AreEqual(201, result.StatusCode); + mockExporter.Verify(e => e.Export(calcResult), Times.Once); + } + + [TestMethod] + public void Build_ShouldReturnCalcResultWithDetail() + { + var requestDto = new CalcResultsRequestDto(); + var detail = new CalcResultDetail(); + mockDetailBuilder.Setup(d => d.Construct(requestDto)).Returns(detail); + + var result = calcResultBuilder.Build(requestDto); + + Assert.IsNotNull(result); + Assert.AreEqual(detail, result.CalcResultDetail); + } + } +} + diff --git a/src/EPR.Calculator.API.UnitTests/CalcResultBuilderTests.cs b/src/EPR.Calculator.API.UnitTests/CalcResultBuilderTests.cs new file mode 100644 index 0000000..2ee5904 --- /dev/null +++ b/src/EPR.Calculator.API.UnitTests/CalcResultBuilderTests.cs @@ -0,0 +1,45 @@ +using EPR.Calculator.API.Builder; +using EPR.Calculator.API.Dtos; +using EPR.Calculator.API.Models; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Moq; + +namespace EPR.Calculator.API.UnitTests +{ + [TestClass] + public class CalcResultBuilderTests + { + private Mock mockCalcResultDetailBuilder; + private Mock mockLapcapBuilder; + private CalcResultBuilder calcResultBuilder; + + [TestInitialize] + public void Setup() + { + mockCalcResultDetailBuilder = new Mock(); + mockLapcapBuilder = new Mock(); + calcResultBuilder = new CalcResultBuilder(mockCalcResultDetailBuilder.Object, mockLapcapBuilder.Object); + } + + [TestMethod] + public void Build_ShouldReturnCalcResultWithDetailsAndLapcapData() + { + var resultsRequestDto = new CalcResultsRequestDto(); + var expectedDetail = new CalcResultDetail(); + var expectedLapcapData = new CalcResultLapcapData + { + Name = "SomeName", + CalcResultLapcapDataDetails = new List() + }; + + mockCalcResultDetailBuilder.Setup(m => m.Construct(resultsRequestDto)).Returns(expectedDetail); + mockLapcapBuilder.Setup(m => m.Construct(resultsRequestDto)).Returns(expectedLapcapData); + + var result = calcResultBuilder.Build(resultsRequestDto); + + Assert.IsNotNull(result); + Assert.AreEqual(expectedDetail, result.CalcResultDetail); + Assert.AreEqual(expectedLapcapData, result.CalcResultLapcapData); + } + } +} diff --git a/src/EPR.Calculator.API.UnitTests/CalcResultDetailBuilderTests.cs b/src/EPR.Calculator.API.UnitTests/CalcResultDetailBuilderTests.cs new file mode 100644 index 0000000..29aa085 --- /dev/null +++ b/src/EPR.Calculator.API.UnitTests/CalcResultDetailBuilderTests.cs @@ -0,0 +1,113 @@ +using EPR.Calculator.API.Builder; +using EPR.Calculator.API.Data; +using EPR.Calculator.API.Data.DataModels; +using EPR.Calculator.API.Dtos; +using EPR.Calculator.API.Tests.Controllers; +using Microsoft.EntityFrameworkCore; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Moq; + +namespace EPR.Calculator.API.UnitTests +{ + [TestClass] + public class CalcResultDetailBuilderTests + { + private ApplicationDBContext _context; + private CalcResultDetailBuilder _builder; + + [TestInitialize] + public void SetUp() + { + var options = new DbContextOptionsBuilder() + .UseInMemoryDatabase(databaseName: Guid.NewGuid().ToString()) + .Options; + + _context = new ApplicationDBContext(options); + _builder = new CalcResultDetailBuilder(_context); + SeedDatabase(); + } + + [TestCleanup] + public void TearDown() + { + _context.Dispose(); + } + + private void SeedDatabase() + { + var calculatorRun = new CalculatorRun + { + Id = 1, + Name = "TestRun", + CreatedBy = "TestUser", + CreatedAt = new DateTime(2023, 1, 1), + Financial_Year = "2023", + CalculatorRunOrganisationDataMaster = new CalculatorRunOrganisationDataMaster { CreatedBy = "", CalendarYear = "2024", EffectiveFrom = new DateTime(2023, 1, 1), CreatedAt = new DateTime(2023, 1, 1) }, + CalculatorRunPomDataMaster = new CalculatorRunPomDataMaster { CreatedBy = "", CalendarYear = "2024", EffectiveFrom = new DateTime(2023, 1, 1), CreatedAt = new DateTime(2023, 1, 1) }, + LapcapDataMaster = new LapcapDataMaster + { + LapcapFileName = "LapcapFile.csv", + CreatedAt = new DateTime(2023, 1, 1), + CreatedBy = "TestUser", + ProjectionYear = "2024-25" + }, + DefaultParameterSettingMaster = new DefaultParameterSettingMaster + { + ParameterFileName = "Parameters.csv", + CreatedAt = new DateTime(2023, 1, 1), + CreatedBy = "TestUser", + }, + }; + + _context.CalculatorRuns.Add(calculatorRun); + _context.SaveChanges(); + } + + [TestMethod] + public void Construct_AllPropertiesPresent_ReturnsCorrectData() + { + var result = _builder.Construct(new CalcResultsRequestDto()); + + Assert.AreEqual(1, result.RunId); + Assert.AreEqual("TestRun", result.RunName); + Assert.AreEqual("TestUser", result.RunBy); + Assert.AreEqual(new DateTime(2023, 1, 1), result.RunDate); + Assert.AreEqual("2023", result.FinancialYear); + Assert.AreEqual("01/01/2023 00:00", result.RpdFileORG); + Assert.AreEqual("01/01/2023 00:00", result.RpdFilePOM); + Assert.AreEqual("LapcapFile.csv,01/01/2023 00:00,TestUser", result.LapcapFile); + Assert.AreEqual("Parameters.csv,01/01/2023 00:00,TestUser", result.ParametersFile); + } + + [TestMethod] + public void Construct_MissingOptionalProperties_ReturnsPartialData() + { + _context.CalculatorRuns.RemoveRange(_context.CalculatorRuns); + _context.SaveChangesAsync(); + + var calculatorRun = new CalculatorRun + { + Id = 2, + Name = "RunWithMissingProps", + CreatedBy = "TestUser2", + CreatedAt = new DateTime(2023, 2, 1), + Financial_Year = "2023" + }; + + _context.CalculatorRuns.Add(calculatorRun); + _context.SaveChangesAsync(); + + var result = _builder.Construct(new CalcResultsRequestDto()); + + Assert.AreEqual(2, result.RunId); + Assert.AreEqual("RunWithMissingProps", result.RunName); + Assert.AreEqual("TestUser2", result.RunBy); + Assert.AreEqual(new DateTime(2023, 2, 1), result.RunDate); + Assert.AreEqual("2023", result.FinancialYear); + Assert.IsTrue(string.IsNullOrEmpty(result.RpdFileORG)); + Assert.IsTrue(string.IsNullOrEmpty(result.RpdFilePOM)); + Assert.IsTrue(string.IsNullOrEmpty(result.LapcapFile)); + Assert.IsTrue(string.IsNullOrEmpty(result.ParametersFile)); + } + } +} diff --git a/src/EPR.Calculator.API.UnitTests/CalcResultsExporterTests.cs b/src/EPR.Calculator.API.UnitTests/CalcResultsExporterTests.cs new file mode 100644 index 0000000..d44e609 --- /dev/null +++ b/src/EPR.Calculator.API.UnitTests/CalcResultsExporterTests.cs @@ -0,0 +1,144 @@ +using EPR.Calculator.API.Exporter; +using EPR.Calculator.API.Models; +using EPR.Calculator.API.Services; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Moq; +using System.Text; + +namespace EPR.Calculator.API.UnitTests +{ + [TestClass] + public class CalcResultsExporterTests + { + private CalcResultsExporter _calcResultsExporter; + private Mock _blobStorageServiceMock; + + [TestInitialize] + public void Setup() + { + _blobStorageServiceMock = new Mock(); + _calcResultsExporter = new CalcResultsExporter(_blobStorageServiceMock.Object); + } + + [TestMethod] + public void Export_ShouldUploadCorrectCsvContent() + { + var calcResult = new CalcResult + { + CalcResultDetail = new CalcResultDetail + { + RunName = "Test Run", + RunId = 123, + RunDate = new DateTime(2023, 10, 10, 14, 30, 0), + RunBy = "Tester", + FinancialYear = "2023-24", + LapcapFile = "Lapcap.csv,2023-10-01,John Doe", + ParametersFile = "Params.csv,2023-10-02,Jane Doe", + RpdFileORG = "04/11/2024 12:06", + RpdFilePOM = "04/11/2024 12:07", + } + }; + + var expectedCsvContent = new StringBuilder(); + expectedCsvContent.AppendLine("Run Name,Test Run"); + expectedCsvContent.AppendLine("Run Id,123"); + expectedCsvContent.AppendLine("Run Date,10/10/2023 14:30"); + expectedCsvContent.AppendLine("Run by,Tester"); + expectedCsvContent.AppendLine("Financial Year,2023-24"); + expectedCsvContent.AppendLine("RPD File - ORG,04/11/2024 12:06,RPD File - POM,04/11/2024 12:07"); + expectedCsvContent.AppendLine("Lapcap File,Lapcap.csv,2023-10-01,John Doe"); + expectedCsvContent.AppendLine("Parameters File,Params.csv,2023-10-02,Jane Doe"); + + _calcResultsExporter.Export(calcResult); + + _blobStorageServiceMock.Verify(service => service.UploadResultFileContentAsync( + $"{calcResult.CalcResultDetail.RunId}-{DateTime.Now:yyyy-MM-dd-HHmm}.csv", + It.Is(content => content.ToString() == expectedCsvContent.ToString()) + ), Times.Once); + } + + [TestMethod] + public void Export_ShouldHandleIOExceptionGracefully() + { + var calcResult = new CalcResult + { + CalcResultDetail = new CalcResultDetail + { + RunName = "Test Run", + RunId = 123, + RunDate = DateTime.Now, + RunBy = "Tester", + FinancialYear = "2023-24", + LapcapFile = "Lapcap.csv,2023-10-01,John Doe", + ParametersFile = "Params.csv,2023-10-02,Jane Doe" + } + }; + + _blobStorageServiceMock + .Setup(service => service.UploadResultFileContentAsync(It.IsAny(), It.IsAny())) + .Throws(new IOException("Simulated IO error")); + var exception = Assert.ThrowsException(() => _calcResultsExporter.Export(calcResult)); + Assert.AreEqual("File upload failed: Simulated IO error", exception.Message); + } + + + [TestMethod] + public void AppendFileInfo_ShouldNotAppendIfFilePartsAreInvalid() + { + var calcResult = new CalcResult + { + CalcResultDetail = new CalcResultDetail + { + RunName = "Test Run", + RunId = 123, + RunDate = DateTime.Now, + RunBy = "Tester", + FinancialYear = "2023-24", + LapcapFile = "InvalidFileInfo", + ParametersFile = "InvalidFileInfo", + RpdFileORG = "04/11/2024 12:06", + RpdFilePOM = "04/11/2024 12:07", + } + }; + + var expectedCsvContent = new StringBuilder(); + expectedCsvContent.AppendLine("Run Name,Test Run"); + expectedCsvContent.AppendLine("Run Id,123"); + expectedCsvContent.AppendLine("Run Date," + calcResult.CalcResultDetail.RunDate.ToString("dd/MM/yyyy HH:mm")); + expectedCsvContent.AppendLine("Run by,Tester"); + expectedCsvContent.AppendLine("Financial Year,2023-24"); + expectedCsvContent.AppendLine("RPD File - ORG,04/11/2024 12:06,RPD File - POM,04/11/2024 12:07"); + + _calcResultsExporter.Export(calcResult); + + _blobStorageServiceMock.Verify(service => service.UploadResultFileContentAsync( + $"{calcResult.CalcResultDetail.RunId}-{DateTime.Now:yyyy-MM-dd-HHmm}.csv", + It.Is(content => content.ToString() == expectedCsvContent.ToString()) + ), Times.Once); + } + + [TestMethod] + public void Export_CreatesCorrectCsvContent() + { + var calcResult = new CalcResult + { + CalcResultDetail = new CalcResultDetail + { + RunName = "Test Run", + RunId = 123, + RunDate = DateTime.Now, + RunBy = "Tester", + FinancialYear = "2024", + LapcapFile = "lapcap.csv,2024-11-01,Tester", + ParametersFile = "params.csv,2024-11-01,Tester" + } + }; + + _calcResultsExporter.Export(calcResult); + + _blobStorageServiceMock.Verify(x => x.UploadResultFileContentAsync(It.IsAny(), It.IsAny()), Times.Once); + var expectedFileName = $"{calcResult.CalcResultDetail.RunId}-{DateTime.Now:yyyy-MM-dd-HHmm}.csv"; + _blobStorageServiceMock.Verify(x => x.UploadResultFileContentAsync(expectedFileName, It.IsAny()), Times.Once); + } + } +} diff --git a/src/EPR.Calculator.API.UnitTests/CalculatorInternalControllerTest.cs b/src/EPR.Calculator.API.UnitTests/CalculatorInternalControllerTest.cs index bf42e73..6caa84c 100644 --- a/src/EPR.Calculator.API.UnitTests/CalculatorInternalControllerTest.cs +++ b/src/EPR.Calculator.API.UnitTests/CalculatorInternalControllerTest.cs @@ -1,6 +1,7 @@ using EPR.Calculator.API.Builder; using EPR.Calculator.API.Controllers; using EPR.Calculator.API.Data.DataModels; +using EPR.Calculator.API.Dtos; using EPR.Calculator.API.Exporter; using EPR.Calculator.API.Models; using EPR.Calculator.API.Tests.Controllers; @@ -195,5 +196,26 @@ public void UpdateRpdStatus_With_RunId_When_Successful() } } + + [TestMethod] + public void PrepareCalcResults_ShouldReturnCreatedStatus() + { + var requestDto = new CalcResultsRequestDto() { RunId = 1}; + var calcResult = new CalcResult(); + + var mockCalcResultBuilder = new Mock(); + var controller = new CalculatorInternalController( + dbContext, + new RpdStatusDataValidator(wrapper), + wrapper, + new Mock().Object, + new Mock>().Object + ); + + mockCalcResultBuilder.Setup(b => b.Build(requestDto)).Returns(calcResult); + var result = controller.PrepareCalcResults(requestDto) as ObjectResult; + Assert.IsNotNull(result); + Assert.AreEqual(201, result.StatusCode); + } } } diff --git a/src/EPR.Calculator.API.UnitTests/Controllers/CalculatorInternalControllerTests.cs b/src/EPR.Calculator.API.UnitTests/Controllers/CalculatorInternalControllerTests.cs index bdfb4f3..3849ed9 100644 --- a/src/EPR.Calculator.API.UnitTests/Controllers/CalculatorInternalControllerTests.cs +++ b/src/EPR.Calculator.API.UnitTests/Controllers/CalculatorInternalControllerTests.cs @@ -49,7 +49,8 @@ public void CanCallPrepareCalcResults() RunDate = DateTime.UtcNow, RunBy = "TestValue887677417", FinancialYear = "TestValue2028236729", - RpdFile = "TestValue1468463827", + RpdFileORG = "TestValue1468463827", + RpdFilePOM = "TestValue1468463837", LapcapFile = "TestValue1811770456", ParametersFile = "TestValue1028165412" }, diff --git a/src/EPR.Calculator.API/Builder/CalcResultDetailBuilder.cs b/src/EPR.Calculator.API/Builder/CalcResultDetailBuilder.cs index 9af75ac..89d0e0c 100644 --- a/src/EPR.Calculator.API/Builder/CalcResultDetailBuilder.cs +++ b/src/EPR.Calculator.API/Builder/CalcResultDetailBuilder.cs @@ -1,6 +1,8 @@ -using EPR.Calculator.API.Data; +using EPR.Calculator.API.Constants; +using EPR.Calculator.API.Data; using EPR.Calculator.API.Dtos; using EPR.Calculator.API.Models; +using Microsoft.EntityFrameworkCore; namespace EPR.Calculator.API.Builder { @@ -14,7 +16,37 @@ public CalcResultDetailBuilder(ApplicationDBContext context) public CalcResultDetail Construct(CalcResultsRequestDto resultsRequestDto) { - return new CalcResultDetail(); + var calcResultDetail = context.CalculatorRuns + .Include(o => o.CalculatorRunOrganisationDataMaster) + .Include(o => o.CalculatorRunPomDataMaster) + .Include(o => o.DefaultParameterSettingMaster) + .Include(x => x.LapcapDataMaster) + .ToListAsync(); + + var results = new CalcResultDetail(); + + foreach (var item in calcResultDetail.Result) + { + results.RunId = item.Id; + results.RunName = item.Name; + results.RunBy = item.CreatedBy; + results.RunDate = item.CreatedAt; + results.FinancialYear = item.Financial_Year; + if (item.CalculatorRunOrganisationDataMaster != null) + results.RpdFileORG = item.CalculatorRunOrganisationDataMaster.CreatedAt.ToString(CalculationResults.DateFormat); + if (item.CalculatorRunPomDataMaster != null) + results.RpdFilePOM = item.CalculatorRunPomDataMaster.CreatedAt.ToString(CalculationResults.DateFormat); + if (item.LapcapDataMaster != null) + results.LapcapFile = FormatFileData(item.LapcapDataMaster.LapcapFileName, item.LapcapDataMaster.CreatedAt, item.LapcapDataMaster.CreatedBy); + if (item.DefaultParameterSettingMaster != null) + results.ParametersFile = FormatFileData(item.DefaultParameterSettingMaster.ParameterFileName, item.DefaultParameterSettingMaster.CreatedAt, item.DefaultParameterSettingMaster.CreatedBy); + } + return results; + } + + private static string FormatFileData(string fileName, DateTime createdAt, string createdBy) + { + return $"{fileName},{createdAt.ToString(CalculationResults.DateFormat)},{createdBy}"; } } } \ No newline at end of file diff --git a/src/EPR.Calculator.API/Constants/BlobStorageSettings.cs b/src/EPR.Calculator.API/Constants/BlobStorageSettings.cs new file mode 100644 index 0000000..5dc9a85 --- /dev/null +++ b/src/EPR.Calculator.API/Constants/BlobStorageSettings.cs @@ -0,0 +1,9 @@ +namespace EPR.Calculator.API.Constants +{ + public class BlobStorageSettings + { + public string ConnectionString { get; set; } = string.Empty; + public string ContainerName { get; set; } = string.Empty; + public string CsvFileName { get; set; } = string.Empty; + } +} \ No newline at end of file diff --git a/src/EPR.Calculator.API/Constants/CalculationResults.cs b/src/EPR.Calculator.API/Constants/CalculationResults.cs new file mode 100644 index 0000000..d724cc6 --- /dev/null +++ b/src/EPR.Calculator.API/Constants/CalculationResults.cs @@ -0,0 +1,7 @@ +namespace EPR.Calculator.API.Constants +{ + public class CalculationResults + { + public static readonly string DateFormat = "dd/MM/yyyy HH:mm"; + } +} diff --git a/src/EPR.Calculator.API/EPR.Calculator.API.csproj b/src/EPR.Calculator.API/EPR.Calculator.API.csproj index ecf76a4..478961b 100644 --- a/src/EPR.Calculator.API/EPR.Calculator.API.csproj +++ b/src/EPR.Calculator.API/EPR.Calculator.API.csproj @@ -18,6 +18,7 @@ + diff --git a/src/EPR.Calculator.API/Exporter/CalcResultsExporter.cs b/src/EPR.Calculator.API/Exporter/CalcResultsExporter.cs index 090a859..c5b6aed 100644 --- a/src/EPR.Calculator.API/Exporter/CalcResultsExporter.cs +++ b/src/EPR.Calculator.API/Exporter/CalcResultsExporter.cs @@ -1,12 +1,80 @@ -using EPR.Calculator.API.Models; +using EPR.Calculator.API.Constants; +using EPR.Calculator.API.Models; +using EPR.Calculator.API.Services; +using EPR.Calculator.API.Utils; +using System.Text; namespace EPR.Calculator.API.Exporter { public class CalcResultsExporter : ICalcResultsExporter { + private readonly IBlobStorageService _blobStorageService; + private const string RunName = "Run Name"; + private const string RunId = "Run Id"; + private const string RunDate = "Run Date"; + private const string Runby = "Run by"; + private const string FinancialYear = "Financial Year"; + private const string RPDFileORG = "RPD File - ORG"; + private const string RPDFilePOM = "RPD File - POM"; + private const string LapcapFile = "Lapcap File"; + private const string ParametersFile = "Parameters File"; + + public CalcResultsExporter(IBlobStorageService blobStorageService) + { + _blobStorageService = blobStorageService; + } public void Export(CalcResult results) { - // Code to export the File to Csv + var csvContent = new StringBuilder(); + LoadCalcResultDetail(results, csvContent); + var fileName = GetResultFileName(results.CalcResultDetail.RunId); + try + { + _blobStorageService.UploadResultFileContentAsync(fileName, csvContent); + } + catch (IOException ex) + { + throw new IOException($"File upload failed: {ex.Message}", ex); + } + } + + private static void LoadCalcResultDetail(CalcResult results, StringBuilder csvContent) + { + AppendCsvLine(csvContent, RunName, results.CalcResultDetail.RunName); + AppendCsvLine(csvContent, RunId, results.CalcResultDetail.RunId.ToString()); + AppendCsvLine(csvContent, RunDate, results.CalcResultDetail.RunDate.ToString(CalculationResults.DateFormat)); + AppendCsvLine(csvContent, Runby, results.CalcResultDetail.RunBy); + AppendCsvLine(csvContent, FinancialYear, results.CalcResultDetail.FinancialYear); + AppendRPDFileInfo(csvContent, RPDFileORG, RPDFilePOM, results.CalcResultDetail.RpdFileORG, results.CalcResultDetail.RpdFilePOM); + AppendFileInfo(csvContent, LapcapFile, results.CalcResultDetail.LapcapFile); + AppendFileInfo(csvContent, ParametersFile, results.CalcResultDetail.ParametersFile); + } + + private static void AppendRPDFileInfo(StringBuilder csvContent, string rPDFileORG, string rPDFilePOM, string rpdFileORGValue, string rpdFilePOMValue) + { + csvContent.AppendLine($"{rPDFileORG},{CsvSanitiser.SanitiseData(rpdFileORGValue)},{rPDFilePOM},{CsvSanitiser.SanitiseData(rpdFilePOMValue)}"); + } + + private static void AppendFileInfo(StringBuilder csvContent, string label, string filePath) + { + var fileParts = filePath.Split(','); + if (fileParts.Length >= 3) + { + string fileName = CsvSanitiser.SanitiseData(fileParts[0]); + string date = CsvSanitiser.SanitiseData(fileParts[1]); + string user = CsvSanitiser.SanitiseData(fileParts[2]); + csvContent.AppendLine($"{label},{fileName},{date},{user}"); + } + } + + private static void AppendCsvLine(StringBuilder csvContent, string label, string value) + { + csvContent.AppendLine($"{label},{CsvSanitiser.SanitiseData(value)}"); + } + + private static string GetResultFileName(int runId) + { + return $"{runId}-{DateTime.Now:yyyy-MM-dd-HHmm}.csv"; } } } diff --git a/src/EPR.Calculator.API/Models/CalcResultDetail.cs b/src/EPR.Calculator.API/Models/CalcResultDetail.cs index acdd8dd..894199a 100644 --- a/src/EPR.Calculator.API/Models/CalcResultDetail.cs +++ b/src/EPR.Calculator.API/Models/CalcResultDetail.cs @@ -7,7 +7,8 @@ public class CalcResultDetail public DateTime RunDate { get; set; } public string RunBy { get; set; } = string.Empty; public string FinancialYear { get; set; } = string.Empty; - public string RpdFile { get; set; } = string.Empty; + public string RpdFileORG { get; set; } = string.Empty; + public string RpdFilePOM { get; set; } = string.Empty; public string LapcapFile { get; set; } = string.Empty; public string ParametersFile { get; set; } = string.Empty; } diff --git a/src/EPR.Calculator.API/Models/CalcResultOnePlusFourApportionment.cs b/src/EPR.Calculator.API/Models/CalcResultOnePlusFourApportionment.cs index 2d979c9..c83b407 100644 --- a/src/EPR.Calculator.API/Models/CalcResultOnePlusFourApportionment.cs +++ b/src/EPR.Calculator.API/Models/CalcResultOnePlusFourApportionment.cs @@ -5,4 +5,5 @@ public class CalcResultOnePlusFourApportionment public string Name { get; set; } public IEnumerable CalcResultOnePlusFourApportionmentDetails { get; set; } = new List(); } + } diff --git a/src/EPR.Calculator.API/Program.cs b/src/EPR.Calculator.API/Program.cs index 3833a81..d89dc5f 100644 --- a/src/EPR.Calculator.API/Program.cs +++ b/src/EPR.Calculator.API/Program.cs @@ -1,9 +1,12 @@ using Azure.Messaging.ServiceBus; +using Azure.Storage.Blobs; using EPR.Calculator.API.Builder; +using EPR.Calculator.API.Constants; using EPR.Calculator.API.Data; using EPR.Calculator.API.Exceptions; using EPR.Calculator.API.Exporter; using EPR.Calculator.API.Models; +using EPR.Calculator.API.Services; using EPR.Calculator.API.Validators; using EPR.Calculator.API.Wrapper; using FluentValidation; @@ -29,6 +32,7 @@ builder.Services.AddScoped(); builder.Services.AddScoped, CalcResultsExporter>(); builder.Services.AddScoped(); +builder.Services.AddScoped(); builder.Services.AddValidatorsFromAssemblyContaining(); builder.Services.AddDbContext(options => @@ -36,6 +40,20 @@ options.UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnection")); }); +builder.Services.Configure( + builder.Configuration.GetSection("BlobStorage")); + +builder.Services.AddSingleton(provider => +{ + var configuration = provider.GetRequiredService(); + var connectionString = configuration.GetSection("BlobStorage:ConnectionString").Value; + if (string.IsNullOrEmpty(connectionString)) + { + throw new ArgumentNullException("BlobStorage:ConnectionString", "Blob Storage connection string is not configured."); + } + return new BlobServiceClient(connectionString); +}); + var serviceBusConnectionString = builder.Configuration.GetSection("ServiceBus").GetSection("ConnectionString"); var serviceBusQueueName = builder.Configuration.GetSection("ServiceBus").GetSection("QueueName").Value; #pragma warning disable CS8604 // Possible null reference argument. diff --git a/src/EPR.Calculator.API/Services/BlobStorageService.cs b/src/EPR.Calculator.API/Services/BlobStorageService.cs new file mode 100644 index 0000000..254111e --- /dev/null +++ b/src/EPR.Calculator.API/Services/BlobStorageService.cs @@ -0,0 +1,31 @@ +using Azure.Storage.Blobs; +using EPR.Calculator.API.Constants; +using System.Text; +namespace EPR.Calculator.API.Services +{ + public class BlobStorageService: IBlobStorageService + { + private readonly BlobContainerClient _containerClient; + + public BlobStorageService(BlobServiceClient blobServiceClient, IConfiguration configuration) + { + var settings = configuration.GetSection("BlobStorage").Get() ?? throw new ArgumentNullException("BlobStorage settings are missing in configuration."); + _containerClient = blobServiceClient.GetBlobContainerClient(settings.ContainerName ?? throw new ArgumentNullException("Container name is missing in configuration.")); + } + + public async Task UploadResultFileContentAsync(string fileName, StringBuilder csvContent) + { + try + { + File.WriteAllText(fileName, csvContent.ToString()); + BlobClient blobClient = _containerClient.GetBlobClient(fileName); + using var fileStream = File.OpenRead(fileName); + await blobClient.UploadAsync(fileStream, true); + } + catch (Exception ex) + { + Console.WriteLine($"Error occurred while saving blob content: {ex.Message}"); + } + } + } +} \ No newline at end of file diff --git a/src/EPR.Calculator.API/Services/IBlobStorageService.cs b/src/EPR.Calculator.API/Services/IBlobStorageService.cs new file mode 100644 index 0000000..6c63386 --- /dev/null +++ b/src/EPR.Calculator.API/Services/IBlobStorageService.cs @@ -0,0 +1,9 @@ +using System.Text; + +namespace EPR.Calculator.API.Services +{ + public interface IBlobStorageService + { + Task UploadResultFileContentAsync(string fileName, StringBuilder content); + } +} diff --git a/src/EPR.Calculator.API/Utils/CsvSanitiser.cs b/src/EPR.Calculator.API/Utils/CsvSanitiser.cs new file mode 100644 index 0000000..d6cb6ff --- /dev/null +++ b/src/EPR.Calculator.API/Utils/CsvSanitiser.cs @@ -0,0 +1,25 @@ +using Newtonsoft.Json; + +namespace EPR.Calculator.API.Utils +{ + public static class CsvSanitiser + { + public static string SanitiseData(T value) + { + if (value == null) return string.Empty; + + // If the value is a string, use it directly; otherwise, serialize the object to JSON. + var stringToSanitise = value is string + ? value.ToString() + : JsonConvert.SerializeObject(value); + + // Remove newline, carriage returns, and commas, then trim + stringToSanitise = stringToSanitise.Replace(Environment.NewLine, string.Empty) + .Replace("\t", string.Empty) + .Replace(",", string.Empty) + .Trim(); + + return stringToSanitise; + } + } +} diff --git a/src/EPR.Calculator.API/appsettings.Development.json b/src/EPR.Calculator.API/appsettings.Development.json index b8bdb7e..3bbc394 100644 --- a/src/EPR.Calculator.API/appsettings.Development.json +++ b/src/EPR.Calculator.API/appsettings.Development.json @@ -17,5 +17,10 @@ "QueueName": "Queue1", "PostMessageRetryCount": 3, "PostMessageRetryPeriod": 2 + }, + "AzureBlobStorage": { + "ConnectionString": "ConnectionString", + "ContainerName": "ContainerName", + "CsvFileName": "CsvFileName" } }