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