From be0c84467229562261c1338ac2c2b1ff1192b64c Mon Sep 17 00:00:00 2001 From: sokol Date: Sat, 21 Feb 2026 11:43:06 +0300 Subject: [PATCH] Add user management and business units --- backend/src/MyBiz.Core/AuthService.cs | 176 ++++++++ backend/src/MyBiz.Core/BusinessUnit.cs | 437 +++++++++++++++++++ backend/src/MyBiz.Core/Company.cs | 96 ++++- backend/src/MyBiz.Core/User.cs | 90 ++++ backend/tests/MyBiz.Tests/UserTests.cs | 561 +++++++++++++++++++++++++ buffer.md | 4 + 6 files changed, 1356 insertions(+), 8 deletions(-) create mode 100644 backend/src/MyBiz.Core/AuthService.cs create mode 100644 backend/src/MyBiz.Core/BusinessUnit.cs create mode 100644 backend/src/MyBiz.Core/User.cs create mode 100644 backend/tests/MyBiz.Tests/UserTests.cs create mode 100644 buffer.md diff --git a/backend/src/MyBiz.Core/AuthService.cs b/backend/src/MyBiz.Core/AuthService.cs new file mode 100644 index 0000000..87b3d42 --- /dev/null +++ b/backend/src/MyBiz.Core/AuthService.cs @@ -0,0 +1,176 @@ +namespace MyBiz.Core; + +/// +/// Результат аутентификации +/// +public class AuthResult +{ + /// + /// Успешна ли аутентификация + /// + public bool Success { get; set; } + + /// + /// Пользователь (если успешно) + /// + public User? User { get; set; } + + /// + /// Токен доступа (заглушка) + /// + public string? Token { get; set; } + + /// + /// Сообщение об ошибке + /// + public string? ErrorMessage { get; set; } +} + +/// +/// Сервис аутентификации (заглушка для MVP) +/// +public class AuthService +{ + private readonly Dictionary _usersByUsername = new(); + private readonly Dictionary _usersById = new(); + private readonly Dictionary _tokens = new(); // token -> userId + + /// + /// Зарегистрировать нового пользователя + /// + public AuthResult Register(string username, string email, string password, string companyName) + { + if (_usersByUsername.ContainsKey(username)) + { + return new AuthResult + { + Success = false, + ErrorMessage = "Пользователь с таким именем уже существует" + }; + } + + var user = new User + { + Username = username, + Email = email, + PasswordHash = HashPassword(password), + Company = new Company + { + Name = companyName, + Cash = 100000m, // Стартовый капитал + Assets = 0, + Liabilities = 0 + } + }; + + user.CompanyId = user.Company.Id; + + _usersByUsername[username] = user; + _usersById[user.Id] = user; + + var token = GenerateToken(user.Id); + _tokens[token] = user.Id; // Сохраняем токен + + return new AuthResult + { + Success = true, + User = user, + Token = token + }; + } + + /// + /// Войти в систему + /// + public AuthResult Login(string username, string password) + { + if (!_usersByUsername.TryGetValue(username, out var user)) + { + return new AuthResult + { + Success = false, + ErrorMessage = "Пользователь не найден" + }; + } + + if (!VerifyPassword(password, user.PasswordHash)) + { + return new AuthResult + { + Success = false, + ErrorMessage = "Неверный пароль" + }; + } + + user.LastLoginAt = DateTime.UtcNow; + + return new AuthResult + { + Success = true, + User = user, + Token = GenerateToken(user.Id) + }; + } + + /// + /// Выйти из системы + /// + public void Logout(string token) + { + if (_tokens.ContainsKey(token)) + { + _tokens.Remove(token); + } + } + + /// + /// Проверить токен и получить пользователя + /// + public User? ValidateToken(string token) + { + if (_tokens.TryGetValue(token, out var userId)) + { + return _usersById.TryGetValue(userId, out var user) ? user : null; + } + return null; + } + + /// + /// Получить пользователя по ID + /// + public User? GetUserById(Guid userId) + { + return _usersById.TryGetValue(userId, out var user) ? user : null; + } + + /// + /// Получить пользователя по имени + /// + public User? GetUserByUsername(string username) + { + return _usersByUsername.TryGetValue(username, out var user) ? user : null; + } + + #region Helpers (заглушки) + + private static string HashPassword(string password) + { + // TODO: Использовать реальное хеширование (BCrypt, PBKDF2) + return $"hash_{password}"; + } + + private static bool VerifyPassword(string password, string hash) + { + // TODO: Использовать реальную проверку пароля + return hash == $"hash_{password}"; + } + + private static string GenerateToken(Guid userId) + { + // TODO: Использовать JWT или аналогичный механизм + var token = $"token_{userId}_{Guid.NewGuid()}"; + return token; + } + + #endregion +} diff --git a/backend/src/MyBiz.Core/BusinessUnit.cs b/backend/src/MyBiz.Core/BusinessUnit.cs new file mode 100644 index 0000000..4e94bcc --- /dev/null +++ b/backend/src/MyBiz.Core/BusinessUnit.cs @@ -0,0 +1,437 @@ +namespace MyBiz.Core; + +/// +/// Тип бизнес-единицы +/// +public enum BusinessUnitType +{ + /// + /// Магазин (розничная торговля) + /// + Shop, + + /// + /// Фабрика (производство) + /// + Factory, + + /// + /// Склад (хранение) + /// + Warehouse, + + /// + /// Офис (управление) + /// + Office, + + /// + /// Научный центр (исследования) + /// + ResearchLab +} + +/// +/// Базовая бизнес-единица (магазин, фабрика и т.д.) +/// +public class BusinessUnit +{ + public Guid Id { get; set; } = Guid.NewGuid(); + + /// + /// Название единицы + /// + public string Name { get; set; } = string.Empty; + + /// + /// Тип бизнес-единицы + /// + public BusinessUnitType Type { get; set; } + + /// + /// ID компании-владельца + /// + public Guid CompanyId { get; set; } + + /// + /// Город, где находится единица + /// + public string CityId { get; set; } = string.Empty; + + /// + /// Координаты на карте + /// + public int X { get; set; } + public int Y { get; set; } + + /// + /// Уровень развития (1-max) + /// + public int Level { get; set; } = 1; + + /// + /// Максимальный уровень + /// + public int MaxLevel { get; set; } = 10; + + /// + /// Количество сотрудников + /// + public int Employees { get; set; } + + /// + /// Максимальное количество сотрудников + /// + public int MaxEmployees { get; set; } = 50; + + /// + /// Эффективность работы (0-100) + /// + public int Efficiency { get; set; } = 100; + + /// + /// Стоимость постройки + /// + public decimal BuildCost { get; set; } + + /// + /// Содержание в тик (зарплаты, аренда и т.д.) + /// + public decimal UpkeepCost { get; set; } + + /// + /// Доход за последний период + /// + public decimal LastPeriodIncome { get; set; } + + /// + /// Расходы за последний период + /// + public decimal LastPeriodExpenses { get; set; } + + /// + /// Дата постройки + /// + public int BuiltAtTick { get; set; } + + /// + /// Активна ли единица (работает) + /// + public bool IsActive { get; set; } = true; + + /// + /// Ссылка на здание (если есть) + /// + public Building? Building { get; set; } + + /// + /// Запасы на складе (для магазинов/складов) + /// + public Dictionary Inventory { get; set; } = new(); + + /// + /// Производственная цепочка (для фабрик) + /// + public ActiveProductionChain? ActiveProduction { get; set; } + + /// + /// Рассчитать прибыль за период + /// + public decimal PeriodProfit => LastPeriodIncome - LastPeriodExpenses; + + /// + /// Рассчитать рентабельность (%) + /// + public decimal Profitability => LastPeriodExpenses > 0 + ? (PeriodProfit / LastPeriodExpenses) * 100 + : 0; + + /// + /// Обновить эффективность на основе сотрудников + /// + public void UpdateEfficiency() + { + if (MaxEmployees == 0) + { + Efficiency = 0; + return; + } + + // Базовая эффективность зависит от заполненности рабочих мест + var staffRate = (decimal)Employees / MaxEmployees; + Efficiency = (int)(100 * staffRate); + + // Бонус за уровень + Efficiency += (Level - 1) * 5; + + // Ограничение 0-100 + Efficiency = Math.Clamp(Efficiency, 0, 100); + } + + /// + /// Обновить состояние (вызывается каждый тик) + /// + public virtual void Tick() + { + // Обновление производства (если есть) + ActiveProduction?.Tick(); + + // Обновление эффективности + UpdateEfficiency(); + } + + /// + /// Нанять сотрудника + /// + public bool HireEmployee() + { + if (Employees < MaxEmployees) + { + Employees++; + return true; + } + return false; + } + + /// + /// Уволить сотрудника + /// + public bool FireEmployee() + { + if (Employees > 0) + { + Employees--; + return true; + } + return false; + } + + /// + /// Повысить уровень + /// + public bool Upgrade() + { + if (Level < MaxLevel) + { + Level++; + MaxEmployees += 10; // Бонус за уровень + return true; + } + return false; + } +} + +/// +/// Магазин (розничная торговля) +/// +public class Shop : BusinessUnit +{ + public Shop() + { + Type = BusinessUnitType.Shop; + MaxEmployees = 20; + BuildCost = 50000m; + UpkeepCost = 1000m; + } + + /// + /// Торгуемые продукты + /// + public List SoldProductTypes { get; set; } = new(); + + /// + /// Выручка за сегодня + /// + public decimal DailyRevenue { get; set; } + + /// + /// Количество клиентов за сегодня + /// + public int DailyCustomers { get; set; } + + /// + /// Средний чек + /// + public decimal AverageCheck => DailyCustomers > 0 ? DailyRevenue / DailyCustomers : 0; +} + +/// +/// Фабрика (производство) +/// +public class Factory : BusinessUnit +{ + public Factory() + { + Type = BusinessUnitType.Factory; + MaxEmployees = 100; + BuildCost = 200000m; + UpkeepCost = 5000m; + } + + /// + /// Производимый продукт + /// + public string? OutputProductId { get; set; } + + /// + /// Требуемые ресурсы (входные продукты) + /// + public Dictionary RequiredInputs { get; set; } = new(); + + /// + /// Произведено за последний тик + /// + public int LastTickOutput { get; set; } + + /// + /// Простой (нет ресурсов) + /// + public bool IsIdle => ActiveProduction == null || !ActiveProduction.IsActive; +} + +/// +/// Склад (хранение) +/// +public class Warehouse : BusinessUnit +{ + public Warehouse() + { + Type = BusinessUnitType.Warehouse; + MaxEmployees = 10; + BuildCost = 30000m; + UpkeepCost = 500m; + } + + /// + /// Вместимость склада (единиц товара) + /// + public int Capacity { get; set; } = 10000; + + /// + /// Использовано места + /// + public int UsedCapacity => Inventory.Values.Sum(); + + /// + /// Свободно места + /// + public int FreeCapacity => Capacity - UsedCapacity; + + /// + /// Добавить товар на склад + /// + public bool AddToInventory(string productId, int quantity) + { + if (quantity > FreeCapacity) + { + return false; + } + + if (Inventory.ContainsKey(productId)) + { + Inventory[productId] += quantity; + } + else + { + Inventory[productId] = quantity; + } + + return true; + } + + /// + /// Взять товар со склада + /// + public int RemoveFromInventory(string productId, int quantity) + { + if (!Inventory.ContainsKey(productId)) + { + return 0; + } + + var actualQuantity = Math.Min(quantity, Inventory[productId]); + Inventory[productId] -= actualQuantity; + + if (Inventory[productId] == 0) + { + Inventory.Remove(productId); + } + + return actualQuantity; + } +} + +/// +/// Офис (управление компанией) +/// +public class Office : BusinessUnit +{ + public Office() + { + Type = BusinessUnitType.Office; + MaxEmployees = 50; + BuildCost = 100000m; + UpkeepCost = 3000m; + } + + /// + /// Бонус к управлению (влияет на эффективность других зданий) + /// + public int ManagementBonus { get; set; } = 5; + + /// + /// Количество управляемых зданий + /// + public int ManagedBuildings { get; set; } +} + +/// +/// Научный центр (исследования) +/// +public class ResearchLab : BusinessUnit +{ + public ResearchLab() + { + Type = BusinessUnitType.ResearchLab; + MaxEmployees = 30; + BuildCost = 150000m; + UpkeepCost = 4000m; + } + + /// + /// Текущее исследование + /// + public string? CurrentResearch { get; set; } + + /// + /// Прогресс исследования (0-100%) + /// + public int ResearchProgress { get; set; } + + /// + /// Наука за тик + /// + public int SciencePerTick => Employees * 10 * Efficiency / 100; + + /// + /// Начать исследование + /// + public void StartResearch(string technologyId) + { + CurrentResearch = technologyId; + ResearchProgress = 0; + } + + /// + /// Обновление исследования + /// + public override void Tick() + { + base.Tick(); + + if (CurrentResearch != null && ResearchProgress < 100) + { + ResearchProgress = Math.Min(100, ResearchProgress + SciencePerTick); + } + } +} diff --git a/backend/src/MyBiz.Core/Company.cs b/backend/src/MyBiz.Core/Company.cs index a1d228d..47ef743 100644 --- a/backend/src/MyBiz.Core/Company.cs +++ b/backend/src/MyBiz.Core/Company.cs @@ -7,44 +7,124 @@ public class Company { public Guid Id { get; set; } = Guid.NewGuid(); public string Name { get; set; } = string.Empty; - + + /// + /// ID владельца (пользователя) + /// + public Guid OwnerId { get; set; } + /// /// Доступные деньги /// public decimal Cash { get; set; } - + /// /// Активы компании /// public decimal Assets { get; set; } - + /// /// Пассивы (долги) /// public decimal Liabilities { get; set; } - + /// /// Прибыль за последний период /// public decimal LastPeriodProfit { get; set; } - + /// /// Список зданий компании /// public List Buildings { get; set; } = new(); - + + /// + /// Бизнес-единицы компании (магазины, фабрики и т.д.) + /// + public List BusinessUnits { get; set; } = new(); + /// /// Складские запасы /// public Dictionary Inventory { get; set; } = new(); - + /// /// Открытые технологии /// public HashSet UnlockedTechnologies { get; set; } = new(); - + + /// + /// Дата основания + /// + public DateTime FoundedAt { get; set; } = DateTime.UtcNow; + /// /// Рассчитать чистую стоимость /// public decimal NetWorth => Assets - Liabilities + Cash; + + /// + /// Получить все магазины + /// + public IEnumerable Shops => BusinessUnits.OfType(); + + /// + /// Получить все фабрики + /// + public IEnumerable Factories => BusinessUnits.OfType(); + + /// + /// Получить все склады + /// + public IEnumerable Warehouses => BusinessUnits.OfType(); + + /// + /// Добавить бизнес-единицу + /// + public void AddBusinessUnit(BusinessUnit unit) + { + unit.CompanyId = Id; + BusinessUnits.Add(unit); + } + + /// + /// Удалить бизнес-единицу + /// + public void RemoveBusinessUnit(Guid unitId) + { + var unit = BusinessUnits.FirstOrDefault(u => u.Id == unitId); + if (unit != null) + { + BusinessUnits.Remove(unit); + } + } + + /// + /// Обновить состояние всех единиц (вызывается каждый тик) + /// + public void Tick() + { + foreach (var unit in BusinessUnits) + { + unit.Tick(); + } + } + + /// + /// Рассчитать общую прибыль за период + /// + public decimal CalculateTotalProfit() + { + return BusinessUnits.Sum(u => u.PeriodProfit); + } + + /// + /// Рассчитать общую стоимость активов + /// + public decimal CalculateAssets() + { + return Buildings.Sum(b => b.TypeConfig?.BuildCost ?? 0) + + BusinessUnits.Sum(u => u.BuildCost) + + Inventory.Sum(i => i.Key.BasePrice * i.Value); + } } diff --git a/backend/src/MyBiz.Core/User.cs b/backend/src/MyBiz.Core/User.cs new file mode 100644 index 0000000..35b03a8 --- /dev/null +++ b/backend/src/MyBiz.Core/User.cs @@ -0,0 +1,90 @@ +namespace MyBiz.Core; + +/// +/// Пользователь системы +/// +public class User +{ + public Guid Id { get; set; } = Guid.NewGuid(); + + /// + /// Имя пользователя (логин) + /// + public string Username { get; set; } = string.Empty; + + /// + /// Email + /// + public string Email { get; set; } = string.Empty; + + /// + /// Хеш пароля (не хранит сам пароль) + /// + public string PasswordHash { get; set; } = string.Empty; + + /// + /// Дата регистрации + /// + public DateTime CreatedAt { get; set; } = DateTime.UtcNow; + + /// + /// Дата последнего входа + /// + public DateTime? LastLoginAt { get; set; } + + /// + /// Активен ли пользователь + /// + public bool IsActive { get; set; } = true; + + /// + /// Компания, принадлежащая пользователю + /// + public Company? Company { get; set; } + + /// + /// ID компании (для быстрого доступа) + /// + public Guid? CompanyId { get; set; } + + /// + /// Статистика игры + /// + public UserStats Stats { get; set; } = new(); +} + +/// +/// Статистика пользователя +/// +public class UserStats +{ + /// + /// Всего игр сыграно + /// + public int GamesPlayed { get; set; } + + /// + /// Всего часов в игре + /// + public int TotalHoursPlayed { get; set; } + + /// + /// Максимальная чистая стоимость за всё время + /// + public decimal MaxNetWorth { get; set; } + + /// + /// Всего зданий построено + /// + public int TotalBuildingsBuilt { get; set; } + + /// + /// Дата начала первой игры + /// + public DateTime? FirstGameDate { get; set; } + + /// + /// Дата последней игры + /// + public DateTime? LastGameDate { get; set; } +} diff --git a/backend/tests/MyBiz.Tests/UserTests.cs b/backend/tests/MyBiz.Tests/UserTests.cs new file mode 100644 index 0000000..7749731 --- /dev/null +++ b/backend/tests/MyBiz.Tests/UserTests.cs @@ -0,0 +1,561 @@ +using Xunit; +using MyBiz.Core; + +namespace MyBiz.Tests; + +public class UserTests +{ + [Fact] + public void User_Creation_ShouldInitializeProperties() + { + var user = new User + { + Username = "testuser", + Email = "test@example.com" + }; + + Assert.NotNull(user.Id); + Assert.Equal("testuser", user.Username); + Assert.Equal("test@example.com", user.Email); + Assert.True(user.IsActive); + Assert.Null(user.Company); + Assert.NotNull(user.Stats); + } + + [Fact] + public void User_Company_Assignment_ShouldWork() + { + var user = new User(); + var company = new Company { Name = "Test Corp" }; + + user.Company = company; + user.CompanyId = company.Id; + + Assert.NotNull(user.Company); + Assert.Equal(company.Id, user.CompanyId); + Assert.Equal("Test Corp", user.Company.Name); + } + + [Fact] + public void UserStats_ShouldInitializeDefaults() + { + var stats = new UserStats(); + + Assert.Equal(0, stats.GamesPlayed); + Assert.Equal(0, stats.TotalHoursPlayed); + Assert.Equal(0, stats.MaxNetWorth); + Assert.Equal(0, stats.TotalBuildingsBuilt); + Assert.Null(stats.FirstGameDate); + Assert.Null(stats.LastGameDate); + } +} + +public class AuthServiceTests +{ + [Fact] + public void AuthService_Register_ShouldCreateUser() + { + var authService = new AuthService(); + + var result = authService.Register("testuser", "test@example.com", "password123", "Test Corp"); + + Assert.True(result.Success); + Assert.NotNull(result.User); + Assert.NotNull(result.Token); + Assert.Equal("testuser", result.User.Username); + Assert.NotNull(result.User.Company); + Assert.Equal("Test Corp", result.User.Company.Name); + } + + [Fact] + public void AuthService_Register_DuplicateUsername_ShouldFail() + { + var authService = new AuthService(); + + authService.Register("testuser", "test@example.com", "password123", "Test Corp"); + var result = authService.Register("testuser", "other@example.com", "password456", "Other Corp"); + + Assert.False(result.Success); + Assert.NotNull(result.ErrorMessage); + Assert.Null(result.User); + } + + [Fact] + public void AuthService_Login_CorrectCredentials_ShouldSucceed() + { + var authService = new AuthService(); + + authService.Register("testuser", "test@example.com", "password123", "Test Corp"); + var result = authService.Login("testuser", "password123"); + + Assert.True(result.Success); + Assert.NotNull(result.User); + Assert.NotNull(result.Token); + Assert.Equal("testuser", result.User.Username); + } + + [Fact] + public void AuthService_Login_WrongPassword_ShouldFail() + { + var authService = new AuthService(); + + authService.Register("testuser", "test@example.com", "password123", "Test Corp"); + var result = authService.Login("testuser", "wrongpassword"); + + Assert.False(result.Success); + Assert.NotNull(result.ErrorMessage); + Assert.Null(result.User); + } + + [Fact] + public void AuthService_Login_NonExistentUser_ShouldFail() + { + var authService = new AuthService(); + + var result = authService.Login("nonexistent", "password123"); + + Assert.False(result.Success); + Assert.NotNull(result.ErrorMessage); + Assert.Null(result.User); + } + + [Fact] + public void AuthService_Logout_ShouldInvalidateToken() + { + var authService = new AuthService(); + + var registerResult = authService.Register("testuser", "test@example.com", "password123", "Test Corp"); + var token = registerResult.Token!; + + authService.Logout(token); + var user = authService.ValidateToken(token); + + Assert.Null(user); + } + + [Fact] + public void AuthService_ValidateToken_ValidToken_ShouldReturnUser() + { + var authService = new AuthService(); + + var result = authService.Register("testuser", "test@example.com", "password123", "Test Corp"); + var user = authService.ValidateToken(result.Token!); + + Assert.NotNull(user); + Assert.Equal("testuser", user.Username); + } + + [Fact] + public void AuthService_GetUserById_ShouldReturnUser() + { + var authService = new AuthService(); + + var result = authService.Register("testuser", "test@example.com", "password123", "Test Corp"); + var user = authService.GetUserById(result.User!.Id); + + Assert.NotNull(user); + Assert.Equal(result.User.Id, user.Id); + } +} + +public class BusinessUnitTests +{ + [Fact] + public void BusinessUnit_Creation_ShouldInitializeProperties() + { + var unit = new BusinessUnit + { + Name = "Test Unit", + Type = BusinessUnitType.Shop, + CompanyId = Guid.NewGuid(), + CityId = "city_1", + X = 100, + Y = 200 + }; + + Assert.NotNull(unit.Id); + Assert.Equal("Test Unit", unit.Name); + Assert.Equal(BusinessUnitType.Shop, unit.Type); + Assert.Equal(1, unit.Level); + Assert.Equal(100, unit.Efficiency); + Assert.True(unit.IsActive); + } + + [Fact] + public void BusinessUnit_HireEmployee_ShouldIncreaseCount() + { + var unit = new BusinessUnit { MaxEmployees = 10 }; + + var hired = unit.HireEmployee(); + + Assert.True(hired); + Assert.Equal(1, unit.Employees); + } + + [Fact] + public void BusinessUnit_HireEmployee_FullCapacity_ShouldFail() + { + var unit = new BusinessUnit { MaxEmployees = 1, Employees = 1 }; + + var hired = unit.HireEmployee(); + + Assert.False(hired); + Assert.Equal(1, unit.Employees); + } + + [Fact] + public void BusinessUnit_FireEmployee_ShouldDecreaseCount() + { + var unit = new BusinessUnit { Employees = 5 }; + + var fired = unit.FireEmployee(); + + Assert.True(fired); + Assert.Equal(4, unit.Employees); + } + + [Fact] + public void BusinessUnit_FireEmployee_NoEmployees_ShouldFail() + { + var unit = new BusinessUnit { Employees = 0 }; + + var fired = unit.FireEmployee(); + + Assert.False(fired); + Assert.Equal(0, unit.Employees); + } + + [Fact] + public void BusinessUnit_Upgrade_ShouldIncreaseLevel() + { + var unit = new BusinessUnit { Level = 1, MaxLevel = 10 }; + + var upgraded = unit.Upgrade(); + + Assert.True(upgraded); + Assert.Equal(2, unit.Level); + } + + [Fact] + public void BusinessUnit_Upgrade_MaxLevel_ShouldFail() + { + var unit = new BusinessUnit { Level = 10, MaxLevel = 10 }; + + var upgraded = unit.Upgrade(); + + Assert.False(upgraded); + Assert.Equal(10, unit.Level); + } + + [Fact] + public void BusinessUnit_UpdateEfficiency_FullStaff_ShouldBeMax() + { + var unit = new BusinessUnit { MaxEmployees = 10, Employees = 10 }; + + unit.UpdateEfficiency(); + + Assert.Equal(100, unit.Efficiency); + } + + [Fact] + public void BusinessUnit_UpdateEfficiency_HalfStaff_ShouldBeReduced() + { + var unit = new BusinessUnit { MaxEmployees = 10, Employees = 5 }; + + unit.UpdateEfficiency(); + + Assert.Equal(50, unit.Efficiency); + } + + [Fact] + public void BusinessUnit_UpdateEfficiency_NoStaff_ShouldBeZero() + { + var unit = new BusinessUnit { MaxEmployees = 10, Employees = 0 }; + + unit.UpdateEfficiency(); + + Assert.Equal(0, unit.Efficiency); + } +} + +public class ShopTests +{ + [Fact] + public void Shop_Creation_ShouldInitializeDefaults() + { + var shop = new Shop(); + + Assert.Equal(BusinessUnitType.Shop, shop.Type); + Assert.Equal(20, shop.MaxEmployees); + Assert.Equal(50000m, shop.BuildCost); + Assert.Equal(1000m, shop.UpkeepCost); + Assert.NotNull(shop.SoldProductTypes); + Assert.Equal(0, shop.DailyRevenue); + Assert.Equal(0, shop.DailyCustomers); + } + + [Fact] + public void Shop_AverageCheck_Calculation_ShouldBeCorrect() + { + var shop = new Shop { DailyRevenue = 1000m, DailyCustomers = 10 }; + + var avgCheck = shop.AverageCheck; + + Assert.Equal(100m, avgCheck); + } + + [Fact] + public void Shop_AverageCheck_NoCustomers_ShouldBeZero() + { + var shop = new Shop { DailyRevenue = 1000m, DailyCustomers = 0 }; + + var avgCheck = shop.AverageCheck; + + Assert.Equal(0, avgCheck); + } +} + +public class FactoryTests +{ + [Fact] + public void Factory_Creation_ShouldInitializeDefaults() + { + var factory = new Factory(); + + Assert.Equal(BusinessUnitType.Factory, factory.Type); + Assert.Equal(100, factory.MaxEmployees); + Assert.Equal(200000m, factory.BuildCost); + Assert.Equal(5000m, factory.UpkeepCost); + Assert.Null(factory.OutputProductId); + Assert.NotNull(factory.RequiredInputs); + } + + [Fact] + public void Factory_IsIdle_NoProduction_ShouldBeTrue() + { + var factory = new Factory(); + + Assert.True(factory.IsIdle); + } + + [Fact] + public void Factory_IsIdle_ActiveProduction_ShouldBeFalse() + { + var factory = new Factory(); + var config = new ProductionChainConfig + { + Id = "test_chain", + OutputProductId = "product_1", + RequiredBuildingId = "building_1" + }; + factory.ActiveProduction = new ActiveProductionChain + { + Config = config, + Building = new Building(), + IsActive = true + }; + + Assert.False(factory.IsIdle); + } +} + +public class WarehouseTests +{ + [Fact] + public void Warehouse_Creation_ShouldInitializeDefaults() + { + var warehouse = new Warehouse(); + + Assert.Equal(BusinessUnitType.Warehouse, warehouse.Type); + Assert.Equal(10, warehouse.MaxEmployees); + Assert.Equal(30000m, warehouse.BuildCost); + Assert.Equal(500m, warehouse.UpkeepCost); + Assert.Equal(10000, warehouse.Capacity); + } + + [Fact] + public void Warehouse_AddToInventory_ShouldIncreaseStock() + { + var warehouse = new Warehouse(); + + var added = warehouse.AddToInventory("product_1", 100); + + Assert.True(added); + Assert.Equal(100, warehouse.Inventory["product_1"]); + Assert.Equal(100, warehouse.UsedCapacity); + } + + [Fact] + public void Warehouse_AddToInventory_NoCapacity_ShouldFail() + { + var warehouse = new Warehouse { Capacity = 50 }; + + var added = warehouse.AddToInventory("product_1", 100); + + Assert.False(added); + Assert.Empty(warehouse.Inventory); + } + + [Fact] + public void Warehouse_RemoveFromInventory_ShouldDecreaseStock() + { + var warehouse = new Warehouse(); + warehouse.AddToInventory("product_1", 100); + + var removed = warehouse.RemoveFromInventory("product_1", 30); + + Assert.Equal(30, removed); + Assert.Equal(70, warehouse.Inventory["product_1"]); + } + + [Fact] + public void Warehouse_RemoveFromInventory_NoStock_ShouldReturnZero() + { + var warehouse = new Warehouse(); + + var removed = warehouse.RemoveFromInventory("product_1", 30); + + Assert.Equal(0, removed); + } + + [Fact] + public void Warehouse_FreeCapacity_Calculation_ShouldBeCorrect() + { + var warehouse = new Warehouse { Capacity = 1000 }; + warehouse.AddToInventory("product_1", 300); + warehouse.AddToInventory("product_2", 200); + + var freeCapacity = warehouse.FreeCapacity; + + Assert.Equal(500, freeCapacity); + } +} + +public class ResearchLabTests +{ + [Fact] + public void ResearchLab_Creation_ShouldInitializeDefaults() + { + var lab = new ResearchLab(); + + Assert.Equal(BusinessUnitType.ResearchLab, lab.Type); + Assert.Equal(30, lab.MaxEmployees); + Assert.Equal(150000m, lab.BuildCost); + Assert.Equal(4000m, lab.UpkeepCost); + Assert.Null(lab.CurrentResearch); + Assert.Equal(0, lab.ResearchProgress); + } + + [Fact] + public void ResearchLab_StartResearch_ShouldSetCurrentResearch() + { + var lab = new ResearchLab(); + + lab.StartResearch("tech_advanced_production"); + + Assert.Equal("tech_advanced_production", lab.CurrentResearch); + Assert.Equal(0, lab.ResearchProgress); + } + + [Fact] + public void ResearchLab_SciencePerTick_Calculation_ShouldBeCorrect() + { + var lab = new ResearchLab { Employees = 10, Efficiency = 100 }; + + var science = lab.SciencePerTick; + + Assert.Equal(100, science); + } + + [Fact] + public void ResearchLab_Tick_ShouldIncreaseProgress() + { + var lab = new ResearchLab { Employees = 10, Efficiency = 100 }; + lab.StartResearch("tech_advanced_production"); + + lab.Tick(); + + Assert.Equal(33, lab.ResearchProgress); // 10 * 10 * 100 / 100 = 100, но ограничено 33 за тик + } +} + +public class CompanyBusinessUnitTests +{ + [Fact] + public void Company_AddBusinessUnit_ShouldAddToList() + { + var company = new Company { Name = "Test Corp" }; + var shop = new Shop { Name = "My Shop" }; + + company.AddBusinessUnit(shop); + + Assert.Single(company.BusinessUnits); + Assert.Equal(company.Id, shop.CompanyId); + } + + [Fact] + public void Company_RemoveBusinessUnit_ShouldRemoveFromList() + { + var company = new Company(); + var shop = new Shop(); + company.AddBusinessUnit(shop); + + company.RemoveBusinessUnit(shop.Id); + + Assert.Empty(company.BusinessUnits); + } + + [Fact] + public void Company_Shops_Filter_ShouldReturnOnlyShops() + { + var company = new Company(); + company.AddBusinessUnit(new Shop()); + company.AddBusinessUnit(new Factory()); + company.AddBusinessUnit(new Shop()); + + var shops = company.Shops.ToList(); + + Assert.Equal(2, shops.Count); + Assert.All(shops, s => Assert.IsType(s)); + } + + [Fact] + public void Company_Factories_Filter_ShouldReturnOnlyFactories() + { + var company = new Company(); + company.AddBusinessUnit(new Shop()); + company.AddBusinessUnit(new Factory()); + company.AddBusinessUnit(new Factory()); + + var factories = company.Factories.ToList(); + + Assert.Equal(2, factories.Count); + Assert.All(factories, f => Assert.IsType(f)); + } + + [Fact] + public void Company_CalculateTotalProfit_ShouldSumAllUnits() + { + var company = new Company(); + var shop1 = new Shop { LastPeriodIncome = 1000m, LastPeriodExpenses = 500m }; + var shop2 = new Shop { LastPeriodIncome = 2000m, LastPeriodExpenses = 800m }; + company.AddBusinessUnit(shop1); + company.AddBusinessUnit(shop2); + + var totalProfit = company.CalculateTotalProfit(); + + Assert.Equal(1700m, totalProfit); // (1000-500) + (2000-800) + } + + [Fact] + public void Company_Tick_ShouldUpdateAllUnits() + { + var company = new Company(); + var shop = new Shop { Employees = 5, MaxEmployees = 10 }; + company.AddBusinessUnit(shop); + + company.Tick(); + + Assert.Equal(50, shop.Efficiency); // 5/10 = 50% + } +} diff --git a/buffer.md b/buffer.md new file mode 100644 index 0000000..6dfa2b2 --- /dev/null +++ b/buffer.md @@ -0,0 +1,4 @@ +## **sdfsdfsdfsdf** + +Ghbdt +