- 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>
367 lines
14 KiB
C#
367 lines
14 KiB
C#
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}");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
} |