using System.Threading; using System.Threading.Tasks; using FrameworkBLL.PortLet; using FrameworkBLL.Promotion.Adapters; using FrameworkDAL.CustomCode.Promotion; using FrameworkDAL.DTO.Portlet; using GB5Shared.DTO.Framework.Login; using GB5Shared.DTO.Promotion; using Moq; using Newtonsoft.Json; using Xunit; namespace FrameworkTests; /// /// PortletPromotionAdapter — the most cross-reference-heavy adapter built so far. Covers resolving /// PortletType/Module/Report/Menu/FormLinkMenu by natural key, blocking every field with no /// natural-key lookup yet (ProgramId permanently — MPROGRAM has no Code column at all; BIView/KPI/ /// KPIList/PortletKPIOUId/WebServiceSettingId/PortletSettingsId/DefaultCriteriaConfigId/FileId/ /// ReportFormatId pending their own future adapters/lookups), and that Import never maps any blocked /// field even though they're carried in Flags for validation to see. /// public class PortletPromotionAdapterTests { private static LoginDTO Login() => new() { ClientId = -1, UserId = 7 }; private static PortletDTO CleanDto(int portletId = 42) => new() { PortletId = portletId, PortletCode = "SALES_TOP10", PortletName = "Top 10 Sales", PortletTypeId = 0, ModuleId = 0, ReportId = 0, MenuId = 0, FormLinkMenuId = 0, ProgramId = -1, BIViewId = null, KPIId = null, KPIListId = null, PortletKPIOUId = null, WebServiceSettingId = -1, PortletSettingsId = -1, DefaultCriteriaConfigId = -1, FileId = -1, ReportFormatId = -1, PortletSortOrder = 1 }; private static (Mock bll, Mock reportLookup, Mock moduleLookup, Mock menuLookup, Mock typeLookup, Mock portletLookup) Mocks() => (new Mock(), new Mock(), new Mock(), new Mock(), new Mock(), new Mock()); private static PortletPromotionAdapter NewAdapter( (Mock bll, Mock reportLookup, Mock moduleLookup, Mock menuLookup, Mock typeLookup, Mock portletLookup) m) => new(m.bll.Object, m.reportLookup.Object, m.moduleLookup.Object, m.menuLookup.Object, m.typeLookup.Object, m.portletLookup.Object); private static async Task BuildBundle(PortletDTO dto) { var m = Mocks(); m.bll.Setup(b => b.GetPortlet(dto.PortletId, It.IsAny())).ReturnsAsync(JsonConvert.SerializeObject(dto)); if (dto.PortletTypeId is not 0 and not -1) m.typeLookup.Setup(l => l.GetPortletTypeCodeById(dto.PortletTypeId, It.IsAny(), It.IsAny())).ReturnsAsync("GRID_STANDARD"); if (dto.ModuleId is not 0 and not -1) m.moduleLookup.Setup(l => l.GetModuleCodeById(dto.ModuleId, It.IsAny(), It.IsAny())).ReturnsAsync("SALES"); if (dto.ReportId is not 0 and not -1) m.reportLookup.Setup(l => l.GetReportCodeById(dto.ReportId, It.IsAny(), It.IsAny())).ReturnsAsync("SALES_RPT"); if (dto.MenuId is not 0 and not -1) m.menuLookup.Setup(l => l.GetMenuCodeById(dto.MenuId, It.IsAny(), It.IsAny())).ReturnsAsync("sales/dashboard"); if (dto.FormLinkMenuId is not 0 and not -1) m.menuLookup.Setup(l => l.GetMenuCodeById(dto.FormLinkMenuId, It.IsAny(), It.IsAny())).ReturnsAsync("sales/detail"); var adapter = NewAdapter(m); return await adapter.ExportForPromotionAsync(dto.PortletId, Login(), CancellationToken.None); } [Fact] public async Task Validate_CleanPortlet_IsOk() { var bundle = await BuildBundle(CleanDto()); var adapter = NewAdapter(Mocks()); var result = await adapter.ValidateForPromotionAsync(bundle, CancellationToken.None); Assert.True(result.IsPromotable); } [Theory] [InlineData(nameof(PortletDTO.ProgramId))] [InlineData(nameof(PortletDTO.BIViewId))] [InlineData(nameof(PortletDTO.KPIId))] [InlineData(nameof(PortletDTO.KPIListId))] [InlineData(nameof(PortletDTO.PortletKPIOUId))] [InlineData(nameof(PortletDTO.WebServiceSettingId))] [InlineData(nameof(PortletDTO.PortletSettingsId))] [InlineData(nameof(PortletDTO.DefaultCriteriaConfigId))] [InlineData(nameof(PortletDTO.FileId))] [InlineData(nameof(PortletDTO.ReportFormatId))] public async Task Validate_EachUnsupportedCrossReference_Blocks(string property) { var dto = CleanDto(); typeof(PortletDTO).GetProperty(property)!.SetValue(dto, 555); var bundle = await BuildBundle(dto); var adapter = NewAdapter(Mocks()); var result = await adapter.ValidateForPromotionAsync(bundle, CancellationToken.None); Assert.False(result.IsPromotable); Assert.NotEmpty(result.BlockingReasons); } [Fact] public async Task Import_ResolvesAllNaturalKeysAndNeverMapsBlockedFields() { var dto = CleanDto(); dto.PortletTypeId = 10; dto.ModuleId = 20; dto.ReportId = 30; dto.MenuId = 40; dto.FormLinkMenuId = 50; var bundle = await BuildBundle(dto); var m = Mocks(); m.portletLookup.Setup(l => l.GetPortletIdByCode("SALES_TOP10", It.IsAny(), It.IsAny())).ReturnsAsync((int?)null); m.typeLookup.Setup(l => l.GetPortletTypeIdByCode("GRID_STANDARD", It.IsAny(), It.IsAny())).ReturnsAsync(101); m.moduleLookup.Setup(l => l.GetModuleIdByCode("SALES", It.IsAny(), It.IsAny())).ReturnsAsync(102); m.reportLookup.Setup(l => l.GetReportIdByCode("SALES_RPT", It.IsAny(), It.IsAny())).ReturnsAsync(103); m.menuLookup.Setup(l => l.GetMenuIdByCode("sales/dashboard", It.IsAny(), It.IsAny())).ReturnsAsync(104); m.menuLookup.Setup(l => l.GetMenuIdByCode("sales/detail", It.IsAny(), It.IsAny())).ReturnsAsync(105); PortletDTO? captured = null; m.bll.Setup(b => b.SavePortlet(It.IsAny(), It.IsAny())) .Callback((d, _) => { captured = new PortletDTO { PortletId = d.PortletId, PortletTypeId = d.PortletTypeId, ModuleId = d.ModuleId, ReportId = d.ReportId, MenuId = d.MenuId, FormLinkMenuId = d.FormLinkMenuId, ProgramId = d.ProgramId, BIViewId = d.BIViewId, KPIId = d.KPIId, KPIListId = d.KPIListId, PortletKPIOUId = d.PortletKPIOUId, WebServiceSettingId = d.WebServiceSettingId, PortletSettingsId = d.PortletSettingsId, DefaultCriteriaConfigId = d.DefaultCriteriaConfigId, FileId = d.FileId, ReportFormatId = d.ReportFormatId, PortletSourceType = d.PortletSourceType }; d.PortletId = 904; }) .ReturnsAsync("Saved"); var adapter = NewAdapter(m); var newId = await adapter.ImportPromotedAsync(bundle, destinationSourceType: 3, existingDestinationId: null, Login(), CancellationToken.None); Assert.Equal(904, newId); Assert.NotNull(captured); Assert.Equal(101, captured!.PortletTypeId); Assert.Equal(102, captured.ModuleId); Assert.Equal(103, captured.ReportId); Assert.Equal(104, captured.MenuId); Assert.Equal(105, captured.FormLinkMenuId); Assert.Equal((byte)3, captured.PortletSourceType); // Never mapped, regardless of what the origin carried in Flags. Assert.Equal(-1, captured.ProgramId); Assert.Null(captured.BIViewId); Assert.Null(captured.KPIId); Assert.Null(captured.KPIListId); Assert.Null(captured.PortletKPIOUId); Assert.Equal(-1, captured.WebServiceSettingId); Assert.Equal(-1, captured.PortletSettingsId); Assert.Equal(-1, captured.DefaultCriteriaConfigId); Assert.Equal(-1, captured.FileId); Assert.Equal(-1, captured.ReportFormatId); } [Fact] public async Task Import_PortletCodeAlreadyExistsAtDestination_UpdatesRatherThanDuplicating() { var bundle = await BuildBundle(CleanDto()); var m = Mocks(); m.portletLookup.Setup(l => l.GetPortletIdByCode("SALES_TOP10", It.IsAny(), It.IsAny())).ReturnsAsync(700); int? capturedId = null; m.bll.Setup(b => b.SavePortlet(It.IsAny(), It.IsAny())) .Callback((d, _) => capturedId = d.PortletId) .ReturnsAsync("Updated"); var adapter = NewAdapter(m); var resultId = await adapter.ImportPromotedAsync(bundle, 3, existingDestinationId: null, Login(), CancellationToken.None); Assert.Equal(700, resultId); Assert.Equal(700, capturedId); } }