Skip to content
Merged
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
126 changes: 126 additions & 0 deletions src/SignhostAPIClient.IntegrationTests/BadRequestTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
using System.IO;
using System.Threading.Tasks;
using System;
using FluentAssertions;
using Signhost.APIClient.Rest.DataObjects;
using Signhost.APIClient.Rest.ErrorHandling;
using Xunit;

namespace Signhost.APIClient.Rest.IntegrationTests;

public class BadRequestTests
: IntegrationTestBase
{
[Theory]
[MemberData(nameof(BadCreateRequestTestCases))]
public async Task Given_invalid_create_request_When_transaction_is_created_Then_BadRequestException_is_thrown(
CreateTransactionRequest badRequest,
string expectedErrorMessage)
{
// Act
Func<Task> act = () => Client.CreateTransactionAsync(badRequest);

// Assert
var exception = await act.Should()
.ThrowAsync<BadRequestException>();
exception.Which.Message.Should().Be(expectedErrorMessage);
exception.Which.ResponseBody.Should().NotBeNullOrEmpty();
}

[Fact]
public async Task Given_invalid_file_name_When_file_is_uploaded_Then_BadRequestException_is_thrown()
{
// Arrange
CreateTransactionRequest transaction = new() {
SendEmailNotifications = false,
Signers = [
new() {
Id = "signer1",
Email = "test@example.com",
SendSignRequest = false,
}
]
};

var createdTransaction = await Client.CreateTransactionAsync(transaction);
string pdfPath = Path.Combine("TestFiles", "small-example-pdf-file.pdf");
await using var fileStream = File.OpenRead(pdfPath);

// Act
Func<Task> act = () => Client.AddOrReplaceFileToTransactionAsync(
fileStream,
createdTransaction.Id,
"invalid|filename.pdf",
new FileUploadOptions());

// Assert
var exception = await act.Should()
.ThrowAsync<BadRequestException>();
exception.Which.Message.Should().Be("invalid|filename.pdf is not a valid file name.");
exception.Which.ResponseBody.Should().NotBeNullOrEmpty();
}

public static TheoryData<CreateTransactionRequest, string> BadCreateRequestTestCases =>
new() {
{
new CreateTransactionRequest {
SendEmailNotifications = false,
DaysToExpire = 1,
Signers = [
new() {
Email = "test@example.com",
SendSignRequest = true,
SignRequestMessage = "Please sign.",
DaysToRemind = 30,
}
],
},
"The days to remind must be lower than the days to expire."
},
{
new CreateTransactionRequest {
SendEmailNotifications = false,
Signers = [
new() {
Email = "test@example.com",
SendSignRequest = true,
SignRequestMessage = null,
}
],
},
"The sign request message is null or empty."
},
{
new CreateTransactionRequest {
SendEmailNotifications = false,
Signers = [
new() {
Email = "test@example.com",
SendSignRequest = true,
SignRequestMessage = "Please sign.",
SignRequestSubject = "Subject\twith\ttabs",
}
],
},
"Signer Subject: Input contains one or more invalid characters."
},
{
new CreateTransactionRequest {
SendEmailNotifications = false,
Signers = [
new() {
Id = "duplicate-signer-id",
Email = "signer1@example.com",
SendSignRequest = false,
},
new() {
Id = "duplicate-signer-id",
Email = "signer2@example.com",
SendSignRequest = false,
}
],
},
"The signer id is already used by another signer."
},
};
}
30 changes: 30 additions & 0 deletions src/SignhostAPIClient.IntegrationTests/IntegrationTestBase.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
using System;

namespace Signhost.APIClient.Rest.IntegrationTests;

public abstract class IntegrationTestBase
: IDisposable
{
protected readonly SignhostApiClient Client;

protected IntegrationTestBase()
{
var config = TestConfiguration.Instance;

if (!config.IsConfigured) {
throw new InvalidOperationException("Integration tests are not configured.");
}

SignhostApiClientSettings settings = new(config.AppKey, config.UserToken) {
Endpoint = config.ApiBaseUrl,
};

Client = new(settings);
}

public void Dispose()
{
Client?.Dispose();
GC.SuppressFinalize(this);
}
}
41 changes: 10 additions & 31 deletions src/SignhostAPIClient.IntegrationTests/TransactionTests.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Text.Json;
using System.Threading.Tasks;
using FluentAssertions;
using Signhost.APIClient.Rest.DataObjects;
Expand All @@ -10,25 +9,8 @@
namespace Signhost.APIClient.Rest.IntegrationTests;

public class TransactionTests
: IDisposable
: IntegrationTestBase
{
private readonly SignhostApiClient client;
private readonly TestConfiguration config;

public TransactionTests()
{
config = TestConfiguration.Instance;

if (!config.IsConfigured) {
throw new InvalidOperationException("Integration tests are not configured.");
}

SignhostApiClientSettings settings = new(config.AppKey, config.UserToken) {
Endpoint = config.ApiBaseUrl,
};

client = new(settings);
}

[Fact]
public async Task Given_complex_transaction_When_created_and_started_Then_all_properties_are_correctly_persisted()
Expand Down Expand Up @@ -109,7 +91,7 @@ public async Task Given_complex_transaction_When_created_and_started_Then_all_pr
}

// Act - Create transaction
var createdTransaction = await client.CreateTransactionAsync(transaction);
var createdTransaction = await Client.CreateTransactionAsync(transaction);

// Assert - Creation properties
createdTransaction.Should().NotBeNull();
Expand Down Expand Up @@ -200,17 +182,20 @@ public async Task Given_complex_transaction_When_created_and_started_Then_all_pr

// Act - Upload file
await using var fileStream = File.OpenRead(pdfPath);
await client.AddOrReplaceFileToTransactionAsync(
await Client.AddOrReplaceFileToTransactionAsync(
fileStream,
createdTransaction.Id,
"test-document.pdf",
new FileUploadOptions());

// Act - Start transaction
await client.StartTransactionAsync(createdTransaction.Id);
await Client.StartTransactionAsync(createdTransaction.Id);

// No postbacks, so we need to wait a bit for the transaction to be processed.
await Task.Delay(TimeSpan.FromSeconds(5));

// Act - Retrieve final state
var finalTransaction = await client.GetTransactionAsync(createdTransaction.Id);
var finalTransaction = await Client.GetTransactionAsync(createdTransaction.Id);

// Assert - Final transaction state
finalTransaction.Should().NotBeNull();
Expand Down Expand Up @@ -279,7 +264,7 @@ public async Task Given_complex_file_metadata_When_added_to_transaction_Then_all
]
};

var createdTransaction = await client.CreateTransactionAsync(transaction);
var createdTransaction = await Client.CreateTransactionAsync(transaction);
createdTransaction.Should().NotBeNull();

// Arrange - Create a very complex FileMeta
Expand Down Expand Up @@ -433,18 +418,12 @@ public async Task Given_complex_file_metadata_When_added_to_transaction_Then_all
};

// Act
Func<Task> act = () => client.AddOrReplaceFileMetaToTransactionAsync(
Func<Task> act = () => Client.AddOrReplaceFileMetaToTransactionAsync(
fileMeta,
createdTransaction.Id,
"test-document.pdf");

// Assert
await act.Should().NotThrowAsync();
}

public void Dispose()
{
client?.Dispose();
GC.SuppressFinalize(this);
}
}
76 changes: 76 additions & 0 deletions src/SignhostAPIClient.Tests/SignhostApiClientTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -661,4 +661,80 @@ public async Task When_a_minimal_response_is_retrieved_list_and_dictionaries_are
result.Receivers.Should().BeEmpty();
result.Files.Should().BeEmpty();
}

[Fact]
public async Task When_error_response_has_uppercase_Message_Then_exception_message_is_parsed_correctly()
{
MockHttpMessageHandler mockHttp = new();
const string responseBody = """{"Message":"The signer id is already used by another signer."}""";
mockHttp
.Expect(HttpMethod.Get, "http://localhost/api/transaction/transactionId")
.Respond(HttpStatusCode.BadRequest, new StringContent(responseBody));

using var httpClient = mockHttp.ToHttpClient();
SignhostApiClient signhostApiClient = new(settings, httpClient);

Func<Task> act = () => signhostApiClient.GetTransactionAsync("transactionId");
var exception = await act.Should().ThrowAsync<BadRequestException>();
exception.Which.Message.Should().Be("The signer id is already used by another signer.");

mockHttp.VerifyNoOutstandingExpectation();
}

[Fact]
public async Task When_error_response_has_lowercase_message_Then_exception_message_is_parsed_correctly()
{
MockHttpMessageHandler mockHttp = new();
const string responseBody = """{"message":"Bad Request"}""";
mockHttp
.Expect(HttpMethod.Get, "http://localhost/api/transaction/transactionId")
.Respond(HttpStatusCode.BadRequest, new StringContent(responseBody));

using var httpClient = mockHttp.ToHttpClient();
SignhostApiClient signhostApiClient = new(settings, httpClient);

Func<Task> act = () => signhostApiClient.GetTransactionAsync("transactionId");
var exception = await act.Should().ThrowAsync<BadRequestException>();
exception.Which.Message.Should().Be("Bad Request");

mockHttp.VerifyNoOutstandingExpectation();
}

[Fact]
public async Task When_error_response_has_detail_but_no_message_Then_exception_message_uses_detail()
{
MockHttpMessageHandler mockHttp = new();
const string responseBody = """{"type":"https://example.com/problem","detail":"Something went wrong."}""";
mockHttp
.Expect(HttpMethod.Get, "http://localhost/api/transaction/transactionId")
.Respond(HttpStatusCode.BadRequest, new StringContent(responseBody));

using var httpClient = mockHttp.ToHttpClient();
SignhostApiClient signhostApiClient = new(settings, httpClient);

Func<Task> act = () => signhostApiClient.GetTransactionAsync("transactionId");
var exception = await act.Should().ThrowAsync<BadRequestException>();
exception.Which.Message.Should().Be("Something went wrong.");

mockHttp.VerifyNoOutstandingExpectation();
}

[Fact]
public async Task When_error_response_has_uppercase_Detail_but_no_message_Then_exception_message_uses_detail()
{
MockHttpMessageHandler mockHttp = new();
const string responseBody = """{"Type":"https://example.com/problem","Detail":"A detailed error."}""";
mockHttp
.Expect(HttpMethod.Get, "http://localhost/api/transaction/transactionId")
.Respond(HttpStatusCode.BadRequest, new StringContent(responseBody));

using var httpClient = mockHttp.ToHttpClient();
SignhostApiClient signhostApiClient = new(settings, httpClient);

Func<Task> act = () => signhostApiClient.GetTransactionAsync("transactionId");
var exception = await act.Should().ThrowAsync<BadRequestException>();
exception.Which.Message.Should().Be("A detailed error.");

mockHttp.VerifyNoOutstandingExpectation();
}
}
Loading