Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,10 @@ jobs:
--org=$SNYK_ORG_ID
release:
docker:
- image: mcr.microsoft.com/dotnet/core/sdk:3.1
# Pack with a modern SDK (>= 5.0.300) so <PackageReadmeFile> is honored and the
# README is embedded/registered in the package. The build/test jobs stay on 3.1
# because the test suite targets netcoreapp3.1 and needs the 3.1 runtime.
- image: mcr.microsoft.com/dotnet/sdk:8.0
steps:
- checkout
- run:
Expand Down
95 changes: 95 additions & 0 deletions src/Postmark.Tests/ClientCancellationTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using PostmarkDotNet;
using PostmarkDotNet.Model;
using Xunit;

namespace Postmark.Tests
{
/// <summary>
/// Verifies that the CancellationToken accepted by the async client methods is actually
/// threaded down to the underlying HttpClient.SendAsync call.
///
/// These tests pass an already-canceled token: HttpClient.SendAsync short-circuits and
/// throws an OperationCanceledException before any network I/O occurs. If the token were
/// not threaded through, the call would instead attempt a real request and fail with a
/// different error, so this reliably distinguishes "threaded" from "dropped" without a
/// live server, network access, or mutating the global ClientFactory (which would break
/// the parallel integration tests).
/// </summary>
public class ClientCancellationTests
{
// Non-routable base URL: if the token were ever dropped, the call would fail here
// rather than silently contacting the real Postmark API.
private const string BaseUrl = "http://localhost:9";

private static PostmarkClient CanceledClient() => new PostmarkClient("test-server-token", BaseUrl);
private static PostmarkAdminClient CanceledAdminClient() => new PostmarkAdminClient("test-account-token", BaseUrl);

private static CancellationToken Canceled()
{
var cts = new CancellationTokenSource();
cts.Cancel();
return cts.Token;
}

[Fact]
public async Task GetRequest_HonorsCancellationToken()
{
var client = CanceledClient();
await Assert.ThrowsAnyAsync<System.OperationCanceledException>(
() => client.GetServerAsync(Canceled()));
}

[Fact]
public async Task GetRequestWithQueryParams_HonorsCancellationToken()
{
var client = CanceledClient();
await Assert.ThrowsAnyAsync<System.OperationCanceledException>(
() => client.GetBouncesAsync(cancellationToken: Canceled()));
}

[Fact]
public async Task PostRequestWithBody_HonorsCancellationToken()
{
var client = CanceledClient();
await Assert.ThrowsAnyAsync<System.OperationCanceledException>(
() => client.SendMessageAsync(new PostmarkMessage(), Canceled()));
}

[Fact]
public async Task ParamsBatchOverload_HonorsCancellationToken()
{
var client = CanceledClient();
await Assert.ThrowsAnyAsync<System.OperationCanceledException>(
() => client.SendMessagesAsync(Canceled(), new PostmarkMessage()));
}

[Fact]
public async Task TemplatedParamsOverload_HonorsCancellationToken()
{
var client = CanceledClient();
await Assert.ThrowsAnyAsync<System.OperationCanceledException>(
() => client.SendEmailWithTemplateAsync(Canceled(), "template-alias", new { name = "x" },
"to@example.com", "from@example.com"));
}

[Fact]
public async Task AdminClient_HonorsCancellationToken()
{
var admin = CanceledAdminClient();
await Assert.ThrowsAnyAsync<System.OperationCanceledException>(
() => admin.GetServersAsync(cancellationToken: Canceled()));
}

[Fact]
public async Task ExtensionMethod_HonorsCancellationToken()
{
var client = CanceledClient();
var messages = new List<PostmarkMessage> { new PostmarkMessage() };
await Assert.ThrowsAnyAsync<System.OperationCanceledException>(
() => client.SendMessagesAsync(messages, Canceled()));
}
}
}
5 changes: 3 additions & 2 deletions src/Postmark/ISimpleHttpClient.cs
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;

namespace PostmarkDotNet
{
public interface ISimpleHttpClient
{
Task<HttpResponseMessage> SendAsync(HttpRequestMessage request);
Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken = default);
}
}
}
2 changes: 1 addition & 1 deletion src/Postmark/Postmark.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
<PackageIconUrl>https://github.com/wildbit/postmark-dotnet/raw/master/postmark-logo.png</PackageIconUrl>
<RepositoryUrl>https://github.com/wildbit/postmark-dotnet.git</RepositoryUrl>
<RepositoryType>git</RepositoryType>
<Version>5.4.1</Version>
<Version>5.4.2</Version>
<Company>Wildbit, LLC.</Company>
<Description>The official .net client for Postmark.</Description>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
Expand Down
Loading