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);
}
}