using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using FrameworkBLL.MenuGroup;
using FrameworkBLL.MenuSet;
using FrameworkDAL.CustomCode.MenuSet;
using FrameworkDAL.DTO.MenuSet;
using FrameworkDAL.Query.MenuSet;
using GB5Shared.DTO.Framework.Login;
using GB5Shared.QueryExecutor;
using Moq;
using Xunit;
namespace FrameworkTests;
///
/// MenuSetBLL.SaveMenuSetDetail closes the gap MenuGroupBLL.ValidateMenuGroupVisibility was
/// originally built with no caller — this is the one place a MENUGROUPID re-filing override
/// is actually enforced. AutoNumber/KeyInvalidate are passed null! in the rejection test since
/// that path throws before either is touched (fail-fast, before any DAL write).
///
public class MenuSetBLLVisibilityTests
{
private static LoginDTO Login() => new() { ClientId = -1, UserId = -1 };
[Fact]
public async Task SaveMenuSetDetail_MenuGroupIdTargetsAnotherSetsPrivateGroup_RejectsBeforeAnyDalWrite()
{
var dal = new Mock();
var menuGroupBLL = new Mock();
menuGroupBLL
.Setup(x => x.ValidateMenuGroupVisibility(10, 55, It.IsAny(), It.IsAny()))
.ThrowsAsync(new ArgumentException("MenuGroup 10 is private to another MenuSet and is not assignable here."));
var bll = new MenuSetBLL(dal.Object, menuGroupBLL.Object, null!, null!);
var details = new List { new() { MenuSetDetailId = 1, MenuId = 501, MenuGroupId = 10 } };
await Assert.ThrowsAsync(() => bll.SaveMenuSetDetail(55, details, Login(), CancellationToken.None));
dal.Verify(x => x.SaveMenuSetDetail(It.IsAny(), It.IsAny>(), It.IsAny()), Times.Never);
}
[Fact]
public async Task SaveMenuSetDetail_NoMenuGroupIdOverrides_SkipsVisibilityCheckEntirely()
{
var dal = new Mock();
var menuGroupBLL = new Mock();
var bll = new MenuSetBLL(dal.Object, menuGroupBLL.Object, null!, null!);
// No MenuGroupId set on any row — SaveMenuSetDetail would NPE on _keyInvalidate if it
// reached the cache-invalidation call, so this test only proves the validation loop
// is correctly skipped when there's nothing to validate (asserted via Verify(Never)
// before the DAL/cache call would otherwise be reached).
var details = new List { new() { MenuSetDetailId = 1, MenuId = 501 } };
// Exercise just the validation-loop portion of the contract via a DAL that throws,
// to prove control reaches the DAL call without any visibility check firing first.
dal.Setup(x => x.SaveMenuSetDetail(55, It.IsAny>(), It.IsAny()))
.ThrowsAsync(new InvalidOperationException("stop-here-marker"));
var ex = await Assert.ThrowsAsync(
() => bll.SaveMenuSetDetail(55, details, Login(), CancellationToken.None));
Assert.Equal("stop-here-marker", ex.Message);
menuGroupBLL.Verify(x => x.ValidateMenuGroupVisibility(
It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Never);
}
}
///
/// MenuSetDAL.SaveMenuSetDetail ports the same diff-save shape as
/// MenuGroupDAL.SaveMenuGroupDetail for MMENUSETDETAIL, via a mocked IQueryExecutor.
///
public class MenuSetDetailDaoDiffSaveTests
{
private static LoginDTO Login() => new() { ClientId = -1, UserId = -1 };
[Fact]
public async Task SaveMenuSetDetail_MixOfExistingAndOmitted_BuildsDeleteNotInPlusUpsertStatements()
{
List<(string sql, object param)>? captured = null;
var qe = new Mock();
qe.Setup(x => x.ExecuteInTransactionAsync(It.IsAny(), It.IsAny>()))
.Callback>((_, statements) => captured = statements.ToList())
.Returns(Task.CompletedTask);
var dal = new MenuSetDAL(qe.Object);
// MenuSetDetailId 30 survives (update); 40 is omitted from this save (must be deleted).
var details = new List { new() { MenuSetDetailId = 30, MenuId = 501 } };
await dal.SaveMenuSetDetail(menuSetId: 55, details, Login());
Assert.NotNull(captured);
var deleteStatement = Assert.Single(captured!, s => s.sql == MenuSetQB.DELETE_MENUSETDETAIL_NOT_IN);
var survivingIds = ((System.Collections.IEnumerable)deleteStatement.param.GetType().GetProperty("SurvivingIds")!.GetValue(deleteStatement.param)!)
.Cast().ToList();
Assert.Equal(new[] { 30 }, survivingIds);
Assert.Contains(captured!, s => s.sql == MenuSetQB.UPDATE_MENUSETDETAIL);
}
[Fact]
public async Task SaveMenuSetDetail_EmptyList_DeletesAllExistingOverrides()
{
List<(string sql, object param)>? captured = null;
var qe = new Mock();
qe.Setup(x => x.ExecuteInTransactionAsync(It.IsAny(), It.IsAny>()))
.Callback>((_, statements) => captured = statements.ToList())
.Returns(Task.CompletedTask);
var dal = new MenuSetDAL(qe.Object);
await dal.SaveMenuSetDetail(menuSetId: 55, new List(), Login());
var only = Assert.Single(captured!);
Assert.Equal(MenuSetQB.DELETE_MENUSETDETAIL_BY_SET, only.sql);
}
}