using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using FrameworkBLL.Promotion.Adapters;
using FrameworkBLL.ReportHeaderMgmt;
using FrameworkDAL.CustomCode.Promotion;
using FrameworkDAL.DTO.ReportHeader;
using GB5Shared.DTO.Framework.Login;
using GB5Shared.DTO.Promotion;
using Moq;
using Newtonsoft.Json;
using Xunit;
namespace FrameworkTests;
///
/// ReportHeaderPromotionAdapter promotes the full parent+children bundle (unlike ReportView/
/// DrillDown, MREPORTHEADERDETAIL's grid-cell columns are all plain values with no cross-environment
/// FK references) — covers that, the ClientId tenant-literal block (MREPORTHEADER's own unique
/// constraint is (Code, ClientId), not Code alone), and that Import always forces ClientId to -1 at
/// the destination regardless of the origin's own value.
///
public class ReportHeaderPromotionAdapterTests
{
private static LoginDTO Login() => new() { ClientId = -1, UserId = 7 };
private static ReportHeaderDTO CleanDto(int reportHeaderId = 300) => new()
{
ReportHeaderId = reportHeaderId,
ReportHeaderCode = "STD_HEADER",
ReportHeaderName = "Standard Header",
ReportHeaderParticulars = "Company letterhead",
ReportHeaderBaseClinetId = -1,
ReportHeaderSortOrder = 1,
ReportHeaderDetailArray = new List
{
new() { ReportHeaderDetailSlNo = 1, ReportHeaderDetailExpression = "@COMPANYNAME", ReportHeaderDetailAlignment = 1 }
}
};
private static (Mock bll, Mock lookup) Mocks()
=> (new Mock(), new Mock());
private static async Task BuildBundle(ReportHeaderDTO dto)
{
var (bll, lookup) = Mocks();
bll.Setup(b => b.GetReportHeader(dto.ReportHeaderId, It.IsAny(), It.IsAny()))
.ReturnsAsync(JsonConvert.SerializeObject(dto));
var adapter = new ReportHeaderPromotionAdapter(bll.Object, lookup.Object);
return await adapter.ExportForPromotionAsync(dto.ReportHeaderId, Login(), CancellationToken.None);
}
[Fact]
public async Task Export_BundlesParentAndDetailRowsTogether()
{
var bundle = await BuildBundle(CleanDto());
Assert.Equal("REPORTHEADER", bundle.EntityTypeCode);
Assert.Contains("STD_HEADER", bundle.PayloadJson);
Assert.Contains("@COMPANYNAME", bundle.PayloadJson);
}
[Fact]
public async Task Validate_CleanHeader_IsOk()
{
var bundle = await BuildBundle(CleanDto());
var (bll, lookup) = Mocks();
var adapter = new ReportHeaderPromotionAdapter(bll.Object, lookup.Object);
var result = await adapter.ValidateForPromotionAsync(bundle, CancellationToken.None);
Assert.True(result.IsPromotable);
}
[Fact]
public async Task Validate_ScopedToSpecificClient_Blocks()
{
var dto = CleanDto();
dto.ReportHeaderBaseClinetId = 42;
var bundle = await BuildBundle(dto);
var (bll, lookup) = Mocks();
var adapter = new ReportHeaderPromotionAdapter(bll.Object, lookup.Object);
var result = await adapter.ValidateForPromotionAsync(bundle, CancellationToken.None);
Assert.False(result.IsPromotable);
Assert.NotEmpty(result.BlockingReasons);
}
[Fact]
public async Task Import_NewHeader_ForcesClientIdToSentinelRegardlessOfOrigin()
{
var bundle = await BuildBundle(CleanDto());
var (bll, lookup) = Mocks();
lookup.Setup(l => l.GetReportHeaderIdByCode("STD_HEADER", It.IsAny(), It.IsAny()))
.ReturnsAsync((int?)null); // no pre-existing header at destination
ReportHeaderDTO? captured = null;
bll.Setup(b => b.SaveReportHeader(It.IsAny(), It.IsAny(), It.IsAny()))
.Callback((d, _, _) =>
{
captured = new ReportHeaderDTO
{
ReportHeaderId = d.ReportHeaderId,
ReportHeaderBaseClinetId = d.ReportHeaderBaseClinetId,
ReportHeaderSourceType = d.ReportHeaderSourceType
};
d.ReportHeaderId = 950;
})
.ReturnsAsync("Saved");
var adapter = new ReportHeaderPromotionAdapter(bll.Object, lookup.Object);
var newId = await adapter.ImportPromotedAsync(bundle, destinationSourceType: 2, existingDestinationId: null, Login(), CancellationToken.None);
Assert.Equal(950, newId);
Assert.NotNull(captured);
Assert.Equal(0, captured!.ReportHeaderId); // new row
Assert.Equal(-1, captured.ReportHeaderBaseClinetId); // forced, regardless of origin
Assert.Equal((byte)2, captured.ReportHeaderSourceType);
}
[Fact]
public async Task Import_HeaderCodeAlreadyExistsAtDestination_UpdatesRatherThanDuplicating()
{
var bundle = await BuildBundle(CleanDto());
var (bll, lookup) = Mocks();
lookup.Setup(l => l.GetReportHeaderIdByCode("STD_HEADER", It.IsAny(), It.IsAny()))
.ReturnsAsync(600); // a header with this code already exists, created independently
int? capturedId = null;
bll.Setup(b => b.SaveReportHeader(It.IsAny(), It.IsAny(), It.IsAny()))
.Callback((d, _, _) => capturedId = d.ReportHeaderId)
.ReturnsAsync("Updated");
var adapter = new ReportHeaderPromotionAdapter(bll.Object, lookup.Object);
var resultId = await adapter.ImportPromotedAsync(bundle, 2, existingDestinationId: null, Login(), CancellationToken.None);
Assert.Equal(600, resultId);
Assert.Equal(600, capturedId);
}
}