using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; using FrameworkBLL.Promotion.Adapters; using FrameworkBLL.ReportHeaderMgmt; using FrameworkDAL.CustomCode.Promotion; using FrameworkDAL.DTO.ReportHeader; using GB5Shared.DTO.Framework.Login; using GB5Shared.DTO.Promotion; using Moq; using Newtonsoft.Json; using Xunit; namespace FrameworkTests; /// /// ReportHeaderPromotionAdapter promotes the full parent+children bundle (unlike ReportView/ /// DrillDown, MREPORTHEADERDETAIL's grid-cell columns are all plain values with no cross-environment /// FK references) — covers that, the ClientId tenant-literal block (MREPORTHEADER's own unique /// constraint is (Code, ClientId), not Code alone), and that Import always forces ClientId to -1 at /// the destination regardless of the origin's own value. /// public class ReportHeaderPromotionAdapterTests { private static LoginDTO Login() => new() { ClientId = -1, UserId = 7 }; private static ReportHeaderDTO CleanDto(int reportHeaderId = 300) => new() { ReportHeaderId = reportHeaderId, ReportHeaderCode = "STD_HEADER", ReportHeaderName = "Standard Header", ReportHeaderParticulars = "Company letterhead", ReportHeaderBaseClinetId = -1, ReportHeaderSortOrder = 1, ReportHeaderDetailArray = new List { new() { ReportHeaderDetailSlNo = 1, ReportHeaderDetailExpression = "@COMPANYNAME", ReportHeaderDetailAlignment = 1 } } }; private static (Mock bll, Mock lookup) Mocks() => (new Mock(), new Mock()); private static async Task BuildBundle(ReportHeaderDTO dto) { var (bll, lookup) = Mocks(); bll.Setup(b => b.GetReportHeader(dto.ReportHeaderId, It.IsAny(), It.IsAny())) .ReturnsAsync(JsonConvert.SerializeObject(dto)); var adapter = new ReportHeaderPromotionAdapter(bll.Object, lookup.Object); return await adapter.ExportForPromotionAsync(dto.ReportHeaderId, Login(), CancellationToken.None); } [Fact] public async Task Export_BundlesParentAndDetailRowsTogether() { var bundle = await BuildBundle(CleanDto()); Assert.Equal("REPORTHEADER", bundle.EntityTypeCode); Assert.Contains("STD_HEADER", bundle.PayloadJson); Assert.Contains("@COMPANYNAME", bundle.PayloadJson); } [Fact] public async Task Validate_CleanHeader_IsOk() { var bundle = await BuildBundle(CleanDto()); var (bll, lookup) = Mocks(); var adapter = new ReportHeaderPromotionAdapter(bll.Object, lookup.Object); var result = await adapter.ValidateForPromotionAsync(bundle, CancellationToken.None); Assert.True(result.IsPromotable); } [Fact] public async Task Validate_ScopedToSpecificClient_Blocks() { var dto = CleanDto(); dto.ReportHeaderBaseClinetId = 42; var bundle = await BuildBundle(dto); var (bll, lookup) = Mocks(); var adapter = new ReportHeaderPromotionAdapter(bll.Object, lookup.Object); var result = await adapter.ValidateForPromotionAsync(bundle, CancellationToken.None); Assert.False(result.IsPromotable); Assert.NotEmpty(result.BlockingReasons); } [Fact] public async Task Import_NewHeader_ForcesClientIdToSentinelRegardlessOfOrigin() { var bundle = await BuildBundle(CleanDto()); var (bll, lookup) = Mocks(); lookup.Setup(l => l.GetReportHeaderIdByCode("STD_HEADER", It.IsAny(), It.IsAny())) .ReturnsAsync((int?)null); // no pre-existing header at destination ReportHeaderDTO? captured = null; bll.Setup(b => b.SaveReportHeader(It.IsAny(), It.IsAny(), It.IsAny())) .Callback((d, _, _) => { captured = new ReportHeaderDTO { ReportHeaderId = d.ReportHeaderId, ReportHeaderBaseClinetId = d.ReportHeaderBaseClinetId, ReportHeaderSourceType = d.ReportHeaderSourceType }; d.ReportHeaderId = 950; }) .ReturnsAsync("Saved"); var adapter = new ReportHeaderPromotionAdapter(bll.Object, lookup.Object); var newId = await adapter.ImportPromotedAsync(bundle, destinationSourceType: 2, existingDestinationId: null, Login(), CancellationToken.None); Assert.Equal(950, newId); Assert.NotNull(captured); Assert.Equal(0, captured!.ReportHeaderId); // new row Assert.Equal(-1, captured.ReportHeaderBaseClinetId); // forced, regardless of origin Assert.Equal((byte)2, captured.ReportHeaderSourceType); } [Fact] public async Task Import_HeaderCodeAlreadyExistsAtDestination_UpdatesRatherThanDuplicating() { var bundle = await BuildBundle(CleanDto()); var (bll, lookup) = Mocks(); lookup.Setup(l => l.GetReportHeaderIdByCode("STD_HEADER", It.IsAny(), It.IsAny())) .ReturnsAsync(600); // a header with this code already exists, created independently int? capturedId = null; bll.Setup(b => b.SaveReportHeader(It.IsAny(), It.IsAny(), It.IsAny())) .Callback((d, _, _) => capturedId = d.ReportHeaderId) .ReturnsAsync("Updated"); var adapter = new ReportHeaderPromotionAdapter(bll.Object, lookup.Object); var resultId = await adapter.ImportPromotedAsync(bundle, 2, existingDestinationId: null, Login(), CancellationToken.None); Assert.Equal(600, resultId); Assert.Equal(600, capturedId); } }