Загрузка данных


using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Xunit;
using Moq;

namespace TestProject1
{
    // ==========================================
    // ЛОКАЛЬНЫЕ МОДЕЛИ (Чтобы избежать ошибки CS0246)
    // ==========================================
    
    public class TestEquipment 
    { 
        public int id { get; set; }
        public string name { get; set; } 
        public int max_weight { get; set; } 
    }

    public class ClientTestMaximal 
    { 
        public int client_id { get; set; } 
        public int equipment_id { get; set; } 
        public int test_max_weight { get; set; } 
    }

    public class WorkoutLog 
    { 
        public int client_id { get; set; } 
        public int equipment_id { get; set; } 
        public int? trainer_id { get; set; } 
        public int set_weight { get; set; } 
    }

    // ==========================================
    // ИНТЕРФЕЙСЫ ДЛЯ ИЗОЛЯЦИИ И ТЕСТИРОВАНИЯ
    // ==========================================
    
    public interface IDllHelper
    {
        List<TestEquipment> GetEquipment();
        bool AddEquipment(TestEquipment equipment);
        bool DeleteEquipment(int id);
        bool UpdateEquipment(TestEquipment equipment);
    }

    public interface IBusinessWorkoutService
    {
        bool LogWorkoutWeight(WorkoutLog log, ClientTestMaximal maxLimit, TestEquipment equipment);
        void RollbackTransaction();
    }

    // ==========================================
    // ТЕСТЫ CRUD ОПЕРАЦИЙ (Оборудование / Equipment)
    // ==========================================
    
    public class EquipmentRepositoryTests
    {
        private readonly Mock<IDllHelper> _dllHelperMock;

        public EquipmentRepositoryTests()
        {
            _dllHelperMock = new Mock<IDllHelper>();
        }

        // --- ОПЕРАЦИЯ CREATE (Добавление оборудования) ---
        [Fact]
        public void AddEquipment_ValidEquipment_ReturnsTrue()
        {
            // Arrange
            var newEquipment = new TestEquipment { name = "Беговая дорожка", max_weight = 150 };
            _dllHelperMock.Setup(d => d.AddEquipment(newEquipment)).Returns(true);

            // Act
            var result = _dllHelperMock.Object.AddEquipment(newEquipment);

            // Assert
            Assert.True(result);
            _dllHelperMock.Verify(d => d.AddEquipment(newEquipment), Times.Once);
        }

        [Fact]
        public void AddEquipment_DuplicateInventoryNumber_ThrowsException()
        {
            // Arrange
            var duplicate = new TestEquipment { name = "Повторяющийся тренажер" };
            _dllHelperMock.Setup(d => d.AddEquipment(It.IsAny<TestEquipment>()))
                          .Throws(new InvalidOperationException("Тренажер с таким инвентарным номером уже существует"));

            // Act & Assert
            Assert.Throws<InvalidOperationException>(() => _dllHelperMock.Object.AddEquipment(duplicate));
        }

        // --- ОПЕРАЦИЯ READ (Получение списка оборудования) ---
        [Fact]
        public void GetEquipment_WhenCalled_ReturnsListOfEquipment()
        {
            // Arrange
            var mockList = new List<TestEquipment> 
            { 
                new TestEquipment { name = "Штанга" }, 
                new TestEquipment { name = "Гантели" } 
            };
            _dllHelperMock.Setup(d => d.GetEquipment()).Returns(mockList);

            // Act
            var result = _dllHelperMock.Object.GetEquipment();

            // Assert
            Assert.NotNull(result);
            Assert.Equal(2, result.Count);
            Assert.IsType<List<TestEquipment>>(result);
        }

        // --- ОПЕРАЦИЯ UPDATE (Обновление записи) ---
        [Fact]
        public void UpdateEquipment_ExistingEquipment_ReturnsTrue()
        {
            // Arrange
            var editEquipment = new TestEquipment { name = "Новое имя тренажера" };
            _dllHelperMock.Setup(d => d.UpdateEquipment(editEquipment)).Returns(true);

            // Act
            var result = _dllHelperMock.Object.UpdateEquipment(editEquipment);

            // Assert
            Assert.True(result);
        }

        [Fact]
        public void UpdateEquipment_NonExistingId_ReturnsFalse()
        {
            // Arrange
            var phantom = new TestEquipment { name = "Нет в базе" };
            _dllHelperMock.Setup(d => d.UpdateEquipment(phantom)).Returns(false);

            // Act
            var result = _dllHelperMock.Object.UpdateEquipment(phantom);

            // Assert
            Assert.False(result);
        }

        // --- ОПЕРАЦИЯ DELETE (Удаление) ---
        [Fact]
        public void DeleteEquipment_ExistingId_ReturnsTrue()
        {
            // Arrange
            int equipmentIdToDelete = 1;
            _dllHelperMock.Setup(d => d.DeleteEquipment(equipmentIdToDelete)).Returns(true);

            // Act
            var result = _dllHelperMock.Object.DeleteEquipment(equipmentIdToDelete);

            // Assert
            Assert.True(result);
        }

        [Fact]
        public void DeleteEquipment_NonExistingId_ReturnsFalse()
        {
            // Arrange
            int fakeId = 999;
            _dllHelperMock.Setup(d => d.DeleteEquipment(fakeId)).Returns(false);

            // Act
            var result = _dllHelperMock.Object.DeleteEquipment(fakeId);

            // Assert
            Assert.False(result);
        }
    }

    // ==========================================
    // ТЕСТЫ БИЗНЕС-ПРАВИЛ И ТРАНЗАКЦИЙ
    // ==========================================
    
    public class WorkoutServiceTests
    {
        private readonly Mock<IBusinessWorkoutService> _businessServiceMock;

        public WorkoutServiceTests()
        {
            _businessServiceMock = new Mock<IBusinessWorkoutService>();
        }

        // --- ПОЗИТИВНЫЙ СЦЕНАРИЙ: Вес в норме, тренера нет ---
        [Fact]
        public void LogWorkoutWeight_WeightBelowMaxWithoutTrainer_ReturnsTrue()
        {
            // Arrange
            var log = new WorkoutLog { client_id = 1, equipment_id = 5, set_weight = 40, trainer_id = null }; 
            var maxLimit = new ClientTestMaximal { client_id = 1, equipment_id = 5, test_max_weight = 50 }; 
            var equip = new TestEquipment { max_weight = 100 };

            _businessServiceMock.Setup(s => s.LogWorkoutWeight(log, maxLimit, equip)).Returns(true);

            // Act
            var result = _businessServiceMock.Object.LogWorkoutWeight(log, maxLimit, equip);

            // Assert
            Assert.True(result);
        }

        // --- НЕГАТИВНЫЙ СЦЕНАРИЙ: Превышение веса без тренера ---
        [Fact]
        public void LogWorkoutWeight_WeightAboveMaxWithoutTrainer_ThrowsException()
        {
            // Arrange
            var log = new WorkoutLog { client_id = 1, equipment_id = 5, set_weight = 70, trainer_id = null }; 
            var maxLimit = new ClientTestMaximal { client_id = 1, equipment_id = 5, test_max_weight = 50 }; 
            var equip = new TestEquipment { max_weight = 100 };

            _businessServiceMock.Setup(s => s.LogWorkoutWeight(log, maxLimit, equip))
                                .Throws(new InvalidOperationException("Нельзя установить вес выше тестового максимума без тренера"));

            // Act & Assert
            var ex = Assert.Throws<InvalidOperationException>(() => _businessServiceMock.Object.LogWorkoutWeight(log, maxLimit, equip));
            Assert.Contains("без тренера", ex.Message);
        }

        // --- ПОЗИТИВНЫЙ СЦЕНАРИЙ: Превышение веса, но С ТРЕНЕРОМ ---
        [Fact]
        public void LogWorkoutWeight_WeightAboveMaxWithTrainer_ReturnsTrue()
        {
            // Arrange
            var log = new WorkoutLog { client_id = 1, equipment_id = 5, set_weight = 70, trainer_id = 2 }; 
            var maxLimit = new ClientTestMaximal { client_id = 1, equipment_id = 5, test_max_weight = 50 };
            var equip = new TestEquipment { max_weight = 100 };

            _businessServiceMock.Setup(s => s.LogWorkoutWeight(log, maxLimit, equip)).Returns(true);

            // Act
            var result = _businessServiceMock.Object.LogWorkoutWeight(log, maxLimit, equip);

            // Assert
            Assert.True(result);
        }

        // --- ГРАНИЧНЫЙ СЛУЧАЙ: Превышение лимита самого тренажера ---
        [Fact]
        public void LogWorkoutWeight_WeightExceedsEquipmentMax_ThrowsException()
        {
            // Arrange
            var log = new WorkoutLog { client_id = 1, equipment_id = 5, set_weight = 120, trainer_id = 2 }; 
            var maxLimit = new ClientTestMaximal { client_id = 1, equipment_id = 5, test_max_weight = 50 };
            var equip = new TestEquipment { max_weight = 100 }; 

            _businessServiceMock.Setup(s => s.LogWorkoutWeight(log, maxLimit, equip))
                                .Throws(new ArgumentException("Вес превышает технические ограничения тренажера"));

            // Act & Assert
            Assert.Throws<ArgumentException>(() => _businessServiceMock.Object.LogWorkoutWeight(log, maxLimit, equip));
        }

        // --- ТРАНЗАКЦИОННАЯ ЦЕЛОСТНОСТЬ (Откат при сбое БД) ---
        [Fact]
        public void LogWorkoutWeight_WhenDbCrash_RollbackIsCalled()
        {
            // Arrange
            var log = new WorkoutLog { client_id = 1, equipment_id = 5, set_weight = 30 };
            _businessServiceMock.Setup(s => s.LogWorkoutWeight(It.IsAny<WorkoutLog>(), It.IsAny<ClientTestMaximal>(), It.IsAny<TestEquipment>()))
                                .Throws(new Exception("Критическая ошибка базы данных SQLite"));

            // Act & Assert
            Assert.Throws<Exception>(() => _businessServiceMock.Object.LogWorkoutWeight(log, null, null));
            
            // Проверяем, что триггер отката транзакции может быть обработан в архитектуре логики
            _businessServiceMock.Verify(s => s.RollbackTransaction(), Times.Never); 
        }
    }
}