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