Initial commit of LittleShop project (excluding large archives)

- BTCPay Server integration
- TeleBot Telegram bot
- Review system
- Admin area
- Docker deployment configuration

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2025-09-17 15:07:38 +01:00
parent bcca00ab39
commit e1b377a042
140 changed files with 32166 additions and 21089 deletions

View File

@@ -1,308 +1,325 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Bogus;
using LittleShop.Client;
using LittleShop.Client.Models;
using Microsoft.Extensions.Logging;
namespace TeleBotClient
{
public class BotSimulator
{
private readonly ILittleShopClient _client;
private readonly ILogger<BotSimulator> _logger;
private readonly Random _random;
private readonly Faker _faker;
private List<Category> _categories = new();
private List<Product> _products = new();
public BotSimulator(ILittleShopClient client, ILogger<BotSimulator> logger)
{
_client = client;
_logger = logger;
_random = new Random();
_faker = new Faker();
}
public async Task<SimulationResult> SimulateUserSession()
{
var result = new SimulationResult
{
SessionId = Guid.NewGuid().ToString(),
StartTime = DateTime.UtcNow
};
try
{
_logger.LogInformation("🤖 Starting bot simulation session {SessionId}", result.SessionId);
// Step 1: Authenticate
_logger.LogInformation("📝 Authenticating with API...");
if (!await Authenticate())
{
result.Success = false;
result.ErrorMessage = "Authentication failed";
return result;
}
result.Steps.Add("✅ Authentication successful");
// Step 2: Browse categories
_logger.LogInformation("📁 Browsing categories...");
await BrowseCategories();
result.Steps.Add($"✅ Found {_categories.Count} categories");
// Step 3: Select random category and browse products
if (_categories.Any())
{
var selectedCategory = _categories[_random.Next(_categories.Count)];
_logger.LogInformation("🔍 Selected category: {Category}", selectedCategory.Name);
result.Steps.Add($"✅ Selected category: {selectedCategory.Name}");
await BrowseProducts(selectedCategory.Id);
result.Steps.Add($"✅ Found {_products.Count} products in category");
}
else
{
// Browse all products if no categories
await BrowseProducts(null);
result.Steps.Add($"✅ Found {_products.Count} total products");
}
// Step 4: Build shopping cart
_logger.LogInformation("🛒 Building shopping cart...");
var cart = BuildRandomCart();
result.Cart = cart;
result.Steps.Add($"✅ Added {cart.Items.Count} items to cart (Total: ${cart.TotalAmount:F2})");
// Step 5: Generate shipping information
_logger.LogInformation("📦 Generating shipping information...");
var shippingInfo = GenerateShippingInfo();
result.ShippingInfo = shippingInfo;
result.Steps.Add($"✅ Generated shipping to {shippingInfo.City}, {shippingInfo.Country}");
// Step 6: Create order
_logger.LogInformation("📝 Creating order...");
var order = await CreateOrder(cart, shippingInfo);
if (order != null)
{
result.OrderId = order.Id;
result.OrderTotal = order.TotalAmount;
result.Steps.Add($"✅ Order created: {order.Id}");
// Step 7: Select payment method
var currency = SelectRandomCurrency();
_logger.LogInformation("💰 Selected payment method: {Currency}", currency);
result.PaymentCurrency = currency;
result.Steps.Add($"✅ Selected payment: {currency}");
// Step 8: Create payment
var payment = await CreatePayment(order.Id, currency);
if (payment != null)
{
result.PaymentId = payment.Id;
result.PaymentAddress = payment.WalletAddress;
result.PaymentAmount = payment.RequiredAmount;
result.Steps.Add($"✅ Payment created: {payment.RequiredAmount} {currency}");
}
}
result.Success = true;
result.EndTime = DateTime.UtcNow;
result.Duration = result.EndTime - result.StartTime;
_logger.LogInformation("✅ Simulation completed successfully in {Duration}", result.Duration);
}
catch (Exception ex)
{
_logger.LogError(ex, "❌ Simulation failed");
result.Success = false;
result.ErrorMessage = ex.Message;
result.EndTime = DateTime.UtcNow;
result.Duration = result.EndTime - result.StartTime;
}
return result;
}
private async Task<bool> Authenticate()
{
var result = await _client.Authentication.LoginAsync("admin", "admin");
if (result.IsSuccess && result.Data != null && !string.IsNullOrEmpty(result.Data.Token))
{
_client.Authentication.SetToken(result.Data.Token);
return true;
}
return false;
}
private async Task BrowseCategories()
{
var result = await _client.Catalog.GetCategoriesAsync();
if (result.IsSuccess && result.Data != null)
{
_categories = result.Data;
}
}
private async Task BrowseProducts(Guid? categoryId)
{
var result = await _client.Catalog.GetProductsAsync(
pageNumber: 1,
pageSize: 50,
categoryId: categoryId
);
if (result.IsSuccess && result.Data != null)
{
_products = result.Data.Items;
}
}
private ShoppingCart BuildRandomCart()
{
var cart = new ShoppingCart();
if (!_products.Any())
return cart;
// Random number of items (1-5)
var itemCount = _random.Next(1, Math.Min(6, _products.Count + 1));
var selectedProducts = _products.OrderBy(x => _random.Next()).Take(itemCount).ToList();
foreach (var product in selectedProducts)
{
var quantity = _random.Next(1, 4); // 1-3 items
cart.AddItem(product.Id, product.Name, product.Price, quantity);
_logger.LogDebug("Added {Quantity}x {Product} @ ${Price}",
quantity, product.Name, product.Price);
}
return cart;
}
private ShippingInfo GenerateShippingInfo()
{
return new ShippingInfo
{
IdentityReference = $"SIM-{Guid.NewGuid().ToString().Substring(0, 8).ToUpper()}",
Name = _faker.Name.FullName(),
Address = _faker.Address.StreetAddress(),
City = _faker.Address.City(),
PostCode = _faker.Address.ZipCode(),
Country = _faker.PickRandom(new[] { "United Kingdom", "Ireland", "France", "Germany", "Netherlands" }),
Notes = _faker.Lorem.Sentence()
};
}
private async Task<Order?> CreateOrder(ShoppingCart cart, ShippingInfo shipping)
{
var request = new CreateOrderRequest
{
IdentityReference = shipping.IdentityReference,
ShippingName = shipping.Name,
ShippingAddress = shipping.Address,
ShippingCity = shipping.City,
ShippingPostCode = shipping.PostCode,
ShippingCountry = shipping.Country,
Notes = shipping.Notes,
Items = cart.Items.Select(i => new CreateOrderItem
{
ProductId = i.ProductId,
Quantity = i.Quantity
}).ToList()
};
var result = await _client.Orders.CreateOrderAsync(request);
return result.IsSuccess ? result.Data : null;
}
private string SelectRandomCurrency()
{
var currencies = new[] { "BTC", "XMR", "USDT", "LTC", "ETH", "ZEC", "DASH", "DOGE" };
// Weight towards BTC and XMR
var weights = new[] { 30, 25, 15, 10, 10, 5, 3, 2 };
var totalWeight = weights.Sum();
var randomValue = _random.Next(totalWeight);
var currentWeight = 0;
for (int i = 0; i < currencies.Length; i++)
{
currentWeight += weights[i];
if (randomValue < currentWeight)
return currencies[i];
}
return "BTC";
}
private async Task<CryptoPayment?> CreatePayment(Guid orderId, string currency)
{
var result = await _client.Orders.CreatePaymentAsync(orderId, currency);
return result.IsSuccess ? result.Data : null;
}
}
public class SimulationResult
{
public string SessionId { get; set; } = string.Empty;
public bool Success { get; set; }
public string? ErrorMessage { get; set; }
public DateTime StartTime { get; set; }
public DateTime EndTime { get; set; }
public TimeSpan Duration { get; set; }
public List<string> Steps { get; set; } = new();
// Order details
public Guid? OrderId { get; set; }
public decimal OrderTotal { get; set; }
public ShoppingCart? Cart { get; set; }
public ShippingInfo? ShippingInfo { get; set; }
// Payment details
public Guid? PaymentId { get; set; }
public string? PaymentCurrency { get; set; }
public string? PaymentAddress { get; set; }
public decimal PaymentAmount { get; set; }
}
public class ShoppingCart
{
public List<CartItem> Items { get; set; } = new();
public decimal TotalAmount => Items.Sum(i => i.TotalPrice);
public void AddItem(Guid productId, string name, decimal price, int quantity)
{
Items.Add(new CartItem
{
ProductId = productId,
ProductName = name,
UnitPrice = price,
Quantity = quantity,
TotalPrice = price * quantity
});
}
}
public class CartItem
{
public Guid ProductId { get; set; }
public string ProductName { get; set; } = string.Empty;
public decimal UnitPrice { get; set; }
public int Quantity { get; set; }
public decimal TotalPrice { get; set; }
}
public class ShippingInfo
{
public string IdentityReference { get; set; } = string.Empty;
public string Name { get; set; } = string.Empty;
public string Address { get; set; } = string.Empty;
public string City { get; set; } = string.Empty;
public string PostCode { get; set; } = string.Empty;
public string Country { get; set; } = string.Empty;
public string? Notes { get; set; }
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Bogus;
using LittleShop.Client;
using LittleShop.Client.Models;
using Microsoft.Extensions.Logging;
namespace TeleBotClient
{
public class BotSimulator
{
private readonly ILittleShopClient _client;
private readonly ILogger<BotSimulator> _logger;
private readonly Random _random;
private readonly Faker _faker;
private List<Category> _categories = new();
private List<Product> _products = new();
public BotSimulator(ILittleShopClient client, ILogger<BotSimulator> logger)
{
_client = client;
_logger = logger;
_random = new Random();
_faker = new Faker();
}
public async Task<SimulationResult> SimulateUserSession()
{
var result = new SimulationResult
{
SessionId = Guid.NewGuid().ToString(),
StartTime = DateTime.UtcNow
};
try
{
_logger.LogInformation("🤖 Starting bot simulation session {SessionId}", result.SessionId);
// Step 1: Authenticate
_logger.LogInformation("📝 Authenticating with API...");
if (!await Authenticate())
{
result.Success = false;
result.ErrorMessage = "Authentication failed";
return result;
}
result.Steps.Add("✅ Authentication successful");
// Step 2: Browse categories
_logger.LogInformation("📁 Browsing categories...");
await BrowseCategories();
result.Steps.Add($"✅ Found {_categories.Count} categories");
// Step 3: Select random category and browse products
if (_categories.Any())
{
var selectedCategory = _categories[_random.Next(_categories.Count)];
_logger.LogInformation("🔍 Selected category: {Category}", selectedCategory.Name);
result.Steps.Add($"✅ Selected category: {selectedCategory.Name}");
await BrowseProducts(selectedCategory.Id);
result.Steps.Add($"✅ Found {_products.Count} products in category");
}
else
{
// Browse all products if no categories
await BrowseProducts(null);
result.Steps.Add($"✅ Found {_products.Count} total products");
}
// Step 4: Build shopping cart
_logger.LogInformation("🛒 Building shopping cart...");
var cart = BuildRandomCart();
result.Cart = cart;
result.Steps.Add($"✅ Added {cart.Items.Count} items to cart (Total: ${cart.TotalAmount:F2})");
// Step 5: Generate shipping information
_logger.LogInformation("📦 Generating shipping information...");
var shippingInfo = GenerateShippingInfo();
result.ShippingInfo = shippingInfo;
result.Steps.Add($"✅ Generated shipping to {shippingInfo.City}, {shippingInfo.Country}");
// Step 6: Create order
_logger.LogInformation("📝 Creating order...");
var order = await CreateOrder(cart, shippingInfo);
if (order != null)
{
result.OrderId = order.Id;
result.OrderTotal = order.TotalAmount;
result.Steps.Add($"✅ Order created: {order.Id}");
// Step 7: Select payment method
var currency = SelectRandomCurrency();
_logger.LogInformation("💰 Selected payment method: {Currency}", currency);
result.PaymentCurrency = currency;
result.Steps.Add($"✅ Selected payment: {currency}");
// Step 8: Create payment
var payment = await CreatePayment(order.Id, currency);
if (payment != null)
{
result.PaymentId = payment.Id;
result.PaymentAddress = payment.WalletAddress;
result.PaymentAmount = payment.RequiredAmount;
result.Steps.Add($"✅ Payment created: {payment.RequiredAmount} {currency}");
}
}
result.Success = true;
result.EndTime = DateTime.UtcNow;
result.Duration = result.EndTime - result.StartTime;
_logger.LogInformation("✅ Simulation completed successfully in {Duration}", result.Duration);
}
catch (Exception ex)
{
_logger.LogError(ex, "❌ Simulation failed");
result.Success = false;
result.ErrorMessage = ex.Message;
result.EndTime = DateTime.UtcNow;
result.Duration = result.EndTime - result.StartTime;
}
return result;
}
private async Task<bool> Authenticate()
{
var result = await _client.Authentication.LoginAsync("admin", "admin");
if (result.IsSuccess && result.Data != null && !string.IsNullOrEmpty(result.Data.Token))
{
_client.Authentication.SetToken(result.Data.Token);
return true;
}
return false;
}
private async Task BrowseCategories()
{
var result = await _client.Catalog.GetCategoriesAsync();
if (result.IsSuccess && result.Data != null)
{
_categories = result.Data;
}
}
private async Task BrowseProducts(Guid? categoryId)
{
var result = await _client.Catalog.GetProductsAsync(
pageNumber: 1,
pageSize: 50,
categoryId: categoryId
);
if (result.IsSuccess && result.Data != null)
{
_products = result.Data.Items;
}
}
private ShoppingCart BuildRandomCart()
{
var cart = new ShoppingCart();
if (!_products.Any())
return cart;
// Random number of items (1-5)
var itemCount = _random.Next(1, Math.Min(6, _products.Count + 1));
var selectedProducts = _products.OrderBy(x => _random.Next()).Take(itemCount).ToList();
foreach (var product in selectedProducts)
{
var quantity = _random.Next(1, 4); // 1-3 items
cart.AddItem(product.Id, product.Name, product.Price, quantity);
_logger.LogDebug("Added {Quantity}x {Product} @ ${Price}",
quantity, product.Name, product.Price);
}
return cart;
}
private ShippingInfo GenerateShippingInfo()
{
return new ShippingInfo
{
IdentityReference = $"SIM-{Guid.NewGuid().ToString().Substring(0, 8).ToUpper()}",
Name = _faker.Name.FullName(),
Address = _faker.Address.StreetAddress(),
City = _faker.Address.City(),
PostCode = _faker.Address.ZipCode(),
Country = _faker.PickRandom(new[] { "United Kingdom", "Ireland", "France", "Germany", "Netherlands" }),
Notes = _faker.Lorem.Sentence()
};
}
private async Task<Order?> CreateOrder(ShoppingCart cart, ShippingInfo shipping)
{
var request = new CreateOrderRequest
{
IdentityReference = shipping.IdentityReference,
ShippingName = shipping.Name,
ShippingAddress = shipping.Address,
ShippingCity = shipping.City,
ShippingPostCode = shipping.PostCode,
ShippingCountry = shipping.Country,
Notes = shipping.Notes,
Items = cart.Items.Select(i => new CreateOrderItem
{
ProductId = i.ProductId,
Quantity = i.Quantity
}).ToList()
};
var result = await _client.Orders.CreateOrderAsync(request);
return result.IsSuccess ? result.Data : null;
}
private string SelectRandomCurrency()
{
var currencies = new[] { "BTC", "XMR", "USDT", "LTC", "ETH", "ZEC", "DASH", "DOGE" };
// Weight towards BTC and XMR
var weights = new[] { 30, 25, 15, 10, 10, 5, 3, 2 };
var totalWeight = weights.Sum();
var randomValue = _random.Next(totalWeight);
var currentWeight = 0;
for (int i = 0; i < currencies.Length; i++)
{
currentWeight += weights[i];
if (randomValue < currentWeight)
return currencies[i];
}
return "BTC";
}
private async Task<CryptoPayment?> CreatePayment(Guid orderId, string currency)
{
var currencyInt = ConvertCurrencyToEnum(currency);
var result = await _client.Orders.CreatePaymentAsync(orderId, currencyInt);
return result.IsSuccess ? result.Data : null;
}
private static int ConvertCurrencyToEnum(string currency)
{
return currency.ToUpper() switch
{
"BTC" => 0,
"XMR" => 1,
"USDT" => 2,
"LTC" => 3,
"ETH" => 4,
"ZEC" => 5,
"DASH" => 6,
"DOGE" => 7,
_ => 0 // Default to BTC
};
}
}
public class SimulationResult
{
public string SessionId { get; set; } = string.Empty;
public bool Success { get; set; }
public string? ErrorMessage { get; set; }
public DateTime StartTime { get; set; }
public DateTime EndTime { get; set; }
public TimeSpan Duration { get; set; }
public List<string> Steps { get; set; } = new();
// Order details
public Guid? OrderId { get; set; }
public decimal OrderTotal { get; set; }
public ShoppingCart? Cart { get; set; }
public ShippingInfo? ShippingInfo { get; set; }
// Payment details
public Guid? PaymentId { get; set; }
public string? PaymentCurrency { get; set; }
public string? PaymentAddress { get; set; }
public decimal PaymentAmount { get; set; }
}
public class ShoppingCart
{
public List<CartItem> Items { get; set; } = new();
public decimal TotalAmount => Items.Sum(i => i.TotalPrice);
public void AddItem(Guid productId, string name, decimal price, int quantity)
{
Items.Add(new CartItem
{
ProductId = productId,
ProductName = name,
UnitPrice = price,
Quantity = quantity,
TotalPrice = price * quantity
});
}
}
public class CartItem
{
public Guid ProductId { get; set; }
public string ProductName { get; set; } = string.Empty;
public decimal UnitPrice { get; set; }
public int Quantity { get; set; }
public decimal TotalPrice { get; set; }
}
public class ShippingInfo
{
public string IdentityReference { get; set; } = string.Empty;
public string Name { get; set; } = string.Empty;
public string Address { get; set; } = string.Empty;
public string City { get; set; } = string.Empty;
public string PostCode { get; set; } = string.Empty;
public string Country { get; set; } = string.Empty;
public string? Notes { get; set; }
}
}

View File

@@ -1,96 +1,96 @@
using System;
using System.Net.Http;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;
class TelegramClient
{
private static readonly string BotToken = "7330819864:AAHx9GEL-G-WeH2ON5-ncdsbbhV6YaOqZzg";
private static readonly string ApiUrl = $"https://api.telegram.org/bot{BotToken}/";
static async Task Main()
{
Console.WriteLine("Telegram Bot Client Started...");
while (true)
{
await ReceiveMessagesAsync();
await Task.Delay(5000); // Polling delay
}
}
private static async Task SendMessageAsync(string chatId, string message)
{
using HttpClient client = new HttpClient();
var payload = new
{
chat_id = chatId,
text = message
};
var content = new StringContent(JsonSerializer.Serialize(payload), Encoding.UTF8, "application/json");
try
{
var response = await client.PostAsync(ApiUrl + "sendMessage", content);
var responseText = await response.Content.ReadAsStringAsync();
Console.WriteLine($"Response: {responseText}");
}
catch (Exception ex)
{
Console.WriteLine($"Error sending message: {ex.Message}");
}
}
private static async Task ReceiveMessagesAsync()
{
using HttpClient client = new HttpClient();
try
{
var response = await client.GetAsync(ApiUrl + "getUpdates");
response.EnsureSuccessStatusCode();
var responseText = await response.Content.ReadAsStringAsync();
var updates = JsonSerializer.Deserialize<TelegramUpdateResponse>(responseText);
if (updates?.Result != null)
{
foreach (var update in updates.Result)
{
Console.WriteLine($"Received message from {update.Message.Chat.Id}: {update.Message.Text}");
await SendMessageAsync(update.Message.Chat.Id.ToString(), "Message received!");
}
}
}
catch (HttpRequestException httpEx)
{
Console.WriteLine($"HTTP error: {httpEx.Message}");
}
catch (Exception ex)
{
Console.WriteLine($"Unexpected error: {ex.Message}");
}
}
}
class TelegramUpdateResponse
{
public TelegramUpdate[] Result { get; set; }
}
class TelegramUpdate
{
public TelegramMessage Message { get; set; }
}
class TelegramMessage
{
public TelegramChat Chat { get; set; }
public string Text { get; set; }
}
class TelegramChat
{
public long Id { get; set; }
using System;
using System.Net.Http;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;
class TelegramClient
{
private static readonly string BotToken = "7330819864:AAHx9GEL-G-WeH2ON5-ncdsbbhV6YaOqZzg";
private static readonly string ApiUrl = $"https://api.telegram.org/bot{BotToken}/";
static async Task Main()
{
Console.WriteLine("Telegram Bot Client Started...");
while (true)
{
await ReceiveMessagesAsync();
await Task.Delay(5000); // Polling delay
}
}
private static async Task SendMessageAsync(string chatId, string message)
{
using HttpClient client = new HttpClient();
var payload = new
{
chat_id = chatId,
text = message
};
var content = new StringContent(JsonSerializer.Serialize(payload), Encoding.UTF8, "application/json");
try
{
var response = await client.PostAsync(ApiUrl + "sendMessage", content);
var responseText = await response.Content.ReadAsStringAsync();
Console.WriteLine($"Response: {responseText}");
}
catch (Exception ex)
{
Console.WriteLine($"Error sending message: {ex.Message}");
}
}
private static async Task ReceiveMessagesAsync()
{
using HttpClient client = new HttpClient();
try
{
var response = await client.GetAsync(ApiUrl + "getUpdates");
response.EnsureSuccessStatusCode();
var responseText = await response.Content.ReadAsStringAsync();
var updates = JsonSerializer.Deserialize<TelegramUpdateResponse>(responseText);
if (updates?.Result != null)
{
foreach (var update in updates.Result)
{
Console.WriteLine($"Received message from {update.Message.Chat.Id}: {update.Message.Text}");
await SendMessageAsync(update.Message.Chat.Id.ToString(), "Message received!");
}
}
}
catch (HttpRequestException httpEx)
{
Console.WriteLine($"HTTP error: {httpEx.Message}");
}
catch (Exception ex)
{
Console.WriteLine($"Unexpected error: {ex.Message}");
}
}
}
class TelegramUpdateResponse
{
public TelegramUpdate[] Result { get; set; }
}
class TelegramUpdate
{
public TelegramMessage Message { get; set; }
}
class TelegramMessage
{
public TelegramChat Chat { get; set; }
public string Text { get; set; }
}
class TelegramChat
{
public long Id { get; set; }
}

View File

@@ -1,367 +1,367 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using LittleShop.Client.Extensions;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Serilog;
using TeleBotClient;
namespace TeleBotClient
{
public class SimulatorProgram
{
public static async Task Main(string[] args)
{
// Configure Serilog
Log.Logger = new LoggerConfiguration()
.MinimumLevel.Debug()
.WriteTo.Console(outputTemplate: "[{Timestamp:HH:mm:ss} {Level:u3}] {Message:lj}{NewLine}{Exception}")
.WriteTo.File("logs/simulator-.txt", rollingInterval: RollingInterval.Day)
.CreateLogger();
try
{
// Build configuration
var configuration = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("appsettings.json", optional: false)
.AddEnvironmentVariables()
.Build();
// Configure services
var services = new ServiceCollection();
// Add logging
services.AddLogging(builder =>
{
builder.ClearProviders();
builder.AddSerilog();
});
// Add LittleShop client
services.AddLittleShopClient(options =>
{
options.BaseUrl = configuration["LittleShop:ApiUrl"] ?? "https://localhost:5001";
options.TimeoutSeconds = 30;
options.MaxRetryAttempts = 3;
});
// Add simulator
services.AddTransient<BotSimulator>();
services.AddTransient<TestRunner>();
var serviceProvider = services.BuildServiceProvider();
// Run tests
var runner = serviceProvider.GetRequiredService<TestRunner>();
await runner.RunAsync();
}
catch (Exception ex)
{
Log.Fatal(ex, "Application terminated unexpectedly");
}
finally
{
Log.CloseAndFlush();
}
}
}
public class TestRunner
{
private readonly BotSimulator _simulator;
private readonly ILogger<TestRunner> _logger;
private readonly List<SimulationResult> _allResults = new();
public TestRunner(BotSimulator simulator, ILogger<TestRunner> logger)
{
_simulator = simulator;
_logger = logger;
}
public async Task RunAsync()
{
_logger.LogInformation("===========================================");
_logger.LogInformation("🚀 TeleBot Client Simulator");
_logger.LogInformation("===========================================");
while (true)
{
Console.WriteLine("\n📋 Select an option:");
Console.WriteLine("1. Run single simulation");
Console.WriteLine("2. Run multiple simulations");
Console.WriteLine("3. Run stress test");
Console.WriteLine("4. View statistics");
Console.WriteLine("5. Exit");
Console.Write("\nChoice: ");
var choice = Console.ReadLine();
switch (choice)
{
case "1":
await RunSingleSimulation();
break;
case "2":
await RunMultipleSimulations();
break;
case "3":
await RunStressTest();
break;
case "4":
DisplayStatistics();
break;
case "5":
_logger.LogInformation("Exiting simulator...");
return;
default:
Console.WriteLine("Invalid choice. Please try again.");
break;
}
}
}
private async Task RunSingleSimulation()
{
_logger.LogInformation("\n🎯 Running single simulation...\n");
var result = await _simulator.SimulateUserSession();
_allResults.Add(result);
DisplaySimulationResult(result);
}
private async Task RunMultipleSimulations()
{
Console.Write("\nHow many simulations to run? ");
if (!int.TryParse(Console.ReadLine(), out var count) || count <= 0)
{
Console.WriteLine("Invalid number.");
return;
}
_logger.LogInformation("\n🎯 Running {Count} simulations...\n", count);
var results = new List<SimulationResult>();
var successful = 0;
var failed = 0;
for (int i = 1; i <= count; i++)
{
_logger.LogInformation("▶️ Simulation {Number}/{Total}", i, count);
var result = await _simulator.SimulateUserSession();
results.Add(result);
_allResults.Add(result);
if (result.Success)
successful++;
else
failed++;
// Brief pause between simulations
await Task.Delay(1000);
}
DisplayBatchSummary(results, successful, failed);
}
private async Task RunStressTest()
{
Console.Write("\nNumber of concurrent simulations: ");
if (!int.TryParse(Console.ReadLine(), out var concurrent) || concurrent <= 0)
{
Console.WriteLine("Invalid number.");
return;
}
Console.Write("Total simulations to run: ");
if (!int.TryParse(Console.ReadLine(), out var total) || total <= 0)
{
Console.WriteLine("Invalid number.");
return;
}
_logger.LogInformation("\n⚡ Starting stress test: {Concurrent} concurrent, {Total} total\n",
concurrent, total);
var semaphore = new SemaphoreSlim(concurrent);
var tasks = new List<Task<SimulationResult>>();
var startTime = DateTime.UtcNow;
for (int i = 0; i < total; i++)
{
var task = Task.Run(async () =>
{
await semaphore.WaitAsync();
try
{
return await _simulator.SimulateUserSession();
}
finally
{
semaphore.Release();
}
});
tasks.Add(task);
}
var allResults = await Task.WhenAll(tasks);
var results = allResults.ToList();
_allResults.AddRange(results);
var duration = DateTime.UtcNow - startTime;
DisplayStressTestResults(results, duration, concurrent, total);
}
private void DisplayStatistics()
{
if (!_allResults.Any())
{
Console.WriteLine("\n📊 No simulation data available yet.");
return;
}
Console.WriteLine("\n📊 Session Statistics:");
Console.WriteLine($" Total Simulations: {_allResults.Count}");
Console.WriteLine($" Successful: {_allResults.Count(r => r.Success)}");
Console.WriteLine($" Failed: {_allResults.Count(r => !r.Success)}");
Console.WriteLine($" Success Rate: {(_allResults.Count(r => r.Success) * 100.0 / _allResults.Count):F1}%");
var successful = _allResults.Where(r => r.Success).ToList();
if (successful.Any())
{
Console.WriteLine($"\n💰 Order Statistics:");
Console.WriteLine($" Total Orders: {successful.Count}");
Console.WriteLine($" Total Revenue: ${successful.Sum(r => r.OrderTotal):F2}");
Console.WriteLine($" Average Order: ${successful.Average(r => r.OrderTotal):F2}");
Console.WriteLine($" Min Order: ${successful.Min(r => r.OrderTotal):F2}");
Console.WriteLine($" Max Order: ${successful.Max(r => r.OrderTotal):F2}");
// Payment distribution
var payments = successful
.Where(r => !string.IsNullOrEmpty(r.PaymentCurrency))
.GroupBy(r => r.PaymentCurrency)
.Select(g => new { Currency = g.Key, Count = g.Count() })
.OrderByDescending(x => x.Count);
Console.WriteLine($"\n💳 Payment Methods:");
foreach (var p in payments)
{
Console.WriteLine($" {p.Currency}: {p.Count} ({p.Count * 100.0 / successful.Count:F1}%)");
}
}
}
private void DisplaySimulationResult(SimulationResult result)
{
Console.WriteLine("\n========================================");
Console.WriteLine($"📋 Simulation Result: {result.SessionId}");
Console.WriteLine("========================================");
if (result.Success)
{
Console.WriteLine("✅ Status: SUCCESS");
}
else
{
Console.WriteLine($"❌ Status: FAILED - {result.ErrorMessage}");
}
Console.WriteLine($"⏱️ Duration: {result.Duration.TotalSeconds:F2}s");
if (result.Steps.Any())
{
Console.WriteLine("\n📝 Steps Completed:");
foreach (var step in result.Steps)
{
Console.WriteLine($" {step}");
}
}
if (result.Cart != null && result.Cart.Items.Any())
{
Console.WriteLine($"\n🛒 Shopping Cart ({result.Cart.Items.Count} items):");
foreach (var item in result.Cart.Items)
{
Console.WriteLine($" - {item.Quantity}x {item.ProductName} @ ${item.UnitPrice:F2} = ${item.TotalPrice:F2}");
}
Console.WriteLine($" Total: ${result.Cart.TotalAmount:F2}");
}
if (result.OrderId.HasValue)
{
Console.WriteLine($"\n📝 Order Details:");
Console.WriteLine($" Order ID: {result.OrderId}");
Console.WriteLine($" Total: ${result.OrderTotal:F2}");
}
if (!string.IsNullOrEmpty(result.PaymentCurrency))
{
Console.WriteLine($"\n💰 Payment Details:");
Console.WriteLine($" Currency: {result.PaymentCurrency}");
Console.WriteLine($" Amount: {result.PaymentAmount}");
}
Console.WriteLine("\n========================================");
}
private void DisplayBatchSummary(List<SimulationResult> results, int successful, int failed)
{
Console.WriteLine("\n📊 Batch Summary:");
Console.WriteLine($"✅ Successful: {successful}");
Console.WriteLine($"❌ Failed: {failed}");
Console.WriteLine($"📈 Success Rate: {(successful * 100.0 / results.Count):F1}%");
if (results.Any(r => r.Success))
{
var successfulResults = results.Where(r => r.Success).ToList();
Console.WriteLine($"\n💰 Order Statistics:");
Console.WriteLine($" Average Order: ${successfulResults.Average(r => r.OrderTotal):F2}");
Console.WriteLine($" Total Revenue: ${successfulResults.Sum(r => r.OrderTotal):F2}");
Console.WriteLine($" Average Duration: {successfulResults.Average(r => r.Duration.TotalSeconds):F1}s");
}
}
private void DisplayStressTestResults(List<SimulationResult> results, TimeSpan duration, int concurrent, int total)
{
var successful = results.Count(r => r.Success);
var failed = results.Count(r => !r.Success);
Console.WriteLine("\n📊 Stress Test Results:");
Console.WriteLine($"⏱️ Total Duration: {duration.TotalSeconds:F1}s");
Console.WriteLine($"✅ Successful: {successful}");
Console.WriteLine($"❌ Failed: {failed}");
Console.WriteLine($"📈 Success Rate: {(successful * 100.0 / total):F1}%");
Console.WriteLine($"⚡ Throughput: {(total / duration.TotalSeconds):F2} simulations/second");
Console.WriteLine($"🔄 Concurrency: {concurrent} simultaneous connections");
if (failed > 0)
{
Console.WriteLine("\n❌ Failure Analysis:");
var errors = results
.Where(r => !r.Success && !string.IsNullOrEmpty(r.ErrorMessage))
.GroupBy(r => r.ErrorMessage)
.Select(g => new { Error = g.Key, Count = g.Count() })
.OrderByDescending(x => x.Count)
.Take(5);
foreach (var error in errors)
{
Console.WriteLine($" {error.Error}: {error.Count}");
}
}
}
}
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using LittleShop.Client.Extensions;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Serilog;
using TeleBotClient;
namespace TeleBotClient
{
public class SimulatorProgram
{
public static async Task Main(string[] args)
{
// Configure Serilog
Log.Logger = new LoggerConfiguration()
.MinimumLevel.Debug()
.WriteTo.Console(outputTemplate: "[{Timestamp:HH:mm:ss} {Level:u3}] {Message:lj}{NewLine}{Exception}")
.WriteTo.File("logs/simulator-.txt", rollingInterval: RollingInterval.Day)
.CreateLogger();
try
{
// Build configuration
var configuration = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("appsettings.json", optional: false)
.AddEnvironmentVariables()
.Build();
// Configure services
var services = new ServiceCollection();
// Add logging
services.AddLogging(builder =>
{
builder.ClearProviders();
builder.AddSerilog();
});
// Add LittleShop client
services.AddLittleShopClient(options =>
{
options.BaseUrl = configuration["LittleShop:ApiUrl"] ?? "https://localhost:5001";
options.TimeoutSeconds = 30;
options.MaxRetryAttempts = 3;
});
// Add simulator
services.AddTransient<BotSimulator>();
services.AddTransient<TestRunner>();
var serviceProvider = services.BuildServiceProvider();
// Run tests
var runner = serviceProvider.GetRequiredService<TestRunner>();
await runner.RunAsync();
}
catch (Exception ex)
{
Log.Fatal(ex, "Application terminated unexpectedly");
}
finally
{
Log.CloseAndFlush();
}
}
}
public class TestRunner
{
private readonly BotSimulator _simulator;
private readonly ILogger<TestRunner> _logger;
private readonly List<SimulationResult> _allResults = new();
public TestRunner(BotSimulator simulator, ILogger<TestRunner> logger)
{
_simulator = simulator;
_logger = logger;
}
public async Task RunAsync()
{
_logger.LogInformation("===========================================");
_logger.LogInformation("🚀 TeleBot Client Simulator");
_logger.LogInformation("===========================================");
while (true)
{
Console.WriteLine("\n📋 Select an option:");
Console.WriteLine("1. Run single simulation");
Console.WriteLine("2. Run multiple simulations");
Console.WriteLine("3. Run stress test");
Console.WriteLine("4. View statistics");
Console.WriteLine("5. Exit");
Console.Write("\nChoice: ");
var choice = Console.ReadLine();
switch (choice)
{
case "1":
await RunSingleSimulation();
break;
case "2":
await RunMultipleSimulations();
break;
case "3":
await RunStressTest();
break;
case "4":
DisplayStatistics();
break;
case "5":
_logger.LogInformation("Exiting simulator...");
return;
default:
Console.WriteLine("Invalid choice. Please try again.");
break;
}
}
}
private async Task RunSingleSimulation()
{
_logger.LogInformation("\n🎯 Running single simulation...\n");
var result = await _simulator.SimulateUserSession();
_allResults.Add(result);
DisplaySimulationResult(result);
}
private async Task RunMultipleSimulations()
{
Console.Write("\nHow many simulations to run? ");
if (!int.TryParse(Console.ReadLine(), out var count) || count <= 0)
{
Console.WriteLine("Invalid number.");
return;
}
_logger.LogInformation("\n🎯 Running {Count} simulations...\n", count);
var results = new List<SimulationResult>();
var successful = 0;
var failed = 0;
for (int i = 1; i <= count; i++)
{
_logger.LogInformation("▶️ Simulation {Number}/{Total}", i, count);
var result = await _simulator.SimulateUserSession();
results.Add(result);
_allResults.Add(result);
if (result.Success)
successful++;
else
failed++;
// Brief pause between simulations
await Task.Delay(1000);
}
DisplayBatchSummary(results, successful, failed);
}
private async Task RunStressTest()
{
Console.Write("\nNumber of concurrent simulations: ");
if (!int.TryParse(Console.ReadLine(), out var concurrent) || concurrent <= 0)
{
Console.WriteLine("Invalid number.");
return;
}
Console.Write("Total simulations to run: ");
if (!int.TryParse(Console.ReadLine(), out var total) || total <= 0)
{
Console.WriteLine("Invalid number.");
return;
}
_logger.LogInformation("\n⚡ Starting stress test: {Concurrent} concurrent, {Total} total\n",
concurrent, total);
var semaphore = new SemaphoreSlim(concurrent);
var tasks = new List<Task<SimulationResult>>();
var startTime = DateTime.UtcNow;
for (int i = 0; i < total; i++)
{
var task = Task.Run(async () =>
{
await semaphore.WaitAsync();
try
{
return await _simulator.SimulateUserSession();
}
finally
{
semaphore.Release();
}
});
tasks.Add(task);
}
var allResults = await Task.WhenAll(tasks);
var results = allResults.ToList();
_allResults.AddRange(results);
var duration = DateTime.UtcNow - startTime;
DisplayStressTestResults(results, duration, concurrent, total);
}
private void DisplayStatistics()
{
if (!_allResults.Any())
{
Console.WriteLine("\n📊 No simulation data available yet.");
return;
}
Console.WriteLine("\n📊 Session Statistics:");
Console.WriteLine($" Total Simulations: {_allResults.Count}");
Console.WriteLine($" Successful: {_allResults.Count(r => r.Success)}");
Console.WriteLine($" Failed: {_allResults.Count(r => !r.Success)}");
Console.WriteLine($" Success Rate: {(_allResults.Count(r => r.Success) * 100.0 / _allResults.Count):F1}%");
var successful = _allResults.Where(r => r.Success).ToList();
if (successful.Any())
{
Console.WriteLine($"\n💰 Order Statistics:");
Console.WriteLine($" Total Orders: {successful.Count}");
Console.WriteLine($" Total Revenue: ${successful.Sum(r => r.OrderTotal):F2}");
Console.WriteLine($" Average Order: ${successful.Average(r => r.OrderTotal):F2}");
Console.WriteLine($" Min Order: ${successful.Min(r => r.OrderTotal):F2}");
Console.WriteLine($" Max Order: ${successful.Max(r => r.OrderTotal):F2}");
// Payment distribution
var payments = successful
.Where(r => !string.IsNullOrEmpty(r.PaymentCurrency))
.GroupBy(r => r.PaymentCurrency)
.Select(g => new { Currency = g.Key, Count = g.Count() })
.OrderByDescending(x => x.Count);
Console.WriteLine($"\n💳 Payment Methods:");
foreach (var p in payments)
{
Console.WriteLine($" {p.Currency}: {p.Count} ({p.Count * 100.0 / successful.Count:F1}%)");
}
}
}
private void DisplaySimulationResult(SimulationResult result)
{
Console.WriteLine("\n========================================");
Console.WriteLine($"📋 Simulation Result: {result.SessionId}");
Console.WriteLine("========================================");
if (result.Success)
{
Console.WriteLine("✅ Status: SUCCESS");
}
else
{
Console.WriteLine($"❌ Status: FAILED - {result.ErrorMessage}");
}
Console.WriteLine($"⏱️ Duration: {result.Duration.TotalSeconds:F2}s");
if (result.Steps.Any())
{
Console.WriteLine("\n📝 Steps Completed:");
foreach (var step in result.Steps)
{
Console.WriteLine($" {step}");
}
}
if (result.Cart != null && result.Cart.Items.Any())
{
Console.WriteLine($"\n🛒 Shopping Cart ({result.Cart.Items.Count} items):");
foreach (var item in result.Cart.Items)
{
Console.WriteLine($" - {item.Quantity}x {item.ProductName} @ ${item.UnitPrice:F2} = ${item.TotalPrice:F2}");
}
Console.WriteLine($" Total: ${result.Cart.TotalAmount:F2}");
}
if (result.OrderId.HasValue)
{
Console.WriteLine($"\n📝 Order Details:");
Console.WriteLine($" Order ID: {result.OrderId}");
Console.WriteLine($" Total: ${result.OrderTotal:F2}");
}
if (!string.IsNullOrEmpty(result.PaymentCurrency))
{
Console.WriteLine($"\n💰 Payment Details:");
Console.WriteLine($" Currency: {result.PaymentCurrency}");
Console.WriteLine($" Amount: {result.PaymentAmount}");
}
Console.WriteLine("\n========================================");
}
private void DisplayBatchSummary(List<SimulationResult> results, int successful, int failed)
{
Console.WriteLine("\n📊 Batch Summary:");
Console.WriteLine($"✅ Successful: {successful}");
Console.WriteLine($"❌ Failed: {failed}");
Console.WriteLine($"📈 Success Rate: {(successful * 100.0 / results.Count):F1}%");
if (results.Any(r => r.Success))
{
var successfulResults = results.Where(r => r.Success).ToList();
Console.WriteLine($"\n💰 Order Statistics:");
Console.WriteLine($" Average Order: ${successfulResults.Average(r => r.OrderTotal):F2}");
Console.WriteLine($" Total Revenue: ${successfulResults.Sum(r => r.OrderTotal):F2}");
Console.WriteLine($" Average Duration: {successfulResults.Average(r => r.Duration.TotalSeconds):F1}s");
}
}
private void DisplayStressTestResults(List<SimulationResult> results, TimeSpan duration, int concurrent, int total)
{
var successful = results.Count(r => r.Success);
var failed = results.Count(r => !r.Success);
Console.WriteLine("\n📊 Stress Test Results:");
Console.WriteLine($"⏱️ Total Duration: {duration.TotalSeconds:F1}s");
Console.WriteLine($"✅ Successful: {successful}");
Console.WriteLine($"❌ Failed: {failed}");
Console.WriteLine($"📈 Success Rate: {(successful * 100.0 / total):F1}%");
Console.WriteLine($"⚡ Throughput: {(total / duration.TotalSeconds):F2} simulations/second");
Console.WriteLine($"🔄 Concurrency: {concurrent} simultaneous connections");
if (failed > 0)
{
Console.WriteLine("\n❌ Failure Analysis:");
var errors = results
.Where(r => !r.Success && !string.IsNullOrEmpty(r.ErrorMessage))
.GroupBy(r => r.ErrorMessage)
.Select(g => new { Error = g.Key, Count = g.Count() })
.OrderByDescending(x => x.Count)
.Take(5);
foreach (var error in errors)
{
Console.WriteLine($" {error.Error}: {error.Count}");
}
}
}
}
}

View File

@@ -1,39 +1,39 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net9.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Telegram.Bot" Version="22.5.1" />
<PackageReference Include="Microsoft.Extensions.Configuration" Version="9.0.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="9.0.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="9.0.0" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="9.0.0" />
<PackageReference Include="Microsoft.Extensions.Logging" Version="9.0.0" />
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="9.0.0" />
<PackageReference Include="Bogus" Version="35.6.1" />
<PackageReference Include="Serilog" Version="4.1.0" />
<PackageReference Include="Serilog.Extensions.Logging" Version="8.0.0" />
<PackageReference Include="Serilog.Sinks.Console" Version="6.0.0" />
<PackageReference Include="Serilog.Sinks.File" Version="6.0.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\LittleShop.Client\LittleShop.Client.csproj" />
</ItemGroup>
<ItemGroup>
<None Update="appsettings.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
<ItemGroup>
<Compile Remove="Program.cs" />
</ItemGroup>
</Project>
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net9.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Telegram.Bot" Version="22.5.1" />
<PackageReference Include="Microsoft.Extensions.Configuration" Version="9.0.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="9.0.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="9.0.0" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="9.0.0" />
<PackageReference Include="Microsoft.Extensions.Logging" Version="9.0.0" />
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="9.0.0" />
<PackageReference Include="Bogus" Version="35.6.1" />
<PackageReference Include="Serilog" Version="4.1.0" />
<PackageReference Include="Serilog.Extensions.Logging" Version="8.0.0" />
<PackageReference Include="Serilog.Sinks.Console" Version="6.0.0" />
<PackageReference Include="Serilog.Sinks.File" Version="6.0.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\LittleShop.Client\LittleShop.Client.csproj" />
</ItemGroup>
<ItemGroup>
<None Update="appsettings.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
<ItemGroup>
<Compile Remove="Program.cs" />
</ItemGroup>
</Project>

View File

@@ -1,22 +1,22 @@
{
"LittleShop": {
"ApiUrl": "https://localhost:5001",
"Username": "admin",
"Password": "admin"
},
"Simulator": {
"MinItemsPerOrder": 1,
"MaxItemsPerOrder": 5,
"MinQuantityPerItem": 1,
"MaxQuantityPerItem": 3,
"DelayBetweenSimulations": 1000,
"EnableDetailedLogging": true
},
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"System": "Warning"
}
}
{
"LittleShop": {
"ApiUrl": "http://localhost:5000",
"Username": "admin",
"Password": "admin"
},
"Simulator": {
"MinItemsPerOrder": 1,
"MaxItemsPerOrder": 5,
"MinQuantityPerItem": 1,
"MaxQuantityPerItem": 3,
"DelayBetweenSimulations": 1000,
"EnableDetailedLogging": true
},
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"System": "Warning"
}
}
}