mirror of
https://github.com/jellyfin/jellyfin-plugin-bookshelf.git
synced 2024-11-26 23:20:27 +00:00
Add Google Books external id (#80)
* Add error handling, external ID and ID search to the Google Books provider * Fix CodeQL warnings
This commit is contained in:
parent
fa45821aa9
commit
e31429c87e
@ -1,4 +1,3 @@
|
|||||||
|
|
||||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||||
# Visual Studio 15
|
# Visual Studio 15
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Jellyfin.Plugin.Bookshelf", "Jellyfin.Plugin.Bookshelf\Jellyfin.Plugin.Bookshelf.csproj", "{8D744D83-5403-4BA4-8794-760AF69DAC06}"
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Jellyfin.Plugin.Bookshelf", "Jellyfin.Plugin.Bookshelf\Jellyfin.Plugin.Bookshelf.csproj", "{8D744D83-5403-4BA4-8794-760AF69DAC06}"
|
||||||
|
@ -0,0 +1,76 @@
|
|||||||
|
using System.Globalization;
|
||||||
|
using System.Net.Http;
|
||||||
|
using System.Net.Http.Json;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Jellyfin.Extensions.Json;
|
||||||
|
using MediaBrowser.Common.Net;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
|
namespace Jellyfin.Plugin.Bookshelf.Providers.GoogleBooks
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Base class for the Google Books providers.
|
||||||
|
/// </summary>
|
||||||
|
public abstract class BaseGoogleBooksProvider
|
||||||
|
{
|
||||||
|
private readonly ILogger<BaseGoogleBooksProvider> _logger;
|
||||||
|
private readonly IHttpClientFactory _httpClientFactory;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="BaseGoogleBooksProvider"/> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="logger">Instance of the <see cref="ILogger{GoogleBooksProvider}"/> interface.</param>
|
||||||
|
/// <param name="httpClientFactory">Instance of the <see cref="IHttpClientFactory"/> interface.</param>
|
||||||
|
protected BaseGoogleBooksProvider(ILogger<BaseGoogleBooksProvider> logger, IHttpClientFactory httpClientFactory)
|
||||||
|
{
|
||||||
|
_logger = logger;
|
||||||
|
_httpClientFactory = httpClientFactory;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get a result from the Google Books API.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="T">Type of expected result.</typeparam>
|
||||||
|
/// <param name="url">API URL to call.</param>
|
||||||
|
/// <param name="cancellationToken">The cancellation token.</param>
|
||||||
|
/// <returns>The API result.</returns>
|
||||||
|
protected async Task<T?> GetResultFromAPI<T>(string url, CancellationToken cancellationToken)
|
||||||
|
where T : class
|
||||||
|
{
|
||||||
|
var response = await _httpClientFactory
|
||||||
|
.CreateClient(NamedClient.Default)
|
||||||
|
.GetAsync(url, cancellationToken)
|
||||||
|
.ConfigureAwait(false);
|
||||||
|
|
||||||
|
if (!response.IsSuccessStatusCode)
|
||||||
|
{
|
||||||
|
var errorResponse = await response.Content.ReadFromJsonAsync<ErrorResponse>(JsonDefaults.Options, cancellationToken).ConfigureAwait(false);
|
||||||
|
|
||||||
|
if (errorResponse != null)
|
||||||
|
{
|
||||||
|
_logger.LogError("Error response from Google Books API: {ErrorMessage} (status code: {StatusCode})", errorResponse.Error.Message, response.StatusCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return await response.Content.ReadFromJsonAsync<T>(JsonDefaults.Options, cancellationToken).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Fetch book data from the Google Books API.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="googleBookId">The volume id.</param>
|
||||||
|
/// <param name="cancellationToken">The cancellation token.</param>
|
||||||
|
/// <returns>The API result.</returns>
|
||||||
|
protected async Task<BookResult?> FetchBookData(string googleBookId, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
cancellationToken.ThrowIfCancellationRequested();
|
||||||
|
|
||||||
|
var url = string.Format(CultureInfo.InvariantCulture, GoogleApiUrls.DetailsUrl, googleBookId);
|
||||||
|
|
||||||
|
return await GetResultFromAPI<BookResult>(url, cancellationToken).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
15
Jellyfin.Plugin.Bookshelf/Providers/GoogleBooks/Error.cs
Normal file
15
Jellyfin.Plugin.Bookshelf/Providers/GoogleBooks/Error.cs
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Net;
|
||||||
|
|
||||||
|
namespace Jellyfin.Plugin.Bookshelf.Providers.GoogleBooks
|
||||||
|
{
|
||||||
|
internal class Error
|
||||||
|
{
|
||||||
|
public HttpStatusCode Code { get; set; }
|
||||||
|
|
||||||
|
public string Message { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
public IEnumerable<ErrorDetails> Errors { get; set; } = Enumerable.Empty<ErrorDetails>();
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,11 @@
|
|||||||
|
namespace Jellyfin.Plugin.Bookshelf.Providers.GoogleBooks
|
||||||
|
{
|
||||||
|
internal class ErrorDetails
|
||||||
|
{
|
||||||
|
public string Message { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
public string Domain { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
public string Reason { get; set; } = string.Empty;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,7 @@
|
|||||||
|
namespace Jellyfin.Plugin.Bookshelf.Providers.GoogleBooks
|
||||||
|
{
|
||||||
|
internal class ErrorResponse
|
||||||
|
{
|
||||||
|
public Error Error { get; set; } = new Error();
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,26 @@
|
|||||||
|
using MediaBrowser.Controller.Entities;
|
||||||
|
using MediaBrowser.Controller.Providers;
|
||||||
|
using MediaBrowser.Model.Entities;
|
||||||
|
using MediaBrowser.Model.Providers;
|
||||||
|
|
||||||
|
namespace Jellyfin.Plugin.Bookshelf.Providers.GoogleBooks
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public class GoogleBooksExternalId : IExternalId
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public string ProviderName => GoogleBooksConstants.ProviderName;
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public string Key => GoogleBooksConstants.ProviderId;
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public ExternalIdMediaType? Type => null; // TODO: No ExternalIdMediaType value for book
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public string? UrlFormatString => "https://books.google.com/books?id={0}";
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public bool Supports(IHasProviderIds item) => item is Book;
|
||||||
|
}
|
||||||
|
}
|
@ -1,32 +1,35 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Globalization;
|
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Net.Http;
|
using System.Net.Http;
|
||||||
using System.Net.Http.Json;
|
using System.Net.Http.Json;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Jellyfin.Extensions.Json;
|
|
||||||
using MediaBrowser.Common.Net;
|
using MediaBrowser.Common.Net;
|
||||||
using MediaBrowser.Controller.Entities;
|
using MediaBrowser.Controller.Entities;
|
||||||
using MediaBrowser.Controller.Providers;
|
using MediaBrowser.Controller.Providers;
|
||||||
using MediaBrowser.Model.Entities;
|
using MediaBrowser.Model.Entities;
|
||||||
using MediaBrowser.Model.Providers;
|
using MediaBrowser.Model.Providers;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
namespace Jellyfin.Plugin.Bookshelf.Providers.GoogleBooks
|
namespace Jellyfin.Plugin.Bookshelf.Providers.GoogleBooks
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Google books image provider.
|
/// Google books image provider.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class GoogleBooksImageProvider : IRemoteImageProvider
|
public class GoogleBooksImageProvider : BaseGoogleBooksProvider, IRemoteImageProvider
|
||||||
{
|
{
|
||||||
|
private readonly ILogger<GoogleBooksImageProvider> _logger;
|
||||||
private readonly IHttpClientFactory _httpClientFactory;
|
private readonly IHttpClientFactory _httpClientFactory;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="GoogleBooksImageProvider"/> class.
|
/// Initializes a new instance of the <see cref="GoogleBooksImageProvider"/> class.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
/// <param name="logger">Instance of the <see cref="ILogger{GoogleBooksProvider}"/> interface.</param>
|
||||||
/// <param name="httpClientFactory">Instance of the <see cref="IHttpClientFactory"/> interface.</param>
|
/// <param name="httpClientFactory">Instance of the <see cref="IHttpClientFactory"/> interface.</param>
|
||||||
public GoogleBooksImageProvider(IHttpClientFactory httpClientFactory)
|
public GoogleBooksImageProvider(ILogger<GoogleBooksImageProvider> logger, IHttpClientFactory httpClientFactory)
|
||||||
|
: base(logger, httpClientFactory)
|
||||||
{
|
{
|
||||||
|
_logger = logger;
|
||||||
_httpClientFactory = httpClientFactory;
|
_httpClientFactory = httpClientFactory;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -74,19 +77,6 @@ namespace Jellyfin.Plugin.Bookshelf.Providers.GoogleBooks
|
|||||||
return list;
|
return list;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<BookResult?> FetchBookData(string googleBookId, CancellationToken cancellationToken)
|
|
||||||
{
|
|
||||||
cancellationToken.ThrowIfCancellationRequested();
|
|
||||||
|
|
||||||
var url = string.Format(CultureInfo.InvariantCulture, GoogleApiUrls.DetailsUrl, googleBookId);
|
|
||||||
|
|
||||||
var httpClient = _httpClientFactory.CreateClient(NamedClient.Default);
|
|
||||||
|
|
||||||
using var response = await httpClient.GetAsync(url, cancellationToken).ConfigureAwait(false);
|
|
||||||
|
|
||||||
return await response.Content.ReadFromJsonAsync<BookResult>(JsonDefaults.Options, cancellationToken).ConfigureAwait(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
private List<string> ProcessBookImage(BookResult bookResult)
|
private List<string> ProcessBookImage(BookResult bookResult)
|
||||||
{
|
{
|
||||||
var images = new List<string>();
|
var images = new List<string>();
|
||||||
|
@ -4,12 +4,10 @@ using System.Globalization;
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Net;
|
using System.Net;
|
||||||
using System.Net.Http;
|
using System.Net.Http;
|
||||||
using System.Net.Http.Json;
|
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Jellyfin.Extensions.Json;
|
|
||||||
using MediaBrowser.Common.Net;
|
using MediaBrowser.Common.Net;
|
||||||
using MediaBrowser.Controller.Entities;
|
using MediaBrowser.Controller.Entities;
|
||||||
using MediaBrowser.Controller.Providers;
|
using MediaBrowser.Controller.Providers;
|
||||||
@ -22,7 +20,7 @@ namespace Jellyfin.Plugin.Bookshelf.Providers.GoogleBooks
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Google books provider.
|
/// Google books provider.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class GoogleBooksProvider : IRemoteMetadataProvider<Book, BookInfo>
|
public class GoogleBooksProvider : BaseGoogleBooksProvider, IRemoteMetadataProvider<Book, BookInfo>
|
||||||
{
|
{
|
||||||
// convert these characters to whitespace for better matching
|
// convert these characters to whitespace for better matching
|
||||||
// there are two dashes with different char codes
|
// there are two dashes with different char codes
|
||||||
@ -69,6 +67,7 @@ namespace Jellyfin.Plugin.Bookshelf.Providers.GoogleBooks
|
|||||||
public GoogleBooksProvider(
|
public GoogleBooksProvider(
|
||||||
ILogger<GoogleBooksProvider> logger,
|
ILogger<GoogleBooksProvider> logger,
|
||||||
IHttpClientFactory httpClientFactory)
|
IHttpClientFactory httpClientFactory)
|
||||||
|
: base(logger, httpClientFactory)
|
||||||
{
|
{
|
||||||
_httpClientFactory = httpClientFactory;
|
_httpClientFactory = httpClientFactory;
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
@ -81,38 +80,59 @@ namespace Jellyfin.Plugin.Bookshelf.Providers.GoogleBooks
|
|||||||
public async Task<IEnumerable<RemoteSearchResult>> GetSearchResults(BookInfo searchInfo, CancellationToken cancellationToken)
|
public async Task<IEnumerable<RemoteSearchResult>> GetSearchResults(BookInfo searchInfo, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
cancellationToken.ThrowIfCancellationRequested();
|
cancellationToken.ThrowIfCancellationRequested();
|
||||||
var list = new List<RemoteSearchResult>();
|
|
||||||
|
|
||||||
var searchResults = await GetSearchResultsInternal(searchInfo, cancellationToken).ConfigureAwait(false);
|
Func<BookResult, RemoteSearchResult> getSearchResultFromBook = (BookResult info) =>
|
||||||
if (searchResults is null)
|
|
||||||
{
|
{
|
||||||
return Enumerable.Empty<RemoteSearchResult>();
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (var result in searchResults.Items)
|
|
||||||
{
|
|
||||||
if (result.VolumeInfo is null)
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
var remoteSearchResult = new RemoteSearchResult();
|
var remoteSearchResult = new RemoteSearchResult();
|
||||||
|
|
||||||
remoteSearchResult.SetProviderId(GoogleBooksConstants.ProviderId, result.Id);
|
remoteSearchResult.SetProviderId(GoogleBooksConstants.ProviderId, info.Id);
|
||||||
remoteSearchResult.SearchProviderName = GoogleBooksConstants.ProviderName;
|
remoteSearchResult.SearchProviderName = GoogleBooksConstants.ProviderName;
|
||||||
remoteSearchResult.Name = result.VolumeInfo.Title;
|
remoteSearchResult.Name = info.VolumeInfo?.Title;
|
||||||
remoteSearchResult.Overview = WebUtility.HtmlDecode(result.VolumeInfo.Description);
|
remoteSearchResult.Overview = WebUtility.HtmlDecode(info.VolumeInfo?.Description);
|
||||||
remoteSearchResult.ProductionYear = GetYearFromPublishedDate(result.VolumeInfo.PublishedDate);
|
remoteSearchResult.ProductionYear = GetYearFromPublishedDate(info.VolumeInfo?.PublishedDate);
|
||||||
|
|
||||||
if (result.VolumeInfo.ImageLinks?.Thumbnail != null)
|
if (info.VolumeInfo?.ImageLinks?.Thumbnail != null)
|
||||||
{
|
{
|
||||||
remoteSearchResult.ImageUrl = result.VolumeInfo.ImageLinks.Thumbnail;
|
remoteSearchResult.ImageUrl = info.VolumeInfo.ImageLinks.Thumbnail;
|
||||||
}
|
}
|
||||||
|
|
||||||
list.Add(remoteSearchResult);
|
return remoteSearchResult;
|
||||||
}
|
};
|
||||||
|
|
||||||
return list;
|
var googleBookId = searchInfo.GetProviderId(GoogleBooksConstants.ProviderId);
|
||||||
|
|
||||||
|
if (!string.IsNullOrWhiteSpace(googleBookId))
|
||||||
|
{
|
||||||
|
var bookData = await FetchBookData(googleBookId, cancellationToken).ConfigureAwait(false);
|
||||||
|
|
||||||
|
if (bookData == null || bookData.VolumeInfo == null)
|
||||||
|
{
|
||||||
|
return Enumerable.Empty<RemoteSearchResult>();
|
||||||
|
}
|
||||||
|
|
||||||
|
return new[] { getSearchResultFromBook(bookData) };
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var searchResults = await GetSearchResultsInternal(searchInfo, cancellationToken).ConfigureAwait(false);
|
||||||
|
if (searchResults is null)
|
||||||
|
{
|
||||||
|
return Enumerable.Empty<RemoteSearchResult>();
|
||||||
|
}
|
||||||
|
|
||||||
|
var list = new List<RemoteSearchResult>();
|
||||||
|
foreach (var result in searchResults.Items)
|
||||||
|
{
|
||||||
|
if (result.VolumeInfo is null)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
list.Add(getSearchResultFromBook(result));
|
||||||
|
}
|
||||||
|
|
||||||
|
return list;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
@ -173,11 +193,7 @@ namespace Jellyfin.Plugin.Bookshelf.Providers.GoogleBooks
|
|||||||
var searchString = GetSearchString(item);
|
var searchString = GetSearchString(item);
|
||||||
var url = string.Format(CultureInfo.InvariantCulture, GoogleApiUrls.SearchUrl, WebUtility.UrlEncode(searchString), 0, 20);
|
var url = string.Format(CultureInfo.InvariantCulture, GoogleApiUrls.SearchUrl, WebUtility.UrlEncode(searchString), 0, 20);
|
||||||
|
|
||||||
var httpClient = _httpClientFactory.CreateClient(NamedClient.Default);
|
return await GetResultFromAPI<SearchResult>(url, cancellationToken).ConfigureAwait(false);
|
||||||
|
|
||||||
using var response = await httpClient.GetAsync(url, cancellationToken).ConfigureAwait(false);
|
|
||||||
|
|
||||||
return await response.Content.ReadFromJsonAsync<SearchResult>(JsonDefaults.Options, cancellationToken).ConfigureAwait(false);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<string?> FetchBookId(BookInfo item, CancellationToken cancellationToken)
|
private async Task<string?> FetchBookId(BookInfo item, CancellationToken cancellationToken)
|
||||||
@ -240,19 +256,6 @@ namespace Jellyfin.Plugin.Bookshelf.Providers.GoogleBooks
|
|||||||
return bookReleaseYear;
|
return bookReleaseYear;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<BookResult?> FetchBookData(string googleBookId, CancellationToken cancellationToken)
|
|
||||||
{
|
|
||||||
cancellationToken.ThrowIfCancellationRequested();
|
|
||||||
|
|
||||||
var url = string.Format(CultureInfo.InvariantCulture, GoogleApiUrls.DetailsUrl, googleBookId);
|
|
||||||
|
|
||||||
var httpClient = _httpClientFactory.CreateClient(NamedClient.Default);
|
|
||||||
|
|
||||||
using var response = await httpClient.GetAsync(url, cancellationToken).ConfigureAwait(false);
|
|
||||||
|
|
||||||
return await response.Content.ReadFromJsonAsync<BookResult>(JsonDefaults.Options, cancellationToken).ConfigureAwait(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
private Book? ProcessBookData(BookResult bookResult, CancellationToken cancellationToken)
|
private Book? ProcessBookData(BookResult bookResult, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
if (bookResult.VolumeInfo is null)
|
if (bookResult.VolumeInfo is null)
|
||||||
|
@ -3,6 +3,7 @@ using Jellyfin.Plugin.Bookshelf.Providers.GoogleBooks;
|
|||||||
using Jellyfin.Plugin.Bookshelf.Tests.Http;
|
using Jellyfin.Plugin.Bookshelf.Tests.Http;
|
||||||
using MediaBrowser.Controller.Entities;
|
using MediaBrowser.Controller.Entities;
|
||||||
using MediaBrowser.Controller.Providers;
|
using MediaBrowser.Controller.Providers;
|
||||||
|
using Microsoft.Extensions.Logging.Abstractions;
|
||||||
using NSubstitute;
|
using NSubstitute;
|
||||||
|
|
||||||
namespace Jellyfin.Plugin.Bookshelf.Tests
|
namespace Jellyfin.Plugin.Bookshelf.Tests
|
||||||
@ -21,7 +22,7 @@ namespace Jellyfin.Plugin.Bookshelf.Tests
|
|||||||
using var client = new HttpClient(mockedMessageHandler);
|
using var client = new HttpClient(mockedMessageHandler);
|
||||||
mockedHttpClientFactory.CreateClient(Arg.Any<string>()).Returns(client);
|
mockedHttpClientFactory.CreateClient(Arg.Any<string>()).Returns(client);
|
||||||
|
|
||||||
IRemoteImageProvider provider = new GoogleBooksImageProvider(mockedHttpClientFactory);
|
IRemoteImageProvider provider = new GoogleBooksImageProvider(NullLogger<GoogleBooksImageProvider>.Instance, mockedHttpClientFactory);
|
||||||
|
|
||||||
var images = await provider.GetImages(new Book()
|
var images = await provider.GetImages(new Book()
|
||||||
{
|
{
|
||||||
|
@ -27,7 +27,7 @@ namespace Jellyfin.Plugin.Bookshelf.Tests
|
|||||||
#region GetSearchResults
|
#region GetSearchResults
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task GetSearchResults_Success()
|
public async Task GetSearchResults_ByName_Success()
|
||||||
{
|
{
|
||||||
var mockedMessageHandler = new MockHttpMessageHandler(new List<(Func<Uri, bool> requestMatcher, MockHttpResponse response)>
|
var mockedMessageHandler = new MockHttpMessageHandler(new List<(Func<Uri, bool> requestMatcher, MockHttpResponse response)>
|
||||||
{
|
{
|
||||||
@ -65,6 +65,131 @@ namespace Jellyfin.Plugin.Bookshelf.Tests
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task GetSearchResults_ByProviderId_Success()
|
||||||
|
{
|
||||||
|
var mockedMessageHandler = new MockHttpMessageHandler(new List<(Func<Uri, bool> requestMatcher, MockHttpResponse response)>
|
||||||
|
{
|
||||||
|
((Uri uri) => uri.AbsoluteUri.Contains("volumes/49T5twEACAAJ"), new MockHttpResponse(HttpStatusCode.OK, GetEnglishTestVolumeResult()))
|
||||||
|
});
|
||||||
|
|
||||||
|
var mockedHttpClientFactory = Substitute.For<IHttpClientFactory>();
|
||||||
|
using var client = new HttpClient(mockedMessageHandler);
|
||||||
|
mockedHttpClientFactory.CreateClient(Arg.Any<string>()).Returns(client);
|
||||||
|
|
||||||
|
IRemoteMetadataProvider<Book, BookInfo> provider = new GoogleBooksProvider(NullLogger<GoogleBooksProvider>.Instance, mockedHttpClientFactory);
|
||||||
|
|
||||||
|
var results = await provider.GetSearchResults(new BookInfo()
|
||||||
|
{
|
||||||
|
ProviderIds = new Dictionary<string, string>()
|
||||||
|
{
|
||||||
|
{ GoogleBooksConstants.ProviderId, "49T5twEACAAJ" }
|
||||||
|
}
|
||||||
|
}, CancellationToken.None);
|
||||||
|
|
||||||
|
Assert.True(results.All(result => result.SearchProviderName == GoogleBooksConstants.ProviderName));
|
||||||
|
|
||||||
|
Assert.Collection(
|
||||||
|
results,
|
||||||
|
first =>
|
||||||
|
{
|
||||||
|
Assert.Equal("Children of Time", first.Name);
|
||||||
|
Assert.True(HasGoogleId("49T5twEACAAJ", first.ProviderIds));
|
||||||
|
Assert.Equal("http://books.google.com/books/content?id=49T5twEACAAJ&printsec=frontcover&img=1&zoom=1&imgtk=AFLRE70U9t4z91EAYhiD2AYOR9pzNu86QDKZebNLQo4K3jMaJ748TC5LvCoZGt9ON4pZ54H8RoIRyCB5IveVDmt49QjeJlbJtWLlZoksRHXInrEVmo2476WXKcLhZOjp41Vu_5Lb05oJ&source=gbs_api", first.ImageUrl);
|
||||||
|
Assert.Equal("<b>Adrian Tchaikovksy's award-winning novel <i>Children of Time</i>, is the epic story of humanity's battle for survival on a terraformed planet.</b><b>" +
|
||||||
|
"<br></b>Who will inherit this new Earth?<br><br>" +
|
||||||
|
"The last remnants of the human race left a dying Earth, desperate to find a new home among the stars. " +
|
||||||
|
"Following in the footsteps of their ancestors, they discover the greatest treasure of the past age - a world terraformed and prepared for human life." +
|
||||||
|
"<br><br>But all is not right in this new Eden. In the long years since the planet was abandoned, the work of its architects has borne disastrous fruit. " +
|
||||||
|
"The planet is not waiting for them, pristine and unoccupied. New masters have turned it from a refuge into mankind's worst nightmare." +
|
||||||
|
"<br><br>Now two civilizations are on a collision course, both testing the boundaries of what they will do to survive. " +
|
||||||
|
"As the fate of humanity hangs in the balance, who are the true heirs of this new Earth?span", first.Overview);
|
||||||
|
Assert.Equal(2018, first.ProductionYear);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task GetSearchResults_ByProviderId_WithInvalidId_ReturnsNoResults()
|
||||||
|
{
|
||||||
|
// API will return a 503 code if the volume id is invalid
|
||||||
|
string errorResponse = @"
|
||||||
|
{
|
||||||
|
""error"": {
|
||||||
|
""code"": 503,
|
||||||
|
""message"": ""Service temporarily unavailable."",
|
||||||
|
""errors"": [
|
||||||
|
{
|
||||||
|
""message"": ""Service temporarily unavailable."",
|
||||||
|
""domain"": ""global"",
|
||||||
|
""reason"": ""backendFailed""
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
";
|
||||||
|
var mockedMessageHandler = new MockHttpMessageHandler(new List<(Func<Uri, bool> requestMatcher, MockHttpResponse response)>
|
||||||
|
{
|
||||||
|
((Uri uri) => uri.AbsoluteUri.Contains("volumes/49T55wEACAA"), new MockHttpResponse(HttpStatusCode.NotFound, errorResponse))
|
||||||
|
});
|
||||||
|
|
||||||
|
var mockedHttpClientFactory = Substitute.For<IHttpClientFactory>();
|
||||||
|
using var client = new HttpClient(mockedMessageHandler);
|
||||||
|
mockedHttpClientFactory.CreateClient(Arg.Any<string>()).Returns(client);
|
||||||
|
|
||||||
|
IRemoteMetadataProvider<Book, BookInfo> provider = new GoogleBooksProvider(NullLogger<GoogleBooksProvider>.Instance, mockedHttpClientFactory);
|
||||||
|
|
||||||
|
var results = await provider.GetSearchResults(new BookInfo()
|
||||||
|
{
|
||||||
|
ProviderIds = new Dictionary<string, string>()
|
||||||
|
{
|
||||||
|
{ GoogleBooksConstants.ProviderId, "49T55wEACAA" }
|
||||||
|
}
|
||||||
|
}, CancellationToken.None);
|
||||||
|
|
||||||
|
Assert.Empty(results);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task GetSearchResults_ByProviderId_WithNonExistentId_ReturnsNoResults()
|
||||||
|
{
|
||||||
|
// API will return a 404 code if the volume is not found
|
||||||
|
string errorResponse = @"
|
||||||
|
{
|
||||||
|
""error"": {
|
||||||
|
""code"": 404,
|
||||||
|
""message"": ""The volume ID could not be found."",
|
||||||
|
""errors"": [
|
||||||
|
{
|
||||||
|
""message"": ""The volume ID could not be found."",
|
||||||
|
""domain"": ""global"",
|
||||||
|
""reason"": ""notFound""
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
";
|
||||||
|
var mockedMessageHandler = new MockHttpMessageHandler(new List<(Func<Uri, bool> requestMatcher, MockHttpResponse response)>
|
||||||
|
{
|
||||||
|
((Uri uri) => uri.AbsoluteUri.Contains("volumes/49T55wEACAAX"), new MockHttpResponse(HttpStatusCode.NotFound, errorResponse))
|
||||||
|
});
|
||||||
|
|
||||||
|
var mockedHttpClientFactory = Substitute.For<IHttpClientFactory>();
|
||||||
|
using var client = new HttpClient(mockedMessageHandler);
|
||||||
|
mockedHttpClientFactory.CreateClient(Arg.Any<string>()).Returns(client);
|
||||||
|
|
||||||
|
IRemoteMetadataProvider<Book, BookInfo> provider = new GoogleBooksProvider(NullLogger<GoogleBooksProvider>.Instance, mockedHttpClientFactory);
|
||||||
|
|
||||||
|
var results = await provider.GetSearchResults(new BookInfo()
|
||||||
|
{
|
||||||
|
ProviderIds = new Dictionary<string, string>()
|
||||||
|
{
|
||||||
|
{ GoogleBooksConstants.ProviderId, "49T55wEACAAX" }
|
||||||
|
}
|
||||||
|
}, CancellationToken.None);
|
||||||
|
|
||||||
|
Assert.Empty(results);
|
||||||
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region GetMetadata
|
#region GetMetadata
|
||||||
@ -216,7 +341,6 @@ namespace Jellyfin.Plugin.Bookshelf.Tests
|
|||||||
Assert.Equal("Children of Time", bookInfo.Name);
|
Assert.Equal("Children of Time", bookInfo.Name);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void GetBookMetadata_WithNameAndDefaultSeriesName_CorrectlyResetSeriesName()
|
public void GetBookMetadata_WithNameAndDefaultSeriesName_CorrectlyResetSeriesName()
|
||||||
{
|
{
|
||||||
|
Loading…
Reference in New Issue
Block a user