using System; using System.Linq; using Xunit; using ytLive.Models; using ytLive.Services; namespace ytLive.Tests; /// /// TASK 39·2 — the three eligibility bars. The rules worth pinning: the view criteria are EITHER/OR (so /// "2 of 3" can mean eligible), the long-form bar doubles on 2027-02-01, and the threshold table carries a /// publication date that fails a test when it goes stale — YouTube moves the goalposts and a stale bar /// must not look current. /// public class YppProgressTests { [Fact] public void TwoOfThreeCanBeEligible_BecauseTheViewRoutesAreEitherOr() { var progress = new YppProgress( Subscribers: 1_200, SubscriberTarget: 1_000, WatchHours: 4_500, WatchHoursTarget: 4_000, ShortsViews: 10_000, ShortsViewsTarget: 10_000_000); Assert.Equal(2, progress.CriteriaMet); Assert.True(progress.MeetsPublishedRule); Assert.Equal("2 of 3 criteria met — YouTube reviews eligibility.", progress.CriteriaMetText); } [Fact] public void SubscribersWithoutEitherViewRoute_IsNotEnough() { var progress = new YppProgress(2_000, 1_000, 100, 4_000, 5_000, 10_000_000); Assert.Equal(1, progress.CriteriaMet); Assert.False(progress.MeetsPublishedRule); Assert.Equal("1 of 3 criteria met — YouTube reviews eligibility.", progress.CriteriaMetText); } [Fact] public void ShortsAlone_IsAQualifyingRoute() { var progress = new YppProgress(1_000, 1_000, 0, 4_000, 12_000_000, 10_000_000); Assert.Equal(2, progress.CriteriaMet); Assert.True(progress.MeetsPublishedRule); } [Fact] public void BarsClampAndNeverOverflow() { var progress = new YppProgress(5_000, 1_000, 20_000, 4_000, 50_000_000, 10_000_000); Assert.Equal(1.0, progress.SubscribersFraction); Assert.Equal(1.0, progress.WatchHoursFraction); Assert.Equal(1.0, progress.ShortsViewsFraction); Assert.Equal(3, progress.CriteriaMet); } [Fact] public void TheLongFormBarDoublesOnTheAnnouncedDate() { var before = YppThresholds.Tier2For(new DateTime(2027, 1, 31)); var after = YppThresholds.Tier2For(new DateTime(2027, 2, 1)); Assert.Equal(4_000, before.WatchHours); Assert.Equal(8_000, after.WatchHours); Assert.Equal(10_000_000, before.ShortsViews); Assert.Equal(20_000_000, after.ShortsViews); } [Fact] public void ThresholdsCarryAPublicationDate_ThatFailsWhenStale() { // YouTube changes these. If the reading is over 180 days old, the drawer would be presenting a // stale bar as current — so the suite refuses to pass until someone re-checks the numbers. var age = DateTime.UtcNow - YppThresholds.AsOfUtc; Assert.True(age < TimeSpan.FromDays(180), $"YPP thresholds were last read on {YppThresholds.AsOfUtc:yyyy-MM-dd} — re-check YouTube's published requirements."); } [Fact] public void AnalyticsResponse_ParsesTheTotal_AndTreatsNoRowsAsNoData() { Assert.Equal(123_456, YouTubeAnalyticsService.ParseTotal( """{"columnHeaders":[{"name":"estimatedMinutesWatched"}],"rows":[[123456]]}""")); // A channel with nothing in the window returns no rows: that is "no data", not zero. Assert.Null(YouTubeAnalyticsService.ParseTotal("""{"columnHeaders":[]}""")); Assert.Null(YouTubeAnalyticsService.ParseTotal("""{"rows":[]}""")); } [Theory] [InlineData(640, "640")] [InlineData(12_345, "12.3k")] [InlineData(1_234_567, "1.23M")] public void ValuesStayCompactAndStableInWidth(long value, string expected) => Assert.Equal(expected, YppProgress.Compact(value)); }