using TourMonitor.Storage; namespace TourMonitor.Tests; public class PriceStoreTests : IDisposable { private readonly string _dbPath = Path.Combine(Path.GetTempPath(), $"tour_monitor_test_{Guid.NewGuid():N}.db"); public void Dispose() { if (File.Exists(_dbPath)) File.Delete(_dbPath); } private static StoredOffer Offer(int price, string room = "Полулюкс", string operatorName = "Level.Travel", string? link = null, string dateFrom = "2026-08-20") => new(9099454, dateFrom, "AI", 12, room, 77, operatorName, price, price / 9, "offer-1", link ?? $"/package_details/offer-1?hotel_id=9099454"); private PriceStore NewStore() => new(_dbPath); [Fact] public async Task SaveScan_WritesHistory_AndDailyBest() { var store = NewStore(); await store.EnsureCreatedAsync(); var offers = new List { Offer(500_000), Offer(475_447) }; await store.SaveScanAsync(9099454, "2026-08-20", "2026-08-06", offers); var bests = await store.GetTodayBestsAsync("2026-08-06"); var best = Assert.Single(bests); Assert.Equal(475_447, best.BestPrice); Assert.Equal("offer-1", best.OfferId); var offer = await store.GetBestOfferAsync(9099454, "2026-08-20", "2026-08-06"); Assert.NotNull(offer); Assert.Equal(475_447, offer!.Price); Assert.Equal("Полулюкс", offer.RoomName); Assert.Equal("AI", offer.Meal); } [Fact] public async Task SaveScan_EmptyOffers_DoesNotCreateDailyBest() { var store = NewStore(); await store.EnsureCreatedAsync(); await store.SaveScanAsync(9099454, "2026-08-20", "2026-08-06", new List()); Assert.Empty(await store.GetTodayBestsAsync("2026-08-06")); } [Fact] public async Task SecondScan_UpsertsDailyBest_AndKeepsHistory() { var store = NewStore(); await store.EnsureCreatedAsync(); await store.SaveScanAsync(9099454, "2026-08-20", "2026-08-06", new List { Offer(500_000) }); await store.SaveScanAsync(9099454, "2026-08-20", "2026-08-07", new List { Offer(450_000, "Люкс") }); var todayBests = await store.GetTodayBestsAsync("2026-08-07"); var best = Assert.Single(todayBests); Assert.Equal(450_000, best.BestPrice); var previous = await store.GetBestBeforeAsync(9099454, "2026-08-20", "2026-08-07"); Assert.NotNull(previous); Assert.Equal(500_000, previous!.BestPrice); Assert.Equal("2026-08-06", previous.Date); var historical = await store.GetHistoricalMinBeforeAsync(9099454, "2026-08-20", "2026-08-07"); Assert.Equal(500_000, historical!.BestPrice); } [Fact] public async Task HistoricalMin_TakesLowestOfAllPreviousDays() { var store = NewStore(); await store.EnsureCreatedAsync(); await store.SaveScanAsync(9099454, "2026-08-20", "2026-08-04", new List { Offer(500_000) }); await store.SaveScanAsync(9099454, "2026-08-20", "2026-08-05", new List { Offer(470_000) }); await store.SaveScanAsync(9099454, "2026-08-20", "2026-08-06", new List { Offer(490_000) }); var historical = await store.GetHistoricalMinBeforeAsync(9099454, "2026-08-20", "2026-08-07"); Assert.Equal(470_000, historical!.BestPrice); } [Fact] public async Task GetCalendar_ReturnsLastScanPrice_AndPreviousForDelta() { var store = NewStore(); await store.EnsureCreatedAsync(); await store.SaveScanAsync(9099454, "2026-10-05", "2026-08-05", new List { Offer(500_000) }); await store.SaveScanAsync(9099454, "2026-10-05", "2026-08-06", new List { Offer(440_000) }); await store.SaveScanAsync(9099454, "2026-10-06", "2026-08-06", new List { Offer(470_000) }); var entries = await store.GetCalendarAsync(); var first = entries.Single(e => e.DateFrom == "2026-10-05"); Assert.Equal(440_000, first.BestPrice); // цена из последнего скана, а не накопленный минимум Assert.Equal(500_000, first.PreviousPrice); var second = entries.Single(e => e.DateFrom == "2026-10-06"); Assert.Equal(470_000, second.BestPrice); Assert.Null(second.PreviousPrice); // первого скана не было — дельты нет } [Fact] public async Task GetCalendar_Range_IsMinMaxOfBestPrice_OverScanDates() { var store = NewStore(); await store.EnsureCreatedAsync(); // одна и та же дата тура, три разные даты сканирования await store.SaveScanAsync(9099454, "2026-10-05", "2026-08-05", new List { Offer(100_000, dateFrom: "2026-10-05") }); await store.SaveScanAsync(9099454, "2026-10-05", "2026-08-06", new List { Offer(130_000, dateFrom: "2026-10-05") }); await store.SaveScanAsync(9099454, "2026-10-05", "2026-08-07", new List { Offer(120_000, dateFrom: "2026-10-05") }); var entry = (await store.GetCalendarAsync()).Single(e => e.DateFrom == "2026-10-05"); Assert.Equal(120_000, entry.BestPrice); // из последнего скана Assert.Equal(100_000, entry.MinPrice); // мин по датам сканирования Assert.Equal(130_000, entry.MaxPrice); // макс по датам сканирования } [Fact] public async Task GetCalendar_ReturnsBookingLink_ForBestOfferOfLastScan() { var store = NewStore(); await store.EnsureCreatedAsync(); await store.SaveScanAsync(9099454, "2026-10-05", "2026-08-05", new List { Offer(500_000, "Стандарт", "ANEX Tour", link: "/package_details/offer-old", dateFrom: "2026-10-05"), Offer(475_000, "Делюкс", "Pegas", link: "/package_details/offer-best", dateFrom: "2026-10-05"), }); await store.SaveScanAsync(9099454, "2026-10-05", "2026-08-06", new List { Offer(440_000, "Люкс", "Level.Travel", link: "/package_details/offer-latest", dateFrom: "2026-10-05"), Offer(620_000, "Стандарт", "Pegas", link: "/package_details/offer-expensive", dateFrom: "2026-10-05"), }); var entry = (await store.GetCalendarAsync()).Single(e => e.DateFrom == "2026-10-05"); Assert.Equal(440_000, entry.BestPrice); Assert.Equal("/package_details/offer-latest", entry.Link); // ссылка из последнего скана, а не старого } [Fact] public async Task GetBestOffer_ReturnsCheapestOfLatestScan() { var store = NewStore(); await store.EnsureCreatedAsync(); await store.SaveScanAsync(9099454, "2026-08-20", "2026-08-06", new List { Offer(500_000), Offer(460_000, "Стандарт", "ANEX Tour") }); await store.SaveScanAsync(9099454, "2026-08-20", "2026-08-07", new List { Offer(510_000), Offer(475_000, "Делюкс", "Pegas") }); var offer = await store.GetBestOfferAsync(9099454, "2026-08-20", "2026-08-07"); Assert.Equal(475_000, offer!.Price); Assert.Equal("Делюкс", offer.RoomName); Assert.Equal("Pegas", offer.OperatorName); } }