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