using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using FrameworkBLL.MetaReport;
using FrameworkDAL.CustomCode.MetaReport;
using FrameworkDAL.DTO.MetaReport;
using GB5Shared.DTO.Framework.CommonConfig;
using GB5Shared.DTO.Framework.Login;
using GB5Shared.GenerateAutoNumber;
using GB5Shared.QueryExecutor;
using Microsoft.Extensions.Options;
using Moq;
using Xunit;
namespace FrameworkTests;
///
/// Covers MetaReportBLL's two real responsibilities beyond pass-through CRUD: assembling the
/// flat DAL rows (Variants/Aspects/Options/OptionVariant links) back into the nested aggregate
/// GetMetaReport returns, and assigning AutoNumber ids to every new Variant/Aspect/Option before
/// SaveMetaReportDefinition hands off to the DAL — mirroring MenuGroupBLLTests'
/// SaveMenuGroupDetail_MultipleNewLeaves regression test pattern (real AutoNumber + mocked
/// IQueryExecutor, since AutoNumber's own dependencies are both interfaces).
///
public class MetaReportBLLTests
{
private static LoginDTO Login(int userId = 7) => new() { ClientId = -1, UserId = userId };
private static AutoNumber RealAutoNumberReserving(long postIncrementAutoId)
{
var qe = new Mock();
// AutoNumber.GetNumberAsync reads results[0] as a long (AutoId's SQL type).
qe.Setup(x => x.QueryAsync(
It.IsAny(), It.IsAny(), It.IsAny(),
It.IsAny(), It.IsAny(), It.IsAny()))
.ReturnsAsync(new List { postIncrementAutoId });
return new AutoNumber(qe.Object, Mock.Of>(
o => o.Value == new Gb5SystemDTO { DataBaseType = 0 }));
}
[Fact]
public async Task SaveMetaReportDefinition_NewVariantsAspectsOptions_EachCollectionGetsDistinctSequentialIds()
{
// newAutoId=102, reserving N ids => firstNumber = 102 - N; asserted independently per
// collection (Variants/Aspects/Options), matching SaveMenuGroupDetail's regression-test
// scope — each entity type has its own independent MAUTONUMBER counter in real life.
var dal = new Mock();
var autoNumber = RealAutoNumberReserving(102);
var bll = new MetaReportBLL(dal.Object, autoNumber, null!);
var dto = new MetaReportDTO
{
MetaReportId = 55,
MetaReportName = "Trial Balance",
Variants = new List
{
new() { VariantId = 0, VariantName = "TB AsOn Account", MenuId = 1, ReportId = 9 },
new() { VariantId = 0, VariantName = "TB AsOn SubAccount", MenuId = 2, ReportId = 9 },
},
Aspects = new List
{
new()
{
AspectId = 0, AspectName = "Type", AspectType = "radio",
Options = new List
{
new() { OptionId = 0, Label = "AsOn", Value = "ASON" },
new() { OptionId = 0, Label = "Detail", Value = "DETAIL" },
},
},
new() { AspectId = 0, AspectName = "Level", AspectType = "button", Options = new List() },
},
};
try
{
await bll.SaveMetaReportDefinition(dto, Login(), CancellationToken.None);
}
catch (NullReferenceException)
{
// KeyInvalidate is null! — its cache-invalidation tail runs after every id is
// already assigned, same tolerance as MenuGroupBLLTests' precedent.
}
var variantIds = dto.Variants.Select(v => v.VariantId).OrderBy(i => i).ToList();
Assert.Equal(new[] { 100, 101 }, variantIds);
var aspectIds = dto.Aspects.Select(a => a.AspectId).OrderBy(i => i).ToList();
Assert.Equal(new[] { 100, 101 }, aspectIds);
var optionIds = dto.Aspects.SelectMany(a => a.Options).Select(o => o.OptionId).OrderBy(i => i).ToList();
Assert.Equal(new[] { 100, 101 }, optionIds);
// Every option/aspect/variant was correctly stamped with its parent id.
Assert.All(dto.Variants, v => Assert.Equal(55, v.MetaReportId));
Assert.All(dto.Aspects, a => Assert.Equal(55, a.MetaReportId));
Assert.All(dto.Aspects[0].Options, o => Assert.Equal(dto.Aspects[0].AspectId, o.AspectId));
}
[Fact]
public async Task SaveMetaReportDefinition_MissingName_Throws()
{
var bll = new MetaReportBLL(Mock.Of(), null!, null!);
var dto = new MetaReportDTO { MetaReportId = 1, MetaReportName = "" };
await Assert.ThrowsAsync(() => bll.SaveMetaReportDefinition(dto, Login(), CancellationToken.None));
}
[Fact]
public async Task SaveMetaReportDefinition_UnknownAspectType_Throws()
{
var bll = new MetaReportBLL(Mock.Of(), null!, null!);
var dto = new MetaReportDTO
{
MetaReportId = 1,
MetaReportName = "Trial Balance",
Aspects = new List { new() { AspectName = "Type", AspectType = "textbox" } },
};
await Assert.ThrowsAsync(() => bll.SaveMetaReportDefinition(dto, Login(), CancellationToken.None));
}
[Fact]
public async Task GetMetaReport_UnknownId_ReturnsNull()
{
var dal = new Mock();
dal.Setup(x => x.GetMetaReport(999, It.IsAny(), It.IsAny()))
.ReturnsAsync((MetaReportDTO?)null);
var bll = new MetaReportBLL(dal.Object, null!, null!);
var result = await bll.GetMetaReport(999, Login(), CancellationToken.None);
Assert.Null(result);
}
[Fact]
public async Task GetMetaReport_AssemblesAggregate_ApplicabilityGroupedByOption_OptionsGroupedByAspect()
{
var dal = new Mock();
dal.Setup(x => x.GetMetaReport(55, It.IsAny(), It.IsAny()))
.ReturnsAsync(new MetaReportDTO { MetaReportId = 55, MetaReportName = "Trial Balance" });
dal.Setup(x => x.GetVariantsByMetaReportId(55, It.IsAny(), It.IsAny()))
.ReturnsAsync(new List
{
new() { VariantId = 1, VariantName = "TB AsOn Account" },
new() { VariantId = 2, VariantName = "TB AsOn SubAccount" },
});
dal.Setup(x => x.GetAspectsByMetaReportId(55, It.IsAny(), It.IsAny()))
.ReturnsAsync(new List
{
new() { AspectId = 10, AspectName = "Type" },
new() { AspectId = 11, AspectName = "Level" },
});
dal.Setup(x => x.GetOptionsByAspectIds(It.Is>(ids => ids.Contains(10) && ids.Contains(11)), It.IsAny(), It.IsAny()))
.ReturnsAsync(new List
{
new() { OptionId = 100, AspectId = 10, Label = "AsOn" },
new() { OptionId = 101, AspectId = 11, Label = "Account" },
});
dal.Setup(x => x.GetApplicableVariantsByOptionIds(It.Is>(ids => ids.Contains(100) && ids.Contains(101)), It.IsAny(), It.IsAny()))
.ReturnsAsync(new List
{
new() { OptionId = 100, VariantId = 1 },
new() { OptionId = 100, VariantId = 2 },
new() { OptionId = 101, VariantId = 1 },
// OptionId 101 -> VariantId 2 deliberately absent, proving "not every option
// links to every variant" survives the grouping.
});
var bll = new MetaReportBLL(dal.Object, null!, null!);
var result = await bll.GetMetaReport(55, Login(), CancellationToken.None);
Assert.NotNull(result);
Assert.Equal(2, result!.Aspects.Count);
var typeAspect = result.Aspects.Single(a => a.AspectId == 10);
var typeOption = Assert.Single(typeAspect.Options);
Assert.Equal(new[] { 1, 2 }, typeOption.ApplicableVariantIds.OrderBy(i => i));
var levelAspect = result.Aspects.Single(a => a.AspectId == 11);
var levelOption = Assert.Single(levelAspect.Options);
Assert.Equal(new[] { 1 }, levelOption.ApplicableVariantIds);
}
}