using System.Threading; using System.Threading.Tasks; using FrameworkBLL.Promotion; using FrameworkDAL.CustomCode.Promotion; using FrameworkDAL.DTO.Promotion; using GB5Shared.DTO.Framework.Login; using GB5Shared.Validation; using Microsoft.Extensions.Logging.Abstractions; using Moq; using Xunit; namespace FrameworkTests; /// /// PromotionCounterpartyBLL — the admin CRUD layer for MPROMOTIONCOUNTERPARTY that was previously /// missing entirely (only an internal, active-only read existed, used by PromotionPackageCrypto). /// Covers the RSA PEM validation (fail fast at save time, not at the first real encrypt attempt) /// and insert-vs-update decided by EnvironmentCode existence, since this table's PK is the natural /// key itself, not an AutoNumber ID. /// public class PromotionCounterpartyBLLTests { // A real, throwaway RSA public key — genuinely parses, not just a plausible-looking string. private const string ValidPem = """ -----BEGIN PUBLIC KEY----- MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuJW1kHlYeFIqtxwiP/vU DXGdBuC5EASKjqQ2rSoz1zPYTN/4GEQnNcFGAUCOVuv1JQFzb51B/OAQwTq5k0+e +hWoUd2dh6F+qM2Xb2T1KqgjRbR454k1fBrgdSP+ls5x7Qw/6iGhXb33HGVNRGnp +W8osWU+d45mTSKNEEKd6ExNrg+/P5wDi5FbA1x5v2Dg2QMiIk++DuQLqyf55H+A azdORGHQ5HWaQ/SDhf6qMVCLkNqt36lumM8fltc4M62+51+wvFeM70Lp2KiDZvk1 8EK1eVRtsJXB6fh5RdZSCvQFZ4OSeXabIjNAj3onofIY1xHCb2l9VAr3IM1jbJHh XwIDAQAB -----END PUBLIC KEY----- """; private static LoginDTO Login() => new() { ClientId = -1, UserId = 7 }; private static PromotionCounterpartyDTO CleanDto(string environmentCode = "DEVSYS") => new() { EnvironmentCode = environmentCode, DisplayName = "Central DEVSYS", PublicKeyPem = ValidPem, IsActive = true }; private static (Mock dal, Mock validation) Mocks() => (new Mock(), new Mock()); private static PromotionCounterpartyBLL NewBll((Mock dal, Mock validation) m) => new(m.dal.Object, m.validation.Object, NullLogger.Instance); [Fact] public async Task Save_FreshEnvironmentCode_Inserts() { var m = Mocks(); m.dal.Setup(d => d.ExistsByEnvironmentCode("DEVSYS", It.IsAny(), It.IsAny())) .ReturnsAsync(false); PromotionCounterpartyDTO? captured = null; m.dal.Setup(d => d.InsertCounterparty(It.IsAny(), It.IsAny(), It.IsAny())) .Callback((d, _, _) => captured = d) .ReturnsAsync(1); var bll = NewBll(m); var result = await bll.SavePromotionCounterparty(CleanDto(), Login(), CancellationToken.None); Assert.Contains("DEVSYS", result); Assert.NotNull(captured); Assert.Equal(7, captured!.CreatedById); m.dal.Verify(d => d.UpdateCounterparty(It.IsAny(), It.IsAny(), It.IsAny()), Times.Never); } [Fact] public async Task Save_ExistingEnvironmentCode_Updates() { var m = Mocks(); m.dal.Setup(d => d.ExistsByEnvironmentCode("DEVSYS", It.IsAny(), It.IsAny())) .ReturnsAsync(true); m.dal.Setup(d => d.UpdateCounterparty(It.IsAny(), It.IsAny(), It.IsAny())) .ReturnsAsync(1); var bll = NewBll(m); var result = await bll.SavePromotionCounterparty(CleanDto(), Login(), CancellationToken.None); m.dal.Verify(d => d.InsertCounterparty(It.IsAny(), It.IsAny(), It.IsAny()), Times.Never); m.dal.Verify(d => d.UpdateCounterparty(It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); } [Fact] public async Task Save_MalformedPublicKeyPem_ThrowsBeforeTouchingDal() { var dto = CleanDto(); dto.PublicKeyPem = "not a real PEM at all"; var m = Mocks(); var bll = NewBll(m); await Assert.ThrowsAsync(() => bll.SavePromotionCounterparty(dto, Login(), CancellationToken.None)); m.dal.Verify(d => d.InsertCounterparty(It.IsAny(), It.IsAny(), It.IsAny()), Times.Never); m.dal.Verify(d => d.UpdateCounterparty(It.IsAny(), It.IsAny(), It.IsAny()), Times.Never); } [Fact] public async Task Delete_CallsDalWithEnvironmentCode() { var m = Mocks(); m.dal.Setup(d => d.DeleteCounterparty("DEVSYS", It.IsAny(), It.IsAny())) .ReturnsAsync(1); var bll = NewBll(m); await bll.DeletePromotionCounterparty("DEVSYS", Login(), CancellationToken.None); m.dal.Verify(d => d.DeleteCounterparty("DEVSYS", It.IsAny(), It.IsAny()), Times.Once); } }