using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; using FrameworkBLL.DrillDown; using FrameworkBLL.Promotion.Adapters; using FrameworkDAL.CustomCode.Promotion; using FrameworkDAL.DTO.DrillDown; using GB5Shared.DTO.Framework.Login; using GB5Shared.DTO.Promotion; using Moq; using Newtonsoft.Json; using Xunit; namespace FrameworkTests; /// /// DrillDownPromotionAdapter supports the Report-anchored case (resolving FromReportCode/ /// ToReportCode/ModuleCode, all natural keys the read query already joins for) and conservatively /// blocks Menu/Portlet/WebForm-anchored drill-downs and any detail row referencing AttributeId, /// since none of those have remapping support yet. Also covers that DrillDown, having no SourceType /// concept at all, doesn't try to stamp one. /// public class DrillDownPromotionAdapterTests { private static LoginDTO Login() => new() { ClientId = -1, UserId = 7 }; private static DrillDownDTO CleanDto(int drillDownId = 55) => new() { DrillDownId = drillDownId, DrillDownCode = "SALES_TO_DETAIL", DrillDownDescription = "Sales summary to detail", DrillDownType = 0, DrillDownFromType = 2, DrillDownToType = 2, FromReportId = 100, FromReportCode = "SALES_SUMMARY", ToReportId = 200, ToReportCode = "SALES_DETAIL", ModuleId = 9, ModuleCode = "SALES", DrillDownSortOrder = 1, DrillDownDetailArray = new List { new() { DrillDownDetailSlNo = 1, AttributeId = -1, DrillDownDetailSourceField = "CustomerId" } } }; private static (Mock bll, Mock reportLookup, Mock moduleLookup) Mocks() => (new Mock(), new Mock(), new Mock()); private static async Task BuildBundle(DrillDownDTO dto) { var (bll, reportLookup, moduleLookup) = Mocks(); bll.Setup(b => b.GetDrillDown(dto.DrillDownId, It.IsAny())) .ReturnsAsync(JsonConvert.SerializeObject(dto)); var adapter = new DrillDownPromotionAdapter(bll.Object, reportLookup.Object, moduleLookup.Object); return await adapter.ExportForPromotionAsync(dto.DrillDownId, Login(), CancellationToken.None); } [Fact] public async Task Export_BuildsPayloadWithNaturalKeysAndDetails() { var bundle = await BuildBundle(CleanDto()); Assert.Equal("DRILLDOWN", bundle.EntityTypeCode); Assert.Contains("SALES_SUMMARY", bundle.PayloadJson); Assert.Contains("SALES_DETAIL", bundle.PayloadJson); Assert.Contains("SALES", bundle.PayloadJson); Assert.Contains("CustomerId", bundle.PayloadJson); } [Fact] public async Task Validate_CleanDrillDown_IsOk() { var bundle = await BuildBundle(CleanDto()); var (bll, reportLookup, moduleLookup) = Mocks(); var adapter = new DrillDownPromotionAdapter(bll.Object, reportLookup.Object, moduleLookup.Object); var result = await adapter.ValidateForPromotionAsync(bundle, CancellationToken.None); Assert.True(result.IsPromotable); } [Fact] public async Task Validate_MenuAnchored_Blocks() { var dto = CleanDto(); dto.FromMenuId = 42; var bundle = await BuildBundle(dto); var (bll, reportLookup, moduleLookup) = Mocks(); var adapter = new DrillDownPromotionAdapter(bll.Object, reportLookup.Object, moduleLookup.Object); var result = await adapter.ValidateForPromotionAsync(bundle, CancellationToken.None); Assert.False(result.IsPromotable); Assert.Contains(result.BlockingReasons, r => r.Contains("FromMenuId")); } [Fact] public async Task Validate_DetailRowReferencesAttributeId_Blocks() { var dto = CleanDto(); dto.DrillDownDetailArray[0].AttributeId = 33; var bundle = await BuildBundle(dto); var (bll, reportLookup, moduleLookup) = Mocks(); var adapter = new DrillDownPromotionAdapter(bll.Object, reportLookup.Object, moduleLookup.Object); var result = await adapter.ValidateForPromotionAsync(bundle, CancellationToken.None); Assert.False(result.IsPromotable); Assert.Contains(result.BlockingReasons, r => r.Contains("AttributeId")); } [Fact] public async Task Import_ResolvesReportAndModuleCodes_CallsSaveDrillDown() { var bundle = await BuildBundle(CleanDto()); var (bll, reportLookup, moduleLookup) = Mocks(); reportLookup.Setup(l => l.GetReportIdByCode("SALES_SUMMARY", It.IsAny(), It.IsAny())) .ReturnsAsync(1100); reportLookup.Setup(l => l.GetReportIdByCode("SALES_DETAIL", It.IsAny(), It.IsAny())) .ReturnsAsync(1200); moduleLookup.Setup(l => l.GetModuleIdByCode("SALES", It.IsAny(), It.IsAny())) .ReturnsAsync(19); bll.Setup(b => b.SaveDrillDown(It.IsAny(), It.IsAny())) .Callback((d, _) => d.DrillDownId = 9001) .ReturnsAsync("DrillDown saved successfully with DrillDownId 9001."); var adapter = new DrillDownPromotionAdapter(bll.Object, reportLookup.Object, moduleLookup.Object); // destinationSourceType is passed but unused — DrillDown has no SourceType concept. var newId = await adapter.ImportPromotedAsync(bundle, destinationSourceType: 3, existingDestinationId: null, Login(), CancellationToken.None); Assert.Equal(9001, newId); bll.Verify(b => b.SaveDrillDown( It.Is(d => d.FromReportId == 1100 && d.ToReportId == 1200 && d.ModuleId == 19 && d.DrillDownDetailArray[0].AttributeId == -1), It.IsAny()), Times.Once); } [Fact] public async Task Import_UnknownFromReportCode_ThrowsBeforeSaving() { var bundle = await BuildBundle(CleanDto()); var (bll, reportLookup, moduleLookup) = Mocks(); reportLookup.Setup(l => l.GetReportIdByCode("SALES_SUMMARY", It.IsAny(), It.IsAny())) .ReturnsAsync((int?)null); var adapter = new DrillDownPromotionAdapter(bll.Object, reportLookup.Object, moduleLookup.Object); await Assert.ThrowsAsync( () => adapter.ImportPromotedAsync(bundle, 3, null, Login(), CancellationToken.None)); bll.Verify(b => b.SaveDrillDown(It.IsAny(), It.IsAny()), Times.Never); } }