using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Logging.Abstractions; using Predictalytics.Application.Interfaces; using Predictalytics.Application.Services; using Predictalytics.Domain.Entities; using Predictalytics.Domain.Enums; using Predictalytics.Domain.Interfaces; using Predictalytics.Infrastructure.Data; using Predictalytics.Infrastructure.Data.Repositories; using Moq; using System; using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; using Xunit; namespace Predictalytics.Application.Tests.Services; public class AnalyticsServiceTests { private AppDbContext CreateDbContext() { var options = new DbContextOptionsBuilder() .UseInMemoryDatabase(databaseName: Guid.NewGuid().ToString()) .Options; return new AppDbContext(options); } [Fact] public async Task GetTraderDeepDiveAsync_CalculatesCorrectMetrics() { // Arrange using var db = CreateDbContext(); var traderRepo = new TraderRepository(db); var tradeRepo = new TradeRepository(db); var marketRepo = new MarketRepository(db); var discoveryMock = new Mock(); var pnlEngineMock = new Mock(); var alertRepoMock = new Mock(); var watchlistRepoMock = new Mock(); var providers = new List(); var analyticsService = new AnalyticsService( traderRepo, tradeRepo, alertRepoMock.Object, watchlistRepoMock.Object, marketRepo, discoveryMock.Object, providers, pnlEngineMock.Object, NullLogger.Instance ); var trader = new Trader { Id = 1, PlatformUserId = "0x1", DisplayName = "Trader 1" }; db.Traders.Add(trader); var market = new Market { Id = 10, PlatformMarketId = 1L, Question = "Q?" }; var outcome = new MarketOutcome { Id = 100, MarketId = 10, Label = "Yes", TokenId = "t100", CurrentPrice = 0.50m }; market.Outcomes.Add(outcome); db.Markets.Add(market); var baseTime = DateTime.UtcNow.AddDays(-5); db.Trades.Add(new Trade { Id = 501, TraderId = 1, DbMarketId = 10, MarketOutcomeId = 100, Side = TradeSide.Buy, Price = 0.40m, Size = 100m, Amount = 40m, ExecutedAt = baseTime }); db.Trades.Add(new Trade { Id = 502, TraderId = 1, DbMarketId = 10, MarketOutcomeId = 100, Side = TradeSide.Sell, Price = 0.60m, Size = 100m, Amount = 60m, ExecutedAt = baseTime.AddHours(24) // 24 hours holding duration }); db.MarketOutcomePriceSnapshots.Add(new MarketOutcomePriceSnapshot { Id = 1, MarketOutcomeId = 100, Price = 0.50m, Timestamp = baseTime.AddHours(2) }); db.MarketOutcomePriceSnapshots.Add(new MarketOutcomePriceSnapshot { Id = 2, MarketOutcomeId = 100, Price = 0.60m, Timestamp = baseTime.AddHours(12) }); await db.SaveChangesAsync(); // Act var deepDive = await analyticsService.GetTraderDeepDiveAsync(1); // Assert Assert.NotNull(deepDive); Assert.Equal(24.0, (double)deepDive.AvgHoldDurationHours, 2); // Entry Quality: Buy at 0.40, subsequent prices are 0.50 and 0.60 (avg 0.55). // Entry Quality = 50 + ((0.55 - 0.40) / 0.40) * 100 = 50 + 0.375 * 100 = 87.5 Assert.Equal(87.5m, deepDive.EntryQuality); } private class MockDiscoveryService : IDiscoveryService { public Task ImportTraderAsync(PlatformType platform, string platformUserId, string displayName, bool isAutoDiscovered = true, CancellationToken ct = default) { return Task.FromResult(1); } public Task ScanTopHoldersAsync(CancellationToken ct) => Task.CompletedTask; public Task> RunDiscoveryAsync(PlatformType platform, CancellationToken ct = default) { return Task.FromResult>(new List()); } } }