Skip to content
65 changes: 65 additions & 0 deletions .github/workflows/dotnet-tests.yml
Original file line number Diff line number Diff line change
@@ -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/**/*
Original file line number Diff line number Diff line change
@@ -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<IRepository<Partner>> _partnersRepositoryMock;
private readonly PartnersController _partnersController;
private readonly Fixture _fixture;

/// <summary>
/// Конструктор
/// </summary>
public SetPartnerPromoCodeLimitAsyncTests()
{
_partnersRepositoryMock = new Mock<IRepository<Partner>>();
_partnersController = new PartnersController(_partnersRepositoryMock.Object);
_fixture = new Fixture();
}

/// <summary>
/// Фабричный метод для создания Partner
/// </summary>
/// <param name="setup">Делегат для изменения свойств экземпляра Partner</param>
/// <returns></returns>
private Partner BuildPartner(Action<Partner> setup = null)
{
var partner = new Partner
{
Id = Guid.NewGuid(),
Name = "Test Partner",
IsActive = true,
NumberIssuedPromoCodes = 5,
PartnerLimits = new List<PartnerPromoCodeLimit>()
};
setup?.Invoke(partner);
return partner;
}

/// <summary>
/// Фабричный метод для создания PartnerPromoCodeLimit
/// </summary>
/// <param name="setup">Делегат для изменения свойств экземпляра PartnerPromoCodeLimit</param>
/// <returns></returns>
private PartnerPromoCodeLimit BuildPartnerPromoCodeLimit(Guid partnerId,Action<PartnerPromoCodeLimit> 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<SetPartnerPromoCodeLimitRequest>();

// Act
var result = await _partnersController.SetPartnerPromoCodeLimitAsync(partnerId, request);

// Assert
result.Should().BeOfType<NotFoundResult>();
}

[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<SetPartnerPromoCodeLimitRequest>();

// Act
var result = await _partnersController.SetPartnerPromoCodeLimitAsync(partner.Id, request);

// Assert
result.Should().BeOfType<BadRequestObjectResult>();
}

/// <summary>
/// Если партнеру выставляется лимит и лимит не закончился, то количество обнуляется
/// </summary>
/// <returns></returns>
[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<SetPartnerPromoCodeLimitRequest>();
request.Limit = 100;//Выставляем новый лимит

// Act
await _partnersController.SetPartnerPromoCodeLimitAsync(partner.Id, request);

// Assert
partner.NumberIssuedPromoCodes.Should().Be(0);
}

/// <summary>
/// Если партнеру выставляется лимит и лимит закончился, то количество не обнуляется
/// </summary>
/// <returns></returns>
[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<SetPartnerPromoCodeLimitRequest>();
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<SetPartnerPromoCodeLimitRequest>();
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<SetPartnerPromoCodeLimitRequest>();
request.Limit = 0;//Выставляем лимит равный нулю

// Act
var result = await _partnersController.SetPartnerPromoCodeLimitAsync(partner.Id, request);

// Assert
result.Should().BeOfType<BadRequestObjectResult>();
}

[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<SetPartnerPromoCodeLimitRequest>();
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<CreatedAtActionResult>();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -79,59 +79,50 @@ public async Task<IActionResult> 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);
}

[HttpPost("{id}/canceledLimits")]
public async Task<IActionResult> 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);
Expand All @@ -145,5 +136,17 @@ public async Task<IActionResult> CancelPartnerPromoCodeLimitAsync(Guid id)

return NoContent();
}

private IActionResult? ValidatePartner(Partner? partner)
{
if (partner == null)
return NotFound();

//Если партнер заблокирован, то нужно выдать исключение
if (!partner.IsActive)
return BadRequest("Данный партнер не активен");

return null;
}
}
}
3 changes: 3 additions & 0 deletions Homeworks/UnitTests/src/PromoCodeFactory.sln
Original file line number Diff line number Diff line change
Expand Up @@ -37,4 +37,7 @@ Global
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {4F03B6FA-AFE1-40F0-94AF-1518DBBBD560}
EndGlobalSection
EndGlobal