Project restructuring.
This commit is contained in:
@@ -1,12 +1,6 @@
|
|||||||
using MangaReader.Core.Search;
|
using MangaReader.Core.Search;
|
||||||
using MangaReader.Core.Search.MangaDex;
|
using MangaReader.Core.Sources.MangaDex.Search;
|
||||||
using MangaReader.Core.Sources.NatoManga.Search;
|
using MangaReader.Core.Sources.NatoManga.Search;
|
||||||
using Microsoft.Extensions.DependencyInjection.Extensions;
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
#pragma warning disable IDE0130 // Namespace does not match folder structure
|
#pragma warning disable IDE0130 // Namespace does not match folder structure
|
||||||
namespace Microsoft.Extensions.DependencyInjection;
|
namespace Microsoft.Extensions.DependencyInjection;
|
||||||
|
|||||||
@@ -2,5 +2,5 @@
|
|||||||
|
|
||||||
public class ArtistEntity : MangaDexEntity
|
public class ArtistEntity : MangaDexEntity
|
||||||
{
|
{
|
||||||
public required ArtistAttributes Attributes { get; set; }
|
public ArtistAttributes? Attributes { get; set; }
|
||||||
}
|
}
|
||||||
@@ -2,5 +2,5 @@
|
|||||||
|
|
||||||
public class AuthorEntity : MangaDexEntity
|
public class AuthorEntity : MangaDexEntity
|
||||||
{
|
{
|
||||||
public required AuthorAttributes Attributes { get; set; }
|
public AuthorAttributes? Attributes { get; set; }
|
||||||
}
|
}
|
||||||
@@ -2,8 +2,15 @@
|
|||||||
|
|
||||||
public class ChapterAttributes
|
public class ChapterAttributes
|
||||||
{
|
{
|
||||||
|
public string? Volume { get; set; }
|
||||||
public string? Chapter { get; set; }
|
public string? Chapter { get; set; }
|
||||||
public string? Title { get; set; }
|
public string? Title { get; set; }
|
||||||
public int Pages { get; set; }
|
public string? TranslatedLanguage { get; set; }
|
||||||
|
public string? ExternalUrl { get; set; }
|
||||||
public DateTime PublishAt { get; set; }
|
public DateTime PublishAt { get; set; }
|
||||||
|
public DateTime ReadableAt { get; set; }
|
||||||
|
public DateTime CreatedAt { get; set; }
|
||||||
|
public DateTime UpdatedAt { get; set; }
|
||||||
|
public int Pages { get; set; }
|
||||||
|
public int Version { get; set; }
|
||||||
}
|
}
|
||||||
@@ -2,5 +2,5 @@
|
|||||||
|
|
||||||
public class ChapterEntity : MangaDexEntity
|
public class ChapterEntity : MangaDexEntity
|
||||||
{
|
{
|
||||||
public required ChapterAttributes Attributes { get; set; }
|
public ChapterAttributes? Attributes { get; set; }
|
||||||
}
|
}
|
||||||
@@ -2,5 +2,5 @@
|
|||||||
|
|
||||||
public class CoverArtEntity : MangaDexEntity
|
public class CoverArtEntity : MangaDexEntity
|
||||||
{
|
{
|
||||||
public required CoverArtAttributes Attributes { get; set; }
|
public CoverArtAttributes? Attributes { get; set; }
|
||||||
}
|
}
|
||||||
@@ -2,6 +2,10 @@
|
|||||||
|
|
||||||
public interface IMangaDexClient
|
public interface IMangaDexClient
|
||||||
{
|
{
|
||||||
Task<MangaDexResponse> GetMangaAsync(Guid mangaGuid, CancellationToken cancellationToken);
|
Task<MangaDexResponse?> SearchMangaByTitleAsync(string title, CancellationToken cancellationToken);
|
||||||
Task<MangaDexResponse> GetFeedAsync(Guid mangaGuid, CancellationToken cancellationToken);
|
Task<MangaDexResponse?> SearchMangaByAuthorAsync(string author, CancellationToken cancellationToken);
|
||||||
|
Task<MangaDexResponse?> SearchMangaByGroupAsync(string group, CancellationToken cancellationToken);
|
||||||
|
Task<MangaDexResponse?> GetMangaAsync(Guid mangaGuid, CancellationToken cancellationToken);
|
||||||
|
Task<MangaDexResponse?> GetFeedAsync(Guid mangaGuid, CancellationToken cancellationToken);
|
||||||
|
Task<MangaDexChapterResponse?> GetChapterAsync(Guid chapterGuid, CancellationToken cancellationToken);
|
||||||
}
|
}
|
||||||
8
MangaReader.Core/Sources/MangaDex/Api/MangaDexChapter.cs
Normal file
8
MangaReader.Core/Sources/MangaDex/Api/MangaDexChapter.cs
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
namespace MangaReader.Core.Sources.MangaDex.Api;
|
||||||
|
|
||||||
|
public class MangaDexChapter
|
||||||
|
{
|
||||||
|
public required string Hash { get; set; }
|
||||||
|
public List<string> Data { get; set; } = [];
|
||||||
|
public List<string> DataSaver { get; set; } = [];
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
namespace MangaReader.Core.Sources.MangaDex.Api;
|
||||||
|
|
||||||
|
public class MangaDexChapterResponse
|
||||||
|
{
|
||||||
|
public required string Result { get; set; }
|
||||||
|
public required string BaseUrl { get; set; }
|
||||||
|
public MangaDexChapter? Chapter { get; set; }
|
||||||
|
}
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
using MangaReader.Core.HttpService;
|
using MangaReader.Core.HttpService;
|
||||||
|
using System.Text;
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
|
|
||||||
namespace MangaReader.Core.Sources.MangaDex.Api
|
namespace MangaReader.Core.Sources.MangaDex.Api
|
||||||
@@ -18,22 +19,55 @@ namespace MangaReader.Core.Sources.MangaDex.Api
|
|||||||
_jsonSerializerOptions.Converters.Add(new MangaDexEntityConverter());
|
_jsonSerializerOptions.Converters.Add(new MangaDexEntityConverter());
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<MangaDexResponse> GetAsync(string url, CancellationToken cancellationToken)
|
private async Task<MangaDexResponse?> GetAsync(string url, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
string response = await httpService.GetStringAsync(url, cancellationToken);
|
string response = await httpService.GetStringAsync(url, cancellationToken);
|
||||||
|
|
||||||
return JsonSerializer.Deserialize<MangaDexResponse>(response, _jsonSerializerOptions)
|
return JsonSerializer.Deserialize<MangaDexResponse>(response, _jsonSerializerOptions);
|
||||||
?? new() { Response = "failed", Result = "unknown" };
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<MangaDexResponse> GetMangaAsync(Guid mangaGuid, CancellationToken cancellationToken)
|
public async Task<MangaDexResponse?> SearchMangaByTitleAsync(string title, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
string normalizedKeyword = GetNormalizedKeyword(title);
|
||||||
|
|
||||||
|
return await GetAsync($"https://api.mangadex.org/manga?title={normalizedKeyword}&limit=5", cancellationToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<MangaDexResponse?> SearchMangaByAuthorAsync(string author, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
string normalizedKeyword = GetNormalizedKeyword(author);
|
||||||
|
|
||||||
|
return await GetAsync($"https://api.mangadex.org/manga?author={normalizedKeyword}&limit=5", cancellationToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<MangaDexResponse?> SearchMangaByGroupAsync(string group, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
string normalizedKeyword = GetNormalizedKeyword(group);
|
||||||
|
|
||||||
|
return await GetAsync($"https://api.mangadex.org/manga?group={normalizedKeyword}&limit=5", cancellationToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static string GetNormalizedKeyword(string keyword)
|
||||||
|
{
|
||||||
|
return keyword.ToLowerInvariant().Normalize(NormalizationForm.FormD);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<MangaDexResponse?> GetMangaAsync(Guid mangaGuid, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
return await GetAsync($"https://api.mangadex.org/manga/{mangaGuid}?includes[]=artist&includes[]=author&includes[]=cover_art", cancellationToken);
|
return await GetAsync($"https://api.mangadex.org/manga/{mangaGuid}?includes[]=artist&includes[]=author&includes[]=cover_art", cancellationToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<MangaDexResponse> GetFeedAsync(Guid mangaGuid, CancellationToken cancellationToken)
|
public async Task<MangaDexResponse?> GetFeedAsync(Guid mangaGuid, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
return await GetAsync($"https://api.mangadex.org/manga/{mangaGuid}/feed?translatedLanguage[]=en&limit=96&includes[]=scanlation_group&includes[]=user&order[volume]=desc&order[chapter]=desc&offset=0&contentRating[]=safe&contentRating[]=suggestive&contentRating[]=erotica&contentRating[]=pornographic", cancellationToken);
|
return await GetAsync($"https://api.mangadex.org/manga/{mangaGuid}/feed?translatedLanguage[]=en&limit=96&includes[]=scanlation_group&includes[]=user&order[volume]=desc&order[chapter]=desc&offset=0&contentRating[]=safe&contentRating[]=suggestive&contentRating[]=erotica&contentRating[]=pornographic", cancellationToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task<MangaDexChapterResponse?> GetChapterAsync(Guid chapterGuid, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
string url = $"https://api.mangadex.org/at-home/server/{chapterGuid}?forcePort443=false";
|
||||||
|
string response = await httpService.GetStringAsync(url, cancellationToken);
|
||||||
|
|
||||||
|
return JsonSerializer.Deserialize<MangaDexChapterResponse>(response, _jsonSerializerOptions);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -22,6 +22,7 @@ public class MangaDexEntityConverter : JsonConverter<MangaDexEntity>
|
|||||||
"chapter" => JsonSerializer.Deserialize<ChapterEntity>(root.GetRawText(), options),
|
"chapter" => JsonSerializer.Deserialize<ChapterEntity>(root.GetRawText(), options),
|
||||||
"scanlation_group" => JsonSerializer.Deserialize<ScanlationGroupEntity>(root.GetRawText(), options),
|
"scanlation_group" => JsonSerializer.Deserialize<ScanlationGroupEntity>(root.GetRawText(), options),
|
||||||
"cover_art" => JsonSerializer.Deserialize<CoverArtEntity>(root.GetRawText(), options),
|
"cover_art" => JsonSerializer.Deserialize<CoverArtEntity>(root.GetRawText(), options),
|
||||||
|
"user" => JsonSerializer.Deserialize<UserEntity>(root.GetRawText(), options),
|
||||||
_ => throw new NotSupportedException($"Unknown type '{type}'")
|
_ => throw new NotSupportedException($"Unknown type '{type}'")
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,5 +2,5 @@
|
|||||||
|
|
||||||
public class MangaEntity : MangaDexEntity
|
public class MangaEntity : MangaDexEntity
|
||||||
{
|
{
|
||||||
public required MangaAttributes Attributes { get; set; }
|
public MangaAttributes? Attributes { get; set; }
|
||||||
}
|
}
|
||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
public class PersonAttributes
|
public class PersonAttributes
|
||||||
{
|
{
|
||||||
|
public required string Name { get; set; }
|
||||||
public string? ImageUrl { get; set; }
|
public string? ImageUrl { get; set; }
|
||||||
public Dictionary<string, string> Biography { get; set; } = [];
|
public Dictionary<string, string> Biography { get; set; } = [];
|
||||||
public string? Twitter { get; set; }
|
public string? Twitter { get; set; }
|
||||||
|
|||||||
@@ -2,5 +2,5 @@
|
|||||||
|
|
||||||
public class TagEntity : MangaDexEntity
|
public class TagEntity : MangaDexEntity
|
||||||
{
|
{
|
||||||
public required TagAttributes Attributes { get; set; }
|
public TagAttributes? Attributes { get; set; }
|
||||||
}
|
}
|
||||||
8
MangaReader.Core/Sources/MangaDex/Api/UserAttributes.cs
Normal file
8
MangaReader.Core/Sources/MangaDex/Api/UserAttributes.cs
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
namespace MangaReader.Core.Sources.MangaDex.Api;
|
||||||
|
|
||||||
|
public class UserAttributes
|
||||||
|
{
|
||||||
|
public required string UserName { get; set; }
|
||||||
|
public List<string> Roles { get; set; } = [];
|
||||||
|
public int Version { get; set; }
|
||||||
|
}
|
||||||
6
MangaReader.Core/Sources/MangaDex/Api/UserEntity.cs
Normal file
6
MangaReader.Core/Sources/MangaDex/Api/UserEntity.cs
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
namespace MangaReader.Core.Sources.MangaDex.Api;
|
||||||
|
|
||||||
|
public class UserEntity : MangaDexEntity
|
||||||
|
{
|
||||||
|
public UserAttributes? Attributes { get; set; }
|
||||||
|
}
|
||||||
@@ -1,12 +1,10 @@
|
|||||||
using MangaReader.Core.HttpService;
|
using MangaReader.Core.Search;
|
||||||
using MangaReader.Core.Sources;
|
using MangaReader.Core.Sources.MangaDex.Api;
|
||||||
using MangaReader.Core.Sources.MangaDex.Search;
|
|
||||||
using System.Text;
|
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
|
|
||||||
namespace MangaReader.Core.Search.MangaDex;
|
namespace MangaReader.Core.Sources.MangaDex.Search;
|
||||||
|
|
||||||
public partial class MangaDexSearchProvider(IHttpService httpService) : MangaSearchProviderBase<MangaDexSearchResult>(httpService), IMangaSourceComponent
|
public partial class MangaDexSearchProvider(IMangaDexClient mangaDexClient) : IMangaSearchProvider, IMangaSourceComponent
|
||||||
{
|
{
|
||||||
[GeneratedRegex(@"[^a-z0-9\s-]")]
|
[GeneratedRegex(@"[^a-z0-9\s-]")]
|
||||||
private static partial Regex InvalidSlugCharactersRegex();
|
private static partial Regex InvalidSlugCharactersRegex();
|
||||||
@@ -16,33 +14,48 @@ public partial class MangaDexSearchProvider(IHttpService httpService) : MangaSea
|
|||||||
|
|
||||||
public string SourceId => "MangaDex";
|
public string SourceId => "MangaDex";
|
||||||
|
|
||||||
protected override string GetSearchUrl(string keyword)
|
public async Task<MangaSearchResult[]> SearchAsync(string keyword, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
string normalizedKeyword = keyword.ToLowerInvariant().Normalize(NormalizationForm.FormD);
|
MangaDexResponse? response = await mangaDexClient.SearchMangaByTitleAsync(keyword, cancellationToken);
|
||||||
|
|
||||||
return $"https://api.mangadex.org/manga?title={normalizedKeyword}&limit=5";
|
if (response == null || (response is not MangaDexCollectionResponse collectionResponse))
|
||||||
}
|
return [];
|
||||||
|
|
||||||
protected override MangaSearchResult[] GetSearchResult(MangaDexSearchResult searchResult)
|
|
||||||
{
|
|
||||||
List<MangaSearchResult> mangaSearchResults = [];
|
List<MangaSearchResult> mangaSearchResults = [];
|
||||||
|
|
||||||
foreach (MangaDexSearchResultData searchResultData in searchResult.Data)
|
foreach (MangaDexEntity entity in collectionResponse.Data)
|
||||||
{
|
{
|
||||||
string title = searchResultData.Attributes.Title.FirstOrDefault().Value;
|
MangaSearchResult? mangaSearchResult = GetMangaSearchResult(entity);
|
||||||
|
|
||||||
|
if (mangaSearchResult == null)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
mangaSearchResults.Add(mangaSearchResult);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return [.. mangaSearchResults];
|
||||||
|
}
|
||||||
|
|
||||||
|
private static MangaSearchResult? GetMangaSearchResult(MangaDexEntity entity)
|
||||||
|
{
|
||||||
|
if (entity is not MangaEntity mangaEntity)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
if (mangaEntity.Attributes == null)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
string title = mangaEntity.Attributes.Title.FirstOrDefault().Value;
|
||||||
string slug = GenerateSlug(title);
|
string slug = GenerateSlug(title);
|
||||||
|
|
||||||
MangaSearchResult mangaSearchResult = new()
|
MangaSearchResult mangaSearchResult = new()
|
||||||
{
|
{
|
||||||
Title = title,
|
Title = title,
|
||||||
Url = $"https://mangadex.org/title/{searchResultData.Id}/{slug}",
|
Url = $"https://mangadex.org/title/{mangaEntity.Id}/{slug}",
|
||||||
Thumbnail = GetThumbnail(searchResultData)
|
Thumbnail = GetThumbnail(mangaEntity)
|
||||||
};
|
};
|
||||||
|
|
||||||
mangaSearchResults.Add(mangaSearchResult);
|
return mangaSearchResult;
|
||||||
}
|
|
||||||
|
|
||||||
return [.. mangaSearchResults];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static string GenerateSlug(string title)
|
public static string GenerateSlug(string title)
|
||||||
@@ -57,19 +70,19 @@ public partial class MangaDexSearchProvider(IHttpService httpService) : MangaSea
|
|||||||
return title.Trim('-');
|
return title.Trim('-');
|
||||||
}
|
}
|
||||||
|
|
||||||
private static string? GetThumbnail(MangaDexSearchResultData searchResultData)
|
private static string? GetThumbnail(MangaDexEntity mangaDexEntity)
|
||||||
{
|
{
|
||||||
var coverArtRelationship = searchResultData.Relationships.FirstOrDefault(x => x.Type == "cover_art");
|
CoverArtEntity? coverArtEntity = (CoverArtEntity?)mangaDexEntity.Relationships.FirstOrDefault(entity =>
|
||||||
|
entity is CoverArtEntity);
|
||||||
|
|
||||||
if (coverArtRelationship == null)
|
if (coverArtEntity == null || string.IsNullOrWhiteSpace(coverArtEntity.Attributes?.FileName))
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
if (coverArtRelationship.Attributes.TryGetValue("fileName", out object? fileNameValue) == false)
|
string? fileName = coverArtEntity.Attributes?.FileName;
|
||||||
|
|
||||||
|
if (string.IsNullOrWhiteSpace(coverArtEntity.Attributes?.FileName))
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
if (fileNameValue == null)
|
return $"https://mangadex.org/covers/{mangaDexEntity.Id}/{fileName}";
|
||||||
return null;
|
|
||||||
|
|
||||||
return $"https://mangadex.org/covers/{searchResultData.Id}/{fileNameValue}";
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -11,8 +11,7 @@
|
|||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<None Remove="Search\MangaDex\SampleSearchResult.json" />
|
<None Remove="Search\MangaDex\SampleSearchResult.json" />
|
||||||
<None Remove="WebCrawlers\MangaDex\MetadataSample-Feed.json" />
|
<None Remove="Sources\MangaDex\Api\Manga-Chapter-Response.json" />
|
||||||
<None Remove="WebCrawlers\MangaDex\MetadataSample.json" />
|
|
||||||
<None Remove="WebCrawlers\Samples\MangaNato - Please Go Home, Akutsu-San!.htm" />
|
<None Remove="WebCrawlers\Samples\MangaNato - Please Go Home, Akutsu-San!.htm" />
|
||||||
<None Remove="Search\NatoManga\SampleSearchResult.json" />
|
<None Remove="Search\NatoManga\SampleSearchResult.json" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
@@ -27,10 +26,11 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<EmbeddedResource Include="Search\MangaDex\SampleSearchResult.json" />
|
<EmbeddedResource Include="Sources\MangaDex\Api\Manga-Chapter-Response.json" />
|
||||||
|
<EmbeddedResource Include="Sources\MangaDex\Api\Manga-Search-Response.json" />
|
||||||
<EmbeddedResource Include="Search\NatoManga\SampleSearchResult.json" />
|
<EmbeddedResource Include="Search\NatoManga\SampleSearchResult.json" />
|
||||||
<EmbeddedResource Include="WebCrawlers\MangaDex\MetadataSample-Feed.json" />
|
<EmbeddedResource Include="Sources\MangaDex\Api\Manga-Feed-Response.json" />
|
||||||
<EmbeddedResource Include="WebCrawlers\MangaDex\MetadataSample.json" />
|
<EmbeddedResource Include="Sources\MangaDex\Api\Manga-Response.json" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
@@ -56,4 +56,8 @@
|
|||||||
<Using Include="Xunit" />
|
<Using Include="Xunit" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<Folder Include="Search\MangaDex\" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
@@ -1,50 +0,0 @@
|
|||||||
using MangaReader.Core.HttpService;
|
|
||||||
using MangaReader.Core.Search;
|
|
||||||
using MangaReader.Core.Search.MangaDex;
|
|
||||||
using MangaReader.Tests.Utilities;
|
|
||||||
using NSubstitute;
|
|
||||||
using Shouldly;
|
|
||||||
|
|
||||||
namespace MangaReader.Tests.Search.MangaDex;
|
|
||||||
|
|
||||||
public class MangaDexSearchTests
|
|
||||||
{
|
|
||||||
class MangaDexSearchProviderTestWrapper(IHttpService httpService) : MangaDexSearchProvider(httpService)
|
|
||||||
{
|
|
||||||
internal string Test_GetSearchUrl(string keyword) => GetSearchUrl(keyword);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void Get_Search_Url()
|
|
||||||
{
|
|
||||||
// Arrange
|
|
||||||
IHttpService httpService = Substitute.For<IHttpService>();
|
|
||||||
MangaDexSearchProviderTestWrapper searchProvider = new(httpService);
|
|
||||||
|
|
||||||
// Act
|
|
||||||
string url = searchProvider.Test_GetSearchUrl("Gal can't be");
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
url.ShouldBe("https://api.mangadex.org/manga?title=gal can't be&limit=5");
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public async Task Get_Search_Result()
|
|
||||||
{
|
|
||||||
string resourceName = "MangaReader.Tests.Search.MangaDex.SampleSearchResult.json";
|
|
||||||
string searchResultJson = await ResourceHelper.ReadJsonResourceAsync(resourceName);
|
|
||||||
|
|
||||||
IHttpService httpService = Substitute.For<IHttpService>();
|
|
||||||
|
|
||||||
httpService.GetStringAsync(Arg.Any<string>(), CancellationToken.None)
|
|
||||||
.Returns(Task.FromResult(searchResultJson));
|
|
||||||
|
|
||||||
MangaDexSearchProvider searchProvider = new(httpService);
|
|
||||||
MangaSearchResult[] searchResult = await searchProvider.SearchAsync("Gal Can't Be Kind", CancellationToken.None);
|
|
||||||
|
|
||||||
searchResult.Length.ShouldBe(3);
|
|
||||||
searchResult[0].Title.ShouldBe("Gals Can’t Be Kind to Otaku!?");
|
|
||||||
searchResult[0].Url.ShouldBe("https://mangadex.org/title/ee96e2b7-9af2-4864-9656-649f4d3b6fec/gals-can-t-be-kind-to-otaku");
|
|
||||||
searchResult[0].Thumbnail.ShouldBe("https://mangadex.org/covers/ee96e2b7-9af2-4864-9656-649f4d3b6fec/6b3073de-bb65-4723-8113-6068bf8c6eb4.jpg");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"result": "ok",
|
||||||
|
"baseUrl": "https:\/\/cmdxd98sb0x3yprd.mangadex.network",
|
||||||
|
"chapter": {
|
||||||
|
"hash": "f867bd09bc8b19a37cf5486134acdda1",
|
||||||
|
"data": [ "1-5ae1738e10f5440a74f11832cf6203be5bd938f72d5a80d42cd149ee21287901.png", "2-e65c0795407b284664a83fba4c80ed5efceece5e74c4b0d5e0539890aa0d6dcc.png", "3-7506a36de3ec1f02bf5f85350a4b1676da89d095cfb98e88cd9a5840c9b72f3f.png", "4-3fb99ff194103ab51ece97f4a02a130742c7fa9413926ac1437992fc881d5d26.png", "5-21111c18ae1c04294a8ded2b0ef5395ae14a3d8d9e49b32a8187f71d12e5c4e4.png", "6-1168409fbb61f5c58746b324d6e9fe6225437f86b29da25e5c9bc9c6921f5afd.png", "7-1cf3170f68e0e1e8baf187635c721783c809ed2af2842ae194ad88b5ffcd8863.png", "8-b36237f62aadb2d6e9cb21ed65723d0e020e7509143c9186049e79591b2cc7b0.png", "9-d438f6d05eccfdf827b825e78432dcff0632df6193ce10288ce049774327b9ad.png", "10-ad8e53b6d8276bee6a05fefb41eb84ddb19a88ae3d7481f9ded7eaf43907cb7a.png", "11-219faea5a79409d92a6c02a97a5ef84e34c046ba3e5e079c87d1ecb6fc9b90a4.png", "12-dd32f11cdc417859593250c0315da7cad42e458f44ee17753794deca9b0c4d6f.png", "13-b6b8e15b5abd7e53af32cbfa1ac6dc1cb48fb314f2b5bb7412c9e393cf78224e.png" ],
|
||||||
|
"dataSaver": [ "1-7b0a40f35edd75f14d0aa9c0369f8bb05e41687165d97368b572f3c3c5b3db31.jpg", "2-9034c103958951d81c7ef3aedd43ad7ce58be33b85d6bfa72fd5e9cd3b2645b0.jpg", "3-62889381cc52713e234d2423d5da8a8da35d69f2b0a5a2671770922a91a86bdd.jpg", "4-864816f3e007c7a0cf7bd8f8fb4ee4bda5f3df04b32806737e323ab6a29f1c75.jpg", "5-b4beb6f2d40c13c96c9520de77ae00b24cea5357bf6e5d1eb1b04cc4e28f6abe.jpg", "6-ea57e27e2802b112a600a450f78e397f54377ffa8065c5eee9cb155e630df162.jpg", "7-ff7f9b9018b804c4edadaa32993585f1dab48919a6a51ccd7244020458f6bc4e.jpg", "8-100943bb7543c35265d8b05ba5a14aab8e2c92897e6fedd6ae5252d555a6c5a7.jpg", "9-2e618c48e4f98a8641b9b480e9da5ebb41dac59420500b7375454dfe643fabec.jpg", "10-327e4bb775e9f0c0964d5f7a35ed5c1f44feb09d5adf9eaad835b1e313422707.jpg", "11-6657ffa04caba29942063836f75f69979c4a7415243a8340bedaa3fdf07fc72b.jpg", "12-550cc7aac42468711ad9233392cf437576f6bfc54f067eb7493578813ceb6609.jpg", "13-b886b4ed986a473478e3db7bb18fe2faea567a1ad5e520408967410dcf8838d1.jpg" ]
|
||||||
|
}
|
||||||
|
}
|
||||||
161
MangaReader.Tests/Sources/MangaDex/Api/MangaDexClientTests.cs
Normal file
161
MangaReader.Tests/Sources/MangaDex/Api/MangaDexClientTests.cs
Normal file
@@ -0,0 +1,161 @@
|
|||||||
|
using MangaReader.Core.HttpService;
|
||||||
|
using MangaReader.Core.Sources.MangaDex.Api;
|
||||||
|
using MangaReader.Tests.Utilities;
|
||||||
|
using NSubstitute;
|
||||||
|
using Shouldly;
|
||||||
|
|
||||||
|
namespace MangaReader.Tests.Sources.MangaDex.Api;
|
||||||
|
|
||||||
|
public class MangaDexClientTests
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public async Task Search_Manga()
|
||||||
|
{
|
||||||
|
string searchResultJson = await ReadJsonResourceAsync("Manga-Search-Response.json");
|
||||||
|
|
||||||
|
IHttpService httpService = Substitute.For<IHttpService>();
|
||||||
|
|
||||||
|
httpService.GetStringAsync(Arg.Any<string>(), CancellationToken.None)
|
||||||
|
.Returns(Task.FromResult(searchResultJson));
|
||||||
|
|
||||||
|
MangaDexClient mangaDexClient = new(httpService);
|
||||||
|
MangaDexResponse? mangaDexResponse = await mangaDexClient.SearchMangaByTitleAsync("Some random text", CancellationToken.None);
|
||||||
|
|
||||||
|
// Testing here
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task Get_Manga_Metadata()
|
||||||
|
{
|
||||||
|
string searchResultJson = await ReadJsonResourceAsync("Manga-Response.json");
|
||||||
|
|
||||||
|
IHttpService httpService = Substitute.For<IHttpService>();
|
||||||
|
|
||||||
|
httpService.GetStringAsync(Arg.Any<string>(), CancellationToken.None)
|
||||||
|
.Returns(Task.FromResult(searchResultJson));
|
||||||
|
|
||||||
|
MangaDexClient mangaDexClient = new(httpService);
|
||||||
|
MangaDexResponse? mangaDexResponse = await mangaDexClient.GetMangaAsync(Guid.NewGuid(), CancellationToken.None);
|
||||||
|
|
||||||
|
mangaDexResponse.ShouldNotBeNull();
|
||||||
|
mangaDexResponse.Response.ShouldBe("entity");
|
||||||
|
mangaDexResponse.ShouldBeOfType<MangaDexEntityResponse>();
|
||||||
|
|
||||||
|
MangaDexEntityResponse? mangaDexEntityResponse = mangaDexResponse as MangaDexEntityResponse;
|
||||||
|
mangaDexEntityResponse.ShouldNotBeNull();
|
||||||
|
mangaDexEntityResponse.Data.ShouldNotBeNull();
|
||||||
|
mangaDexEntityResponse.Data.ShouldBeOfType<MangaEntity>();
|
||||||
|
|
||||||
|
MangaEntity? mangaEntity = mangaDexEntityResponse.Data as MangaEntity;
|
||||||
|
mangaEntity.ShouldNotBeNull();
|
||||||
|
|
||||||
|
mangaEntity.Attributes.ShouldNotBeNull();
|
||||||
|
mangaEntity.Attributes.Title.ShouldContainKey("en");
|
||||||
|
mangaEntity.Attributes.Title["en"].ShouldBe("Gals Can’t Be Kind to Otaku!?");
|
||||||
|
|
||||||
|
mangaEntity.Attributes.Description.ShouldContainKey("en");
|
||||||
|
mangaEntity.Attributes.Description["en"].ShouldBe("Takuya Seo is an otaku who likes \"anime for girls\" and can't say he likes it out loud. One day, he hooks up with two gals from his class, Amane and Ijichi, but it seems that Amane is also an otaku...");
|
||||||
|
|
||||||
|
mangaEntity.Attributes.Tags.Count.ShouldBe(5);
|
||||||
|
|
||||||
|
mangaEntity.Attributes.Tags[0].Attributes.ShouldNotBeNull();
|
||||||
|
mangaEntity.Attributes.Tags[0].Attributes!.Name.ShouldContainKey("en");
|
||||||
|
mangaEntity.Attributes.Tags[0].Attributes!.Name["en"].ShouldBe("Romance");
|
||||||
|
|
||||||
|
mangaEntity.Attributes.Tags[1].Attributes.ShouldNotBeNull();
|
||||||
|
mangaEntity.Attributes.Tags[1].Attributes!.Name.ShouldContainKey("en");
|
||||||
|
mangaEntity.Attributes.Tags[1].Attributes!.Name["en"].ShouldBe("Comedy");
|
||||||
|
|
||||||
|
mangaEntity.Attributes.Tags[2].Attributes.ShouldNotBeNull();
|
||||||
|
mangaEntity.Attributes.Tags[2].Attributes!.Name.ShouldContainKey("en");
|
||||||
|
mangaEntity.Attributes.Tags[2].Attributes!.Name["en"].ShouldBe("School Life");
|
||||||
|
|
||||||
|
mangaEntity.Attributes.Tags[3].Attributes.ShouldNotBeNull();
|
||||||
|
mangaEntity.Attributes.Tags[3].Attributes!.Name.ShouldContainKey("en");
|
||||||
|
mangaEntity.Attributes.Tags[3].Attributes!.Name["en"].ShouldBe("Slice of Life");
|
||||||
|
|
||||||
|
mangaEntity.Attributes.Tags[4].Attributes.ShouldNotBeNull();
|
||||||
|
mangaEntity.Attributes.Tags[4].Attributes!.Name.ShouldContainKey("en");
|
||||||
|
mangaEntity.Attributes.Tags[4].Attributes!.Name["en"].ShouldBe("Gyaru");
|
||||||
|
|
||||||
|
mangaEntity.Relationships.Count.ShouldBe(4);
|
||||||
|
mangaEntity.Relationships[0].ShouldBeOfType<AuthorEntity>();
|
||||||
|
mangaEntity.Relationships[1].ShouldBeOfType<ArtistEntity>();
|
||||||
|
mangaEntity.Relationships[2].ShouldBeOfType<CoverArtEntity>();
|
||||||
|
mangaEntity.Relationships[3].ShouldBeOfType<CreatorEntity>();
|
||||||
|
|
||||||
|
AuthorEntity authorEntity = (mangaEntity.Relationships[0] as AuthorEntity)!;
|
||||||
|
authorEntity.Attributes.ShouldNotBeNull();
|
||||||
|
authorEntity.Attributes.Name.ShouldBe("Norishiro-chan");
|
||||||
|
|
||||||
|
ArtistEntity artistEntity = (mangaEntity.Relationships[1] as ArtistEntity)!;
|
||||||
|
artistEntity.Attributes.ShouldNotBeNull();
|
||||||
|
artistEntity.Attributes.Name.ShouldBe("Sakana Uozimi");
|
||||||
|
|
||||||
|
CoverArtEntity coverArtEntity = (mangaEntity.Relationships[2] as CoverArtEntity)!;
|
||||||
|
coverArtEntity.Attributes.ShouldNotBeNull();
|
||||||
|
coverArtEntity.Attributes.FileName.ShouldBe("6b3073de-bb65-4723-8113-6068bf8c6eb4.jpg");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task Get_Manga_Feed()
|
||||||
|
{
|
||||||
|
string searchResultJson = await ReadJsonResourceAsync("Manga-Feed-Response.json");
|
||||||
|
|
||||||
|
IHttpService httpService = Substitute.For<IHttpService>();
|
||||||
|
|
||||||
|
httpService.GetStringAsync(Arg.Any<string>(), CancellationToken.None)
|
||||||
|
.Returns(Task.FromResult(searchResultJson));
|
||||||
|
|
||||||
|
MangaDexClient mangaDexClient = new(httpService);
|
||||||
|
MangaDexResponse? mangaDexResponse = await mangaDexClient.GetFeedAsync(Guid.NewGuid(), CancellationToken.None);
|
||||||
|
|
||||||
|
mangaDexResponse.ShouldNotBeNull();
|
||||||
|
mangaDexResponse.Response.ShouldBe("collection");
|
||||||
|
mangaDexResponse.ShouldBeOfType<MangaDexCollectionResponse>();
|
||||||
|
|
||||||
|
MangaDexCollectionResponse mangaDexEntityResponse = (mangaDexResponse as MangaDexCollectionResponse)!;
|
||||||
|
|
||||||
|
List<ChapterEntity> chapterEntities = [.. mangaDexEntityResponse.Data.FindAll(entity => entity is ChapterEntity).Cast<ChapterEntity>()];
|
||||||
|
chapterEntities.Count.ShouldBe(82);
|
||||||
|
|
||||||
|
chapterEntities[0].Attributes.ShouldNotBeNull();
|
||||||
|
chapterEntities[0].Attributes!.Volume.ShouldBeNull();
|
||||||
|
chapterEntities[0].Attributes!.Chapter.ShouldBe("69");
|
||||||
|
chapterEntities[0].Attributes!.Title.ShouldBe("Otaku & Gyaru & Playing Couple");
|
||||||
|
|
||||||
|
chapterEntities[1].Attributes.ShouldNotBeNull();
|
||||||
|
chapterEntities[1].Attributes!.Volume.ShouldBe("9");
|
||||||
|
chapterEntities[1].Attributes!.Chapter.ShouldBe("68");
|
||||||
|
chapterEntities[1].Attributes!.Title.ShouldBe("Otaku & Gyaru & A Couple Date");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task Get_Chapters()
|
||||||
|
{
|
||||||
|
string searchResultJson = await ReadJsonResourceAsync("Manga-Chapter-Response.json");
|
||||||
|
|
||||||
|
IHttpService httpService = Substitute.For<IHttpService>();
|
||||||
|
|
||||||
|
httpService.GetStringAsync(Arg.Any<string>(), CancellationToken.None)
|
||||||
|
.Returns(Task.FromResult(searchResultJson));
|
||||||
|
|
||||||
|
MangaDexClient mangaDexClient = new(httpService);
|
||||||
|
MangaDexChapterResponse? mangaDexChapterResponse = await mangaDexClient.GetChapterAsync(Guid.NewGuid(), CancellationToken.None);
|
||||||
|
|
||||||
|
mangaDexChapterResponse.ShouldNotBeNull();
|
||||||
|
mangaDexChapterResponse.Chapter.ShouldNotBeNull();
|
||||||
|
mangaDexChapterResponse.Chapter.Hash.ShouldBe("f867bd09bc8b19a37cf5486134acdda1");
|
||||||
|
mangaDexChapterResponse.Chapter.Data.Count.ShouldBe(13);
|
||||||
|
mangaDexChapterResponse.Chapter.Data[0].ShouldBe("1-5ae1738e10f5440a74f11832cf6203be5bd938f72d5a80d42cd149ee21287901.png");
|
||||||
|
mangaDexChapterResponse.Chapter.Data[12].ShouldBe("13-b6b8e15b5abd7e53af32cbfa1ac6dc1cb48fb314f2b5bb7412c9e393cf78224e.png");
|
||||||
|
mangaDexChapterResponse.Chapter.DataSaver.Count.ShouldBe(13);
|
||||||
|
mangaDexChapterResponse.Chapter.DataSaver[0].ShouldBe("1-7b0a40f35edd75f14d0aa9c0369f8bb05e41687165d97368b572f3c3c5b3db31.jpg");
|
||||||
|
mangaDexChapterResponse.Chapter.DataSaver[12].ShouldBe("13-b886b4ed986a473478e3db7bb18fe2faea567a1ad5e520408967410dcf8838d1.jpg");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static async Task<string> ReadJsonResourceAsync(string resourceName)
|
||||||
|
{
|
||||||
|
return await ResourceHelper.ReadJsonResourceAsync($"MangaReader.Tests.Sources.MangaDex.Api.{resourceName}");
|
||||||
|
}
|
||||||
|
}
|
||||||
108
MangaReader.Tests/Sources/MangaDex/Search/MangaDexSearchTests.cs
Normal file
108
MangaReader.Tests/Sources/MangaDex/Search/MangaDexSearchTests.cs
Normal file
@@ -0,0 +1,108 @@
|
|||||||
|
using MangaReader.Core.Search;
|
||||||
|
using MangaReader.Core.Sources.MangaDex.Api;
|
||||||
|
using MangaReader.Core.Sources.MangaDex.Search;
|
||||||
|
using NSubstitute;
|
||||||
|
using Shouldly;
|
||||||
|
|
||||||
|
namespace MangaReader.Tests.Sources.MangaDex.Search;
|
||||||
|
|
||||||
|
public class MangaDexSearchTests
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public async Task Get_Search_Result()
|
||||||
|
{
|
||||||
|
MangaDexCollectionResponse collectionResponse = new()
|
||||||
|
{
|
||||||
|
Result = "ok",
|
||||||
|
Response = "collection",
|
||||||
|
Data =
|
||||||
|
[
|
||||||
|
new MangaEntity()
|
||||||
|
{
|
||||||
|
Id = new Guid("ee96e2b7-9af2-4864-9656-649f4d3b6fec"),
|
||||||
|
Type = "manga",
|
||||||
|
Attributes = new()
|
||||||
|
{
|
||||||
|
Title = new()
|
||||||
|
{
|
||||||
|
{ "en", "Gals Can’t Be Kind to Otaku!?" }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Relationships =
|
||||||
|
[
|
||||||
|
new CoverArtEntity()
|
||||||
|
{
|
||||||
|
Id = new Guid("a06943fd-6309-49a8-a66a-8df0f6dc41eb"),
|
||||||
|
Type = "cover_art",
|
||||||
|
Attributes = new()
|
||||||
|
{
|
||||||
|
FileName = "6b3073de-bb65-4723-8113-6068bf8c6eb4.jpg"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
new MangaEntity()
|
||||||
|
{
|
||||||
|
Id = new Guid("16c34950-954c-4f0d-808e-d8278a546339"),
|
||||||
|
Type = "manga",
|
||||||
|
Attributes = new()
|
||||||
|
{
|
||||||
|
Title = new()
|
||||||
|
{
|
||||||
|
{ "en", "Suufungo no Mirai ga Wakaru You ni Natta Kedo, Onnagokoro wa Wakaranai." }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Relationships =
|
||||||
|
[
|
||||||
|
new CoverArtEntity()
|
||||||
|
{
|
||||||
|
Id = new Guid("ee8588b5-145f-4eee-981a-eb604856fbd2"),
|
||||||
|
Type = "cover_art",
|
||||||
|
Attributes = new()
|
||||||
|
{
|
||||||
|
FileName = "7d301e1e-642b-4b7d-b65b-9777b36e80bf.jpg"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
new MangaEntity()
|
||||||
|
{
|
||||||
|
Id = new Guid("f395bfc6-e52f-4f64-9cfb-87037215d214"),
|
||||||
|
Type = "manga",
|
||||||
|
Attributes = new()
|
||||||
|
{
|
||||||
|
Title = new()
|
||||||
|
{
|
||||||
|
{ "en", "Ienai Himitsu No Aishikata" }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Relationships =
|
||||||
|
[
|
||||||
|
new CoverArtEntity()
|
||||||
|
{
|
||||||
|
Id = new Guid("40df2d2e-b786-4aa9-9218-e3ed168cd96e"),
|
||||||
|
Type = "cover_art",
|
||||||
|
Attributes = new()
|
||||||
|
{
|
||||||
|
FileName = "c00a33cd-b26b-4554-a9f0-d6885c81eb36.jpg"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
IMangaDexClient mangaDexClient = Substitute.For<IMangaDexClient>();
|
||||||
|
|
||||||
|
mangaDexClient.SearchMangaByTitleAsync(Arg.Any<string>(), CancellationToken.None)
|
||||||
|
.Returns(collectionResponse);
|
||||||
|
|
||||||
|
MangaDexSearchProvider searchProvider = new(mangaDexClient);
|
||||||
|
MangaSearchResult[] searchResult = await searchProvider.SearchAsync("Gal Can't Be Kind", CancellationToken.None);
|
||||||
|
|
||||||
|
searchResult.Length.ShouldBe(3);
|
||||||
|
searchResult[0].Title.ShouldBe("Gals Can’t Be Kind to Otaku!?");
|
||||||
|
searchResult[0].Url.ShouldBe("https://mangadex.org/title/ee96e2b7-9af2-4864-9656-649f4d3b6fec/gals-can-t-be-kind-to-otaku");
|
||||||
|
searchResult[0].Thumbnail.ShouldBe("https://mangadex.org/covers/ee96e2b7-9af2-4864-9656-649f4d3b6fec/6b3073de-bb65-4723-8113-6068bf8c6eb4.jpg");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,51 +0,0 @@
|
|||||||
using MangaReader.Core.HttpService;
|
|
||||||
using MangaReader.Core.Sources.MangaDex.Api;
|
|
||||||
using MangaReader.Tests.Utilities;
|
|
||||||
using NSubstitute;
|
|
||||||
using Shouldly;
|
|
||||||
|
|
||||||
namespace MangaReader.Tests.WebCrawlers.MangaDex;
|
|
||||||
|
|
||||||
public class MangaDexMetadataTests
|
|
||||||
{
|
|
||||||
[Fact]
|
|
||||||
public async Task Get_Manga_Metadata()
|
|
||||||
{
|
|
||||||
string resourceName = "MangaReader.Tests.WebCrawlers.MangaDex.MetadataSample.json";
|
|
||||||
string searchResultJson = await ResourceHelper.ReadJsonResourceAsync(resourceName);
|
|
||||||
|
|
||||||
IHttpService httpService = Substitute.For<IHttpService>();
|
|
||||||
|
|
||||||
httpService.GetStringAsync(Arg.Any<string>(), CancellationToken.None)
|
|
||||||
.Returns(Task.FromResult(searchResultJson));
|
|
||||||
|
|
||||||
MangaDexClient mangaDexClient = new(httpService);
|
|
||||||
MangaDexResponse mangaDexResponse = await mangaDexClient.GetMangaAsync(Guid.NewGuid(), CancellationToken.None);
|
|
||||||
|
|
||||||
mangaDexResponse.Response.ShouldBe("entity");
|
|
||||||
mangaDexResponse.ShouldBeOfType<MangaDexEntityResponse>();
|
|
||||||
|
|
||||||
MangaDexEntityResponse? mangaDexEntityResponse = mangaDexResponse as MangaDexEntityResponse;
|
|
||||||
mangaDexEntityResponse.ShouldNotBeNull();
|
|
||||||
mangaDexEntityResponse.Data.ShouldNotBeNull();
|
|
||||||
mangaDexEntityResponse.Data.ShouldBeOfType<MangaEntity>();
|
|
||||||
|
|
||||||
MangaEntity? mangaEntity = mangaDexEntityResponse.Data as MangaEntity;
|
|
||||||
mangaEntity.ShouldNotBeNull();
|
|
||||||
mangaEntity.Attributes.Title.ShouldContainKey("en");
|
|
||||||
mangaEntity.Attributes.Title["en"].ShouldBe("Gals Can’t Be Kind to Otaku!?");
|
|
||||||
mangaEntity.Attributes.Description.ShouldContainKey("en");
|
|
||||||
mangaEntity.Attributes.Description["en"].ShouldBe("Takuya Seo is an otaku who likes \"anime for girls\" and can't say he likes it out loud. One day, he hooks up with two gals from his class, Amane and Ijichi, but it seems that Amane is also an otaku...");
|
|
||||||
mangaEntity.Attributes.Tags.Count.ShouldBe(5);
|
|
||||||
mangaEntity.Attributes.Tags[0].Attributes.Name.ShouldContainKey("en");
|
|
||||||
mangaEntity.Attributes.Tags[0].Attributes.Name["en"].ShouldBe("Romance");
|
|
||||||
mangaEntity.Attributes.Tags[1].Attributes.Name.ShouldContainKey("en");
|
|
||||||
mangaEntity.Attributes.Tags[1].Attributes.Name["en"].ShouldBe("Comedy");
|
|
||||||
mangaEntity.Attributes.Tags[2].Attributes.Name.ShouldContainKey("en");
|
|
||||||
mangaEntity.Attributes.Tags[2].Attributes.Name["en"].ShouldBe("School Life");
|
|
||||||
mangaEntity.Attributes.Tags[3].Attributes.Name.ShouldContainKey("en");
|
|
||||||
mangaEntity.Attributes.Tags[3].Attributes.Name["en"].ShouldBe("Slice of Life");
|
|
||||||
mangaEntity.Attributes.Tags[4].Attributes.Name.ShouldContainKey("en");
|
|
||||||
mangaEntity.Attributes.Tags[4].Attributes.Name["en"].ShouldBe("Gyaru");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user