using JSMR.Application.Enums; using JSMR.Application.Integrations.DLSite.Models; using JSMR.Application.Integrations.DLSite.Models.ReleasedWorks; using JSMR.Domain.Enums; using JSMR.Domain.ValueObjects; using JSMR.Infrastructure.Integrations.DLSite; using JSMR.Infrastructure.Integrations.DLSite.Mapping; using JSMR.Infrastructure.Integrations.DLSite.Models; using JSMR.Infrastructure.Integrations.DLSite.Models.NewWorks; using JSMR.Tests.Http; using JSMR.Tests.Utilities; using Microsoft.Extensions.Logging; using NSubstitute; using Shouldly; using System.Net; using System.Text; namespace JSMR.Tests.Integrations.DLSite; public class DLSiteClientTests { private static async Task ReadJsonResourceAsync(string resourceName) { return await ResourceHelper.ReadAsync($"JSMR.Tests.Integrations.DLSite.{resourceName}"); } private static async Task<(DLSiteClient Client, FakeHttpMessageHandler Handler)> GetDLSiteClientThatReturns(string resourceName) { string content = await ReadJsonResourceAsync(resourceName); FakeHttpMessageHandler handler = new(_ => { return new HttpResponseMessage(HttpStatusCode.OK) { Content = new StringContent(content, Encoding.UTF8, "application/json") }; }); HttpClient httpClient = new(handler) { BaseAddress = new Uri("https://www.fake-dlsite.com/") }; var logger = Substitute.For>(); var client = new DLSiteClient(httpClient, logger); return (client, handler); } [Fact] public async Task GetVoiceWorkDetailsAsync_Should_Call_Expected_Request() { var (client, handler) = await GetDLSiteClientThatReturns("Product-Info.json"); await client.GetVoiceWorkDetailsAsync(["RJ01230163", "RJ01536422"], CancellationToken.None); handler.Requests.Count.ShouldBe(1); HttpRequestMessage httpRequest = handler.Requests[0]; httpRequest.Method.ShouldBe(HttpMethod.Get); httpRequest.ShouldHavePath("/maniax/product/info/ajax"); httpRequest.ShouldHaveQueryParam("product_id", "RJ01230163,RJ01536422"); } [Fact] public async Task Deserialize_Product_Info_Collection() { var (client, handler) = await GetDLSiteClientThatReturns("Product-Info.json"); var result = await client.GetVoiceWorkDetailsAsync(["RJ01230163", "RJ01536422"], CancellationToken.None); result.Count.ShouldBe(2); result.ShouldContainKey("RJ01230163"); result["RJ01230163"].Title.ShouldBe("[Azur Lane ASMR] Commander Pampering Team! Golden Hind's Tentacular Treatment"); result["RJ01230163"].HasTrial.ShouldBeTrue(); result["RJ01230163"].HasDLPlay.ShouldBeTrue(); result["RJ01230163"].HasReviews.ShouldBeTrue(); result["RJ01230163"].SupportedLanguages.Count.ShouldBe(1); result["RJ01230163"].SupportedLanguages.Select(x => x.Language).ShouldContain(Language.English); result["RJ01230163"].DownloadCount.ShouldBe(659); result["RJ01230163"].WishlistCount.ShouldBe(380); // Testing this one for empty array of prices result.ShouldContainKey("RJ01536422"); } [Fact] public void Map_Basic_ProductInfoCollection() { ProductInfoCollection productInfoCollection = new() { { "RG0001", new ProductInfo() { WorkName = "Product 1", WorkNameMasked = "Product 1 (Masked)", WishlistCount = 250, DownloadCount = 100, Options = ["TRI", "DLP", "JPN"], TitleId = "SE0001", TitleName = "Series 1" } }, { "RG0002", new ProductInfo() { WorkName = "Product 2", WorkNameMasked = "Product 2 (Masked)", WishlistCount = 100, DownloadCount = 50, Options = ["TRI", "ENG", "DOT", "VET"], TitleId = "SE0001", TitleName = "Series 1", TranslationInfo = new() { OriginalWorkNumber = "RG0001", Language = "ENG" } } } }; // General VoiceWorkDetailCollection mappedCollection = DLSiteToDomainMapper.Map(productInfoCollection); mappedCollection.Count.ShouldBe(2); mappedCollection.ShouldContainKey("RG0001"); mappedCollection.ShouldContainKey("RG0002"); // RG0001 VoiceWorkDetails voiceWorkDetails = mappedCollection["RG0001"]; voiceWorkDetails.Title.ShouldBe("Product 1"); voiceWorkDetails.WishlistCount.ShouldBe(250); voiceWorkDetails.DownloadCount.ShouldBe(100); voiceWorkDetails.HasTrial.ShouldBe(true); voiceWorkDetails.HasDLPlay.ShouldBe(true); voiceWorkDetails.AI.ShouldBe(AIGeneration.None); voiceWorkDetails.SupportedLanguages.ShouldBe([SupportedLanguage.Japanese]); voiceWorkDetails.Series.ShouldNotBeNull(); voiceWorkDetails.Series.Identifier.ShouldBe("SE0001"); voiceWorkDetails.Series.Name.ShouldBe("Series 1"); // RG0002 VoiceWorkDetails secondVoiceWorkDetails = mappedCollection["RG0002"]; secondVoiceWorkDetails.Title.ShouldBe("Product 2"); secondVoiceWorkDetails.WishlistCount.ShouldBe(100); secondVoiceWorkDetails.DownloadCount.ShouldBe(50); secondVoiceWorkDetails.HasTrial.ShouldBe(true); secondVoiceWorkDetails.HasDLPlay.ShouldBe(false); secondVoiceWorkDetails.AI.ShouldBe(AIGeneration.None); secondVoiceWorkDetails.SupportedLanguages.ShouldBe([SupportedLanguage.English]); secondVoiceWorkDetails.Series.ShouldNotBeNull(); secondVoiceWorkDetails.Series.Identifier.ShouldBe("SE0001"); secondVoiceWorkDetails.Series.Name.ShouldBe("Series 1"); secondVoiceWorkDetails.Translation.ShouldNotBeNull(); secondVoiceWorkDetails.Translation.OriginalProductId.ShouldBe("RG0001"); secondVoiceWorkDetails.Translation.Language.ShouldBe(Language.English); secondVoiceWorkDetails.Translation.Kind.ShouldBe(TranslationKind.Official | TranslationKind.Recommended); } [Fact] public async Task GetReleasedWorksAsync_Should_Call_Expected_Request() { var (client, handler) = await GetDLSiteClientThatReturns("Released-Works.json"); ReleasedWorksRequest request = new(Locale.English, new(2025, 1, 1), 1); await client.GetReleasedWorksAsync(request, CancellationToken.None); handler.Requests.Count.ShouldBe(1); HttpRequestMessage httpRequest = handler.Requests[0]; httpRequest.Method.ShouldBe(HttpMethod.Get); httpRequest.ShouldHavePath("/maniax/new/work/api"); httpRequest.ShouldHaveQueryParam("locale", "en-us"); httpRequest.ShouldHaveQueryParam("date", "2025-01-01"); httpRequest.ShouldHaveQueryParam("period", "1"); } [Fact] public async Task Map_Basic_Released_Work_Collection() { NewWorksApiResponse response = new() { Meta = new() { Code = 200 }, Data = new() { Products = [ new() { Id = "RG1", Name = "The Title", NameMasked = "Masked Title", Description = "The description", DescriptionMasked = "The masked description" } ] } }; ReleasedWorksCollection collection = DLSiteReleasedWorksMapper.Map(response); collection.Count.ShouldBe(1); collection.ShouldContainKey("RG1"); ReleasedWork releasedWork = collection["RG1"]; releasedWork.ProductId.ShouldBe("RG1"); releasedWork.Title.ShouldBe("The Title"); releasedWork.MaskedTitle.ShouldBe("Masked Title"); releasedWork.Description.ShouldBe("The description"); releasedWork.MaskedDescription.ShouldBe("The masked description"); } [Fact] public async Task Deserialize_Released_Work_Collection() { var (client, handler) = await GetDLSiteClientThatReturns("Released-Works.json"); var request = new ReleasedWorksRequest(Locale.English, new(2025, 1, 1), 1); var result = await client.GetReleasedWorksAsync(request, CancellationToken.None); result.Count.ShouldBe(13); result.ShouldContainKey("RJ01588345"); ReleasedWork releasedWork = result["RJ01588345"]; releasedWork.Title.ShouldBe("魔都一贅肉のスゴイデブ"); releasedWork.MaskedTitle.ShouldBe("魔都一贅肉のスゴイデブ"); releasedWork.Description.ShouldBe("圧巻の超連続・激太りご褒美パラダイス、ここに!全24ページ描き下ろし!太→激太への肥満化シークエンスが11作収録!餌付け、自己肥育、お風呂、縛り…などなど多種多様の太り方でご褒美を堪能せよ!"); releasedWork.MaskedDescription.ShouldBe("圧巻の超連続・激太りご褒美パラダイス、ここに!全24ページ描き下ろし!太→激太への肥満化シークエンスが11作収録!餌付け、自己肥育、お風呂、縛り…などなど多種多様の太り方でご褒美を堪能せよ!"); } }