diff --git "a/Homeworks/04 \320\224\320\276\320\261\320\260\320\262\320\273\320\265\320\275\320\270\320\265 \321\216\320\275\320\270\321\202 \321\202\320\265\321\201\321\202\320\276\320\262/src/PromoCodeFactory.UnitTests/PromoCodeFactory.UnitTests.csproj" "b/Homeworks/04 \320\224\320\276\320\261\320\260\320\262\320\273\320\265\320\275\320\270\320\265 \321\216\320\275\320\270\321\202 \321\202\320\265\321\201\321\202\320\276\320\262/src/PromoCodeFactory.UnitTests/PromoCodeFactory.UnitTests.csproj" index 3e205e938..59a2e3622 100644 --- "a/Homeworks/04 \320\224\320\276\320\261\320\260\320\262\320\273\320\265\320\275\320\270\320\265 \321\216\320\275\320\270\321\202 \321\202\320\265\321\201\321\202\320\276\320\262/src/PromoCodeFactory.UnitTests/PromoCodeFactory.UnitTests.csproj" +++ "b/Homeworks/04 \320\224\320\276\320\261\320\260\320\262\320\273\320\265\320\275\320\270\320\265 \321\216\320\275\320\270\321\202 \321\202\320\265\321\201\321\202\320\276\320\262/src/PromoCodeFactory.UnitTests/PromoCodeFactory.UnitTests.csproj" @@ -14,10 +14,14 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive - + - + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + diff --git "a/Homeworks/04 \320\224\320\276\320\261\320\260\320\262\320\273\320\265\320\275\320\270\320\265 \321\216\320\275\320\270\321\202 \321\202\320\265\321\201\321\202\320\276\320\262/src/PromoCodeFactory.UnitTests/WebHost/Controllers/Partners/SetLimitTests.cs" "b/Homeworks/04 \320\224\320\276\320\261\320\260\320\262\320\273\320\265\320\275\320\270\320\265 \321\216\320\275\320\270\321\202 \321\202\320\265\321\201\321\202\320\276\320\262/src/PromoCodeFactory.UnitTests/WebHost/Controllers/Partners/SetLimitTests.cs" index c84e0d8ac..f6b25bf61 100644 --- "a/Homeworks/04 \320\224\320\276\320\261\320\260\320\262\320\273\320\265\320\275\320\270\320\265 \321\216\320\275\320\270\321\202 \321\202\320\265\321\201\321\202\320\276\320\262/src/PromoCodeFactory.UnitTests/WebHost/Controllers/Partners/SetLimitTests.cs" +++ "b/Homeworks/04 \320\224\320\276\320\261\320\260\320\262\320\273\320\265\320\275\320\270\320\265 \321\216\320\275\320\270\321\202 \321\202\320\265\321\201\321\202\320\276\320\262/src/PromoCodeFactory.UnitTests/WebHost/Controllers/Partners/SetLimitTests.cs" @@ -1,29 +1,201 @@ +using AwesomeAssertions; +using Microsoft.AspNetCore.Mvc; +using Moq; +using PromoCodeFactory.Core.Abstractions.Repositories; +using PromoCodeFactory.Core.Domain.Administration; +using PromoCodeFactory.Core.Domain.PromoCodeManagement; +using PromoCodeFactory.Core.Exceptions; +using PromoCodeFactory.WebHost.Controllers; +using PromoCodeFactory.WebHost.Models.Partners; +using Soenneker.Utils.AutoBogus; + namespace PromoCodeFactory.UnitTests.WebHost.Controllers.Partners; public class SetLimitTests { + private readonly Mock> _partnersRepositoryMock; + private readonly Mock> _partnerLimitsRepositoryMock; + private readonly PartnersController _partnersController; + + public SetLimitTests() + { + _partnersRepositoryMock = new Mock>(); + _partnerLimitsRepositoryMock = new Mock>(); + + _partnersController = new PartnersController(_partnersRepositoryMock.Object, _partnerLimitsRepositoryMock.Object); + } + [Fact] public async Task CreateLimit_WhenPartnerNotFound_ReturnsNotFound() { + //Arrange + var partnerId = Guid.NewGuid(); + var request = new PartnerPromoCodeLimitCreateRequest(DateTimeOffset.UtcNow.AddDays(1), 100); + _partnersRepositoryMock.Setup(x => x.GetById(partnerId, true, It.IsAny())) + .ReturnsAsync((Partner?)null); + + //Act + var result = await _partnersController.CreateLimit(partnerId, request, CancellationToken.None); + + //Assert + var notFoundResult = result.Result.Should().BeOfType().Which; + notFoundResult.Value.Should().BeOfType(); + var problemDetails = notFoundResult.Value as ProblemDetails; + + problemDetails!.Title.Should().Be("Partner not found"); + problemDetails.Detail.Should().Be($"Partner with Id {partnerId} not found."); } [Fact] public async Task CreateLimit_WhenPartnerBlocked_ReturnsUnprocessableEntity() { + //Arrange + var partnerId = Guid.NewGuid(); + var partner = CreatePartner(partnerId, false); + var request = new PartnerPromoCodeLimitCreateRequest(DateTimeOffset.UtcNow.AddDays(1), 100); + _partnersRepositoryMock.Setup(x => x.GetById(partnerId, true, It.IsAny())) + .ReturnsAsync(partner); + + //Act + var result = await _partnersController.CreateLimit(partnerId, request, CancellationToken.None); + + //Assert + var unprocessableResult = result.Result.Should().BeOfType().Which; + unprocessableResult.Value.Should().BeOfType(); + var problemDetails = unprocessableResult.Value as ProblemDetails; + + problemDetails!.Title.Should().Be("Partner blocked"); + problemDetails.Detail.Should().Be($"Cannot create limit for a blocked partner."); } [Fact] public async Task CreateLimit_WhenValidRequest_ReturnsCreatedAndAddsLimit() { + //Arrange + const int PROMO_CODE_LIMIT = 100; + + var partnerId = Guid.NewGuid(); + var partner = CreatePartner(partnerId, true); + var request = new PartnerPromoCodeLimitCreateRequest(DateTimeOffset.UtcNow.AddDays(1), PROMO_CODE_LIMIT); + _partnersRepositoryMock.Setup(x => x.GetById(partnerId, true, It.IsAny())) + .ReturnsAsync(partner); + + //Act + var result = await _partnersController.CreateLimit(partnerId, request, CancellationToken.None); + + //Assert + var createdResult = result.Result.Should().BeOfType().Which; + createdResult.Value.Should().BeOfType(); + var response = createdResult.Value as PartnerPromoCodeLimitResponse; + + response!.Limit.Should().Be(100); + response.CreatedAt.Should().BeCloseTo(DateTimeOffset.UtcNow, TimeSpan.FromSeconds(5)); + _partnerLimitsRepositoryMock.Verify( + x => x.Add(It.Is(l => l.Limit == PROMO_CODE_LIMIT && l.Partner.Id == partnerId), It.IsAny()), + Times.Once); } [Fact] public async Task CreateLimit_WhenValidRequestWithActiveLimits_CancelsOldLimitsAndAddsNew() { + //Arrange + const int PROMO_CODE_LIMIT = 100; + + var partnerId = Guid.NewGuid(); + var limitId = Guid.NewGuid(); + var partner = CreatePartnerWithLimit(partnerId, limitId, isActive: true); + + var request = new PartnerPromoCodeLimitCreateRequest(DateTimeOffset.UtcNow.AddDays(1), PROMO_CODE_LIMIT); + _partnersRepositoryMock.Setup(x => x.GetById(partnerId, true, It.IsAny())) + .ReturnsAsync(partner); + + var oldLimit = partner.PartnerLimits.First(); + + //Act + var result = await _partnersController.CreateLimit(partnerId, request, CancellationToken.None); + + //Assert + var createdResult = result.Result.Should().BeOfType().Which; + createdResult.Value.Should().BeOfType(); + var response = createdResult.Value as PartnerPromoCodeLimitResponse; + + oldLimit.CanceledAt.Should().NotBeNull(); + var newLimit = result.Value; + + _partnersRepositoryMock.Verify( + x => x.Update(It.Is(p => p.PartnerLimits.Any(l => l.Id == oldLimit.Id)), It.IsAny()), + Times.Once); + _partnerLimitsRepositoryMock.Verify( + x => x.Add(It.Is(l => l.Id == response.Id), It.IsAny()), + Times.Once); } [Fact] public async Task CreateLimit_WhenUpdateThrowsEntityNotFoundException_ReturnsNotFound() { + //Arrange + var partnerId = Guid.NewGuid(); + var partner = CreatePartnerWithLimit(partnerId, Guid.NewGuid(), isActive: true); + var request = new PartnerPromoCodeLimitCreateRequest(DateTimeOffset.UtcNow.AddDays(1), 100); + _partnersRepositoryMock.Setup(x => x.GetById(partnerId, true, It.IsAny())) + .ReturnsAsync(partner); + _partnersRepositoryMock.Setup(x => x.Update(It.IsAny(), It.IsAny())) + .ThrowsAsync(new EntityNotFoundException(typeof(Partner), partnerId)); + + //Act + var result = await _partnersController.CreateLimit(partnerId, request, CancellationToken.None); + + //Assert + result.Result.Should().BeOfType(); + } + + private static Partner CreatePartner( + Guid partnerId, + bool isActive) + { + var partner = new AutoFaker() + .RuleFor(p => p.Id, _ => partnerId) + .RuleFor(p => p.IsActive, _ => isActive) + .RuleFor(p => p.PartnerLimits, _ => []) + .Generate(); + partner.Manager = new AutoFaker().Generate(); + return partner; + } + + private static Partner CreatePartnerWithLimit( + Guid partnerId, + Guid limitId, + bool isActive, + DateTimeOffset? canceledAt = null) + { + var role = new AutoFaker() + .RuleFor(r => r.Id, _ => Guid.NewGuid()) + .Generate(); + + var employee = new AutoFaker() + .RuleFor(e => e.Id, _ => Guid.NewGuid()) + .RuleFor(e => e.Role, role) + .Generate(); + + var limits = new List(); + + var partner = new AutoFaker() + .RuleFor(p => p.Id, _ => partnerId) + .RuleFor(p => p.IsActive, _ => isActive) + .RuleFor(p => p.Manager, employee) + .RuleFor(p => p.PartnerLimits, limits) + .Generate(); + + var limit = new AutoFaker() + .RuleFor(l => l.Id, _ => limitId) + .RuleFor(l => l.Partner, partner) + .RuleFor(l => l.CanceledAt, _ => canceledAt) + .RuleFor(l => l.CreatedAt, _ => DateTimeOffset.UtcNow.AddDays(-1)) + .RuleFor(l => l.EndAt, _ => DateTimeOffset.UtcNow.AddDays(30)) + .RuleFor(l => l.Limit, 2) + .Generate(); + + limits.Add(limit); + return partner; } } diff --git "a/Homeworks/04 \320\224\320\276\320\261\320\260\320\262\320\273\320\265\320\275\320\270\320\265 \321\216\320\275\320\270\321\202 \321\202\320\265\321\201\321\202\320\276\320\262/src/PromoCodeFactory.UnitTests/WebHost/Controllers/PromoCodes/CreateTests.cs" "b/Homeworks/04 \320\224\320\276\320\261\320\260\320\262\320\273\320\265\320\275\320\270\320\265 \321\216\320\275\320\270\321\202 \321\202\320\265\321\201\321\202\320\276\320\262/src/PromoCodeFactory.UnitTests/WebHost/Controllers/PromoCodes/CreateTests.cs" index 8f66ae5c3..45f01459d 100644 --- "a/Homeworks/04 \320\224\320\276\320\261\320\260\320\262\320\273\320\265\320\275\320\270\320\265 \321\216\320\275\320\270\321\202 \321\202\320\265\321\201\321\202\320\276\320\262/src/PromoCodeFactory.UnitTests/WebHost/Controllers/PromoCodes/CreateTests.cs" +++ "b/Homeworks/04 \320\224\320\276\320\261\320\260\320\262\320\273\320\265\320\275\320\270\320\265 \321\216\320\275\320\270\321\202 \321\202\320\265\321\201\321\202\320\276\320\262/src/PromoCodeFactory.UnitTests/WebHost/Controllers/PromoCodes/CreateTests.cs" @@ -1,29 +1,201 @@ +using System.Linq.Expressions; +using AwesomeAssertions; +using Microsoft.AspNetCore.Mvc; +using Moq; +using PromoCodeFactory.Core.Abstractions.Repositories; +using PromoCodeFactory.Core.Domain.Administration; +using PromoCodeFactory.Core.Domain.PromoCodeManagement; +using PromoCodeFactory.WebHost.Controllers; +using PromoCodeFactory.WebHost.Models.PromoCodes; +using Soenneker.Utils.AutoBogus; + namespace PromoCodeFactory.UnitTests.WebHost.Controllers.PromoCodes; public class CreateTests { + private readonly Mock> _partnersRepositoryMock; + private readonly Mock> _promoCodeRepositoryMock; + private readonly Mock> _customerRepositoryMock; + private readonly Mock> _preferenceRepositoryMock; + private readonly Mock> _customerPromoCodeRepositoryMock; + private readonly PromoCodesController _promoCodesController; + + public CreateTests() + { + _partnersRepositoryMock = new Mock>(); + _promoCodeRepositoryMock = new Mock>(); + _customerRepositoryMock = new Mock>(); + _preferenceRepositoryMock= new Mock>(); + _customerPromoCodeRepositoryMock = new Mock>(); + + _promoCodesController = new PromoCodesController(_promoCodeRepositoryMock.Object, _customerRepositoryMock.Object, _customerPromoCodeRepositoryMock.Object, _partnersRepositoryMock.Object, _preferenceRepositoryMock.Object); + } + + [Fact] public async Task Create_WhenPartnerNotFound_ReturnsNotFound() { + //Arrange + var partnerId = Guid.NewGuid(); + var request = new PromoCodeCreateRequest("Code", "ServiceInfo", partnerId, DateTime.Now, DateTime.Now, new Guid()); + _partnersRepositoryMock.Setup(x => x.GetById(partnerId, true, It.IsAny())) + .ReturnsAsync((Partner?)null); + + //Act + var result = await _promoCodesController.Create(request, CancellationToken.None); + + //Assert + var notFoundResult = result.Result.Should().BeOfType().Which; + notFoundResult.Value.Should().BeOfType(); + var problemDetails = notFoundResult.Value as ProblemDetails; + + problemDetails!.Title.Should().Be("Partner not found"); + problemDetails.Detail.Should().Be($"Partner with Id {partnerId} not found."); } [Fact] public async Task Create_WhenPreferenceNotFound_ReturnsNotFound() { + //Arrange + var partnerId = Guid.NewGuid(); + var preferenceId = Guid.NewGuid(); + var partner = CreatePartner(partnerId, true); + var request = new PromoCodeCreateRequest("CODE", "ServiceInfo", partnerId, DateTime.UtcNow.AddDays(1), DateTime.UtcNow.AddDays(30), preferenceId); + _partnersRepositoryMock.Setup(x => x.GetById(partnerId, true, It.IsAny())) + .ReturnsAsync(partner); + _preferenceRepositoryMock.Setup(x => x.GetById(preferenceId, false, It.IsAny())) + .ReturnsAsync((Preference?)null); + + //Act + var result = await _promoCodesController.Create(request, CancellationToken.None); + + //Assert + var notFoundResult = result.Result.Should().BeOfType().Which; + notFoundResult.Value.Should().BeOfType(); + var problemDetails = notFoundResult.Value as ProblemDetails; + problemDetails!.Title.Should().Be("Preference not found"); + problemDetails.Detail.Should().Be($"Preference with Id {preferenceId} not found."); } [Fact] public async Task Create_WhenNoActiveLimit_ReturnsUnprocessableEntity() { + //Arrange + var partnerId = Guid.NewGuid(); + var preferenceId = Guid.NewGuid(); + var partner = CreatePartner(partnerId, true); + var preference = CreatePreference(preferenceId); + var request = new PromoCodeCreateRequest("CODE", "ServiceInfo", partnerId, DateTime.UtcNow.AddDays(1), DateTime.UtcNow.AddDays(30), preferenceId); + _partnersRepositoryMock.Setup(x => x.GetById(partnerId, true, It.IsAny())) + .ReturnsAsync(partner); + _preferenceRepositoryMock.Setup(x => x.GetById(preferenceId, false, It.IsAny())) + .ReturnsAsync(preference); + + //Act + var result = await _promoCodesController.Create(request, CancellationToken.None); + + //Assert + var unprocessableResult = result.Result.Should().BeOfType().Which; + unprocessableResult.StatusCode.Should().Be(422); + unprocessableResult.Value.Should().BeOfType(); + var problemDetails = unprocessableResult.Value as ProblemDetails; + problemDetails!.Title.Should().Be("No active limit"); + problemDetails.Detail.Should().Be("Partner has no active promo code limit."); } [Fact] public async Task Create_WhenLimitExceeded_ReturnsUnprocessableEntity() { + //Arrange + var partnerId = Guid.NewGuid(); + var preferenceId = Guid.NewGuid(); + var partner = CreatePartner(partnerId, true, limit: 1, issuedCount: 1); + var preference = CreatePreference(preferenceId); + var request = new PromoCodeCreateRequest("CODE", "ServiceInfo", partnerId, DateTime.UtcNow.AddDays(1), DateTime.UtcNow.AddDays(30), preferenceId); + _partnersRepositoryMock.Setup(x => x.GetById(partnerId, true, It.IsAny())) + .ReturnsAsync(partner); + _preferenceRepositoryMock.Setup(x => x.GetById(preferenceId, false, It.IsAny())) + .ReturnsAsync(preference); + + //Act + var result = await _promoCodesController.Create(request, CancellationToken.None); + + //Assert + var unprocessableResult = result.Result.Should().BeOfType().Which; + unprocessableResult.StatusCode.Should().Be(422); + unprocessableResult.Value.Should().BeOfType(); + var problemDetails = unprocessableResult.Value as ProblemDetails; + problemDetails!.Title.Should().Be("Limit exceeded"); + problemDetails.Detail.Should().Be($"Cannot create promo code. Limit would be exceeded (current: 1/1)."); } [Fact] public async Task Create_WhenValidRequest_ReturnsCreatedAndIncrementsIssuedCount() { + //Arrange + var partnerId = Guid.NewGuid(); + var preferenceId = Guid.NewGuid(); + var partner = CreatePartner(partnerId, true, limit: 100, issuedCount: 0); + var preference = CreatePreference(preferenceId); + var request = new PromoCodeCreateRequest("CODE", "ServiceInfo", partnerId, DateTime.UtcNow.AddDays(1), DateTime.UtcNow.AddDays(30), preferenceId); + _partnersRepositoryMock.Setup(x => x.GetById(partnerId, true, It.IsAny())) + .ReturnsAsync(partner); + _preferenceRepositoryMock.Setup(x => x.GetById(preferenceId, false, It.IsAny())) + .ReturnsAsync(preference); + _customerRepositoryMock.Setup(x => x.GetWhere(It.IsAny>>(), It.IsAny(), default)) + .ReturnsAsync(new List()); + + //Act + var result = await _promoCodesController.Create(request, CancellationToken.None); + + //Assert + var createdResult = result.Result.Should().BeOfType().Which; + createdResult.Value.Should().BeOfType(); + var response = createdResult.Value as PromoCodeShortResponse; + response!.Code.Should().Be("CODE"); + + _promoCodeRepositoryMock.Verify( + x => x.Add(It.IsAny(), It.IsAny()), + Times.Once); + _partnersRepositoryMock.Verify( + x => x.Update(It.Is(p => p.PartnerLimits.Any(l => l.IssuedCount == 1)), It.IsAny()), + Times.Once); + } + + private static Partner CreatePartner( + Guid partnerId, + bool isActive, + int limit = 0, + int issuedCount = 0) + { + var partner = new AutoFaker() + .RuleFor(p => p.Id, _ => partnerId) + .RuleFor(p => p.IsActive, _ => isActive) + .RuleFor(p => p.PartnerLimits, _ => []) + .Generate(); + partner.Manager = new AutoFaker().Generate(); + + if (limit > 0) + { + var partnerLimit = new PartnerPromoCodeLimit + { + Id = Guid.NewGuid(), + Partner = partner, + Limit = limit, + IssuedCount = issuedCount, + CreatedAt = DateTimeOffset.UtcNow.AddDays(-1), + EndAt = DateTimeOffset.UtcNow.AddDays(30) + }; + partner.PartnerLimits.Add(partnerLimit); + } + + return partner; + } + + private static Preference CreatePreference(Guid preferenceId) + { + return new AutoFaker() + .RuleFor(p => p.Id, _ => preferenceId) + .Generate(); } }