using System.Threading; using System.Threading.Tasks; using FrameworkBLL.PortletType; 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; /// /// PortletTypePromotionAdapter — no child rows, no real cross-environment FK (confirmed via schema /// search: PortletTypeTenantId is the only reference-shaped column and has no FK constraint). /// Covers PortletTypeTenantId always being forced to null at the destination (same "shared/ /// framework-level by definition" reasoning as ReportHeader forcing ClientId), and the /// PortletTypeCode-already-exists-at-destination update path (UKMPORTLETTYPE_CODE, confirmed via /// schema to be a real unique constraint). /// public class PortletTypePromotionAdapterTests { private static LoginDTO Login() => new() { ClientId = -1, UserId = 7 }; private static PortletTypeDTO CleanDto(int portletTypeId = 42) => new() { PortletTypeId = portletTypeId, PortletTypeCode = "GRID_STANDARD", PortletTypeName = "Standard Grid", PortletTypeTenantId = 42, // origin-specific — must never survive to the destination PortletTypeSortOrder = 1 }; private static (Mock bll, Mock lookup) Mocks() => (new Mock(), new Mock()); private static async Task BuildBundle(PortletTypeDTO dto) { var (bll, lookup) = Mocks(); bll.Setup(b => b.GetPortletType(dto.PortletTypeId, It.IsAny())).ReturnsAsync(JsonConvert.SerializeObject(dto)); var adapter = new PortletTypePromotionAdapter(bll.Object, lookup.Object); return await adapter.ExportForPromotionAsync(dto.PortletTypeId, Login(), CancellationToken.None); } [Fact] public async Task Validate_CleanPortletType_IsOk() { var bundle = await BuildBundle(CleanDto()); var (bll, lookup) = Mocks(); var adapter = new PortletTypePromotionAdapter(bll.Object, lookup.Object); var result = await adapter.ValidateForPromotionAsync(bundle, CancellationToken.None); Assert.True(result.IsPromotable); } [Fact] public async Task Import_ForcesTenantIdToNull_RegardlessOfOrigin() { var bundle = await BuildBundle(CleanDto()); // PortletTypeTenantId=42 at origin var (bll, lookup) = Mocks(); lookup.Setup(l => l.GetPortletTypeIdByCode("GRID_STANDARD", It.IsAny(), It.IsAny())) .ReturnsAsync((int?)null); PortletTypeDTO? captured = null; bll.Setup(b => b.SavePortletType(It.IsAny(), It.IsAny())) .Callback((d, _) => { captured = new PortletTypeDTO { PortletTypeId = d.PortletTypeId, PortletTypeTenantId = d.PortletTypeTenantId, PortletTypeSourceType = d.PortletTypeSourceType }; d.PortletTypeId = 903; }) .ReturnsAsync("Saved"); var adapter = new PortletTypePromotionAdapter(bll.Object, lookup.Object); var newId = await adapter.ImportPromotedAsync(bundle, destinationSourceType: 2, existingDestinationId: null, Login(), CancellationToken.None); Assert.Equal(903, newId); Assert.NotNull(captured); Assert.Null(captured!.PortletTypeTenantId); Assert.Equal((byte)2, captured.PortletTypeSourceType); } [Fact] public async Task Import_CodeAlreadyExistsAtDestination_UpdatesRatherThanDuplicating() { var bundle = await BuildBundle(CleanDto()); var (bll, lookup) = Mocks(); lookup.Setup(l => l.GetPortletTypeIdByCode("GRID_STANDARD", It.IsAny(), It.IsAny())) .ReturnsAsync(600); int? capturedId = null; bll.Setup(b => b.SavePortletType(It.IsAny(), It.IsAny())) .Callback((d, _) => capturedId = d.PortletTypeId) .ReturnsAsync("Updated"); var adapter = new PortletTypePromotionAdapter(bll.Object, lookup.Object); var resultId = await adapter.ImportPromotedAsync(bundle, 2, existingDestinationId: null, Login(), CancellationToken.None); Assert.Equal(600, resultId); Assert.Equal(600, capturedId); } }