diff --git a/README.md b/README.md index d8f0aef..3e821da 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ This repository contains the C# source code for the .NET clients to the PDFinch You need to have an account with enough credits and an active set of API keys. You can [register an account or log in here](https://www.pdfinch.com/account/login). # Basic usage -Our API currently supports one thing: generating PDFs from HTML. You can do so by calling `IPdfClient.GeneratePdfFromHtmlAsync()`: +Our API currently supports one thing: generating PDFs from HTML, with some variations. The most simple way is available by calling `IPdfClient.GeneratePdfFromHtmlAsync()`: ```C# IPdfClient pdfClient = ... // see chapter "Obtaining an IPdfClient" below @@ -31,8 +31,29 @@ else throw new InvalidOperationException($"Error generating PDF: {pdfResult.StatusMessage}"); } ``` + +You can also merge multiple blocks of HTML: + +```C# +PdfResult pdfResult = await pdfClient.GenerateMergedPdfFromHtmlAsync(new [] +{ + new PdfRequest("

Your-Html-String

") + { + MarginBottom = 10, + MarginTop = 10, + MarginLeft = 10, + MarginRight = 10, + Landscape = false, + GrayScale = false, + }, new PdfRequest("

Your-Second-Html

") + { + Landscape = true, + }, +}); +``` + # Handling responses -`PDFinch.Client.Common.IPdfClient.GeneratePdfFromHtmlAsync()` returns a `Task>`. This means the call should be `await`ed, and the return value must be checked for success. +The `PDFinch.Client.Common.IPdfClient.Generate...Async()` methods return a `Task>`. This means the call should be `await`ed, and the return value must be checked for success. If `PdfResult.Success` is `false`, `.Data` will be `null` and `.StatusMessage` will contain a machine-readable (JSON) error message returned by the API. diff --git a/TestClients/PDFinch.TestClient.ASPNET60/Pages/Index.cshtml.cs b/TestClients/PDFinch.TestClient.ASPNET60/Pages/Index.cshtml.cs index 1deb1db..905186c 100644 --- a/TestClients/PDFinch.TestClient.ASPNET60/Pages/Index.cshtml.cs +++ b/TestClients/PDFinch.TestClient.ASPNET60/Pages/Index.cshtml.cs @@ -18,7 +18,7 @@ public IndexModel(IPdfClientFactory pdfClientFactory/*, IPdfClient pdfClient*/) public async Task OnGet() { - // TODO: separate page for DI + // TODO: separate page for single-client DI //// Typed client (IPdfClient in constructor) //var pdfResult = await _pdfClient.GeneratePdfFromHtmlAsync($"

Typed IPdfClient

Generated on {DateTime.Now:F}."); diff --git a/TestClients/PDFinch.TestClient.ASPNET60/Pages/Merge.cshtml b/TestClients/PDFinch.TestClient.ASPNET60/Pages/Merge.cshtml new file mode 100644 index 0000000..81c1aee --- /dev/null +++ b/TestClients/PDFinch.TestClient.ASPNET60/Pages/Merge.cshtml @@ -0,0 +1,12 @@ +@page +@model PDFinch.TestClient.ASPNET60.Pages.MergeModel +@{ +} + +

Error

+ +

If you see this, an error occurred. The error:

+ +
@Model.StatusMessage
+ +

Hit reload to try again.

diff --git a/TestClients/PDFinch.TestClient.ASPNET60/Pages/Merge.cshtml.cs b/TestClients/PDFinch.TestClient.ASPNET60/Pages/Merge.cshtml.cs new file mode 100644 index 0000000..88648a0 --- /dev/null +++ b/TestClients/PDFinch.TestClient.ASPNET60/Pages/Merge.cshtml.cs @@ -0,0 +1,58 @@ +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.RazorPages; +using PDFinch.Client.Common; + +namespace PDFinch.TestClient.ASPNET60.Pages +{ + public class MergeModel : PageModel + { + private readonly IPdfClientFactory _pdfClientFactory; + + public MergeModel(IPdfClientFactory pdfClientFactory) + { + _pdfClientFactory = pdfClientFactory; + } + + public string? StatusMessage { get; private set; } + + public async Task OnGet() + { + // Use the factory to request a named client: + var client = _pdfClientFactory.GetPdfClient("Develop"); + + var requests = new List + { + new ($"

Portrait

Generated on {DateTime.Now:F}.

") + { + Landscape = false, + MarginTop = 42, + MarginBottom = 41, + MarginLeft = 40, + MarginRight = 39, + }, + new ($"

Landscape

Generated on {DateTime.Now:F}.

") + { + Landscape = true, + GrayScale = true, + + }, + new ($"

Portrait

(again)

Generated on {DateTime.Now:F}.

") + { + Landscape = false + }, + }; + + var pdfResult = await client.GenerateMergedPdfFromHtmlAsync(requests); + + // Returning that PDF when it succeeded. + if (pdfResult.Success) + { + return File(pdfResult.Data, "application/pdf"); + } + + StatusMessage = pdfResult.StatusMessage; + + return Page(); + } + } +} diff --git a/src/PDFinch.Client.Common/HttpPdfClient.cs b/src/PDFinch.Client.Common/HttpPdfClient.cs index 4bb95e3..9e3bc1e 100644 --- a/src/PDFinch.Client.Common/HttpPdfClient.cs +++ b/src/PDFinch.Client.Common/HttpPdfClient.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.IO; using System.Net; using System.Net.Http; @@ -32,36 +33,57 @@ protected HttpPdfClient(PdfClientOptions pdfinchOptions) /// protected abstract Task AuthenticateClientAsync(HttpRequestMessage httpRequestMessage); + /// + public Task> GeneratePdfFromHtmlAsync(PdfRequest pdfRequest) => GeneratePdfFromHtmlAsync(pdfRequest.Html, pdfRequest); + /// public async Task> GeneratePdfFromHtmlAsync(string html, PdfOptions? options = null) { - HttpRequestMessage httpRequestMessage = new(HttpMethod.Post, Resources.CreatePdfEndpoint + options?.ToQueryString()); - - httpRequestMessage.Content = new StringContent(html, Encoding.UTF8, "text/html"); + HttpRequestMessage httpRequestMessage = new(HttpMethod.Post, Resources.CreatePdfEndpoint + options?.ToQueryString()) + { + Content = new StringContent(html, Encoding.UTF8, "text/html") + }; - HttpClient? autenticatedHttpClient; + return await ExecuteRequestAsync(httpRequestMessage); + } - try - { - autenticatedHttpClient = await AuthenticateClientAsync(httpRequestMessage); - } - catch (Exception ex) - { - return new PdfResult(otherError: true, statusMessage: GetExceptionJson(ex)); - } + /// + public async Task> GenerateMergedPdfFromHtmlAsync(IEnumerable pdfRequests) + { + var formContent = new MultipartFormDataContent(); - // ReSharper disable once ConstantConditionalAccessQualifier - implementor can return null. - if (autenticatedHttpClient?.BaseAddress == null) + var i = 0; + + foreach (var request in pdfRequests) { - const string errorMessage = $"{nameof(AuthenticateClientAsync)}() must return an {nameof(HttpClient)} with its {nameof(HttpClient.BaseAddress)} set"; + formContent.Add(new StringContent(request.Html, Encoding.UTF8, "text/html"), $"d[{i}].body"); - return new PdfResult(otherError: true, statusMessage: PdfResult.JsonStatus(errorMessage)); + formContent.Add(new StringContent(request.Landscape.ToString()), $"d[{i}].landscape"); + formContent.Add(new StringContent(request.GrayScale.ToString()), $"d[{i}].grayscale"); + formContent.Add(new StringContent(request.MarginLeft.ToString()), $"d[{i}].marginleft"); + formContent.Add(new StringContent(request.MarginRight.ToString()), $"d[{i}].marginright"); + formContent.Add(new StringContent(request.MarginTop.ToString()), $"d[{i}].margintop"); + formContent.Add(new StringContent(request.MarginBottom.ToString()), $"d[{i}].marginbottom"); + + i++; } + HttpRequestMessage httpRequestMessage = new(HttpMethod.Post, Resources.MergePdfEndpoint) + { + Content = formContent + }; + + return await ExecuteRequestAsync(httpRequestMessage); + } + + private async Task> ExecuteRequestAsync(HttpRequestMessage httpRequestMessage) + { try { + var autenticatedHttpClient = await AuthenticateClientImplAsync(httpRequestMessage); + var response = await autenticatedHttpClient.SendAsync(httpRequestMessage); - + if (response.IsSuccessStatusCode) { var stream = await response.Content.ReadAsStreamAsync(); @@ -85,6 +107,19 @@ public async Task> GeneratePdfFromHtmlAsync(string html, PdfOp } } + private async Task AuthenticateClientImplAsync(HttpRequestMessage httpRequestMessage) + { + var autenticatedHttpClient = await AuthenticateClientAsync(httpRequestMessage); + + // ReSharper disable once ConditionalAccessQualifierIsNonNullableAccordingToAPIContract - implementor can return null. + if (autenticatedHttpClient?.BaseAddress == null) + { + throw new InvalidOperationException($"{nameof(AuthenticateClientAsync)}() must return an {nameof(HttpClient)} with its {nameof(HttpClient.BaseAddress)} set"); + } + + return autenticatedHttpClient; + } + private static string GetExceptionJson(Exception ex) { return JsonSerializer.Serialize(new diff --git a/src/PDFinch.Client.Common/IPdfClient.cs b/src/PDFinch.Client.Common/IPdfClient.cs index 530173a..f1dc4cb 100644 --- a/src/PDFinch.Client.Common/IPdfClient.cs +++ b/src/PDFinch.Client.Common/IPdfClient.cs @@ -1,4 +1,5 @@ -using System.IO; +using System.Collections.Generic; +using System.IO; using System.Threading.Tasks; namespace PDFinch.Client.Common @@ -22,5 +23,15 @@ public interface IPdfClient /// Generate a PDF using the given HTML and options. /// Task> GeneratePdfFromHtmlAsync(string html, PdfOptions? options = null); + + /// + /// Generate a PDF using the given HTML and options. + /// + Task> GeneratePdfFromHtmlAsync(PdfRequest pdfRequest); + + /// + /// Generate a PDF using the given HTML and options. + /// + Task> GenerateMergedPdfFromHtmlAsync(IEnumerable pdfRequests); } } diff --git a/src/PDFinch.Client.Common/PdfRequest.cs b/src/PDFinch.Client.Common/PdfRequest.cs new file mode 100644 index 0000000..7d6984f --- /dev/null +++ b/src/PDFinch.Client.Common/PdfRequest.cs @@ -0,0 +1,21 @@ +namespace PDFinch.Client.Common +{ + /// + /// Represents a request to generate a PDF from the given HTML and . + /// + public class PdfRequest : PdfOptions + { + /// + /// The HTML to generate a PDF from. + /// + public string Html { get; } + + /// + /// Instantiate a request with the given . + /// + public PdfRequest(string html) + { + Html = html; + } + } +} diff --git a/src/PDFinch.Client.Common/Resources.cs b/src/PDFinch.Client.Common/Resources.cs index 47fd3af..7a184b2 100644 --- a/src/PDFinch.Client.Common/Resources.cs +++ b/src/PDFinch.Client.Common/Resources.cs @@ -27,6 +27,11 @@ public static class Resources /// public const string CreatePdfEndpoint = "pdf/create"; + /// + /// Endpoint for merging multiple HTML requests to a single PDF. + /// + public const string MergePdfEndpoint = "pdf/merge"; + /// /// HttpClient name for authentication clients. /// diff --git a/test/PDFinch.Client.Common.Tests/BadPdfClient.cs b/test/PDFinch.Client.Common.Tests/BadPdfClient.cs deleted file mode 100644 index 20427e7..0000000 --- a/test/PDFinch.Client.Common.Tests/BadPdfClient.cs +++ /dev/null @@ -1,16 +0,0 @@ -using System; -using System.Net.Http; -using System.Threading.Tasks; - -namespace PDFinch.Client.Common.Tests -{ - public class BadPdfClient : HttpPdfClient - { - public BadPdfClient(PdfClientOptions pdfinchOptions) : base(pdfinchOptions) { } - - protected override Task AuthenticateClientAsync(HttpRequestMessage httpRequestMessage) - { - throw new InvalidOperationException("Tis but a test"); - } - } -} diff --git a/test/PDFinch.Client.Common.Tests/HttpClientExtensionTests.cs b/test/PDFinch.Client.Common.Tests/HttpClientTokenTests.cs similarity index 98% rename from test/PDFinch.Client.Common.Tests/HttpClientExtensionTests.cs rename to test/PDFinch.Client.Common.Tests/HttpClientTokenTests.cs index 5d87762..6705627 100644 --- a/test/PDFinch.Client.Common.Tests/HttpClientExtensionTests.cs +++ b/test/PDFinch.Client.Common.Tests/HttpClientTokenTests.cs @@ -7,7 +7,7 @@ namespace PDFinch.Client.Common.Tests { - public class HttpClientExtensionTests + public class HttpClientTokenTests { [Test] public async Task GetTokenAsync_Parses_Json() diff --git a/test/PDFinch.Client.Common.Tests/HttpPdfClientTests.cs b/test/PDFinch.Client.Common.Tests/HttpPdfClientTests.cs index 48b795d..ba73ad9 100644 --- a/test/PDFinch.Client.Common.Tests/HttpPdfClientTests.cs +++ b/test/PDFinch.Client.Common.Tests/HttpPdfClientTests.cs @@ -1,4 +1,6 @@ using System; +using System.Collections.Generic; +using System.Linq; using System.Net; using System.Net.Http; using Moq; @@ -6,63 +8,263 @@ using System.Threading.Tasks; using Moq.Protected; using PDFinch.Client.Tests.Shared; +using System.Threading; namespace PDFinch.Client.Common.Tests { public class HttpPdfClientTests { + private readonly Func _ok = (_, _) => new HttpResponseMessage(HttpStatusCode.OK); + +#pragma warning disable CS8618 // SetUp + private HttpClient _httpClient; + private Mock _httpHandlerMock; + private HttpPdfClient _classUnderTest; + private Mock _pdfClientMock; +#pragma warning restore CS8618 + + [SetUp] + public void SetUp() + { + // Loose for non-overridable Dispose() that gets called. + _httpHandlerMock = new Mock(MockBehavior.Loose); + + _httpClient = new HttpClient(_httpHandlerMock.Object) + { + BaseAddress = new Uri("https://throw-when-called") + }; + + var options = new PdfClientOptions { ApiKey = "api-01" }; + + _pdfClientMock = new Mock(MockBehavior.Strict, options); + + _pdfClientMock.Protected().Setup>("AuthenticateClientAsync", ItExpr.IsAny()) + .ReturnsAsync(_httpClient) + .Verifiable(); + + _classUnderTest = _pdfClientMock.Object; + } + + private void SetUpRequests(Func authenticationResponse, Func pdfResponse) + { + _httpHandlerMock.MockResponse((request, token) => + request.RequestUri?.AbsolutePath == "/" + Resources.OAuth2Endpoint + ? authenticationResponse(request, token) + : pdfResponse(request, token)) + .Verifiable(); + } + [Test] public async Task AuthenticateClientAsync_Handles_Exception() { // Arrange - var options = new PdfClientOptions - { - Name = "Throws", - ApiKey = "api-01", - ApiSecret = "secret-01" - }; + var options = new PdfClientOptions { ApiKey = "api-01" }; - var classUnderTest = new BadPdfClient(options); + var classUnderTest = new Mock(MockBehavior.Strict, options); + + classUnderTest.Protected().Setup>("AuthenticateClientAsync", ItExpr.IsAny()) + .ThrowsAsync(new InvalidOperationException("Tis but a test")) + .Verifiable(); // Act - var pdfResult = await classUnderTest.GeneratePdfFromHtmlAsync("

Html

"); + var pdfResult = await classUnderTest.Object.GeneratePdfFromHtmlAsync("

Html

"); // Assert Assert.IsFalse(pdfResult.Success); Assert.IsTrue(pdfResult.OtherError); Assert.IsFalse(pdfResult.IsOutOfCredits); + + classUnderTest.VerifyAll(); + _httpHandlerMock.VerifyAll(); + } + + [Test] + public async Task GeneratePdfFromHtmlAsync_Requires_HttpClientBaseAddress() + { + // Arrange + _httpClient.BaseAddress = null; + + // Act + var pdfResult = await _classUnderTest.GeneratePdfFromHtmlAsync("

Html

"); + + Assert.IsFalse(pdfResult.Success); + Assert.IsTrue(pdfResult.StatusMessage!.Contains("BaseAddress")); + + _pdfClientMock.VerifyAll(); + _httpHandlerMock.VerifyAll(); + } + + [Test] + public async Task GeneratePdfFromHtmlAsync_Handles_Exception() + { + // Arrange + SetUpRequests(_ok, (_, _) => throw new InvalidOperationException("Tis but a test")); + + // Act + var pdfResult = await _classUnderTest.GeneratePdfFromHtmlAsync("

Html

"); + + Assert.IsFalse(pdfResult.Success); + Assert.IsTrue(pdfResult.OtherError); + Assert.IsFalse(pdfResult.IsOutOfCredits); + + _pdfClientMock.VerifyAll(); + _httpHandlerMock.VerifyAll(); } [Test] public async Task GeneratePdfFromHtmlAsync_Calls_AuthenticateClientAsync() { // Arrange - var options = new PdfClientOptions + SetUpRequests(_ok, _ok); + + // Act + var pdfResult = await _classUnderTest.GeneratePdfFromHtmlAsync("html"); + + // Assert + Assert.IsTrue(pdfResult.Success); + + _pdfClientMock.VerifyAll(); + _httpHandlerMock.VerifyAll(); + } + + [Test] + public async Task GeneratePdfFromHtmlAsync_Handles_402() + { + // Arrange + SetUpRequests(_ok, (_, _) => new HttpResponseMessage(HttpStatusCode.PaymentRequired)); + + // Act + var pdfResult = await _classUnderTest.GeneratePdfFromHtmlAsync("

Html

"); + + Assert.IsFalse(pdfResult.Success); + Assert.IsFalse(pdfResult.OtherError); + Assert.IsTrue(pdfResult.IsOutOfCredits); + + _pdfClientMock.VerifyAll(); + _httpHandlerMock.VerifyAll(); + } + + [Test] + public async Task GeneratePdfFromHtmlAsync_Handles_500() + { + // Arrange + SetUpRequests(_ok, (_, _) => new HttpResponseMessage(HttpStatusCode.InternalServerError)); + + // Act + var pdfResult = await _classUnderTest.GeneratePdfFromHtmlAsync("

Html

"); + + Assert.IsFalse(pdfResult.Success); + Assert.IsTrue(pdfResult.OtherError); + Assert.IsFalse(pdfResult.IsOutOfCredits); + + _pdfClientMock.VerifyAll(); + _httpHandlerMock.VerifyAll(); + } + + [Test] + public async Task GeneratePdfFromHtmlAsync_PdfRequest_Uses_Parameters() + { + // Arrange + var pdfRequest = new PdfRequest("

html

") { - ApiKey = "key-01", - ApiSecret = "secret-01" + Landscape = true, + GrayScale = true, + MarginLeft = 1, + MarginRight = 2, + MarginTop = 3, + MarginBottom = 4, }; + + SetUpRequests(_ok, (request, cancellationToken) => + { + var query = request.RequestUri.ParseQueryString(); + + Assert.AreEqual(pdfRequest.Html, request.Content!.ReadAsStringAsync(cancellationToken).Result); - var handler = new Mock(MockBehavior.Strict); + Assert.AreEqual(pdfRequest.Landscape.ToString(), query["landscape"]); + Assert.AreEqual(pdfRequest.GrayScale.ToString(), query["grayscale"]); + Assert.AreEqual(pdfRequest.MarginLeft.ToString(), query["marginleft"]); + Assert.AreEqual(pdfRequest.MarginRight.ToString(), query["marginright"]); + Assert.AreEqual(pdfRequest.MarginTop.ToString(), query["margintop"]); + Assert.AreEqual(pdfRequest.MarginBottom.ToString(), query["marginbottom"]); - handler.MockResponse((_, _) => new HttpResponseMessage(HttpStatusCode.OK)); + return _ok(request, cancellationToken); + }); - var client = new HttpClient(handler.Object) + // Act + var pdfResult = await _classUnderTest.GeneratePdfFromHtmlAsync(pdfRequest); + + // Assert + Assert.IsTrue(pdfResult.Success); + + _pdfClientMock.VerifyAll(); + _httpHandlerMock.VerifyAll(); + } + + [Test] + public async Task GenerateMergedPdfFromHtmlAsync_Converts_Request_To_Multipart() + { + // Arrange + var requests = new List { - BaseAddress = new Uri("https://throw-when-called") + new ("

html

") + { + Landscape = true, + GrayScale = true, + MarginLeft = 1, + MarginRight = 2, + MarginTop = 3, + MarginBottom = 4, + }, + new ("

html

") + { + Landscape = false, + GrayScale = false, + MarginLeft = 42, + MarginRight = 41, + MarginTop = 40, + MarginBottom = 39, + } }; - var classUnderTest = new Mock(MockBehavior.Strict, options); - - classUnderTest.Protected().Setup>("AuthenticateClientAsync", ItExpr.IsAny()) - .ReturnsAsync(client); + SetUpRequests(_ok, (request, cancellationToken) => + { + string? GetValue(MultipartStreamProvider provider, string name) + { + var stringContent = provider.Contents.FirstOrDefault(c => c.Headers.ContentDisposition!.Name!.Equals($"\"{name}\"", StringComparison.InvariantCultureIgnoreCase)); + + return stringContent?.ReadAsStringAsync(cancellationToken).Result; + } + + var requestBody = request.Content!.ReadAsMultipartAsync(cancellationToken).Result; + + Assert.AreEqual(requests[0].Html, GetValue(requestBody, "d[0].body")); + Assert.AreEqual(requests[0].GrayScale.ToString(), GetValue(requestBody, "d[0].grayscale")); + Assert.AreEqual(requests[0].Landscape.ToString(), GetValue(requestBody, "d[0].landscape")); + Assert.AreEqual(requests[0].MarginLeft.ToString(), GetValue(requestBody, "d[0].marginleft")); + Assert.AreEqual(requests[0].MarginRight.ToString(), GetValue(requestBody, "d[0].marginright")); + Assert.AreEqual(requests[0].MarginTop.ToString(), GetValue(requestBody, "d[0].margintop")); + Assert.AreEqual(requests[0].MarginBottom.ToString(), GetValue(requestBody, "d[0].marginbottom")); + + Assert.AreEqual(requests[1].Html, GetValue(requestBody, "d[1].body")); + Assert.AreEqual(requests[1].GrayScale.ToString(), GetValue(requestBody, "d[1].grayscale")); + Assert.AreEqual(requests[1].Landscape.ToString(), GetValue(requestBody, "d[1].landscape")); + Assert.AreEqual(requests[1].MarginLeft.ToString(), GetValue(requestBody, "d[1].marginleft")); + Assert.AreEqual(requests[1].MarginRight.ToString(), GetValue(requestBody, "d[1].marginright")); + Assert.AreEqual(requests[1].MarginTop.ToString(), GetValue(requestBody, "d[1].margintop")); + Assert.AreEqual(requests[1].MarginBottom.ToString(), GetValue(requestBody, "d[1].marginbottom")); + + return _ok(request, cancellationToken); + }); // Act - var pdfResult = await classUnderTest.Object.GeneratePdfFromHtmlAsync("html"); + var pdfResult = await _classUnderTest.GenerateMergedPdfFromHtmlAsync(requests); // Assert Assert.IsTrue(pdfResult.Success); - handler.VerifyAll(); + + _pdfClientMock.VerifyAll(); + _httpHandlerMock.VerifyAll(); } } } \ No newline at end of file diff --git a/test/PDFinch.Client.Common.Tests/PDFinch.Client.Common.Tests.csproj b/test/PDFinch.Client.Common.Tests/PDFinch.Client.Common.Tests.csproj index f12a122..8e4542f 100644 --- a/test/PDFinch.Client.Common.Tests/PDFinch.Client.Common.Tests.csproj +++ b/test/PDFinch.Client.Common.Tests/PDFinch.Client.Common.Tests.csproj @@ -8,6 +8,7 @@ + diff --git a/test/PDFinch.Client.Extensions.Tests/PdfClientTests.cs b/test/PDFinch.Client.Extensions.Tests/PdfClientTests.cs index f389833..ee49eb1 100644 --- a/test/PDFinch.Client.Extensions.Tests/PdfClientTests.cs +++ b/test/PDFinch.Client.Extensions.Tests/PdfClientTests.cs @@ -56,100 +56,6 @@ public async Task AuthenticateClientAsync_Sets_ApiKeyProperty() _httpHandlerMock.VerifyAll(); } - [Test] - public async Task GeneratePdfFromHtmlAsync_Requires_HttpClientBaseAddress() - { - // Arrange - var options = new PdfClientOptions - { - ApiKey = "api-01", - ApiSecret = "secret-01" - }; - - _httpClient.BaseAddress = null; - var classUnderTest = new PdfClient(_httpClient, options); - - // Act - var pdfResult = await classUnderTest.GeneratePdfFromHtmlAsync("

Html

"); - - Assert.IsFalse(pdfResult.Success); - Assert.IsTrue(pdfResult.StatusMessage!.Contains("BaseAddress")); - - _httpHandlerMock.VerifyAll(); - } - - [Test] - public async Task GeneratePdfFromHtmlAsync_Handles_402() - { - // Arrange - var options = new PdfClientOptions - { - ApiKey = "api-01", - ApiSecret = "secret-01" - }; - - _httpHandlerMock.MockResponse((request, _) => new HttpResponseMessage(HttpStatusCode.PaymentRequired)).Verifiable(); - - var classUnderTest = new PdfClient(_httpClient, options); - - // Act - var pdfResult = await classUnderTest.GeneratePdfFromHtmlAsync("

Html

"); - - Assert.IsFalse(pdfResult.Success); - Assert.IsFalse(pdfResult.OtherError); - Assert.IsTrue(pdfResult.IsOutOfCredits); - - _httpHandlerMock.VerifyAll(); - } - - [Test] - public async Task GeneratePdfFromHtmlAsync_Handles_500() - { - // Arrange - var options = new PdfClientOptions - { - ApiKey = "api-01", - ApiSecret = "secret-01" - }; - - _httpHandlerMock.MockResponse((request, _) => new HttpResponseMessage(HttpStatusCode.InternalServerError)).Verifiable(); - - var classUnderTest = new PdfClient(_httpClient, options); - - // Act - var pdfResult = await classUnderTest.GeneratePdfFromHtmlAsync("

Html

"); - - Assert.IsFalse(pdfResult.Success); - Assert.IsTrue(pdfResult.OtherError); - Assert.IsFalse(pdfResult.IsOutOfCredits); - - _httpHandlerMock.VerifyAll(); - } - - [Test] - public async Task GeneratePdfFromHtmlAsync_Handles_Exception() - { - // Arrange - var options = new PdfClientOptions - { - ApiKey = "api-01", - ApiSecret = "secret-01" - }; - - _httpHandlerMock.MockResponse((request, _) => throw new InvalidOperationException("Tis but a test")).Verifiable(); - - var classUnderTest = new PdfClient(_httpClient, options); - - // Act - var pdfResult = await classUnderTest.GeneratePdfFromHtmlAsync("

Html

"); - - Assert.IsFalse(pdfResult.Success); - Assert.IsTrue(pdfResult.OtherError); - Assert.IsFalse(pdfResult.IsOutOfCredits); - - _httpHandlerMock.VerifyAll(); - } - [Test] public void NameFromOptions() { diff --git a/test/PDFinch.Client.Tests.Shared/HttpMessageHandlerMockExtensions.cs b/test/PDFinch.Client.Tests.Shared/HttpMessageHandlerMockExtensions.cs index 22f1b01..f113ea3 100644 --- a/test/PDFinch.Client.Tests.Shared/HttpMessageHandlerMockExtensions.cs +++ b/test/PDFinch.Client.Tests.Shared/HttpMessageHandlerMockExtensions.cs @@ -25,7 +25,7 @@ public static IReturnsResult SetupAuth( { var handlerReturns = httpHandlerMock.MockResponse((request, cancellationToken) => { - if (request.RequestUri!.ToString().EndsWith(Resources.OAuth2Endpoint)) + if (request.RequestUri?.AbsolutePath == "/" + Resources.OAuth2Endpoint) { // TODO: find ((Request.Content as UrlEncodedFormsContent).Keys) or something in _options, return 401 otherwise. authCallback?.Invoke();