From 693af13ddd673b980439240e954221238d49dfab Mon Sep 17 00:00:00 2001 From: Goodman74 Date: Fri, 10 Apr 2026 21:15:36 +0300 Subject: [PATCH] test(hw04): add controller tests and extract shared test helpers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Удалён лишний foreach в PromoCodesController.cs - Рефакторинг тестов: выделен общий PartnerCreater - Добавлены тесты: - PartnersController.CreateLimit - PromoCodesController.Create - Добавлены shared helpers: - PromoCodeCreateRequestCreater - PreferenceCreater --- .../.editorconfig" | 3 + .../Controllers/Partners/CancelLimitTests.cs" | 46 +--- .../Controllers/Partners/CreateLimitTests.cs" | 209 ++++++++++++++++++ .../Controllers/Partners/SetLimitTests.cs" | 29 --- .../Partners/Shared/PartnerCreater.cs" | 71 ++++++ .../Partners/Shared/PreferenceCreater.cs" | 18 ++ .../Shared/PromoCodeCreateRequestCreater.cs" | 19 ++ .../Controllers/PromoCodes/CreateTests.cs" | 209 ++++++++++++++++++ .../Controllers/PromoCodesController.cs" | 5 - 9 files changed, 535 insertions(+), 74 deletions(-) create mode 100644 "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.DataAccess/.editorconfig" create mode 100644 "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/CreateLimitTests.cs" delete mode 100644 "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" create mode 100644 "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/Shared/PartnerCreater.cs" create mode 100644 "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/Shared/PreferenceCreater.cs" create mode 100644 "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/Shared/PromoCodeCreateRequestCreater.cs" 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.DataAccess/.editorconfig" "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.DataAccess/.editorconfig" new file mode 100644 index 000000000..adb3daf5a --- /dev/null +++ "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.DataAccess/.editorconfig" @@ -0,0 +1,3 @@ + +[*.cs] +csharp_style_namespace_declarations = block_scoped:silent 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/CancelLimitTests.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/CancelLimitTests.cs" index 71243b02f..caa1ccb63 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/CancelLimitTests.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/CancelLimitTests.cs" @@ -5,6 +5,7 @@ using PromoCodeFactory.Core.Domain.Administration; using PromoCodeFactory.Core.Domain.PromoCodeManagement; using PromoCodeFactory.Core.Exceptions; +using PromoCodeFactory.UnitTests.WebHost.Controllers.Partners.Shared; using PromoCodeFactory.WebHost.Controllers; using Soenneker.Utils.AutoBogus; @@ -50,7 +51,7 @@ public async Task CancelLimit_WhenPartnerBlocked_ReturnsUnprocessableEntity() // Arrange var partnerId = Guid.NewGuid(); var limitId = Guid.NewGuid(); - var partner = CreatePartnerWithLimit(partnerId, limitId, isActive: false); + var partner = PartnerCreater.CreateWithOneActiveLimit(partnerId, limitId, isActive: false); _partnersRepositoryMock .Setup(r => r.GetById(partnerId, true, It.IsAny())) @@ -74,7 +75,7 @@ public async Task CancelLimit_WhenLimitNotFound_ReturnsNotFound() var partnerId = Guid.NewGuid(); var limitId = Guid.NewGuid(); var otherLimitId = Guid.NewGuid(); - var partner = CreatePartnerWithLimit(partnerId, otherLimitId, isActive: true); + var partner = PartnerCreater.CreateWithOneActiveLimit(partnerId, otherLimitId, isActive: true); _partnersRepositoryMock .Setup(r => r.GetById(partnerId, true, It.IsAny())) @@ -97,7 +98,7 @@ public async Task CancelLimit_WhenLimitAlreadyCanceled_ReturnsUnprocessableEntit // Arrange var partnerId = Guid.NewGuid(); var limitId = Guid.NewGuid(); - var partner = CreatePartnerWithLimit(partnerId, limitId, isActive: true, canceledAt: DateTimeOffset.UtcNow); + var partner = PartnerCreater.CreateWithCanceledLimit(partnerId, limitId, isActive: true, canceledAt: DateTimeOffset.UtcNow); _partnersRepositoryMock .Setup(r => r.GetById(partnerId, true, It.IsAny())) @@ -120,7 +121,7 @@ public async Task CancelLimit_WhenValidRequest_ReturnsNoContentAndUpdatesPartner // Arrange var partnerId = Guid.NewGuid(); var limitId = Guid.NewGuid(); - var partner = CreatePartnerWithLimit(partnerId, limitId, isActive: true); + var partner = PartnerCreater.CreateWithOneActiveLimit(partnerId, limitId, isActive: true); _partnersRepositoryMock .Setup(r => r.GetById(partnerId, true, It.IsAny())) @@ -147,7 +148,7 @@ public async Task CancelLimit_WhenUpdateThrowsEntityNotFoundException_ReturnsNot // Arrange var partnerId = Guid.NewGuid(); var limitId = Guid.NewGuid(); - var partner = CreatePartnerWithLimit(partnerId, limitId, isActive: true); + var partner = PartnerCreater.CreateWithOneActiveLimit(partnerId, limitId, isActive: true); _partnersRepositoryMock .Setup(r => r.GetById(partnerId, true, It.IsAny())) @@ -163,39 +164,4 @@ public async Task CancelLimit_WhenUpdateThrowsEntityNotFoundException_ReturnsNot // Assert result.Should().BeOfType(); } - - 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)) - .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/Partners/CreateLimitTests.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/CreateLimitTests.cs" new file mode 100644 index 000000000..34fee3570 --- /dev/null +++ "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/CreateLimitTests.cs" @@ -0,0 +1,209 @@ +using AwesomeAssertions; +using Bogus; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Routing; +using Moq; +using PromoCodeFactory.Core.Abstractions.Repositories; +using PromoCodeFactory.Core.Domain.PromoCodeManagement; +using PromoCodeFactory.Core.Exceptions; +using PromoCodeFactory.UnitTests.WebHost.Controllers.Partners.Shared; +using PromoCodeFactory.WebHost.Controllers; +using PromoCodeFactory.WebHost.Mapping; +using PromoCodeFactory.WebHost.Models.Partners; + +namespace PromoCodeFactory.UnitTests.WebHost.Controllers.Partners; + +public class CreateLimitTests +{ + private readonly Mock> _partnersRepositoryMock; + private readonly Mock> _partnerLimitsRepositoryMock; + private readonly CancellationToken _ctNone = CancellationToken.None; + private readonly PartnersController _controller; + + public CreateLimitTests() + { + _partnersRepositoryMock = new Mock>(); + _partnerLimitsRepositoryMock = new Mock>(); + _controller = new PartnersController(_partnersRepositoryMock.Object, _partnerLimitsRepositoryMock.Object); + } + + [Fact] + public async Task CreateLimit_WhenPartnerNotFound_ReturnsNotFound() + { + // Arrange + var partnerId = Guid.NewGuid(); + const int limit = 1; + var request = new PartnerPromoCodeLimitCreateRequest(DateTime.UtcNow, limit); + + Partner? returnedPartner = null; + + _partnersRepositoryMock + .Setup(x => x.GetById(partnerId, withIncludes: true, _ctNone)) + .ReturnsAsync(returnedPartner); + + // Act + var result = await _controller.CreateLimit(partnerId, request, _ctNone); + + // Assert + var notFound = result.Result.Should().BeOfType().Subject; + var problemDetails = notFound.Value.Should().BeOfType().Subject; + problemDetails.Title.Should().Be("Partner not found"); + + _partnersRepositoryMock.Verify(x => x.Update(It.IsAny(), _ctNone), Times.Never); + _partnerLimitsRepositoryMock.Verify(x => x.Add(It.IsAny(),_ctNone), Times.Never); + } + + [Fact] + public async Task CreateLimit_WhenPartnerBlocked_ReturnsUnprocessableEntity() + { + // Arrange + var partnerId = Guid.NewGuid(); + const int limit = 1; + var request = new PartnerPromoCodeLimitCreateRequest(DateTime.UtcNow, limit); + + var returnedPartner = PartnerCreater.Create(partnerId, isActive: false); + + _partnersRepositoryMock + .Setup(x => x.GetById(partnerId, withIncludes: true, _ctNone)) + .ReturnsAsync(returnedPartner); + + // Act + var result = await _controller.CreateLimit(partnerId, request, _ctNone); + + // Assert + var unprocessable = result.Result.Should().BeOfType().Subject; + var problemDetails = unprocessable.Value.Should().BeOfType().Subject; + problemDetails.Title.Should().Be("Partner blocked"); + + _partnersRepositoryMock.Verify(x => x.Update(It.IsAny(), _ctNone), Times.Never); + _partnerLimitsRepositoryMock.Verify(x => x.Add(It.IsAny(), _ctNone), Times.Never); + } + + [Fact] + public async Task CreateLimit_WhenValidRequest_ReturnsCreatedAndAddsLimit() + { + // Arrange + var partnerId = Guid.NewGuid(); + var newLimit = new Randomizer().Int(1, 10); + var endAtLimits = DateTime.UtcNow.AddDays(1); + + var request = new PartnerPromoCodeLimitCreateRequest(endAtLimits, newLimit); + + var returnedPartnerWithoutActiveLimits = PartnerCreater.Create(partnerId, isActive: true); + + PartnerPromoCodeLimit? newPartnerPromoCodeLimit = null; + + _partnersRepositoryMock + .Setup(x => x.GetById(partnerId, withIncludes: true, _ctNone)) + .ReturnsAsync(returnedPartnerWithoutActiveLimits); + + _partnerLimitsRepositoryMock + .Setup(x => x.Add(It.IsAny(), _ctNone)) + .Callback((limit, _) => newPartnerPromoCodeLimit = limit); + + // Act + var result = await _controller.CreateLimit(partnerId, request, _ctNone); + + // Assert + var created = result.Result.Should().BeOfType().Subject; + + created.ActionName.Should().Be("GetLimit"); + + newPartnerPromoCodeLimit.Should().NotBeNull(); + newPartnerPromoCodeLimit.Limit.Should().Be(newLimit); + newPartnerPromoCodeLimit.EndAt.Should().Be(endAtLimits); + + var routeDict = created.RouteValues.Should().BeOfType().Subject; + routeDict["partnerId"].Should().Be(partnerId); + routeDict["limitId"].Should().Be(newPartnerPromoCodeLimit.Id); + + var obj = created.Value.Should().BeOfType().Subject; + obj.Should().BeEquivalentTo(PartnersMapper.ToPartnerPromoCodeLimitResponse(newPartnerPromoCodeLimit)); + } + + [Fact] + public async Task CreateLimit_WhenValidRequestWithActiveLimit_CancelsOldLimitsAndAddsNew() + { + // Arrange + var partnerId = Guid.NewGuid(); + var limitId = Guid.NewGuid(); + var returnedPartnerWithActiveLimit = PartnerCreater.CreateWithOneActiveLimit(partnerId, limitId, isActive: true); + + var newLimit = new Randomizer().Int(1, 10); + var endAtLimits = DateTime.UtcNow.AddDays(1); + + var request = new PartnerPromoCodeLimitCreateRequest(endAtLimits, newLimit); + + PartnerPromoCodeLimit? newPartnerPromoCodeLimit = null; + Partner? updatedPartner = null; + + _partnersRepositoryMock + .Setup(x => x.GetById(partnerId, withIncludes: true, _ctNone)) + .ReturnsAsync(returnedPartnerWithActiveLimit); + + _partnersRepositoryMock + .Setup(x => x.Update(It.IsAny(), _ctNone)) + .Callback((partner, _) => updatedPartner = partner); + + _partnerLimitsRepositoryMock + .Setup(x => x.Add(It.IsAny(), _ctNone)) + .Callback((limit, _) => newPartnerPromoCodeLimit = limit); + + // Act + var result = await _controller.CreateLimit(partnerId, request, _ctNone); + + // Assert + updatedPartner.Should().NotBeNull(); + updatedPartner.PartnerLimits.Should().OnlyContain(l => l.CanceledAt != null); + + var created = result.Result.Should().BeOfType().Subject; + + created.ActionName.Should().Be("GetLimit"); + + newPartnerPromoCodeLimit.Should().NotBeNull(); + newPartnerPromoCodeLimit.Limit.Should().Be(newLimit); + newPartnerPromoCodeLimit.EndAt.Should().Be(endAtLimits); + newPartnerPromoCodeLimit!.CanceledAt.Should().BeNull(); + + var routeDict = created.RouteValues.Should().BeOfType().Subject; + routeDict["partnerId"].Should().Be(partnerId); + routeDict["limitId"].Should().Be(newPartnerPromoCodeLimit.Id); + + var obj = created.Value.Should().BeOfType().Subject; + obj.Should().BeEquivalentTo(PartnersMapper.ToPartnerPromoCodeLimitResponse(newPartnerPromoCodeLimit)); + + _partnersRepositoryMock.Verify(x => x.Update(It.IsAny(), _ctNone), Times.Once); + _partnerLimitsRepositoryMock.Verify(x => x.Add(It.IsAny(), _ctNone), Times.Once); + } + + [Fact] + public async Task CreateLimit_WhenUpdateThrowsEntityNotFoundException_ReturnsNotFound() + { + // Arrange + var partnerId = Guid.NewGuid(); + var limitId = Guid.NewGuid(); + var returnedPartnerWithActiveLimit = PartnerCreater.CreateWithOneActiveLimit(partnerId, limitId, isActive: true); + + var newLimit = new Randomizer().Int(1, 10); + var endAtLimits = DateTime.UtcNow.AddDays(1); + + var request = new PartnerPromoCodeLimitCreateRequest(endAtLimits, newLimit); + + _partnersRepositoryMock + .Setup(x => x.GetById(partnerId, withIncludes: true, _ctNone)) + .ReturnsAsync(returnedPartnerWithActiveLimit); + + _partnersRepositoryMock + .Setup(x => x.Update(It.IsAny(), _ctNone)) + .ThrowsAsync(new EntityNotFoundException(partnerId)); + + // Act + var result = await _controller.CreateLimit(partnerId, request, _ctNone); + + // Assert + result.Result.Should().BeOfType(); + + _partnersRepositoryMock.Verify(x => x.Update(It.IsAny(), _ctNone), Times.Once); + _partnerLimitsRepositoryMock.Verify(x => x.Add(It.IsAny(), _ctNone), Times.Never); + } +} 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" deleted file mode 100644 index c84e0d8ac..000000000 --- "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" +++ /dev/null @@ -1,29 +0,0 @@ -namespace PromoCodeFactory.UnitTests.WebHost.Controllers.Partners; - -public class SetLimitTests -{ - [Fact] - public async Task CreateLimit_WhenPartnerNotFound_ReturnsNotFound() - { - } - - [Fact] - public async Task CreateLimit_WhenPartnerBlocked_ReturnsUnprocessableEntity() - { - } - - [Fact] - public async Task CreateLimit_WhenValidRequest_ReturnsCreatedAndAddsLimit() - { - } - - [Fact] - public async Task CreateLimit_WhenValidRequestWithActiveLimits_CancelsOldLimitsAndAddsNew() - { - } - - [Fact] - public async Task CreateLimit_WhenUpdateThrowsEntityNotFoundException_ReturnsNotFound() - { - } -} 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/Shared/PartnerCreater.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/Shared/PartnerCreater.cs" new file mode 100644 index 000000000..e1f1e7ed3 --- /dev/null +++ "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/Shared/PartnerCreater.cs" @@ -0,0 +1,71 @@ +using Bogus; +using PromoCodeFactory.Core.Domain.Administration; +using PromoCodeFactory.Core.Domain.PromoCodeManagement; +using Soenneker.Utils.AutoBogus; + +namespace PromoCodeFactory.UnitTests.WebHost.Controllers.Partners.Shared; + +internal static class PartnerCreater +{ + internal static Partner CreateWithOneActiveLimit(Guid partnerId, Guid limitId, bool isActive) + { + var limits = new List(); + var partner = Create(partnerId, isActive); + partner.PartnerLimits = limits; + + var limit = new AutoFaker() + .RuleFor(l => l.Id, _ => limitId) + .RuleFor(l => l.Partner, partner) + .RuleFor(l => l.CanceledAt, _ => null) + .RuleFor(l => l.CreatedAt, _ => DateTimeOffset.UtcNow.AddDays(-1)) + .RuleFor(l => l.EndAt, _ => DateTimeOffset.UtcNow.AddDays(30)) + .RuleFor(l => l.Limit, _ => new Randomizer().Number(100,200)) + .RuleFor(l => l.IssuedCount, _ => 0) + .Generate(); + + limits.Add(limit); + return partner; + } + + internal static Partner CreateWithCanceledLimit(Guid partnerId, Guid limitId, bool isActive, DateTimeOffset? canceledAt) + { + var partner = CreateWithOneActiveLimit(partnerId, limitId, isActive); + + partner.PartnerLimits.ElementAt(0).CanceledAt = canceledAt; + + return partner; + } + + + internal static Partner Create(Guid partnerId, bool isActive) + { + 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 partner = new AutoFaker() + .RuleFor(p => p.Id, _ => partnerId) + .RuleFor(p => p.IsActive, _ => isActive) + .RuleFor(p => p.Manager, employee) + .RuleFor(p => p.PartnerLimits, []) + .Generate(); + + return partner; + } + + internal static Partner? CreateWithLimitExceeded(Guid partnerId, Guid limitId) + { + var partner = CreateWithOneActiveLimit(partnerId, limitId, true); + var partnerPromoCodeLimit = partner.PartnerLimits.ElementAt(0); + + var limit = partnerPromoCodeLimit.Limit; + partnerPromoCodeLimit.IssuedCount = 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/Partners/Shared/PreferenceCreater.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/Shared/PreferenceCreater.cs" new file mode 100644 index 000000000..b8962d932 --- /dev/null +++ "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/Shared/PreferenceCreater.cs" @@ -0,0 +1,18 @@ +using PromoCodeFactory.Core.Domain.PromoCodeManagement; +using PromoCodeFactory.WebHost.Models.PromoCodes; +using Soenneker.Utils.AutoBogus; + +namespace PromoCodeFactory.UnitTests.WebHost.Controllers.Partners.Shared; + +internal static class PreferenceCreater +{ + internal static Preference CreateWithoutCustomers(Guid preferenceId) + { + var obj = new AutoFaker() + .RuleFor(r => r.Id, _ => preferenceId) + .RuleFor(r => r.Customers, _ => []) + .Generate(); + + return obj; + } +} 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/Shared/PromoCodeCreateRequestCreater.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/Shared/PromoCodeCreateRequestCreater.cs" new file mode 100644 index 000000000..44efb56e6 --- /dev/null +++ "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/Shared/PromoCodeCreateRequestCreater.cs" @@ -0,0 +1,19 @@ +using PromoCodeFactory.WebHost.Models.PromoCodes; +using Soenneker.Utils.AutoBogus; + +namespace PromoCodeFactory.UnitTests.WebHost.Controllers.Partners.Shared; + +internal static class PromoCodeCreateRequestCreater +{ + internal static PromoCodeCreateRequest Create(Guid partnerId, Guid preferenceId) + { + var request = new AutoFaker() + .RuleFor(r => r.PartnerId, _ => partnerId) + .RuleFor(r => r.PreferenceId, _ => preferenceId) + .RuleFor(x => x.BeginDate, f => DateTimeOffset.UtcNow.AddDays(f.Random.Int(1, 30))) + .RuleFor(x => x.EndDate, (f, x) => x.BeginDate.AddDays(f.Random.Int(1, 30))) + .Generate(); + + return request; + } +} 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..95a16fda7 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,238 @@ +using AwesomeAssertions; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Routing; +using Moq; +using PromoCodeFactory.Core.Abstractions.Repositories; +using PromoCodeFactory.Core.Domain.PromoCodeManagement; +using PromoCodeFactory.UnitTests.WebHost.Controllers.Partners.Shared; +using PromoCodeFactory.WebHost.Controllers; +using PromoCodeFactory.WebHost.Mapping; +using PromoCodeFactory.WebHost.Models.PromoCodes; +using System.Linq.Expressions; + namespace PromoCodeFactory.UnitTests.WebHost.Controllers.PromoCodes; public class CreateTests { + private readonly Mock> _partnersRepositoryMock; + private readonly Mock> _preferencesRepositoryMock; + private readonly Mock> _promoCodesRepositoryMock; + private readonly Mock> _customersRepositoryMock; + private readonly Mock> _customerPromoCodesRepositoryMock; + private readonly CancellationToken _ctNone = CancellationToken.None; + private readonly PromoCodesController _controller; + + public CreateTests() + { + _partnersRepositoryMock = new Mock>(); + _preferencesRepositoryMock = new Mock>(); + _customerPromoCodesRepositoryMock = new Mock>(); + _promoCodesRepositoryMock = new Mock>(); + _customersRepositoryMock = new Mock>(); + + _controller = new PromoCodesController(_promoCodesRepositoryMock.Object, _customersRepositoryMock.Object, + _customerPromoCodesRepositoryMock.Object, _partnersRepositoryMock.Object, + _preferencesRepositoryMock.Object); + } + [Fact] public async Task Create_WhenPartnerNotFound_ReturnsNotFound() { + // Arrange + var partnerId = Guid.NewGuid(); + var preferenceId = Guid.NewGuid(); + var request = PromoCodeCreateRequestCreater.Create(partnerId, preferenceId); + + Partner? returnedPartner = null; + + _partnersRepositoryMock + .Setup(x => x.GetById(partnerId, withIncludes: true, _ctNone)) + .ReturnsAsync(returnedPartner); + + // Act + var result = await _controller.Create(request, _ctNone); + + // Assert + var notFound = result.Result.Should().BeOfType().Subject; + var problemDetails = notFound.Value.Should().BeOfType().Subject; + problemDetails.Title.Should().Be("Partner not found"); + + _promoCodesRepositoryMock.Verify(x => x.Add(It.IsAny(), _ctNone), Times.Never); + _partnersRepositoryMock.Verify(x => x.Update(It.IsAny(), _ctNone), Times.Never); + _customersRepositoryMock.Verify(x => x.GetWhere(It.IsAny>>(), + withIncludes: false, ct: _ctNone), Times.Never); } [Fact] public async Task Create_WhenPreferenceNotFound_ReturnsNotFound() { + // Arrange + var partnerId = Guid.NewGuid(); + var preferenceId = Guid.NewGuid(); + var request = PromoCodeCreateRequestCreater.Create(partnerId, preferenceId); + + Partner? returnedPartner = PartnerCreater.Create(partnerId, isActive: true); + Preference? returnedPreference = null; + + _partnersRepositoryMock + .Setup(x => x.GetById(request.PartnerId, withIncludes: true, _ctNone)) + .ReturnsAsync(returnedPartner); + + _preferencesRepositoryMock + .Setup(x => x.GetById(request.PreferenceId, withIncludes: false, ct: _ctNone)) + .ReturnsAsync(returnedPreference); + + // Act + var result = await _controller.Create(request, _ctNone); + + // Assert + var notFound = result.Result.Should().BeOfType().Subject; + var problemDetails = notFound.Value.Should().BeOfType().Subject; + problemDetails.Title.Should().Be("Preference not found"); + + _promoCodesRepositoryMock.Verify(x => x.Add(It.IsAny(), _ctNone), Times.Never); + _partnersRepositoryMock.Verify(x => x.Update(It.IsAny(), _ctNone), Times.Never); + _customersRepositoryMock.Verify(x => x.GetWhere(It.IsAny>>(), + withIncludes: false, ct: _ctNone), Times.Never); } [Fact] public async Task Create_WhenNoActiveLimit_ReturnsUnprocessableEntity() { + // Arrange + var partnerId = Guid.NewGuid(); + var preferenceId = Guid.NewGuid(); + var request = PromoCodeCreateRequestCreater.Create(partnerId, preferenceId); + var limitId = Guid.NewGuid(); + Partner? returnedPartnerNoActiveLimit = PartnerCreater.CreateWithCanceledLimit(partnerId, limitId, isActive: true, + canceledAt: DateTimeOffset.UtcNow); + + Preference? returnedPreference = PreferenceCreater.CreateWithoutCustomers(preferenceId); + List customers = []; + + _partnersRepositoryMock + .Setup(x => x.GetById(request.PartnerId, withIncludes: true, _ctNone)) + .ReturnsAsync(returnedPartnerNoActiveLimit); + + _preferencesRepositoryMock + .Setup(x => x.GetById(request.PreferenceId, withIncludes: false, ct: _ctNone)) + .ReturnsAsync(returnedPreference); + + _customersRepositoryMock + .Setup(x => x.GetWhere(It.IsAny>>(), withIncludes: false, ct: _ctNone)) + .ReturnsAsync(customers); + + // Act + var result = await _controller.Create(request, _ctNone); + + // Assert + var objectResult = result.Result.Should().BeOfType().Subject; + objectResult.StatusCode.Should().Be(StatusCodes.Status422UnprocessableEntity); + + var problemDetails = objectResult.Value.Should().BeOfType().Subject; + problemDetails.Title.Should().Be("No active limit"); + + _promoCodesRepositoryMock.Verify(x => x.Add(It.IsAny(), _ctNone), Times.Never); + _partnersRepositoryMock.Verify(x => x.Update(It.IsAny(), _ctNone), Times.Never); } [Fact] public async Task Create_WhenLimitExceeded_ReturnsUnprocessableEntity() { + // Arrange + var partnerId = Guid.NewGuid(); + var preferenceId = Guid.NewGuid(); + var request = PromoCodeCreateRequestCreater.Create(partnerId, preferenceId); + var limitId = Guid.NewGuid(); + Partner? returnedPartnerWithLimitExceeded = PartnerCreater.CreateWithLimitExceeded(partnerId, limitId); + + Preference? returnedPreference = PreferenceCreater.CreateWithoutCustomers(preferenceId); + List customers = []; + + _partnersRepositoryMock + .Setup(x => x.GetById(request.PartnerId, withIncludes: true, _ctNone)) + .ReturnsAsync(returnedPartnerWithLimitExceeded); + + _preferencesRepositoryMock + .Setup(x => x.GetById(request.PreferenceId, withIncludes: false, ct: _ctNone)) + .ReturnsAsync(returnedPreference); + + _customersRepositoryMock + .Setup(x => x.GetWhere(It.IsAny>>(), withIncludes: false, ct: _ctNone)) + .ReturnsAsync(customers); + + // Act + var result = await _controller.Create(request, _ctNone); + + // Assert + var objectResult = result.Result.Should().BeOfType().Subject; + objectResult.StatusCode.Should().Be(StatusCodes.Status422UnprocessableEntity); + + var problemDetails = objectResult.Value.Should().BeOfType().Subject; + problemDetails.Title.Should().Be("Limit exceeded"); + + _promoCodesRepositoryMock.Verify(x => x.Add(It.IsAny(), _ctNone), Times.Never); + _partnersRepositoryMock.Verify(x => x.Update(It.IsAny(), _ctNone), Times.Never); } [Fact] public async Task Create_WhenValidRequest_ReturnsCreatedAndIncrementsIssuedCount() { + // Arrange + var partnerId = Guid.NewGuid(); + var preferenceId = Guid.NewGuid(); + var request = PromoCodeCreateRequestCreater.Create(partnerId, preferenceId); + var limitId = Guid.NewGuid(); + Partner? partnerWithOneActiveLimit = PartnerCreater.CreateWithOneActiveLimit(partnerId, limitId, true); + var oldValueIssuedCount = partnerWithOneActiveLimit.PartnerLimits.ElementAt(0).IssuedCount; + + Preference? returnedPreference = PreferenceCreater.CreateWithoutCustomers(preferenceId); + List customers = []; + + PromoCode? newPromoCode = null; + Partner? updatedPartnerWithLimit = null; + + _partnersRepositoryMock + .Setup(x => x.GetById(request.PartnerId, withIncludes: true, _ctNone)) + .ReturnsAsync(partnerWithOneActiveLimit); + + _preferencesRepositoryMock + .Setup(x => x.GetById(request.PreferenceId, withIncludes: false, ct: _ctNone)) + .ReturnsAsync(returnedPreference); + + _customersRepositoryMock + .Setup(x => x.GetWhere(It.IsAny>>(), withIncludes: false, ct: _ctNone)) + .ReturnsAsync(customers); + + _promoCodesRepositoryMock + .Setup(x => x.Add(It.IsAny(), _ctNone)) + .Callback((promoCode, _) => newPromoCode = promoCode); + + _partnersRepositoryMock + .Setup(x => x.Update(It.IsAny(), _ctNone)) + .Callback((partner, _) => updatedPartnerWithLimit = partner); + + // Act + var result = await _controller.Create(request, _ctNone); + + // Assert + newPromoCode.Should().NotBeNull(); + + updatedPartnerWithLimit.Should().NotBeNull(); + var newValueIssuedCount = updatedPartnerWithLimit.PartnerLimits.ElementAt(0).IssuedCount; + newValueIssuedCount.Should().Be(oldValueIssuedCount + 1); + + var created = result.Result.Should().BeOfType().Subject; + created.ActionName.Should().Be("GetById"); + + var routeDict = created.RouteValues.Should().BeOfType().Subject; + routeDict["id"].Should().Be(newPromoCode.Id); + + var obj = created.Value.Should().BeOfType().Subject; + obj.Should().BeEquivalentTo(PromoCodesMapper.ToPromoCodeShortResponse(newPromoCode)); + + _promoCodesRepositoryMock.Verify(x => x.Add(It.IsAny(), _ctNone), Times.Once); + _partnersRepositoryMock.Verify(x => x.Update(It.IsAny(), _ctNone), Times.Once); } } 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.WebHost/Controllers/PromoCodesController.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.WebHost/Controllers/PromoCodesController.cs" index 17c0cf553..a59384c7b 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.WebHost/Controllers/PromoCodesController.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.WebHost/Controllers/PromoCodesController.cs" @@ -108,11 +108,6 @@ public async Task> Create(PromoCodeCreateRe }).ToList() }; - foreach (var cpc in promoCode.CustomerPromoCodes) - { - cpc.PromoCodeId = promoCode.Id; - } - await promoCodesRepository.Add(promoCode, ct); activeLimit.IssuedCount += 1;