using JSMR.Application.Scanning.Contracts; using JSMR.Infrastructure.Http; using JSMR.Infrastructure.Scanning; using JSMR.Tests.Utilities; using NSubstitute; using Shouldly; namespace JSMR.Tests.Scanning; public class VoiceWorkScannerTests { private static async Task ReadResourceAsync(string resourceName) { return await ResourceHelper.ReadAsync($"JSMR.Tests.Scanning.{resourceName}"); } [Fact] public async Task Scan_With_Japanese_Locale() { string html = await ReadResourceAsync("Japanese-Page.html"); IHttpService httpService = Substitute.For(); httpService.GetStringAsync(Arg.Any(), CancellationToken.None) .Returns(Task.FromResult(html)); HtmlLoader loader = new(httpService); JapaneseVoiceWorksScanner scanner = new(loader); VoiceWorkScanOptions options = new( PageNumber: 1, PageSize: 100, ExcludeAIGeneratedWorks: true, ExcludePartiallyAIGeneratedWorks: true, ExcludedMakerIds: [] ); var result = await scanner.ScanPageAsync(options, CancellationToken.None); result.Count.ShouldBe(1); result[0].ExpectedDate.ShouldBeNull(); result[0].SalesDate.ShouldBe(new DateOnly(2025, 10, 15)); result[0].ProductId.ShouldBe("RJ01462066"); result[0].ProductName.ShouldBe("小悪魔ドSメンズエステ嬢の暴発解禁キワキワ施術"); result[0].Description.ShouldBe("リピート指名していたオキニ嬢の在籍店が閉店し途方に暮れていた貴方だったが、ある日彼女の個人SNSで別のお店のリンクを発見する。大慌てで当日夜の予約を確保し、今度こそ抜いてくれないかと淡い期待を胸に駅近くのマンションの一室へ…。"); result[0].Maker.ShouldBe("シルトクレーテ"); result[0].MakerId.ShouldBe("RG40741"); result[0].Creators.ShouldBe(["柚木つばめ"]); result[0].Genres.ShouldBe(["体験版"]); result[0].Tags.ShouldBe(["バイノーラル/ダミヘ", "手コキ", "足コキ", "パイズリ", "言葉責め", "焦らし", "乳首責め", "本番なし"]); result[0].Type.ShouldBe(DLSiteWorkType.Released); result[0].Downloads.ShouldBe(1220); } [Fact] public async Task Scan_With_English_Locale() { string englishPageHtml = await ReadResourceAsync("English-Page.html"); IHttpService httpService = Substitute.For(); httpService.GetStringAsync(Arg.Any(), CancellationToken.None) .Returns(Task.FromResult(englishPageHtml)); HtmlLoader loader = new(httpService); EnglishVoiceWorksScanner scanner = new(loader); VoiceWorkScanOptions options = new( PageNumber: 1, PageSize: 100, ExcludeAIGeneratedWorks: true, ExcludePartiallyAIGeneratedWorks: true, ExcludedMakerIds: [] ); var result = await scanner.ScanPageAsync(options, CancellationToken.None); result.Count.ShouldBe(2); result[0].ExpectedDate.ShouldBeNull(); result[0].SalesDate.ShouldBe(new DateOnly(2025, 9, 6)); result[0].ProductId.ShouldBe("RJ00000001"); result[0].ProductName.ShouldBe("Title of Product"); result[0].Description.ShouldBe("Description of the product."); result[0].Maker.ShouldBe("The Maker"); result[0].MakerId.ShouldBe("RG00001"); result[0].Creators.ShouldBe(["Some Creator"]); result[0].Genres.ShouldBe(["Voice", "Trial version"]); result[0].Tags.ShouldBe(["Male Protagonist", "Gal", "Uniform", "Harem", "Big Breasts", "Tanned Skin / Suntan"]); result[0].Type.ShouldBe(DLSiteWorkType.Released); result[0].Downloads.ShouldBe(1000); result[1].ExpectedDate.ShouldBe(new DateOnly(2025, 10, 11)); result[1].SalesDate.ShouldBeNull(); result[1].ProductId.ShouldBe("RJ00000002"); result[1].Type.ShouldBe(DLSiteWorkType.Announced); } [Fact] public async Task Scan_With_Updated_English_Locale() { string html = await ReadResourceAsync("English-Page-Updated.html"); IHttpService httpService = Substitute.For(); httpService.GetStringAsync(Arg.Any(), CancellationToken.None) .Returns(Task.FromResult(html)); HtmlLoader loader = new(httpService); EnglishVoiceWorksScanner scanner = new(loader); VoiceWorkScanOptions options = new( PageNumber: 1, PageSize: 100, ExcludeAIGeneratedWorks: true, ExcludePartiallyAIGeneratedWorks: true, ExcludedMakerIds: [] ); var result = await scanner.ScanPageAsync(options, CancellationToken.None); result.Count.ShouldBe(1); result[0].ExpectedDate.ShouldBeNull(); result[0].SalesDate.ShouldBe(new DateOnly(2025, 10, 16)); result[0].ProductId.ShouldBe("RJ01455722"); result[0].ProductName.ShouldBe("[ENG Sub] Welcome to Soleil!"); result[0].Description.ShouldBe(string.Empty); // Waiting on this to get fixed on the site result[0].Maker.ShouldBe("Translators Unite"); result[0].MakerId.ShouldBe("RG60289"); result[0].Creators.ShouldBe(["沼倉愛美"]); result[0].Genres.ShouldBe(["All Ages", "Trial version"]); result[0].Tags.ShouldBe(["Moe", "Healing", "Binaural", "ASMR", "Ear Cleaning", "Slice of Life / Daily Living", "Heartwarming", "Whispering"]); result[0].Type.ShouldBe(DLSiteWorkType.Released); result[0].Downloads.ShouldBe(1); } }