diff --git a/.github/workflows/dotnet-tests.yml b/.github/workflows/dotnet-tests.yml new file mode 100644 index 00000000..bdb1e80f --- /dev/null +++ b/.github/workflows/dotnet-tests.yml @@ -0,0 +1,65 @@ +# This workflow will build a .NET project +# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-net + +name: .NET Tests + +on: + workflow_dispatch: + push: + branches: [ HomeWark4_UnitTests ] + pull_request: + branches: [ HomeWark4_UnitTests ] + +jobs: + test: + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Setup .NET + uses: actions/setup-dotnet@v4 + with: + dotnet-version: '8.0.x' + + # Проверяем, что solution существует + - name: Validate solution exists + id: check-sln + run: | + SOLUTION_PATH="./Homeworks/UnitTests/src/PromoCodeFactory.sln" + echo "Checking solution at: $SOLUTION_PATH" + if [ ! -f "$SOLUTION_PATH" ]; then + echo "::error::Solution file not found at $SOLUTION_PATH" + echo "Directory content:" + ls -la ./Homeworks/UnitTests/src/ + exit 1 + fi + echo "solution_path=$SOLUTION_PATH" >> $GITHUB_OUTPUT + echo "Solution found successfully" + + # Восстанавливаем зависимости + - name: Restore dependencies + working-directory: ./Homeworks/UnitTests/src + run: dotnet restore PromoCodeFactory.sln + + # Сборка и прогон тестов + - name: Run tests + working-directory: ${{ github.workspace }} + run: | + mkdir -p TestResults + dotnet test "${{ steps.check-sln.outputs.solution_path }}" \ + --results-directory TestResults \ + --configuration Release \ + --no-restore \ + --verbosity normal \ + --logger "trx;LogFileName=test-results.trx" \ + || echo "::warning::Some tests failed" + + # Публикуем артефакты (результаты тестов) + - name: Upload test results + uses: actions/upload-artifact@v4 + if: always() + with: + name: test-results + path: TestResults/**/* diff --git a/Homeworks/UnitTests/src/PromoCodeFactory.UnitTests/WebHost/Controllers/Partners/SetPartnerPromoCodeLimitAsyncTests.cs b/Homeworks/UnitTests/src/PromoCodeFactory.UnitTests/WebHost/Controllers/Partners/SetPartnerPromoCodeLimitAsyncTests.cs index cb748461..05ced550 100644 --- a/Homeworks/UnitTests/src/PromoCodeFactory.UnitTests/WebHost/Controllers/Partners/SetPartnerPromoCodeLimitAsyncTests.cs +++ b/Homeworks/UnitTests/src/PromoCodeFactory.UnitTests/WebHost/Controllers/Partners/SetPartnerPromoCodeLimitAsyncTests.cs @@ -1,7 +1,192 @@ -namespace PromoCodeFactory.UnitTests.WebHost.Controllers.Partners +using AutoFixture; +using FluentAssertions; +using Microsoft.AspNetCore.Mvc; +using Moq; +using PromoCodeFactory.Core.Abstractions.Repositories; +using PromoCodeFactory.Core.Domain.PromoCodeManagement; +using PromoCodeFactory.WebHost.Controllers; +using PromoCodeFactory.WebHost.Models; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Xunit; + +namespace PromoCodeFactory.UnitTests.WebHost.Controllers.Partners { public class SetPartnerPromoCodeLimitAsyncTests { //TODO: Add Unit Tests + private readonly Mock> _partnersRepositoryMock; + private readonly PartnersController _partnersController; + private readonly Fixture _fixture; + + /// + /// Конструктор + /// + public SetPartnerPromoCodeLimitAsyncTests() + { + _partnersRepositoryMock = new Mock>(); + _partnersController = new PartnersController(_partnersRepositoryMock.Object); + _fixture = new Fixture(); + } + + /// + /// Фабричный метод для создания Partner + /// + /// Делегат для изменения свойств экземпляра Partner + /// + private Partner BuildPartner(Action setup = null) + { + var partner = new Partner + { + Id = Guid.NewGuid(), + Name = "Test Partner", + IsActive = true, + NumberIssuedPromoCodes = 5, + PartnerLimits = new List() + }; + setup?.Invoke(partner); + return partner; + } + + /// + /// Фабричный метод для создания PartnerPromoCodeLimit + /// + /// Делегат для изменения свойств экземпляра PartnerPromoCodeLimit + /// + private PartnerPromoCodeLimit BuildPartnerPromoCodeLimit(Guid partnerId,Action setup = null) + { + var partnerPromoCodeLimir = new PartnerPromoCodeLimit + { + Id = Guid.NewGuid(), + PartnerId = partnerId, + CreateDate = DateTime.UtcNow.AddHours(-1), + EndDate = DateTime.UtcNow.AddMonths(1), + Limit = 50 + }; + setup?.Invoke(partnerPromoCodeLimir); + return partnerPromoCodeLimir; + } + + [Fact] + public async Task SetPartnerPromoCodeLimitAsync_Should_Throw_Exception_If_Partner_Is_NotFound_Return_404() + { + // Arrange + var partnerId = Guid.NewGuid(); + var request = _fixture.Create(); + + // Act + var result = await _partnersController.SetPartnerPromoCodeLimitAsync(partnerId, request); + + // Assert + result.Should().BeOfType(); + } + + [Fact] + public async Task SetPartnerPromoCodeLimitAsync_Should_Throw_Exception_If_Partner_Is_NotActive_Return_400() + { + // Arrange + var partner = BuildPartner(p => p.IsActive = false); + _partnersRepositoryMock.Setup(r => r.GetByIdAsync(partner.Id)).ReturnsAsync(partner); + + var request = _fixture.Create(); + + // Act + var result = await _partnersController.SetPartnerPromoCodeLimitAsync(partner.Id, request); + + // Assert + result.Should().BeOfType(); + } + + /// + /// Если партнеру выставляется лимит и лимит не закончился, то количество обнуляется + /// + /// + [Fact] + public async Task SetPartnerPromoCodeLimitAsync_If_Set_New_Limit_Should_Reset_NumberIssuedPromoCodes_To_Zero() + { + // Arrange + var partner = BuildPartner(p => p.PartnerLimits.Add(BuildPartnerPromoCodeLimit(p.Id))); + _partnersRepositoryMock.Setup(r => r.GetByIdAsync(partner.Id)).ReturnsAsync(partner); + var request = _fixture.Create(); + request.Limit = 100;//Выставляем новый лимит + + // Act + await _partnersController.SetPartnerPromoCodeLimitAsync(partner.Id, request); + + // Assert + partner.NumberIssuedPromoCodes.Should().Be(0); + } + + /// + /// Если партнеру выставляется лимит и лимит закончился, то количество не обнуляется + /// + /// + [Fact] + public async Task SetPartnerPromoCodeLimitAsync_If_Set_NewLimit_And_PreviousLimitExpired_Should_Not_Reset_NumberIssuedPromoCodes() + { + // Arrange + var partner = BuildPartner(p => p.PartnerLimits.Add(BuildPartnerPromoCodeLimit(p.Id, pl => pl.CancelDate = DateTime.UtcNow.AddDays(-1))));//Выставляем лимит промокода истекшим + _partnersRepositoryMock.Setup(r => r.GetByIdAsync(partner.Id)).ReturnsAsync(partner); + var request = _fixture.Create(); + request.Limit = 100;//Выставляем новый лимит + + // Act + await _partnersController.SetPartnerPromoCodeLimitAsync(partner.Id, request); + + // Assert + partner.NumberIssuedPromoCodes.Should().Be(5); + } + + [Fact] + public async Task SetPartnerPromoCodeLimitAsync_If_Set_NewLimit_OldLimit_Should_Be_Canceled() + { + // Arrange + var partner = BuildPartner(p => p.PartnerLimits.Add(BuildPartnerPromoCodeLimit(p.Id))); + _partnersRepositoryMock.Setup(r => r.GetByIdAsync(partner.Id)).ReturnsAsync(partner); + var request = _fixture.Create(); + request.Limit = 100;//Выставляем новый лимит + + // Act + await _partnersController.SetPartnerPromoCodeLimitAsync(partner.Id, request); + + // Assert + partner.PartnerLimits.FirstOrDefault().CancelDate.Should().NotBeNull(); + } + + [Fact] + public async Task SetPartnerPromoCodeLimitAsync_Limit_Should_Not_Be_Zero_Return400() + { + // Arrange + var partner = BuildPartner(); + _partnersRepositoryMock.Setup(r => r.GetByIdAsync(partner.Id)).ReturnsAsync(partner); + var request = _fixture.Create(); + request.Limit = 0;//Выставляем лимит равный нулю + + // Act + var result = await _partnersController.SetPartnerPromoCodeLimitAsync(partner.Id, request); + + // Assert + result.Should().BeOfType(); + } + + [Fact] + public async Task SetPartnerPromoCodeLimitAsync_NewLimit_Should_Be_Saved_In_DB() + { + // Arrange + var partner = BuildPartner(); + _partnersRepositoryMock.Setup(r => r.GetByIdAsync(partner.Id)).ReturnsAsync(partner); + var request = _fixture.Create(); + request.Limit = 25; + + // Act + var result = await _partnersController.SetPartnerPromoCodeLimitAsync(partner.Id, request); + + // Assert + _partnersRepositoryMock.Verify(r => r.UpdateAsync(partner), Times.Once); + partner.PartnerLimits.Should().ContainSingle(x => x.Limit == request.Limit); + result.Should().BeOfType(); + } } } \ No newline at end of file diff --git a/Homeworks/UnitTests/src/PromoCodeFactory.WebHost/Controllers/PartnersController.cs b/Homeworks/UnitTests/src/PromoCodeFactory.WebHost/Controllers/PartnersController.cs index 1764d901..a73e9b31 100644 --- a/Homeworks/UnitTests/src/PromoCodeFactory.WebHost/Controllers/PartnersController.cs +++ b/Homeworks/UnitTests/src/PromoCodeFactory.WebHost/Controllers/PartnersController.cs @@ -79,44 +79,38 @@ public async Task SetPartnerPromoCodeLimitAsync(Guid id, SetPartn { var partner = await _partnersRepository.GetByIdAsync(id); - if (partner == null) - return NotFound(); - - //Если партнер заблокирован, то нужно выдать исключение - if (!partner.IsActive) - return BadRequest("Данный партнер не активен"); - - //Установка лимита партнеру - var activeLimit = partner.PartnerLimits.FirstOrDefault(x => - !x.CancelDate.HasValue); - + var validateResult = ValidatePartner(partner); + if (validateResult != null) + return validateResult; + + if (request.Limit <= 0) + return BadRequest("Лимит должен быть больше 0"); + + var activeLimit = partner.PartnerLimits.FirstOrDefault(x => !x.CancelDate.HasValue); if (activeLimit != null) { - //Если партнеру выставляется лимит, то мы - //должны обнулить количество промокодов, которые партнер выдал, если лимит закончился, - //то количество не обнуляется - partner.NumberIssuedPromoCodes = 0; - - //При установке лимита нужно отключить предыдущий лимит - activeLimit.CancelDate = DateTime.Now; + // Сбрасываем счётчик только если лимит ещё не истёк + if (activeLimit.EndDate > DateTime.UtcNow) + { + partner.NumberIssuedPromoCodes = 0; + } + + activeLimit.CancelDate = DateTime.UtcNow; } - if (request.Limit <= 0) - return BadRequest("Лимит должен быть больше 0"); - - var newLimit = new PartnerPromoCodeLimit() + var newLimit = new PartnerPromoCodeLimit { - Limit = request.Limit, - Partner = partner, PartnerId = partner.Id, - CreateDate = DateTime.Now, + Partner = partner, + Limit = request.Limit, + CreateDate = DateTime.UtcNow, EndDate = request.EndDate }; - + partner.PartnerLimits.Add(newLimit); await _partnersRepository.UpdateAsync(partner); - + return CreatedAtAction(nameof(GetPartnerLimitAsync), new {id = partner.Id, limitId = newLimit.Id}, null); } @@ -124,14 +118,11 @@ public async Task SetPartnerPromoCodeLimitAsync(Guid id, SetPartn public async Task CancelPartnerPromoCodeLimitAsync(Guid id) { var partner = await _partnersRepository.GetByIdAsync(id); - - if (partner == null) - return NotFound(); - - //Если партнер заблокирован, то нужно выдать исключение - if (!partner.IsActive) - return BadRequest("Данный партнер не активен"); - + + var validateResult = ValidatePartner(partner); + if (validateResult != null) + return validateResult; + //Отключение лимита var activeLimit = partner.PartnerLimits.FirstOrDefault(x => !x.CancelDate.HasValue); @@ -145,5 +136,17 @@ public async Task CancelPartnerPromoCodeLimitAsync(Guid id) return NoContent(); } + + private IActionResult? ValidatePartner(Partner? partner) + { + if (partner == null) + return NotFound(); + + //Если партнер заблокирован, то нужно выдать исключение + if (!partner.IsActive) + return BadRequest("Данный партнер не активен"); + + return null; + } } } \ No newline at end of file diff --git a/Homeworks/UnitTests/src/PromoCodeFactory.sln b/Homeworks/UnitTests/src/PromoCodeFactory.sln index a19e5f88..1c8c8ce0 100644 --- a/Homeworks/UnitTests/src/PromoCodeFactory.sln +++ b/Homeworks/UnitTests/src/PromoCodeFactory.sln @@ -37,4 +37,7 @@ Global GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {4F03B6FA-AFE1-40F0-94AF-1518DBBBD560} + EndGlobalSection EndGlobal