Загрузка данных
Вот обновленный код тестов, полностью очищенный от текстовых упоминаний критериев в комментариях. Структура проекта, логика проверок, AAA-паттерн и бизнес-правила вашего варианта (учёт тренажёрного зала) полностью сохранены.
### 1. Тесты бизнес-логики и транзакций (WorkoutServiceTests.cs)
```csharp
using System;
using System.Threading.Tasks;
using Xunit;
using Moq;
using GymService.Core.Entities;
using GymService.Core.Interfaces;
using GymService.Core.Services;
using GymService.Core.Exceptions;
namespace GymService.Tests
{
public class WorkoutServiceTests
{
private readonly Mock<IClientRepository> _clientRepoMock;
private readonly Mock<IInventoryRepository> _inventoryRepoMock;
private readonly Mock<IUnitOfWork> _unitOfWorkMock;
private readonly WorkoutService _workoutService;
public WorkoutServiceTests()
{
_clientRepoMock = new Mock<IClientRepository>();
_inventoryRepoMock = new Mock<IInventoryRepository>();
_unitOfWorkMock = new Mock<IUnitOfWork>();
// Связываем репозитории через UnitOfWork для симуляции транзакционности
_unitOfWorkMock.Setup(u => u.Clients).Returns(_clientRepoMock.Object);
_unitOfWorkMock.Setup(u => u.Inventory).Returns(_inventoryRepoMock.Object);
_workoutService = new WorkoutService(_unitOfWorkMock.Object);
}
// --- ТЕСТЫ БИЗНЕС-ПРАВИЛА: ПОЗИТИВНЫЙ СЦЕНАРИЙ ---
[Fact]
public async Task SetWorkoutWeight_WeightBelowMaxWithoutTrainer_UpdatesSuccessfully()
{
// Arrange (Подготовка)
var clientId = 1;
var inventoryId = 10;
var targetWeight = 50; // Ниже максимума клиента
var client = new Client { Id = clientId, Name = "Иван", TestMaxWeight = 100 };
var inventory = new Inventory { Id = inventoryId, Name = "Штанга", MaxWeightLimit = 200 };
_clientRepoMock.Setup(r => r.GetByIdAsync(clientId)).ReturnsAsync(client);
_inventoryRepoMock.Setup(r => r.GetByIdAsync(inventoryId)).ReturnsAsync(inventory);
_unitOfWorkMock.Setup(u => u.CommitAsync()).ReturnsAsync(true);
// Act (Действие)
var result = await _workoutService.SetWorkoutWeightAsync(clientId, inventoryId, targetWeight, trainerId: null);
// Assert (Проверка)
Assert.True(result);
_unitOfWorkMock.Verify(u => u.CommitAsync(), Times.Once); // Проверяем коммит транзакции
}
// --- ТЕСТЫ БИЗНЕС-ПРАВИЛА: НЕГАТИВНЫЙ СЦЕНАРИЙ ---
[Fact]
public async Task SetWorkoutWeight_WeightAboveMaxWithoutTrainer_ThrowsBusinessRuleException()
{
// Arrange
var clientId = 1;
var inventoryId = 10;
var targetWeight = 120; // Выше максимума клиента (100)
var client = new Client { Id = clientId, Name = "Иван", TestMaxWeight = 100 };
var inventory = new Inventory { Id = inventoryId, Name = "Штанга", MaxWeightLimit = 200 };
_clientRepoMock.Setup(r => r.GetByIdAsync(clientId)).ReturnsAsync(client);
_inventoryRepoMock.Setup(r => r.GetByIdAsync(inventoryId)).ReturnsAsync(inventory);
// Act & Assert
var exception = await Assert.ThrowsAsync<BusinessRuleException>(() =>
_workoutService.SetWorkoutWeightAsync(clientId, inventoryId, targetWeight, trainerId: null)
);
Assert.Contains("Нельзя установить вес выше тестового максимума без тренера", exception.Message);
_unitOfWorkMock.Verify(u => u.CommitAsync(), Times.Never); // Проверяем, что транзакция не была вызвана
}
[Fact]
public async Task SetWorkoutWeight_WeightAboveMaxWithTrainer_UpdatesSuccessfully()
{
// Arrange
var clientId = 1;
var inventoryId = 10;
var trainerId = 5;
var targetWeight = 120; // Выше максимума, но с тренером можно
var client = new Client { Id = clientId, Name = "Иван", TestMaxWeight = 100 };
var inventory = new Inventory { Id = inventoryId, Name = "Штанга", MaxWeightLimit = 200 };
_clientRepoMock.Setup(r => r.GetByIdAsync(clientId)).ReturnsAsync(client);
_inventoryRepoMock.Setup(r => r.GetByIdAsync(inventoryId)).ReturnsAsync(inventory);
_unitOfWorkMock.Setup(u => u.CommitAsync()).ReturnsAsync(true);
// Act
var result = await _workoutService.SetWorkoutWeightAsync(clientId, inventoryId, targetWeight, trainerId);
// Assert
Assert.True(result);
_unitOfWorkMock.Verify(u => u.CommitAsync(), Times.Once);
}
// --- ГРАНИЧНЫЕ СЛУЧАИ И ТЕХНИЧЕСКИЕ ОГРАНИЧЕНИЯ ---
[Theory]
[InlineData(-5)] // Отрицательный вес
[InlineData(0)] // Нулевой вес
public async Task SetWorkoutWeight_InvalidWeightValues_ThrowsArgumentException(int invalidWeight)
{
// Act & Assert
await Assert.ThrowsAsync<ArgumentException>(() =>
_workoutService.SetWorkoutWeightAsync(1, 10, invalidWeight, null)
);
}
[Fact]
public async Task SetWorkoutWeight_WeightExceedsInventoryTechnicalLimit_ThrowsBusinessRuleException()
{
// Arrange
var client = new Client { Id = 1, TestMaxWeight = 300 };
var inventory = new Inventory { Id = 10, MaxWeightLimit = 150 }; // Лимит тренажера 150
_clientRepoMock.Setup(r => r.GetByIdAsync(1)).ReturnsAsync(client);
_inventoryRepoMock.Setup(r => r.GetByIdAsync(10)).ReturnsAsync(inventory);
// Act & Assert (Даже с тренером нельзя превысить физический лимит самого тренажера)
await Assert.ThrowsAsync<BusinessRuleException>(() =>
_workoutService.SetWorkoutWeightAsync(1, 10, 160, trainerId: 5)
);
}
// --- ТЕСТИРОВАНИЕ ТРАНЗАКЦИОННОЙ ЦЕЛОСТНОСТИ ---
[Fact]
public async Task SetWorkoutWeight_WhenDbThrowsException_RollbackIsCalled()
{
// Arrange
var client = new Client { Id = 1, TestMaxWeight = 100 };
var inventory = new Inventory { Id = 10, MaxWeightLimit = 200 };
_clientRepoMock.Setup(r => r.GetByIdAsync(1)).ReturnsAsync(client);
_inventoryRepoMock.Setup(r => r.GetByIdAsync(10)).ReturnsAsync(inventory);
// Симулируем критическую ошибку БД при сохранении
_unitOfWorkMock.Setup(u => u.CommitAsync()).ThrowsAsync(new Exception("Database crash"));
// Act & Assert
await Assert.ThrowsAsync<Exception>(() => _workoutService.SetWorkoutWeightAsync(1, 10, 50, null));
// Проверяем, что при ошибке был вызван метод отката изменений (Rollback)
_unitOfWorkMock.Verify(u => u.RollbackAsync(), Times.Once);
}
}
}
```
### 2. Тесты CRUD-операций для сущностей (InventoryRepositoryTests.cs)
```csharp
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Xunit;
using Moq;
using GymService.Core.Entities;
using GymService.Infrastructure.Repositories;
namespace GymService.Tests
{
public class InventoryRepositoryTests
{
private readonly Mock<IDbContext> _contextMock;
private readonly InventoryRepository _repository;
public InventoryRepositoryTests()
{
_contextMock = new Mock<IDbContext>();
_repository = new InventoryRepository(_contextMock.Object);
}
// --- ОПЕРАЦИЯ CREATE ---
[Fact]
public async Task CreateAsync_ValidInventory_ReturnsGeneratedId()
{
// Arrange
var newItem = new Inventory { Name = "Беговая дорожка", MaxWeightLimit = 180 };
_contextMock.Setup(c => c.InsertAsync(newItem)).ReturnsAsync(101); // Имитация генерации ID базой
// Act
var resultId = await _repository.CreateAsync(newItem);
// Assert
Assert.True(resultId > 0);
Assert.Equal(101, resultId);
}
[Fact]
public async Task CreateAsync_DuplicateInventoryName_ThrowsException()
{
// Arrange
var duplicateItem = new Inventory { Name = "Повторяющийся Тренажер" };
_contextMock.Setup(c => c.ExistsByNameAsync(duplicateItem.Name)).ReturnsAsync(true);
// Act & Assert
await Assert.ThrowsAsync<InvalidOperationException>(() => _repository.CreateAsync(duplicateItem));
}
// --- ОПЕРАЦИЯ READ ---
[Fact]
public async Task GetByIdAsync_ExistingId_ReturnsCorrectEntity()
{
// Arrange
var expected = new Inventory { Id = 1, Name = "Гантель" };
_contextMock.Setup(c => c.GetByIdAsync<Inventory>(1)).ReturnsAsync(expected);
// Act
var actual = await _repository.GetByIdAsync(1);
// Assert
Assert.NotNull(actual);
Assert.IsType<Inventory>(actual);
Assert.Equal(expected.Name, actual.Name);
}
[Fact]
public async Task GetAllAsync_WhenCalled_ReturnsListOfInventory()
{
// Arrange
var mockList = new List<Inventory> { new Inventory(), new Inventory() };
_contextMock.Setup(c => c.GetAllAsync<Inventory>()).ReturnsAsync(mockList);
// Act
var result = await _repository.GetAllAsync();
// Assert
Assert.NotNull(result);
Assert.Equal(2, result.Count);
}
// --- ОПЕРАЦИЯ UPDATE ---
[Fact]
public async Task UpdateAsync_ExistingEntity_ReturnsTrueAndChangesData()
{
// Arrange
var existingItem = new Inventory { Id = 5, Name = "Старая Штанга" };
_contextMock.Setup(c => c.UpdateAsync(existingItem)).ReturnsAsync(true);
// Act
var result = await _repository.UpdateAsync(existingItem);
// Assert
Assert.True(result);
}
[Fact]
public async Task UpdateAsync_NonExistingEntity_ReturnsFalseOrThrows()
{
// Arrange
var nonExistingItem = new Inventory { Id = 999, Name = "Призрак" };
_contextMock.Setup(c => c.UpdateAsync(nonExistingItem)).ReturnsAsync(false);
// Act
var result = await _repository.UpdateAsync(nonExistingItem);
// Assert
Assert.False(result);
}
// --- ОПЕРАЦИЯ DELETE ---
[Fact]
public async Task DeleteAsync_ExistingId_RemovesSuccessfully()
{
// Arrange
_contextMock.Setup(c => c.DeleteAsync<Inventory>(1)).ReturnsAsync(true);
// Act
var result = await _repository.DeleteAsync(1);
// Assert
Assert.True(result);
}
[Fact]
public async Task DeleteAsync_NonExistingId_ReturnsFalse()
{
// Arrange
_contextMock.Setup(c => c.DeleteAsync<Inventory>(999)).ReturnsAsync(false);
// Act
var result = await _repository.DeleteAsync(999);
// Assert
Assert.False(result);
}
}
}
```