From 75a73d1c271fe0f211bfbb7cf7d730a13e4f766c Mon Sep 17 00:00:00 2001 From: Anthony Timmers Date: Thu, 27 Nov 2025 16:13:35 +0100 Subject: [PATCH 01/28] Add missing verifications for CSC, OIDC, and Onfido --- .../Rest/DataObjects/CscVerification.cs | 41 +++++++++++++++++++ .../DataObjects/EherkenningVerification.cs | 21 ++++++++++ .../Rest/DataObjects/OidcVerification.cs | 19 +++++++++ .../Rest/DataObjects/OnfidoVerification.cs | 37 +++++++++++++++++ 4 files changed, 118 insertions(+) create mode 100644 src/SignhostAPIClient/Rest/DataObjects/CscVerification.cs create mode 100644 src/SignhostAPIClient/Rest/DataObjects/EherkenningVerification.cs create mode 100644 src/SignhostAPIClient/Rest/DataObjects/OidcVerification.cs create mode 100644 src/SignhostAPIClient/Rest/DataObjects/OnfidoVerification.cs diff --git a/src/SignhostAPIClient/Rest/DataObjects/CscVerification.cs b/src/SignhostAPIClient/Rest/DataObjects/CscVerification.cs new file mode 100644 index 0000000..109074e --- /dev/null +++ b/src/SignhostAPIClient/Rest/DataObjects/CscVerification.cs @@ -0,0 +1,41 @@ +using System.Collections.Generic; + +namespace Signhost.APIClient.Rest.DataObjects +{ + /// + /// Cloud Signature Consortium (CSC) verification. + /// + public class CscVerification + : IVerification + { + /// + /// Gets the . + /// + public string Type => "CSC Qualified"; + + /// + /// Gets or sets the provider identifier. + /// + public string Provider { get; set; } + + /// + /// Gets or sets the certificate issuer. + /// + public string Issuer { get; set; } + + /// + /// Gets or sets the certificate subject. + /// + public string Subject { get; set; } + + /// + /// Gets or sets the certificate thumbprint. + /// + public string Thumbprint { get; set; } + + /// + /// Gets or sets additional user data. + /// + public Dictionary AdditionalUserData { get; set; } + } +} diff --git a/src/SignhostAPIClient/Rest/DataObjects/EherkenningVerification.cs b/src/SignhostAPIClient/Rest/DataObjects/EherkenningVerification.cs new file mode 100644 index 0000000..adee19d --- /dev/null +++ b/src/SignhostAPIClient/Rest/DataObjects/EherkenningVerification.cs @@ -0,0 +1,21 @@ +namespace Signhost.APIClient.Rest.DataObjects +{ + public class EherkenningVerification + : IVerification + { + /// + /// Gets the . + /// + public string Type => "eHerkenning"; + + /// + /// Gets or sets the Uid. + /// + public string Uid { get; set; } + + /// + /// Gets or sets the entity concern ID / KVK number. + /// + public string EntityConcernIdKvkNr { get; set; } + } +} diff --git a/src/SignhostAPIClient/Rest/DataObjects/OidcVerification.cs b/src/SignhostAPIClient/Rest/DataObjects/OidcVerification.cs new file mode 100644 index 0000000..208b012 --- /dev/null +++ b/src/SignhostAPIClient/Rest/DataObjects/OidcVerification.cs @@ -0,0 +1,19 @@ +namespace Signhost.APIClient.Rest.DataObjects +{ + /// + /// OpenID Connect identification. + /// + public class OidcVerification + : IVerification + { + /// + /// Gets the . + /// + public string Type => "OpenID Providers"; + + /// + /// Gets or sets the OIDC provider name. + /// + public string ProviderName { get; set; } + } +} diff --git a/src/SignhostAPIClient/Rest/DataObjects/OnfidoVerification.cs b/src/SignhostAPIClient/Rest/DataObjects/OnfidoVerification.cs new file mode 100644 index 0000000..7a9d166 --- /dev/null +++ b/src/SignhostAPIClient/Rest/DataObjects/OnfidoVerification.cs @@ -0,0 +1,37 @@ +using System; +using System.Collections.Generic; + +namespace Signhost.APIClient.Rest.DataObjects +{ + /// + /// Onfido identity verification. + /// + public class OnfidoVerification + : IVerification + { + /// + /// Gets the . + /// + public string Type => "Onfido"; + + /// + /// Gets or sets the Onfido workflow identifier. + /// + public Guid? WorkflowId { get; set; } + + /// + /// Gets or sets the Onfido workflow run identifier. + /// + public Guid? WorkflowRunId { get; set; } + + /// + /// Gets or sets the Onfido API version. + /// + public int? Version { get; set; } + + /// + /// Gets or sets raw Onfido attributes (availability not guaranteed). + /// + public Dictionary Attributes { get; set; } + } +} From 23a5c3a034cb77fdbedbcbdd9deed00434a9ec3e Mon Sep 17 00:00:00 2001 From: Anthony Timmers Date: Thu, 27 Nov 2025 16:41:00 +0100 Subject: [PATCH 02/28] Add ResponseBody property to exceptions --- src/SignhostAPIClient/Rest/ApiResponse.cs | 9 +++- .../BadAuthorizationException.cs | 1 + ...pResponseMessageErrorHandlingExtensions.cs | 45 +++++++++++++------ .../Rest/ErrorHandling/SignhostException.cs | 1 + .../SignhostRestApiClientException.cs | 5 +++ 5 files changed, 46 insertions(+), 15 deletions(-) diff --git a/src/SignhostAPIClient/Rest/ApiResponse.cs b/src/SignhostAPIClient/Rest/ApiResponse.cs index f6c6c12..880dcd3 100644 --- a/src/SignhostAPIClient/Rest/ApiResponse.cs +++ b/src/SignhostAPIClient/Rest/ApiResponse.cs @@ -25,7 +25,14 @@ public void EnsureAvailableStatusCode() if (HttpStatusCode == HttpStatusCode.Gone) { throw new ErrorHandling.GoneException( httpResponse.ReasonPhrase, - Value); + Value) + { + // TO-DO: Make async in v5 + ResponseBody = httpResponse.Content.ReadAsStringAsync() + .ConfigureAwait(false) + .GetAwaiter() + .GetResult(), + }; } } } diff --git a/src/SignhostAPIClient/Rest/ErrorHandling/BadAuthorizationException.cs b/src/SignhostAPIClient/Rest/ErrorHandling/BadAuthorizationException.cs index 04bc32b..7dc3a19 100644 --- a/src/SignhostAPIClient/Rest/ErrorHandling/BadAuthorizationException.cs +++ b/src/SignhostAPIClient/Rest/ErrorHandling/BadAuthorizationException.cs @@ -7,6 +7,7 @@ namespace Signhost.APIClient.Rest.ErrorHandling { + // TO-DO: Use this instead of Unauthorized exception in v5 [Serializable] public class BadAuthorizationException : SignhostRestApiClientException diff --git a/src/SignhostAPIClient/Rest/ErrorHandling/HttpResponseMessageErrorHandlingExtensions.cs b/src/SignhostAPIClient/Rest/ErrorHandling/HttpResponseMessageErrorHandlingExtensions.cs index 745b17c..3ad5df5 100644 --- a/src/SignhostAPIClient/Rest/ErrorHandling/HttpResponseMessageErrorHandlingExtensions.cs +++ b/src/SignhostAPIClient/Rest/ErrorHandling/HttpResponseMessageErrorHandlingExtensions.cs @@ -12,6 +12,9 @@ namespace Signhost.APIClient.Rest.ErrorHandling /// public static class HttpResponseMessageErrorHandlingExtensions { + private const string OutOfCreditsApiProblemType = + "https://api.signhost.com/problem/subscription/out-of-credits"; + /// /// Throws an exception if the /// has an error code. @@ -55,13 +58,14 @@ public static async Task EnsureSignhostSuccessStatusCodeAsy string errorType = string.Empty; string errorMessage = "Unknown Signhost error"; + string responseBody = string.Empty; if (response.Content != null) { - string responsejson = await response.Content.ReadAsStringAsync() + responseBody = await response.Content.ReadAsStringAsync() .ConfigureAwait(false); var error = JsonConvert.DeserializeAnonymousType( - responsejson, + responseBody, new { Type = string.Empty, Message = string.Empty, @@ -71,33 +75,46 @@ public static async Task EnsureSignhostSuccessStatusCodeAsy errorMessage = error.Message; } + // TO-DO: Use switch pattern in v5 + Exception exception = null; switch (response.StatusCode) { case HttpStatusCode.Unauthorized: - throw new System.UnauthorizedAccessException( + exception = new UnauthorizedAccessException( errorMessage); + break; + case HttpStatusCode.BadRequest: - throw new BadRequestException( + exception = new BadRequestException( errorMessage); - case HttpStatusCode.PaymentRequired - when errorType == "https://api.signhost.com/problem/subscription/out-of-credits": - if (string.IsNullOrEmpty(errorMessage)) { - errorMessage = "The credit bundle has been exceeded."; - } + break; - throw new OutOfCreditsException( + case HttpStatusCode.PaymentRequired + when errorType == OutOfCreditsApiProblemType: + exception = new OutOfCreditsException( errorMessage); + break; + case HttpStatusCode.NotFound: - throw new NotFoundException( + exception = new NotFoundException( errorMessage); + break; + case HttpStatusCode.InternalServerError: - throw new InternalServerErrorException( + exception = new InternalServerErrorException( errorMessage, response.Headers.RetryAfter); + break; + default: - throw new SignhostRestApiClientException( + exception = new SignhostRestApiClientException( errorMessage); + break; + } + + if (exception is SignhostRestApiClientException signhostException) { + signhostException.ResponseBody = responseBody; } - System.Diagnostics.Debug.Fail("Should not be reached"); + throw exception; } } } diff --git a/src/SignhostAPIClient/Rest/ErrorHandling/SignhostException.cs b/src/SignhostAPIClient/Rest/ErrorHandling/SignhostException.cs index fd06b20..a0e7bcf 100644 --- a/src/SignhostAPIClient/Rest/ErrorHandling/SignhostException.cs +++ b/src/SignhostAPIClient/Rest/ErrorHandling/SignhostException.cs @@ -3,6 +3,7 @@ namespace Signhost.APIClient.Rest.ErrorHandling { + // TO-DO: Remove in v5 [Serializable] [Obsolete("Unused will be removed")] public class SignhostException diff --git a/src/SignhostAPIClient/Rest/ErrorHandling/SignhostRestApiClientException.cs b/src/SignhostAPIClient/Rest/ErrorHandling/SignhostRestApiClientException.cs index 600a3c5..ff620e0 100644 --- a/src/SignhostAPIClient/Rest/ErrorHandling/SignhostRestApiClientException.cs +++ b/src/SignhostAPIClient/Rest/ErrorHandling/SignhostRestApiClientException.cs @@ -33,5 +33,10 @@ protected SignhostRestApiClientException( { } #endif + + /// + /// Gets or sets the response body returned from the Signhost REST API. + /// + public string ResponseBody { get; set; } } } From 9b976acd83690fb9c789d0285c1244d462537f5e Mon Sep 17 00:00:00 2001 From: Anthony Timmers Date: Thu, 27 Nov 2025 17:24:19 +0100 Subject: [PATCH 03/28] Fix non-breaking data object discrepencies with API docs --- src/SignhostAPIClient/Rest/DataObjects/Activity.cs | 4 ++++ .../Rest/DataObjects/ActivityType.cs | 1 + .../Rest/DataObjects/DigidVerification.cs | 2 ++ src/SignhostAPIClient/Rest/DataObjects/Field.cs | 2 ++ .../Rest/DataObjects/IVerification.cs | 1 + .../Rest/DataObjects/PhoneNumberVerification.cs | 2 ++ src/SignhostAPIClient/Rest/DataObjects/Receiver.cs | 9 ++++++++- src/SignhostAPIClient/Rest/DataObjects/Signer.cs | 14 ++++++++++++++ .../Rest/DataObjects/SurfnetVerification.cs | 8 +++++++- 9 files changed, 41 insertions(+), 2 deletions(-) diff --git a/src/SignhostAPIClient/Rest/DataObjects/Activity.cs b/src/SignhostAPIClient/Rest/DataObjects/Activity.cs index dccf8ca..31a703c 100644 --- a/src/SignhostAPIClient/Rest/DataObjects/Activity.cs +++ b/src/SignhostAPIClient/Rest/DataObjects/Activity.cs @@ -1,4 +1,5 @@ using System; +using Newtonsoft.Json; namespace Signhost.APIClient.Rest.DataObjects { @@ -8,6 +9,9 @@ public class Activity public ActivityType Code { get; set; } + [JsonProperty("Activity")] + public string ActivityValue { get; set; } + public string Info { get; set; } public DateTimeOffset CreatedDateTime { get; set; } diff --git a/src/SignhostAPIClient/Rest/DataObjects/ActivityType.cs b/src/SignhostAPIClient/Rest/DataObjects/ActivityType.cs index e954356..4b85586 100644 --- a/src/SignhostAPIClient/Rest/DataObjects/ActivityType.cs +++ b/src/SignhostAPIClient/Rest/DataObjects/ActivityType.cs @@ -7,6 +7,7 @@ namespace Signhost.APIClient.Rest.DataObjects /// /// type. /// + // TO-DO: Remove unused activity types in v5. public enum ActivityType { /// diff --git a/src/SignhostAPIClient/Rest/DataObjects/DigidVerification.cs b/src/SignhostAPIClient/Rest/DataObjects/DigidVerification.cs index b403481..c5be757 100644 --- a/src/SignhostAPIClient/Rest/DataObjects/DigidVerification.cs +++ b/src/SignhostAPIClient/Rest/DataObjects/DigidVerification.cs @@ -6,5 +6,7 @@ public class DigidVerification public string Type => "DigiD"; public string Bsn { get; set; } + + public bool? SecureDownload { get; set; } } } diff --git a/src/SignhostAPIClient/Rest/DataObjects/Field.cs b/src/SignhostAPIClient/Rest/DataObjects/Field.cs index 2c2482b..a7453c8 100644 --- a/src/SignhostAPIClient/Rest/DataObjects/Field.cs +++ b/src/SignhostAPIClient/Rest/DataObjects/Field.cs @@ -2,8 +2,10 @@ { public class Field { + // TO-DO: Make enum in v5. public string Type { get; set; } + // TO-DO: Can be boolean, number, string, should be fixed in v5. public string Value { get; set; } public Location Location { get; set; } diff --git a/src/SignhostAPIClient/Rest/DataObjects/IVerification.cs b/src/SignhostAPIClient/Rest/DataObjects/IVerification.cs index fe76ce2..5ab6b93 100644 --- a/src/SignhostAPIClient/Rest/DataObjects/IVerification.cs +++ b/src/SignhostAPIClient/Rest/DataObjects/IVerification.cs @@ -3,6 +3,7 @@ namespace Signhost.APIClient.Rest.DataObjects { + // TO-DO: Split to verification and authentication in v5 [JsonConverter(typeof(JsonVerificationConverter))] public interface IVerification { diff --git a/src/SignhostAPIClient/Rest/DataObjects/PhoneNumberVerification.cs b/src/SignhostAPIClient/Rest/DataObjects/PhoneNumberVerification.cs index 8503fbf..14b3870 100644 --- a/src/SignhostAPIClient/Rest/DataObjects/PhoneNumberVerification.cs +++ b/src/SignhostAPIClient/Rest/DataObjects/PhoneNumberVerification.cs @@ -6,5 +6,7 @@ public class PhoneNumberVerification public string Type => "PhoneNumber"; public string Number { get; set; } + + public bool? SecureDownload { get; set; } } } diff --git a/src/SignhostAPIClient/Rest/DataObjects/Receiver.cs b/src/SignhostAPIClient/Rest/DataObjects/Receiver.cs index 8fc286e..5f446c7 100644 --- a/src/SignhostAPIClient/Rest/DataObjects/Receiver.cs +++ b/src/SignhostAPIClient/Rest/DataObjects/Receiver.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using Newtonsoft.Json; namespace Signhost.APIClient.Rest.DataObjects @@ -15,6 +16,8 @@ private Receiver(IReadOnlyList activities) Activities = activities; } + public string Id { get; set; } + public string Name { get; set; } public string Email { get; set; } @@ -27,6 +30,10 @@ private Receiver(IReadOnlyList activities) public string Reference { get; set; } + public DateTimeOffset? CreatedDateTime { get; set; } + + public DateTimeOffset? ModifiedDateTime { get; set; } + public IReadOnlyList Activities { get; set; } = new List().AsReadOnly(); diff --git a/src/SignhostAPIClient/Rest/DataObjects/Signer.cs b/src/SignhostAPIClient/Rest/DataObjects/Signer.cs index 77cada5..e3c8870 100644 --- a/src/SignhostAPIClient/Rest/DataObjects/Signer.cs +++ b/src/SignhostAPIClient/Rest/DataObjects/Signer.cs @@ -64,6 +64,20 @@ private Signer(IReadOnlyList activities) public string DelegateSignerName { get; set; } + public DateTimeOffset? SignedDateTime { get; set; } + + public DateTimeOffset? RejectDateTime { get; set; } + + public DateTimeOffset? CreatedDateTime { get; set; } + + public DateTimeOffset? SignerDelegationDateTime { get; set; } + + public DateTimeOffset? ModifiedDateTime { get; set; } + + public string ShowUrl { get; set; } + + public string ReceiptUrl { get; set; } + public IReadOnlyList Activities { get; private set; } = new List().AsReadOnly(); diff --git a/src/SignhostAPIClient/Rest/DataObjects/SurfnetVerification.cs b/src/SignhostAPIClient/Rest/DataObjects/SurfnetVerification.cs index 466551c..c65632f 100644 --- a/src/SignhostAPIClient/Rest/DataObjects/SurfnetVerification.cs +++ b/src/SignhostAPIClient/Rest/DataObjects/SurfnetVerification.cs @@ -1,8 +1,14 @@ -namespace Signhost.APIClient.Rest.DataObjects +using System.Collections.Generic; + +namespace Signhost.APIClient.Rest.DataObjects { public class SurfnetVerification : IVerification { public string Type => "SURFnet"; + + public string Uid { get; set; } + + public IDictionary Attributes { get; set; } } } From 6f004ef29150bfab2e7e96ea8393caf06ef9b51b Mon Sep 17 00:00:00 2001 From: Anthony Timmers Date: Thu, 27 Nov 2025 19:15:22 +0100 Subject: [PATCH 04/28] Add integration tests --- .github/workflows/publish_nuget_package.yml | 1 + .../README.md | 27 ++ .../SignhostAPIClient.IntegrationTests.csproj | 32 +++ .../TestConfiguration.cs | 38 +++ .../TestFiles/small-example-pdf-file.pdf | Bin 0 -> 4107 bytes .../TransactionTests.cs | 261 ++++++++++++++++++ src/SignhostAPIClient.sln | 6 + 7 files changed, 365 insertions(+) create mode 100644 src/SignhostAPIClient.IntegrationTests/README.md create mode 100644 src/SignhostAPIClient.IntegrationTests/SignhostAPIClient.IntegrationTests.csproj create mode 100644 src/SignhostAPIClient.IntegrationTests/TestConfiguration.cs create mode 100644 src/SignhostAPIClient.IntegrationTests/TestFiles/small-example-pdf-file.pdf create mode 100644 src/SignhostAPIClient.IntegrationTests/TransactionTests.cs diff --git a/.github/workflows/publish_nuget_package.yml b/.github/workflows/publish_nuget_package.yml index dc459d1..77780ea 100644 --- a/.github/workflows/publish_nuget_package.yml +++ b/.github/workflows/publish_nuget_package.yml @@ -53,6 +53,7 @@ jobs: - name: Test with Coverage run: dotnet test --no-build src/SignhostAPIClient.Tests/SignhostAPIClient.Tests.csproj --collect:"XPlat Code Coverage" -c Release + # Note: Integration tests are excluded from CI/CD as they require live credentials - name: Pack run: dotnet pack src/SignhostAPIClient/SignhostAPIClient.csproj /p:Version=${{ env.LATEST_VERSION }} diff --git a/src/SignhostAPIClient.IntegrationTests/README.md b/src/SignhostAPIClient.IntegrationTests/README.md new file mode 100644 index 0000000..2caaae0 --- /dev/null +++ b/src/SignhostAPIClient.IntegrationTests/README.md @@ -0,0 +1,27 @@ +# Integration Tests + +This project contains integration tests that require live API credentials to run. + +## Configuration + +The tests require Signhost API credentials configured via .NET User Secrets: + +```bash +cd src/SignhostAPIClient.IntegrationTests +dotnet user-secrets set "Signhost:AppKey" "your-app-key-here" +dotnet user-secrets set "Signhost:UserToken" "your-user-token-here" +dotnet user-secrets set "Signhost:ApiBaseUrl" "https://api.signhost.com/api" +``` + +## Running Tests + +```bash +dotnet test src/SignhostAPIClient.IntegrationTests/SignhostAPIClient.IntegrationTests.csproj +``` + +## Important Notes + +- These tests are **excluded from CI/CD** pipelines +- These tests are **not packaged** in the NuGet package +- Tests will fail if credentials are not configured +- Tests create real transactions in your Signhost account diff --git a/src/SignhostAPIClient.IntegrationTests/SignhostAPIClient.IntegrationTests.csproj b/src/SignhostAPIClient.IntegrationTests/SignhostAPIClient.IntegrationTests.csproj new file mode 100644 index 0000000..56cc88e --- /dev/null +++ b/src/SignhostAPIClient.IntegrationTests/SignhostAPIClient.IntegrationTests.csproj @@ -0,0 +1,32 @@ + + + net8.0 + false + true + ../signhost.ruleset + signhost-api-client-integration-tests + + + + + + + + + + + + + + + + + + + + + + PreserveNewest + + + diff --git a/src/SignhostAPIClient.IntegrationTests/TestConfiguration.cs b/src/SignhostAPIClient.IntegrationTests/TestConfiguration.cs new file mode 100644 index 0000000..53a7e10 --- /dev/null +++ b/src/SignhostAPIClient.IntegrationTests/TestConfiguration.cs @@ -0,0 +1,38 @@ +using System; +using Microsoft.Extensions.Configuration; + +namespace Signhost.APIClient.Rest.IntegrationTests; + +/// +/// Configuration for integration tests loaded from user secrets only. +/// No appsettings.json is used to prevent accidental credential commits. +/// +public class TestConfiguration +{ + private static readonly Lazy LazyInstance = + new(() => new TestConfiguration()); + + private TestConfiguration() + { + var builder = new ConfigurationBuilder() + .AddUserSecrets(optional: false); + + IConfiguration configuration = builder.Build(); + AppKey = configuration["Signhost:AppKey"]; + UserToken = configuration["Signhost:UserToken"]; + ApiBaseUrl = configuration["Signhost:ApiBaseUrl"]; + } + + public static TestConfiguration Instance => LazyInstance.Value; + + public string AppKey { get; } + + public string UserToken { get; } + + public string ApiBaseUrl { get; } + + public bool IsConfigured => + !string.IsNullOrWhiteSpace(AppKey) && + !string.IsNullOrWhiteSpace(UserToken) && + !string.IsNullOrWhiteSpace(ApiBaseUrl); +} diff --git a/src/SignhostAPIClient.IntegrationTests/TestFiles/small-example-pdf-file.pdf b/src/SignhostAPIClient.IntegrationTests/TestFiles/small-example-pdf-file.pdf new file mode 100644 index 0000000000000000000000000000000000000000..fd46350c2b98bff7f1971081b2b8da9ee807a6c0 GIT binary patch literal 4107 zcmeHKUyK{Y8Bd^4X-g`Ei26{3MnR{&imYd5?cF^)wo`LHH!0_fYa4OsIHbMZ@!jUu zyYB9~_FYvJghZvLB0K~hsok2&p8Zv}#cWJVYX?)Q3KW1XNV1X{Gj? z@viMVN99k+TW$ICw=>_r`M%$LvolpIR}`AcNKNz}aLr zqIe!s|`pf zq8WVHYxxLbLv>vD{Rn{xU=Np<@_IR2QmLww(ws(0v!q-iS=E?R)pABnr?uJQCrd2j zTEM;s^)P0j9${n#5zp!#iAYmZb4MzJ+DP1IT9r?!I|t*U3Hf7w=qrtUR^w%7_0refD>AE9SAY zFWT9UzK{QYVDU>U$F2S6TjXme-nw$))(C>nG^_6qS zub%q0a%AfV$M32*czeFD&`t!S$%hsi>`m;}8NWC#_zg50+ zsds9#a`OGB<)`kv>D8~}+Z$_V?tcB^#uGojG=T~l8&b9;4*nYY2NhWb?*|No7%f}3kK-TdpN)Q zJ7Q!v(80_=@XPSWf5{m9^Vqj1k)t?{0|0M#Ef!2aoHlg$84p8~sc}-~q{7K8Cjlpn z6O)sO6Q2_?fjFmflH!C6xv{Ztz&?ReMvm{50rk`6oJy!h3}{S5)l~IfN@PN05Q8T0 z2@*{wa@n?+MYtj$i#zXf(&j|O>~T)SOu>Gs8?E`;oKO;6I8+y_-I)O} z33xfmtY{k8``Jd%5A2ZhV!=CH-Jt9aVLk#ObTH9{2hA7X@7N()l6cRoOUuxl{Vs1~ zW9@1Tpkgm1eRs4U7OV3(#xB)#I1FNBrCJc0qnntCDJG zmaRF5HUrk!TxeRG>sHp54;H2TK@(1Pru5B@(h@B(L@cxl`)2F&m3>^GpX;|fZD{|f z-*&w)*DuIIB?mko#xfEj(Rx8%tkzKE`|i2}y*-spQH7@WqgG(H85Cq4(UdNO$-r*q z>d@W?2H~k7uSHQOhjFjhOZCzzKUl>yo6TaPVpUZ@LJ2p#$m}a#cn|m!fjEmpH7`tw zm{OMC#?B(9DS{J6Wmrz&bU5e{cWkb}saKG@UB}KDD@Lo8VTO{{(iw$PrYV3rqEMUY zOt-U)sA}S_GZyAHe=q?K)8H@@Ifv}3VwOKO+XUz30RAR+2Ny<$2{Dz z@Z-#ljr>mwg0fyefq;cLgL#n2K`(Gx8@wG9dAm@Ep{RP3kesUJM9;_(SlJFqzzwL1 z^|Mc-Iwm=$>IY3g({(dBG#|^{@NAi#YW2V8tKH^%fy?E}zSV8R`^e$)XF(eNf-7uW z!+<;8z!lrdwlH%Uk1d2EQ;68MaxFh-o3JU&PRDgDaX@gW0-|9u;+?)IHf_m`28`?Q zO_*~{Z?z!z6`Qrpt{ch4WZiU74%Y!xa}*0jJTeBH#ZMwX7}#sD? zy~B=><93GHz}ur8{wc%UVRabYHcQvpeqsnZ7t{$4`(p9)UWJ zd%V?8yo4lf|NnoZG9d1~1Z9#dE}Sh$*R-^TTFJGSW-@T*^UvriOtX{MUYebOm*+%o T=j}5iURUEgp+qx!d@1}7-TrPa literal 0 HcmV?d00001 diff --git a/src/SignhostAPIClient.IntegrationTests/TransactionTests.cs b/src/SignhostAPIClient.IntegrationTests/TransactionTests.cs new file mode 100644 index 0000000..e395c87 --- /dev/null +++ b/src/SignhostAPIClient.IntegrationTests/TransactionTests.cs @@ -0,0 +1,261 @@ +using System; +using System.IO; +using System.Threading.Tasks; +using FluentAssertions; +using Signhost.APIClient.Rest.DataObjects; +using Xunit; + +namespace Signhost.APIClient.Rest.IntegrationTests; + +public class TransactionTests + : IDisposable +{ + private readonly SignHostApiClient client; + private readonly TestConfiguration config; + + public TransactionTests() + { + config = TestConfiguration.Instance; + + if (!config.IsConfigured) { + throw new InvalidOperationException( + "Integration tests are not configured"); + } + + var settings = new SignHostApiClientSettings(config.AppKey, config.UserToken) { + Endpoint = config.ApiBaseUrl + }; + + client = new SignHostApiClient(settings); + } + + [Fact] + public async Task Given_complex_transaction_When_created_and_started_Then_all_properties_are_correctly_persisted() + { + // Arrange + var testReference = $"IntegrationTest-{DateTime.UtcNow:yyyyMMddHHmmss}"; + var testPostbackUrl = "https://example.com/postback"; + var signerEmail = "john.doe@example.com"; + var signerReference = "SIGNER-001"; + var signerIntroText = "Please review and sign this document carefully."; + var signerExpires = DateTimeOffset.UtcNow.AddDays(15); + var receiverEmail = "receiver@example.com"; + var receiverName = "Jane Receiver"; + var receiverReference = "RECEIVER-001"; + + var transaction = new Transaction { + Seal = false, + Reference = testReference, + PostbackUrl = testPostbackUrl, + DaysToExpire = 30, + SendEmailNotifications = false, + SignRequestMode = 2, + Language = "en-US", + Context = new { + TestContext = "integration-test", + }, + Signers = [ + new Signer { + Id = "signer1", + Email = signerEmail, + Reference = signerReference, + IntroText = signerIntroText, + Expires = signerExpires, + SendSignRequest = false, + SendSignConfirmation = false, + DaysToRemind = 7, + Language = "en-US", + SignRequestMessage = "Please sign this document.", + SignRequestSubject = "Document for Signature", + ReturnUrl = "https://example.com/return", + AllowDelegation = false, + Context = new { + SignerContext = "test-signer", + }, + Verifications = [ + new ScribbleVerification { + RequireHandsignature = true, + ScribbleName = "John Doe", + ScribbleNameFixed = true + } + ], + Authentications = [ + new PhoneNumberVerification { + Number = "+31612345678", + SecureDownload = true, + } + ] + } + ], + Receivers = [ + new Receiver { + Name = receiverName, + Email = receiverEmail, + Language = "en-US", + Message = "The document has been signed.", + Subject = "Signed Document", + Reference = receiverReference, + Context = new { + ReceiverContext = "test-receiver", + } + } + ] + }; + + var pdfPath = Path.Combine("TestFiles", "small-example-pdf-file.pdf"); + if (!File.Exists(pdfPath)) { + throw new FileNotFoundException($"Test PDF file not found at: {pdfPath}"); + } + + // Act - Create transaction + var createdTransaction = await client.CreateTransactionAsync(transaction); + + // Assert - Creation properties + createdTransaction.Should().NotBeNull(); + createdTransaction.Id.Should().NotBeNullOrEmpty(); + createdTransaction.Status.Should().Be(TransactionStatus.WaitingForDocument); + createdTransaction.Seal.Should().BeFalse(); + createdTransaction.Reference.Should().Be(testReference); + createdTransaction.PostbackUrl.Should().Be(testPostbackUrl); + createdTransaction.DaysToExpire.Should().Be(30); + createdTransaction.SendEmailNotifications.Should().BeFalse(); + createdTransaction.SignRequestMode.Should().Be(2); + createdTransaction.Language.Should().Be("en-US"); + createdTransaction.CreatedDateTime.Should().HaveValue(); + createdTransaction.CreatedDateTime.Should().BeCloseTo(DateTimeOffset.UtcNow, TimeSpan.FromMinutes(1)); + createdTransaction.CancelledDateTime.Should().BeNull(); + createdTransaction.CancellationReason.Should().BeNull(); + + // Assert - Context + ((object)createdTransaction.Context).Should().NotBeNull(); + string transactionContextJson = createdTransaction.Context.ToString(); + transactionContextJson.Should().Contain("integration-test"); + + // Assert - Signers + createdTransaction.Signers.Should().HaveCount(1); + var createdSigner = createdTransaction.Signers[0]; + createdSigner.Id.Should().Be("signer1"); + createdSigner.Email.Should().Be(signerEmail); + createdSigner.Reference.Should().Be(signerReference); + createdSigner.IntroText.Should().Be(signerIntroText); + createdSigner.Expires.Should().HaveValue(); + createdSigner.Expires.Should().BeCloseTo(signerExpires, TimeSpan.FromMinutes(1)); + createdSigner.SendSignRequest.Should().BeFalse(); + createdSigner.SendSignConfirmation.Should().BeFalse(); + createdSigner.DaysToRemind.Should().Be(7); + createdSigner.Language.Should().Be("en-US"); + createdSigner.SignRequestMessage.Should().Be("Please sign this document."); + createdSigner.SignRequestSubject.Should().Be("Document for Signature"); + createdSigner.ReturnUrl.Should().Be("https://example.com/return"); + createdSigner.AllowDelegation.Should().BeFalse(); + createdSigner.CreatedDateTime.Should().HaveValue(); + createdSigner.ModifiedDateTime.Should().HaveValue(); + createdSigner.SignedDateTime.Should().BeNull(); + createdSigner.RejectDateTime.Should().BeNull(); + createdSigner.SignerDelegationDateTime.Should().BeNull(); + createdSigner.RejectReason.Should().BeNull(); + createdSigner.Activities.Should().NotBeNull(); + + // Assert - Signer Context + ((object)createdSigner.Context).Should().NotBeNull(); + string signerContextJson = createdSigner.Context.ToString(); + signerContextJson.Should().Contain("test-signer"); + + // Assert - Signer Verifications + createdSigner.Verifications.Should().HaveCount(1); + var verification = createdSigner.Verifications[0].Should().BeOfType().Subject; + verification.ScribbleName.Should().Be("John Doe"); + verification.RequireHandsignature.Should().BeTrue(); + verification.ScribbleNameFixed.Should().BeTrue(); + + // Assert - Signer Authentications + createdSigner.Authentications.Should().HaveCount(1); + var authentication = createdSigner.Authentications[0].Should().BeOfType().Subject; + authentication.Number.Should().Be("+31612345678"); + authentication.SecureDownload.Should().BeTrue(); + + // Assert - Receivers + createdTransaction.Receivers.Should().HaveCount(1); + var createdReceiver = createdTransaction.Receivers[0]; + createdReceiver.Name.Should().Be(receiverName); + createdReceiver.Email.Should().Be(receiverEmail); + createdReceiver.Language.Should().Be("en-US"); + createdReceiver.Message.Should().Be("The document has been signed."); + createdReceiver.Subject.Should().Be("Signed Document"); + createdReceiver.Reference.Should().Be(receiverReference); + createdReceiver.Id.Should().NotBeNullOrEmpty(); + createdReceiver.CreatedDateTime.Should().HaveValue(); + createdReceiver.ModifiedDateTime.Should().HaveValue(); + createdReceiver.Activities.Should().BeNull( + because: "actual API inconsistency - Receiver Activities are null rather than an empty list"); + + // Assert - Receiver Context + ((object)createdReceiver.Context).Should().NotBeNull(); + string receiverContextJson = createdReceiver.Context.ToString(); + receiverContextJson.Should().Contain("test-receiver"); + + // Act - Upload file + await using var fileStream = File.OpenRead(pdfPath); + await client.AddOrReplaceFileToTransactionAsync( + fileStream, + createdTransaction.Id, + "test-document.pdf", + new FileUploadOptions()); + + // Act - Start transaction + await client.StartTransactionAsync(createdTransaction.Id); + + // Act - Retrieve final state + var finalTransaction = await client.GetTransactionAsync(createdTransaction.Id); + + // Assert - Final transaction state + finalTransaction.Should().NotBeNull(); + finalTransaction.Id.Should().Be(createdTransaction.Id); + finalTransaction.Status.Should().BeOneOf( + TransactionStatus.WaitingForSigner, + TransactionStatus.InProgress); + finalTransaction.Reference.Should().Be(testReference); + finalTransaction.PostbackUrl.Should().Be(testPostbackUrl); + finalTransaction.DaysToExpire.Should().Be(30); + finalTransaction.SendEmailNotifications.Should().BeFalse(); + finalTransaction.Language.Should().Be("en-US"); + + // Assert - Files + finalTransaction.Files.Should().NotBeNull(); + finalTransaction.Files.Should().ContainKey("test-document.pdf"); + var fileEntry = finalTransaction.Files["test-document.pdf"]; + fileEntry.Should().NotBeNull(); + fileEntry.DisplayName.Should().Be("test-document.pdf"); + fileEntry.Links.Should().NotBeNull().And.NotBeEmpty(); + + // Assert - Signer in final state + finalTransaction.Signers.Should().HaveCount(1); + var finalSigner = finalTransaction.Signers[0]; + finalSigner.Id.Should().Be("signer1"); + finalSigner.Email.Should().Be(signerEmail); + finalSigner.Reference.Should().Be(signerReference); + finalSigner.ModifiedDateTime.Should().HaveValue(); + finalSigner.ModifiedDateTime.Should().BeOnOrAfter(finalSigner.CreatedDateTime.Value); + finalSigner.Expires.Should().HaveValue(); + finalSigner.SignUrl.Should().NotBeNullOrEmpty(); + finalSigner.ShowUrl.Should().NotBeNullOrEmpty(); + finalSigner.ReceiptUrl.Should().NotBeNullOrEmpty(); + finalSigner.DelegateSignUrl.Should().BeNullOrEmpty(); + finalSigner.DelegateReason.Should().BeNullOrEmpty(); + finalSigner.DelegateSignerEmail.Should().BeNullOrEmpty(); + finalSigner.DelegateSignerName.Should().BeNullOrEmpty(); + + // Assert - Receiver in final state + finalTransaction.Receivers.Should().HaveCount(1); + var finalReceiver = finalTransaction.Receivers[0]; + finalReceiver.Email.Should().Be(receiverEmail); + finalReceiver.Name.Should().Be(receiverName); + finalReceiver.Reference.Should().Be(receiverReference); + } + + public void Dispose() + { + client?.Dispose(); + GC.SuppressFinalize(this); + } +} diff --git a/src/SignhostAPIClient.sln b/src/SignhostAPIClient.sln index 1dc1d2d..fdd4c33 100644 --- a/src/SignhostAPIClient.sln +++ b/src/SignhostAPIClient.sln @@ -7,6 +7,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SignhostAPIClient", "Signho EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SignhostAPIClient.Tests", "SignhostAPIClient.Tests\SignhostAPIClient.Tests.csproj", "{0A2CF5DE-060C-4C92-8F15-7AA26268511D}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SignhostAPIClient.IntegrationTests", "SignhostAPIClient.IntegrationTests\SignhostAPIClient.IntegrationTests.csproj", "{B8F3E9A1-3C4D-4E5F-9A2B-1D3E4F5A6B7C}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -21,6 +23,10 @@ Global {0A2CF5DE-060C-4C92-8F15-7AA26268511D}.Debug|Any CPU.Build.0 = Debug|Any CPU {0A2CF5DE-060C-4C92-8F15-7AA26268511D}.Release|Any CPU.ActiveCfg = Release|Any CPU {0A2CF5DE-060C-4C92-8F15-7AA26268511D}.Release|Any CPU.Build.0 = Release|Any CPU + {B8F3E9A1-3C4D-4E5F-9A2B-1D3E4F5A6B7C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B8F3E9A1-3C4D-4E5F-9A2B-1D3E4F5A6B7C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B8F3E9A1-3C4D-4E5F-9A2B-1D3E4F5A6B7C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B8F3E9A1-3C4D-4E5F-9A2B-1D3E4F5A6B7C}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE From 8a119c87ffcdb6fcd020bd226009c28366b051b2 Mon Sep 17 00:00:00 2001 From: Anthony Timmers Date: Fri, 21 Nov 2025 15:09:16 +0100 Subject: [PATCH 05/28] Make support up to net10.0 and drop netstandard1.4 support Modernizes the codebase by upgrading to .NET 10, enabling use of newer language features and APIs. Removes legacy netstandard1.4 support and related conditional logic to simplify maintenance. Improves compatibility with future .NET releases. --- .github/workflows/publish_nuget_package.yml | 2 +- .../SignhostAPIClient.IntegrationTests.csproj | 2 +- .../SignhostAPIClient.Tests.csproj | 2 +- .../Rest/JsonConverters/JsonBaseConverter.cs | 8 ++--- .../JsonVerificationConverter.cs | 33 +++++++++++++++-- .../Rest/JsonConverters/LevelEnumConverter.cs | 20 ++++++++--- .../Rest/SignHostApiClient.cs | 2 ++ .../StreamContentDigestOptionsExtensions.cs | 36 +++++++------------ .../SignhostAPIClient.csproj | 11 ++---- .../System/SerializableAttribute.cs | 9 ----- 10 files changed, 71 insertions(+), 54 deletions(-) delete mode 100644 src/SignhostAPIClient/System/SerializableAttribute.cs diff --git a/.github/workflows/publish_nuget_package.yml b/.github/workflows/publish_nuget_package.yml index 77780ea..6ab85e6 100644 --- a/.github/workflows/publish_nuget_package.yml +++ b/.github/workflows/publish_nuget_package.yml @@ -15,7 +15,7 @@ jobs: - name: Setup .NET uses: actions/setup-dotnet@v5 with: - dotnet-version: '8.x' + dotnet-version: '10.x' - name: Check for Tag on Current Commit run: | diff --git a/src/SignhostAPIClient.IntegrationTests/SignhostAPIClient.IntegrationTests.csproj b/src/SignhostAPIClient.IntegrationTests/SignhostAPIClient.IntegrationTests.csproj index 56cc88e..3ea6d79 100644 --- a/src/SignhostAPIClient.IntegrationTests/SignhostAPIClient.IntegrationTests.csproj +++ b/src/SignhostAPIClient.IntegrationTests/SignhostAPIClient.IntegrationTests.csproj @@ -1,6 +1,6 @@ - net8.0 + net10.0 false true ../signhost.ruleset diff --git a/src/SignhostAPIClient.Tests/SignhostAPIClient.Tests.csproj b/src/SignhostAPIClient.Tests/SignhostAPIClient.Tests.csproj index cc06f7b..fbe586e 100644 --- a/src/SignhostAPIClient.Tests/SignhostAPIClient.Tests.csproj +++ b/src/SignhostAPIClient.Tests/SignhostAPIClient.Tests.csproj @@ -1,6 +1,6 @@ - net8.0 + net10.0 ../signhost.ruleset test diff --git a/src/SignhostAPIClient/Rest/JsonConverters/JsonBaseConverter.cs b/src/SignhostAPIClient/Rest/JsonConverters/JsonBaseConverter.cs index cc3f3e5..ff5642b 100644 --- a/src/SignhostAPIClient/Rest/JsonConverters/JsonBaseConverter.cs +++ b/src/SignhostAPIClient/Rest/JsonConverters/JsonBaseConverter.cs @@ -8,12 +8,12 @@ namespace Signhost.APIClient.Rest.JsonConverters public abstract class JsonBaseConverter : JsonConverter { -#if TYPEINFO public override bool CanConvert(Type objectType) - => typeof(T).GetTypeInfo().IsAssignableFrom(objectType.GetTypeInfo()); + => typeof(T) +#if TYPEINFO + .GetTypeInfo().IsAssignableFrom(objectType.GetTypeInfo()); #else - public override bool CanConvert(Type objectType) - => typeof(T).IsAssignableFrom(objectType); + .IsAssignableFrom(objectType); #endif public override object ReadJson( diff --git a/src/SignhostAPIClient/Rest/JsonConverters/JsonVerificationConverter.cs b/src/SignhostAPIClient/Rest/JsonConverters/JsonVerificationConverter.cs index 3019809..4e1f9fa 100644 --- a/src/SignhostAPIClient/Rest/JsonConverters/JsonVerificationConverter.cs +++ b/src/SignhostAPIClient/Rest/JsonConverters/JsonVerificationConverter.cs @@ -11,7 +11,11 @@ namespace Signhost.APIClient.Rest.JsonConverters internal class JsonVerificationConverter : JsonBaseConverter { +#if TYPEINFO private static readonly IDictionary VerificationTypes = +#else + private static readonly IDictionary VerificationTypes = +#endif CreateVerificationTypeMap(); public override bool CanWrite @@ -30,7 +34,12 @@ internal static void RegisterVerification() { var verification = (IVerification)Activator.CreateInstance(typeof(T)); - VerificationTypes[verification.Type] = typeof(T).GetTypeInfo(); + VerificationTypes[verification.Type] = +#if TYPEINFO + typeof(T).GetTypeInfo(); +#else + typeof(T); +#endif } protected override IVerification Create( @@ -40,13 +49,18 @@ protected override IVerification Create( var typeName = jsonObject["Type"]?.ToString(); if (VerificationTypes.TryGetValue(typeName, out var verificationType)) { +#if TYPEINFO return (IVerification)Activator.CreateInstance(verificationType.AsType()); +#else + return (IVerification)Activator.CreateInstance(verificationType); +#endif } return new UnknownVerification(); } - private static IDictionary CreateVerificationTypeMap() +#if TYPEINFO + private static Dictionary CreateVerificationTypeMap() { return typeof(JsonVerificationConverter).GetTypeInfo().Assembly.ExportedTypes .Select(t => t.GetTypeInfo()) @@ -60,5 +74,20 @@ private static IDictionary CreateVerificationTypeMap() .Where(t => t.instance.Type != null) .ToDictionary(t => t.instance.Type, t => t.typeInfo); } +#else + private static Dictionary CreateVerificationTypeMap() + { + return typeof(JsonVerificationConverter).Assembly.ExportedTypes + .Where(t => typeof(IVerification).IsAssignableFrom(t)) + .Where(t => !t.IsInterface && !t.IsAbstract) +#pragma warning disable SA1008 // Opening parenthesis must be spaced correctly + .Select(t => ( + type: t, + instance: (IVerification)Activator.CreateInstance(t))) +#pragma warning restore SA1008 // Opening parenthesis must be spaced correctly + .Where(t => t.instance.Type is not null) + .ToDictionary(t => t.instance.Type, t => t.type); + } +#endif } } diff --git a/src/SignhostAPIClient/Rest/JsonConverters/LevelEnumConverter.cs b/src/SignhostAPIClient/Rest/JsonConverters/LevelEnumConverter.cs index c088635..e0e5aee 100644 --- a/src/SignhostAPIClient/Rest/JsonConverters/LevelEnumConverter.cs +++ b/src/SignhostAPIClient/Rest/JsonConverters/LevelEnumConverter.cs @@ -47,11 +47,23 @@ public override void WriteJson( => throw new NotImplementedException(); private static Type GetUnderlyingType(Type type) - => type.GetTypeInfo().IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>) - ? Nullable.GetUnderlyingType(type) - : type; + => +#if TYPEINFO + type.GetTypeInfo().IsGenericType && +#else + type.IsGenericType && +#endif + type.GetGenericTypeDefinition() == typeof(Nullable<>) + ? Nullable.GetUnderlyingType(type) + : type; private static bool IsLevelEnum(Type type) - => type.GetTypeInfo().IsEnum && type == typeof(Level); + => +#if TYPEINFO + type.GetTypeInfo().IsEnum && +#else + type.IsEnum && +#endif + type == typeof(Level); } } diff --git a/src/SignhostAPIClient/Rest/SignHostApiClient.cs b/src/SignhostAPIClient/Rest/SignHostApiClient.cs index 093632b..dd2e3a8 100644 --- a/src/SignhostAPIClient/Rest/SignHostApiClient.cs +++ b/src/SignhostAPIClient/Rest/SignHostApiClient.cs @@ -22,7 +22,9 @@ public class SignHostApiClient private const string ApiVersion = "v1"; private static readonly string Version = typeof(SignHostApiClient) +#if TYPEINFO .GetTypeInfo() +#endif .Assembly.GetCustomAttribute() .Version; diff --git a/src/SignhostAPIClient/Rest/StreamContentDigestOptionsExtensions.cs b/src/SignhostAPIClient/Rest/StreamContentDigestOptionsExtensions.cs index 2aad900..3ae2389 100644 --- a/src/SignhostAPIClient/Rest/StreamContentDigestOptionsExtensions.cs +++ b/src/SignhostAPIClient/Rest/StreamContentDigestOptionsExtensions.cs @@ -63,27 +63,17 @@ private static HashAlgorithm HashAlgorithmCreate( string algorithmName = options.DigestHashAlgorithm; HashAlgorithm algorithm = null; -#if NETSTANDARD1_4 || NETSTANDARD2_0 - switch (algorithmName) { - case "SHA1": - case "SHA-1": - algorithm = SHA1.Create(); - break; - case "SHA256": - case "SHA-256": - algorithm = SHA256.Create(); - break; - case "SHA384": - case "SHA-384": - algorithm = SHA384.Create(); - break; - case "SHA512": - case "SHA-512": - algorithm = SHA512.Create(); - break; - } -#else +#if NET462 algorithm = HashAlgorithm.Create(algorithmName); +#else + algorithm = algorithmName switch { + "SHA1" or "SHA-1" => SHA1.Create(), + "SHA256" or "SHA-256" => SHA256.Create(), + "SHA384" or "SHA-384" => SHA384.Create(), + "SHA512" or "SHA-512" => SHA512.Create(), + + _ => null, + }; #endif if (algorithm == null && options.DigestHashValue == null) { algorithm = DefaultHashAlgorithm(); @@ -98,10 +88,10 @@ private static HashAlgorithm HashAlgorithmCreate( } private static HashAlgorithm DefaultHashAlgorithm() => -#if NETSTANDARD1_4 || NETSTANDARD2_0 - SHA256.Create(); -#else +#if NET462 HashAlgorithm.Create(); +#else + SHA256.Create(); #endif } } diff --git a/src/SignhostAPIClient/SignhostAPIClient.csproj b/src/SignhostAPIClient/SignhostAPIClient.csproj index f8acfff..8c6a83a 100644 --- a/src/SignhostAPIClient/SignhostAPIClient.csproj +++ b/src/SignhostAPIClient/SignhostAPIClient.csproj @@ -1,11 +1,9 @@ - netstandard2.0;netstandard1.4;net462 + net10.0;net9.0;net8.0;netstandard2.0;net462 + 10 ../signhost.ruleset - SERIALIZABLE - TYPEINFO TYPEINFO - SERIALIZABLE Signhost.APIClient @@ -53,9 +51,4 @@ - - - - - diff --git a/src/SignhostAPIClient/System/SerializableAttribute.cs b/src/SignhostAPIClient/System/SerializableAttribute.cs deleted file mode 100644 index 568c0ba..0000000 --- a/src/SignhostAPIClient/System/SerializableAttribute.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace System -{ -#if !SERIALIZABLE - internal sealed class SerializableAttribute - : Attribute - { - } -#endif -} From 6e17fa3b89fbbdda5389fecdfe5b9099be91aa36 Mon Sep 17 00:00:00 2001 From: Anthony Timmers Date: Fri, 21 Nov 2025 18:53:41 +0100 Subject: [PATCH 06/28] Refactor Signhost API Client to use System.Text.Json - Replaced Newtonsoft.Json with System.Text.Json for JSON serialization and deserialization across the API client. - Updated interfaces and classes to reflect consistent naming conventions (e.g., ISignHostApiClient to ISignhostApiClient). - Removed obsolete JsonConverter classes that were specific to Newtonsoft.Json. - Introduced centralized JSON serializer options in SignhostJsonSerializerOptions for consistent serialization behavior. - Added new JSON test resources for various transaction scenarios to ensure compatibility with the updated serialization logic. --- README.md | 4 +- .../SignhostAPIClient.IntegrationTests.csproj | 1 - .../TransactionTests.cs | 8 +- .../APIResponses.Designer.cs | 186 -------- src/SignhostAPIClient.Tests/APIResponses.resx | 443 ------------------ .../AddOrReplaceFileMetaToTransaction.json | 1 + .../JSON/AddTransaction.json | 80 ++++ .../JSON/DeleteTransaction.json | 80 ++++ .../JSON/GetTransaction.json | 80 ++++ .../JSON/JsonResources.cs | 37 ++ .../JSON/MinimalTransactionResponse.json | 4 + .../JSON/MockPostbackInvalid.json | 76 +++ .../JSON/MockPostbackValid.json | 112 +++++ .../JSON/TransactionSingleSignerJson.json | 46 ++ .../LevelEnumConverterTests.cs | 10 +- src/SignhostAPIClient.Tests/PostbackTests.cs | 12 +- .../RequestBodies.Designer.cs | 114 ----- .../RequestBodies.resx | 355 -------------- .../SignhostAPIClient.Tests.csproj | 21 +- .../SignhostApiClientTests.cs | 166 +++---- .../SignhostApiReceiverTests.cs | 9 +- src/SignhostAPIClient.Tests/app.config | 11 - .../Rest/DataObjects/Activity.cs | 4 +- .../Rest/DataObjects/ConsentVerification.cs | 4 - .../Rest/DataObjects/CscVerification.cs | 5 - .../Rest/DataObjects/DigidVerification.cs | 2 - .../DataObjects/EherkenningVerification.cs | 5 - .../DataObjects/EidasLoginVerification.cs | 8 +- .../Rest/DataObjects/FileMeta.cs | 4 +- .../Rest/DataObjects/IPAddressVerification.cs | 15 - .../Rest/DataObjects/IVerification.cs | 20 +- .../Rest/DataObjects/IdealVerification.cs | 2 - .../Rest/DataObjects/IdinVerification.cs | 2 - .../ItsmeIdentificationVerification.cs | 5 - .../Rest/DataObjects/ItsmeSignVerification.cs | 14 - .../Rest/DataObjects/KennisnetVerification.cs | 11 - .../Rest/DataObjects/Level.cs | 4 - .../Rest/DataObjects/OidcVerification.cs | 5 - .../Rest/DataObjects/OnfidoVerification.cs | 5 - .../DataObjects/PhoneNumberVerification.cs | 2 - .../Rest/DataObjects/PostbackTransaction.cs | 18 +- .../Rest/DataObjects/Receiver.cs | 4 +- .../Rest/DataObjects/ScribbleVerification.cs | 2 - .../Rest/DataObjects/Signer.cs | 2 +- .../SigningCertificateVerification.cs | 13 - .../Rest/DataObjects/SurfnetVerification.cs | 2 - .../Rest/DataObjects/Transaction.cs | 34 +- .../Rest/DataObjects/UnknownVerification.cs | 8 - ...pResponseMessageErrorHandlingExtensions.cs | 23 +- .../Rest/HttpContentJsonExtensions.cs | 4 +- .../Rest/ISignHostApiClient.cs | 2 +- .../Rest/ISignhostApiClientSettings.cs | 2 +- src/SignhostAPIClient/Rest/JsonContent.cs | 4 +- .../Rest/JsonConverters/JsonBaseConverter.cs | 33 -- .../JsonVerificationConverter.cs | 93 ---- .../Rest/JsonConverters/LevelEnumConverter.cs | 81 ++-- .../Rest/SignHostApiClient.cs | 38 +- .../Rest/SignHostApiClientSettings.cs | 8 +- .../Rest/SignhostApiReceiver.cs | 12 +- .../Rest/SignhostJsonSerializerOptions.cs | 23 + .../SignhostAPIClient.csproj | 2 +- 61 files changed, 764 insertions(+), 1617 deletions(-) delete mode 100644 src/SignhostAPIClient.Tests/APIResponses.Designer.cs delete mode 100644 src/SignhostAPIClient.Tests/APIResponses.resx create mode 100644 src/SignhostAPIClient.Tests/JSON/AddOrReplaceFileMetaToTransaction.json create mode 100644 src/SignhostAPIClient.Tests/JSON/AddTransaction.json create mode 100644 src/SignhostAPIClient.Tests/JSON/DeleteTransaction.json create mode 100644 src/SignhostAPIClient.Tests/JSON/GetTransaction.json create mode 100644 src/SignhostAPIClient.Tests/JSON/JsonResources.cs create mode 100644 src/SignhostAPIClient.Tests/JSON/MinimalTransactionResponse.json create mode 100644 src/SignhostAPIClient.Tests/JSON/MockPostbackInvalid.json create mode 100644 src/SignhostAPIClient.Tests/JSON/MockPostbackValid.json create mode 100644 src/SignhostAPIClient.Tests/JSON/TransactionSingleSignerJson.json delete mode 100644 src/SignhostAPIClient.Tests/RequestBodies.Designer.cs delete mode 100644 src/SignhostAPIClient.Tests/RequestBodies.resx delete mode 100644 src/SignhostAPIClient.Tests/app.config delete mode 100644 src/SignhostAPIClient/Rest/DataObjects/ItsmeSignVerification.cs delete mode 100644 src/SignhostAPIClient/Rest/DataObjects/KennisnetVerification.cs delete mode 100644 src/SignhostAPIClient/Rest/DataObjects/SigningCertificateVerification.cs delete mode 100644 src/SignhostAPIClient/Rest/DataObjects/UnknownVerification.cs delete mode 100644 src/SignhostAPIClient/Rest/JsonConverters/JsonBaseConverter.cs delete mode 100644 src/SignhostAPIClient/Rest/JsonConverters/JsonVerificationConverter.cs create mode 100644 src/SignhostAPIClient/Rest/SignhostJsonSerializerOptions.cs diff --git a/README.md b/README.md index 6da273b..16fa878 100644 --- a/README.md +++ b/README.md @@ -15,11 +15,11 @@ For full API documentation, please visit [evidos.github.io](https://evidos.githu ### Example code The following code is an example of how to create and start a sign transaction with two documents. ```c# -var settings = new SignHostApiClientSettings( +var settings = new SignhostApiClientSettings( "AppName appkey", "apikey or usertoken")); -var client = new SignHostApiClient(settings); +var client = new SignhostApiClient(settings); var transaction = await client.CreateTransactionAsync(new Transaction { Signers = new List { diff --git a/src/SignhostAPIClient.IntegrationTests/SignhostAPIClient.IntegrationTests.csproj b/src/SignhostAPIClient.IntegrationTests/SignhostAPIClient.IntegrationTests.csproj index 3ea6d79..1d17d43 100644 --- a/src/SignhostAPIClient.IntegrationTests/SignhostAPIClient.IntegrationTests.csproj +++ b/src/SignhostAPIClient.IntegrationTests/SignhostAPIClient.IntegrationTests.csproj @@ -12,7 +12,6 @@ - diff --git a/src/SignhostAPIClient.IntegrationTests/TransactionTests.cs b/src/SignhostAPIClient.IntegrationTests/TransactionTests.cs index e395c87..0e3f630 100644 --- a/src/SignhostAPIClient.IntegrationTests/TransactionTests.cs +++ b/src/SignhostAPIClient.IntegrationTests/TransactionTests.cs @@ -10,7 +10,7 @@ namespace Signhost.APIClient.Rest.IntegrationTests; public class TransactionTests : IDisposable { - private readonly SignHostApiClient client; + private readonly SignhostApiClient client; private readonly TestConfiguration config; public TransactionTests() @@ -22,11 +22,11 @@ public TransactionTests() "Integration tests are not configured"); } - var settings = new SignHostApiClientSettings(config.AppKey, config.UserToken) { + var settings = new SignhostApiClientSettings(config.AppKey, config.UserToken) { Endpoint = config.ApiBaseUrl }; - client = new SignHostApiClient(settings); + client = new SignhostApiClient(settings); } [Fact] @@ -123,7 +123,7 @@ public async Task Given_complex_transaction_When_created_and_started_Then_all_pr createdTransaction.Language.Should().Be("en-US"); createdTransaction.CreatedDateTime.Should().HaveValue(); createdTransaction.CreatedDateTime.Should().BeCloseTo(DateTimeOffset.UtcNow, TimeSpan.FromMinutes(1)); - createdTransaction.CancelledDateTime.Should().BeNull(); + createdTransaction.CanceledDateTime.Should().BeNull(); createdTransaction.CancellationReason.Should().BeNull(); // Assert - Context diff --git a/src/SignhostAPIClient.Tests/APIResponses.Designer.cs b/src/SignhostAPIClient.Tests/APIResponses.Designer.cs deleted file mode 100644 index 9a09269..0000000 --- a/src/SignhostAPIClient.Tests/APIResponses.Designer.cs +++ /dev/null @@ -1,186 +0,0 @@ -//------------------------------------------------------------------------------ -// -// This code was generated by a tool. -// Runtime Version:4.0.30319.42000 -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -//------------------------------------------------------------------------------ - -namespace Signhost.APIClient.Rest.Tests { - using System; - - - /// - /// A strongly-typed resource class, for looking up localized strings, etc. - /// - // This class was auto-generated by the StronglyTypedResourceBuilder - // class via a tool like ResGen or Visual Studio. - // To add or remove a member, edit your .ResX file then rerun ResGen - // with the /str option, or rebuild your VS project. - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] - [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] - [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] - internal class APIResponses { - - private static global::System.Resources.ResourceManager resourceMan; - - private static global::System.Globalization.CultureInfo resourceCulture; - - [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] - internal APIResponses() { - } - - /// - /// Returns the cached ResourceManager instance used by this class. - /// - [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Resources.ResourceManager ResourceManager { - get { - if (object.ReferenceEquals(resourceMan, null)) { - global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Signhost.APIClient.Rest.Tests.APIResponses", typeof(APIResponses).Assembly); - resourceMan = temp; - } - return resourceMan; - } - } - - /// - /// Overrides the current thread's CurrentUICulture property for all - /// resource lookups using this strongly typed resource class. - /// - [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Globalization.CultureInfo Culture { - get { - return resourceCulture; - } - set { - resourceCulture = value; - } - } - - /// - /// Looks up a localized string similar to { - /// "Id": "c487be92-0255-40c7-bd7d-20805a65e7d9", - /// "Status": 20, - /// "File": { - /// "Id": "d4bba0df-f9e5-44c8-89db-f2bb46632d7b", - /// "Name": "contract.pdf" - /// }, - /// "Seal": true, - /// "Signers": [ - /// { - /// "Id": "a2932c07-ca93-4011-96f5-a77d2cd1ec32", - /// "Expires": null, - /// "Email": "user@example.com", - /// "Mobile": "+31612345678", - /// "Iban": null, - /// "BSN": null, - /// "RequireScribbleName": false, - /// "RequireScribble": true, - /// "RequireEmailVerification": true, - /// "R [rest of string was truncated]";. - /// - internal static string AddTransaction { - get { - return ResourceManager.GetString("AddTransaction", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to { - /// "Id": "496bec4d-4ac7-428f-894a-b3e9bade725d", - /// "Status": 20, - /// "File": { - /// "Id": "3149bf06-d01e-4f0d-9aa1-77e100e19772", - /// "Name": "contract.pdf" - /// }, - /// "Seal": true, - /// "Signers": [ - /// { - /// "Id": "4813e178-68a4-4105-b007-5ce9a3630867", - /// "Expires": null, - /// "Email": "user@example.com", - /// "Mobile": "+31612345678", - /// "Iban": null, - /// "BSN": null, - /// "RequireScribbleName": false, - /// "RequireScribble": true, - /// "RequireEmailVerification": true, - /// "R [rest of string was truncated]";. - /// - internal static string DeleteTransaction { - get { - return ResourceManager.GetString("DeleteTransaction", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to { - /// "Id": "c487be92-0255-40c7-bd7d-20805a65e7d9", - /// "Status": 20, - /// "File": { - /// "Id": "d4bba0df-f9e5-44c8-89db-f2bb46632d7b", - /// "Name": "contract.pdf" - /// }, - /// "Seal": true, - /// "Signers": [ - /// { - /// "Id": "a2932c07-ca93-4011-96f5-a77d2cd1ec32", - /// "Expires": null, - /// "Email": "user@example.com", - /// "Mobile": "+31612345678", - /// "Iban": null, - /// "BSN": null, - /// "RequireScribbleName": false, - /// "RequireScribble": true, - /// "RequireEmailVerification": true, - /// "R [rest of string was truncated]";. - /// - internal static string GetTransaction { - get { - return ResourceManager.GetString("GetTransaction", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to { - /// "Id": "c487be92-0255-40c7-bd7d-20805a65e7d9", - /// "Status": 20, - /// "Seal": true, - /// "Signers": [ - /// { - /// "Id": "a2932c07-ca93-4011-96f5-a77d2cd1ec32", - /// "Expires": null, - /// "Email": "user@example.com", - /// "Mobile": "+31612345678", - /// "Iban": null, - /// "BSN": null, - /// "SendSignRequest": true, - /// "SendSignConfirmation": null, - /// "SignRequestMessage": "Hello, could you please sign this document? Best regards, John Doe", - /// "DaysToRemind": 15, - /// "Language": "en-US", - /// "Reference": "Client #123", - /// "ReturnUrl": "h [rest of string was truncated]";. - /// - internal static string GetTransactionCustomVerificationType { - get { - return ResourceManager.GetString("GetTransactionCustomVerificationType", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to { - /// "Id": "50262c3f-9744-45bf-a4c6-8a3whatever", - /// "Status": 5 - ///}. - /// - internal static string MinimalTransactionResponse { - get { - return ResourceManager.GetString("MinimalTransactionResponse", resourceCulture); - } - } - } -} diff --git a/src/SignhostAPIClient.Tests/APIResponses.resx b/src/SignhostAPIClient.Tests/APIResponses.resx deleted file mode 100644 index 197489d..0000000 --- a/src/SignhostAPIClient.Tests/APIResponses.resx +++ /dev/null @@ -1,443 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - text/microsoft-resx - - - 2.0 - - - System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - { - "Id": "c487be92-0255-40c7-bd7d-20805a65e7d9", - "Status": 20, - "File": { - "Id": "d4bba0df-f9e5-44c8-89db-f2bb46632d7b", - "Name": "contract.pdf" - }, - "Seal": true, - "Signers": [ - { - "Id": "a2932c07-ca93-4011-96f5-a77d2cd1ec32", - "Expires": null, - "Email": "user@example.com", - "Mobile": "+31612345678", - "Iban": null, - "BSN": null, - "RequireScribbleName": false, - "RequireScribble": true, - "RequireEmailVerification": true, - "RequireSmsVerification": true, - "RequireIdealVerification": false, - "RequireDigidVerification": false, - "RequireKennisnetVerification": false, - "RequireSurfnetVerification": false, - "SendSignRequest": true, - "SendSignConfirmation": null, - "SignRequestMessage": "Hello, could you please sign this document? Best regards, John Doe", - "DaysToRemind": 15, - "Language": "en-US", - "ScribbleName": "John Doe", - "ScribbleNameFixed": false, - "Reference": "Client #123", - "ReturnUrl": "http://signhost.com", - "Activities": [ - { - "Id": "0b47eb5c-e800-4fe3-9795-09d380dff1f9", - "Code": 103, - "Activity": "Opened", - "CreatedDateTime": "2016-03-17T21:08:55.1349315+01:00" - }, - { - "Id": "761f8678-116c-4e86-a47a-c8312681d285", - "Code": 203, - "Activity": "Signed", - "CreatedDateTime": "2016-03-17T21:13:55.1349315+01:00" - } - ], - "RejectReason": null, - "SignUrl": "http://ui.signhost.com/sign/93dc596f-ab81-4d31-87aa-50352c4c237e", - "SignedDateTime": null, - "RejectDateTime": null, - "CreatedDateTime": "2016-03-17T21:08:55.1349315+01:00", - "ModifiedDateTime": "2016-03-17T21:08:55.1349315+01:00", - "Context": null - } - ], - "Receivers": [ - { - "Id": "2fe3dddf-4b50-49d1-a3d2-45b7d175fb97", - "Name": "John Doe", - "Email": "user@example.com", - "Language": "en-US", - "Message": "Hello, please find enclosed the digital signed document. Best regards, John Doe", - "Reference": null, - "Activities": null, - "CreatedDateTime": "2016-03-17T21:08:55.1349315+01:00", - "ModifiedDateTime": "2016-03-17T21:08:55.1349315+01:00", - "Context": null - } - ], - "Reference": "Contract #123", - "PostbackUrl": "http://example.com/postback.php", - "SignRequestMode": 2, - "DaysToExpire": 30, - "SendEmailNotifications": true, - "CreatedDateTime": "2016-03-17T21:08:55.1349315+01:00", - "ModifiedDateTime": "2016-03-17T21:08:55.1349315+01:00", - "CanceledDateTime": null, - "Context": null -} - - - { - "Id": "496bec4d-4ac7-428f-894a-b3e9bade725d", - "Status": 20, - "File": { - "Id": "3149bf06-d01e-4f0d-9aa1-77e100e19772", - "Name": "contract.pdf" - }, - "Seal": true, - "Signers": [ - { - "Id": "4813e178-68a4-4105-b007-5ce9a3630867", - "Expires": null, - "Email": "user@example.com", - "Mobile": "+31612345678", - "Iban": null, - "BSN": null, - "RequireScribbleName": false, - "RequireScribble": true, - "RequireEmailVerification": true, - "RequireSmsVerification": true, - "RequireIdealVerification": false, - "RequireDigidVerification": false, - "RequireKennisnetVerification": false, - "RequireSurfnetVerification": false, - "SendSignRequest": true, - "SendSignConfirmation": null, - "SignRequestMessage": "Hello, could you please sign this document? Best regards, John Doe", - "DaysToRemind": 15, - "Language": "en-US", - "ScribbleName": "John Doe", - "ScribbleNameFixed": false, - "Reference": "Client #123", - "ReturnUrl": "http://signhost.com", - "Activities": [ - { - "Id": "866183ae-0a3c-4441-a589-6fe3e1a0f1a1", - "Code": 103, - "Activity": "Opened", - "CreatedDateTime": "2016-03-31T21:11:42.0267461+02:00" - }, - { - "Id": "9b7c4de9-6b8d-4d22-9b88-b7d2d0f084b9", - "Code": 203, - "Activity": "Signed", - "CreatedDateTime": "2016-03-31T21:16:42.0267461+02:00" - } - ], - "RejectReason": null, - "SignUrl": "http://ui.signhost.com/sign/2eeaa5b9-9d4d-4418-b79f-4a33810e7147", - "SignedDateTime": null, - "RejectDateTime": null, - "CreatedDateTime": "2016-03-31T21:11:42.0267461+02:00", - "ModifiedDateTime": "2016-03-31T21:11:42.0267461+02:00", - "Context": null - } - ], - "Receivers": [ - { - "Id": "af07aaec-b612-4f7c-bb1b-c32603c9c6a2", - "Name": "John Doe", - "Email": "user@example.com", - "Language": "en-US", - "Message": "Hello, please find enclosed the digital signed document. Best regards, John Doe", - "Reference": null, - "Activities": null, - "CreatedDateTime": "2016-03-31T21:11:42.0267461+02:00", - "ModifiedDateTime": "2016-03-31T21:11:42.0267461+02:00", - "Context": null - } - ], - "Reference": "Contract #123", - "PostbackUrl": "http://example.com/postback.php", - "SignRequestMode": 2, - "DaysToExpire": 30, - "SendEmailNotifications": true, - "CreatedDateTime": "2016-03-31T21:11:42.0267461+02:00", - "ModifiedDateTime": "2016-03-31T21:11:42.0267461+02:00", - "CanceledDateTime": null, - "Context": null -} - - - { - "Id": "c487be92-0255-40c7-bd7d-20805a65e7d9", - "Status": 20, - "File": { - "Id": "d4bba0df-f9e5-44c8-89db-f2bb46632d7b", - "Name": "contract.pdf" - }, - "Seal": true, - "Signers": [ - { - "Id": "a2932c07-ca93-4011-96f5-a77d2cd1ec32", - "Expires": null, - "Email": "user@example.com", - "Mobile": "+31612345678", - "Iban": null, - "BSN": null, - "RequireScribbleName": false, - "RequireScribble": true, - "RequireEmailVerification": true, - "RequireSmsVerification": true, - "RequireIdealVerification": false, - "RequireDigidVerification": false, - "RequireKennisnetVerification": false, - "RequireSurfnetVerification": false, - "SendSignRequest": true, - "SendSignConfirmation": null, - "SignRequestMessage": "Hello, could you please sign this document? Best regards, John Doe", - "DaysToRemind": 15, - "Language": "en-US", - "ScribbleName": "John Doe", - "ScribbleNameFixed": false, - "Reference": "Client #123", - "ReturnUrl": "http://signhost.com", - "Activities": [ - { - "Id": "0b47eb5c-e800-4fe3-9795-09d380dff1f9", - "Code": 103, - "Activity": "Opened", - "CreatedDateTime": "2016-03-17T21:08:55.1349315+01:00" - }, - { - "Id": "761f8678-116c-4e86-a47a-c8312681d285", - "Code": 203, - "Activity": "Signed", - "CreatedDateTime": "2016-03-17T21:13:55.1349315+01:00" - } - ], - "RejectReason": null, - "SignUrl": "http://ui.signhost.com/sign/93dc596f-ab81-4d31-87aa-50352c4c237e", - "SignedDateTime": null, - "RejectDateTime": null, - "CreatedDateTime": "2016-03-17T21:08:55.1349315+01:00", - "ModifiedDateTime": "2016-03-17T21:08:55.1349315+01:00", - "Context": null - } - ], - "Receivers": [ - { - "Id": "2fe3dddf-4b50-49d1-a3d2-45b7d175fb97", - "Name": "John Doe", - "Email": "user@example.com", - "Language": "en-US", - "Message": "Hello, please find enclosed the digital signed document. Best regards, John Doe", - "Reference": null, - "Activities": null, - "CreatedDateTime": "2016-03-17T21:08:55.1349315+01:00", - "ModifiedDateTime": "2016-03-17T21:08:55.1349315+01:00", - "Context": null - } - ], - "Reference": "Contract #123", - "PostbackUrl": "http://example.com/postback.php", - "SignRequestMode": 2, - "DaysToExpire": 30, - "SendEmailNotifications": true, - "CreatedDateTime": "2016-03-17T21:08:55.1349315+01:00", - "ModifiedDateTime": "2016-03-17T21:08:55.1349315+01:00", - "CanceledDateTime": null, - "Context": null -} - - - { - "Id": "c487be92-0255-40c7-bd7d-20805a65e7d9", - "Status": 20, - "Seal": true, - "Signers": [ - { - "Id": "a2932c07-ca93-4011-96f5-a77d2cd1ec32", - "Expires": null, - "Email": "user@example.com", - "Mobile": "+31612345678", - "Iban": null, - "BSN": null, - "SendSignRequest": true, - "SendSignConfirmation": null, - "SignRequestMessage": "Hello, could you please sign this document? Best regards, John Doe", - "DaysToRemind": 15, - "Language": "en-US", - "Reference": "Client #123", - "ReturnUrl": "http://signhost.com", - "Verifications": [ - { - "Type": "CustomVerificationType" - }, - { - "Type": "IPAddress", - "IPAddress": "127.0.0.33" - }, - { - "Type": "PhoneNumber", - "Number": "123" - } - ], - "Activities": [ - { - "Id": "0b47eb5c-e800-4fe3-9795-09d380dff1f9", - "Code": 103, - "Activity": "Opened", - "CreatedDateTime": "2016-03-17T21:08:55.1349315+01:00" - }, - { - "Id": "761f8678-116c-4e86-a47a-c8312681d285", - "Code": 203, - "Activity": "Signed", - "CreatedDateTime": "2016-03-17T21:13:55.1349315+01:00" - } - ], - "SignUrl": "http://ui.signhost.com/sign/93dc596f-ab81-4d31-87aa-50352c4c237e", - "CreatedDateTime": "2016-03-17T21:08:55.1349315+01:00", - "ModifiedDateTime": "2016-03-17T21:08:55.1349315+01:00" - } - ], - "Receivers": [ - { - "Id": "2fe3dddf-4b50-49d1-a3d2-45b7d175fb97", - "Name": "John Doe", - "Email": "user@example.com", - "Language": "en-US", - "Message": "Hello, please find enclosed the digital signed document. Best regards, John Doe", - "CreatedDateTime": "2016-03-17T21:08:55.1349315+01:00", - "ModifiedDateTime": "2016-03-17T21:08:55.1349315+01:00", - } - ], - "Reference": "Contract #123", - "SignRequestMode": 2, - "DaysToExpire": 30, - "SendEmailNotifications": true, - "CreatedDateTime": "2016-03-17T21:08:55.1349315+01:00", - "ModifiedDateTime": "2016-03-17T21:08:55.1349315+01:00" -} - - - { - "Id": "50262c3f-9744-45bf-a4c6-8a3whatever", - "Status": 5 -} - - \ No newline at end of file diff --git a/src/SignhostAPIClient.Tests/JSON/AddOrReplaceFileMetaToTransaction.json b/src/SignhostAPIClient.Tests/JSON/AddOrReplaceFileMetaToTransaction.json new file mode 100644 index 0000000..1fc0000 --- /dev/null +++ b/src/SignhostAPIClient.Tests/JSON/AddOrReplaceFileMetaToTransaction.json @@ -0,0 +1 @@ +{"Signers":{"someSignerId":{"FormSets":["SampleFormSet"]}},"FormSets":{"SampleFormSet":{"SampleCheck":{"Type":"Check","Value":"I agree","Location":{"Search":"test"}}}}} \ No newline at end of file diff --git a/src/SignhostAPIClient.Tests/JSON/AddTransaction.json b/src/SignhostAPIClient.Tests/JSON/AddTransaction.json new file mode 100644 index 0000000..ea0bbe9 --- /dev/null +++ b/src/SignhostAPIClient.Tests/JSON/AddTransaction.json @@ -0,0 +1,80 @@ +{ + "Id":"c487be92-0255-40c7-bd7d-20805a65e7d9", + "Status":20, + "File":{ + "Id":"d4bba0df-f9e5-44c8-89db-f2bb46632d7b", + "Name":"contract.pdf" + }, + "Seal":true, + "Signers":[ + { + "Id":"a2932c07-ca93-4011-96f5-a77d2cd1ec32", + "Expires":null, + "Email":"user@example.com", + "Mobile":"+31612345678", + "Iban":null, + "BSN":null, + "RequireScribbleName":false, + "RequireScribble":true, + "RequireEmailVerification":true, + "RequireSmsVerification":true, + "RequireIdealVerification":false, + "RequireDigidVerification":false, + "RequireKennisnetVerification":false, + "RequireSurfnetVerification":false, + "SendSignRequest":true, + "SendSignConfirmation":null, + "SignRequestMessage":"Hello, could you please sign this document? Best regards, John Doe", + "DaysToRemind":15, + "Language":"en-US", + "ScribbleName":"John Doe", + "ScribbleNameFixed":false, + "Reference":"Client #123", + "ReturnUrl":"http://signhost.com", + "Activities":[ + { + "Id":"0b47eb5c-e800-4fe3-9795-09d380dff1f9", + "Code":103, + "Activity":"Opened", + "CreatedDateTime":"2016-03-17T21:08:55.1349315+01:00" + }, + { + "Id":"761f8678-116c-4e86-a47a-c8312681d285", + "Code":203, + "Activity":"Signed", + "CreatedDateTime":"2016-03-17T21:13:55.1349315+01:00" + } + ], + "RejectReason":null, + "SignUrl":"http://ui.signhost.com/sign/93dc596f-ab81-4d31-87aa-50352c4c237e", + "SignedDateTime":null, + "RejectDateTime":null, + "CreatedDateTime":"2016-03-17T21:08:55.1349315+01:00", + "ModifiedDateTime":"2016-03-17T21:08:55.1349315+01:00", + "Context":null + } + ], + "Receivers":[ + { + "Id":"2fe3dddf-4b50-49d1-a3d2-45b7d175fb97", + "Name":"John Doe", + "Email":"user@example.com", + "Language":"en-US", + "Message":"Hello, please find enclosed the digital signed document. Best regards, John Doe", + "Reference":null, + "Activities":null, + "CreatedDateTime":"2016-03-17T21:08:55.1349315+01:00", + "ModifiedDateTime":"2016-03-17T21:08:55.1349315+01:00", + "Context":null + } + ], + "Reference":"Contract #123", + "PostbackUrl":"http://example.com/postback.php", + "SignRequestMode":2, + "DaysToExpire":30, + "SendEmailNotifications":true, + "CreatedDateTime":"2016-03-17T21:08:55.1349315+01:00", + "ModifiedDateTime":"2016-03-17T21:08:55.1349315+01:00", + "CanceledDateTime":null, + "Context":null +} diff --git a/src/SignhostAPIClient.Tests/JSON/DeleteTransaction.json b/src/SignhostAPIClient.Tests/JSON/DeleteTransaction.json new file mode 100644 index 0000000..d87b427 --- /dev/null +++ b/src/SignhostAPIClient.Tests/JSON/DeleteTransaction.json @@ -0,0 +1,80 @@ +{ + "Id":"496bec4d-4ac7-428f-894a-b3e9bade725d", + "Status":20, + "File":{ + "Id":"3149bf06-d01e-4f0d-9aa1-77e100e19772", + "Name":"contract.pdf" + }, + "Seal":true, + "Signers":[ + { + "Id":"4813e178-68a4-4105-b007-5ce9a3630867", + "Expires":null, + "Email":"user@example.com", + "Mobile":"+31612345678", + "Iban":null, + "BSN":null, + "RequireScribbleName":false, + "RequireScribble":true, + "RequireEmailVerification":true, + "RequireSmsVerification":true, + "RequireIdealVerification":false, + "RequireDigidVerification":false, + "RequireKennisnetVerification":false, + "RequireSurfnetVerification":false, + "SendSignRequest":true, + "SendSignConfirmation":null, + "SignRequestMessage":"Hello, could you please sign this document? Best regards, John Doe", + "DaysToRemind":15, + "Language":"en-US", + "ScribbleName":"John Doe", + "ScribbleNameFixed":false, + "Reference":"Client #123", + "ReturnUrl":"http://signhost.com", + "Activities":[ + { + "Id":"866183ae-0a3c-4441-a589-6fe3e1a0f1a1", + "Code":103, + "Activity":"Opened", + "CreatedDateTime":"2016-03-31T21:11:42.0267461+02:00" + }, + { + "Id":"9b7c4de9-6b8d-4d22-9b88-b7d2d0f084b9", + "Code":203, + "Activity":"Signed", + "CreatedDateTime":"2016-03-31T21:16:42.0267461+02:00" + } + ], + "RejectReason":null, + "SignUrl":"http://ui.signhost.com/sign/2eeaa5b9-9d4d-4418-b79f-4a33810e7147", + "SignedDateTime":null, + "RejectDateTime":null, + "CreatedDateTime":"2016-03-31T21:11:42.0267461+02:00", + "ModifiedDateTime":"2016-03-31T21:11:42.0267461+02:00", + "Context":null + } + ], + "Receivers":[ + { + "Id":"af07aaec-b612-4f7c-bb1b-c32603c9c6a2", + "Name":"John Doe", + "Email":"user@example.com", + "Language":"en-US", + "Message":"Hello, please find enclosed the digital signed document. Best regards, John Doe", + "Reference":null, + "Activities":null, + "CreatedDateTime":"2016-03-31T21:11:42.0267461+02:00", + "ModifiedDateTime":"2016-03-31T21:11:42.0267461+02:00", + "Context":null + } + ], + "Reference":"Contract #123", + "PostbackUrl":"http://example.com/postback.php", + "SignRequestMode":2, + "DaysToExpire":30, + "SendEmailNotifications":true, + "CreatedDateTime":"2016-03-31T21:11:42.0267461+02:00", + "ModifiedDateTime":"2016-03-31T21:11:42.0267461+02:00", + "CanceledDateTime":null, + "Context":null +} diff --git a/src/SignhostAPIClient.Tests/JSON/GetTransaction.json b/src/SignhostAPIClient.Tests/JSON/GetTransaction.json new file mode 100644 index 0000000..ea0bbe9 --- /dev/null +++ b/src/SignhostAPIClient.Tests/JSON/GetTransaction.json @@ -0,0 +1,80 @@ +{ + "Id":"c487be92-0255-40c7-bd7d-20805a65e7d9", + "Status":20, + "File":{ + "Id":"d4bba0df-f9e5-44c8-89db-f2bb46632d7b", + "Name":"contract.pdf" + }, + "Seal":true, + "Signers":[ + { + "Id":"a2932c07-ca93-4011-96f5-a77d2cd1ec32", + "Expires":null, + "Email":"user@example.com", + "Mobile":"+31612345678", + "Iban":null, + "BSN":null, + "RequireScribbleName":false, + "RequireScribble":true, + "RequireEmailVerification":true, + "RequireSmsVerification":true, + "RequireIdealVerification":false, + "RequireDigidVerification":false, + "RequireKennisnetVerification":false, + "RequireSurfnetVerification":false, + "SendSignRequest":true, + "SendSignConfirmation":null, + "SignRequestMessage":"Hello, could you please sign this document? Best regards, John Doe", + "DaysToRemind":15, + "Language":"en-US", + "ScribbleName":"John Doe", + "ScribbleNameFixed":false, + "Reference":"Client #123", + "ReturnUrl":"http://signhost.com", + "Activities":[ + { + "Id":"0b47eb5c-e800-4fe3-9795-09d380dff1f9", + "Code":103, + "Activity":"Opened", + "CreatedDateTime":"2016-03-17T21:08:55.1349315+01:00" + }, + { + "Id":"761f8678-116c-4e86-a47a-c8312681d285", + "Code":203, + "Activity":"Signed", + "CreatedDateTime":"2016-03-17T21:13:55.1349315+01:00" + } + ], + "RejectReason":null, + "SignUrl":"http://ui.signhost.com/sign/93dc596f-ab81-4d31-87aa-50352c4c237e", + "SignedDateTime":null, + "RejectDateTime":null, + "CreatedDateTime":"2016-03-17T21:08:55.1349315+01:00", + "ModifiedDateTime":"2016-03-17T21:08:55.1349315+01:00", + "Context":null + } + ], + "Receivers":[ + { + "Id":"2fe3dddf-4b50-49d1-a3d2-45b7d175fb97", + "Name":"John Doe", + "Email":"user@example.com", + "Language":"en-US", + "Message":"Hello, please find enclosed the digital signed document. Best regards, John Doe", + "Reference":null, + "Activities":null, + "CreatedDateTime":"2016-03-17T21:08:55.1349315+01:00", + "ModifiedDateTime":"2016-03-17T21:08:55.1349315+01:00", + "Context":null + } + ], + "Reference":"Contract #123", + "PostbackUrl":"http://example.com/postback.php", + "SignRequestMode":2, + "DaysToExpire":30, + "SendEmailNotifications":true, + "CreatedDateTime":"2016-03-17T21:08:55.1349315+01:00", + "ModifiedDateTime":"2016-03-17T21:08:55.1349315+01:00", + "CanceledDateTime":null, + "Context":null +} diff --git a/src/SignhostAPIClient.Tests/JSON/JsonResources.cs b/src/SignhostAPIClient.Tests/JSON/JsonResources.cs new file mode 100644 index 0000000..9bbfbe6 --- /dev/null +++ b/src/SignhostAPIClient.Tests/JSON/JsonResources.cs @@ -0,0 +1,37 @@ +using System.IO; +using System.Reflection; + +namespace SignhostAPIClient.Tests.JSON; + +public static class JsonResources +{ + public static string TransactionSingleSignerJson { get; } = + GetJson("TransactionSingleSignerJson"); + + public static string AddOrReplaceFileMetaToTransaction { get; } = + GetJson("AddOrReplaceFileMetaToTransaction"); + public static string AddTransaction { get; } = + GetJson("AddTransaction"); + public static string DeleteTransaction { get; } = + GetJson("DeleteTransaction"); + public static string GetTransaction { get; } = + GetJson("GetTransaction"); + public static string MinimalTransactionResponse { get; } = + GetJson("MinimalTransactionResponse"); + public static string MockPostbackInvalid { get; } = + GetJson("MockPostbackInvalid"); + public static string MockPostbackValid { get; } = + GetJson("MockPostbackValid"); + + private static string GetJson(string fileName) + { + var assembly = Assembly.GetExecutingAssembly(); + string resourceName = $"Signhost.APIClient.Rest.Tests.JSON.{fileName}.json"; + + using var stream = assembly.GetManifestResourceStream(resourceName) + ?? throw new FileNotFoundException($"File not found: {fileName}"); + + using var reader = new StreamReader(stream); + return reader.ReadToEnd(); + } +} diff --git a/src/SignhostAPIClient.Tests/JSON/MinimalTransactionResponse.json b/src/SignhostAPIClient.Tests/JSON/MinimalTransactionResponse.json new file mode 100644 index 0000000..9901626 --- /dev/null +++ b/src/SignhostAPIClient.Tests/JSON/MinimalTransactionResponse.json @@ -0,0 +1,4 @@ +{ + "Id":"50262c3f-9744-45bf-a4c6-8a3whatever", + "Status":5 +} diff --git a/src/SignhostAPIClient.Tests/JSON/MockPostbackInvalid.json b/src/SignhostAPIClient.Tests/JSON/MockPostbackInvalid.json new file mode 100644 index 0000000..ce1c2c4 --- /dev/null +++ b/src/SignhostAPIClient.Tests/JSON/MockPostbackInvalid.json @@ -0,0 +1,76 @@ +{ + "Id":"b10ae331-af78-4e79-a39e-5b64693b6b68", + "Status":20, + "Seal":true, + "Signers":[ + { + "Id":"fa95495d-6c59-48e0-962a-a4552f8d6b85", + "Expires":null, + "Email":"user@example.com", + "Mobile":"+31612345678", + "Iban":null, + "BSN":null, + "RequireScribbleName":false, + "RequireScribble":true, + "RequireEmailVerification":true, + "RequireSmsVerification":true, + "RequireIdealVerification":false, + "RequireDigidVerification":false, + "RequireKennisnetVerification":false, + "RequireSurfnetVerification":false, + "SendSignRequest":true, + "SendSignConfirmation":null, + "SignRequestMessage":"Hello, could you please sign this document? Best regards, John Doe", + "DaysToRemind":15, + "Language":"en-US", + "ScribbleName":"John Doe", + "ScribbleNameFixed":false, + "Reference":"Client #123", + "ReturnUrl":"https://signhost.com", + "Activities":[ + { + "Id":"bcba44a9-c201-4494-9920-2c1f7baebcf0", + "Code":103, + "Activity":"Opened", + "CreatedDateTime":"2016-06-15T23:33:04.1965465+02:00" + }, + { + "Id":"de94cf6e-e1a3-4c33-93bf-2013b036daaf", + "Code":203, + "Activity":"Signed", + "CreatedDateTime":"2016-06-15T23:38:04.1965465+02:00" + } + ], + "RejectReason":null, + "SignUrl":"https://view.signhost.com/sign/d3c93bd6-f1ce-48e7-8c9c-c2babfdd4034", + "SignedDateTime":null, + "RejectDateTime":null, + "CreatedDateTime":"2016-06-15T23:33:04.1965465+02:00", + "ModifiedDateTime":"2016-06-15T23:33:04.1965465+02:00", + "Context":null + } + ], + "Receivers":[ + { + "Id":"97ed6b54-b6d1-46ed-88c1-79779c3b47b1", + "Name":"John Doe", + "Email":"user@example.com", + "Language":"en-US", + "Message":"Hello, please find enclosed the digital signed document. Best regards, John Doe", + "Reference":null, + "Activities":null, + "CreatedDateTime":"2016-06-15T23:33:04.1965465+02:00", + "ModifiedDateTime":"2016-06-15T23:33:04.1965465+02:00", + "Context":null + } + ], + "Reference":"Contract #123", + "PostbackUrl":"https://example.com/postback.php", + "SignRequestMode":2, + "DaysToExpire":30, + "SendEmailNotifications":true, + "CreatedDateTime":"2016-08-31T21:22:56.2467731+02:00", + "ModifiedDateTime":"2016-08-31T21:22:56.2467731+02:00", + "CanceledDateTime":null, + "Context":null +} diff --git a/src/SignhostAPIClient.Tests/JSON/MockPostbackValid.json b/src/SignhostAPIClient.Tests/JSON/MockPostbackValid.json new file mode 100644 index 0000000..62ab673 --- /dev/null +++ b/src/SignhostAPIClient.Tests/JSON/MockPostbackValid.json @@ -0,0 +1,112 @@ +{ + "Id":"b10ae331-af78-4e79-a39e-5b64693b6b68", + "Status":20, + "Files":{ + "file1":{ + "Links":[ + { + "Rel":"file", + "Type":"application/pdf", + "Link":"https://api.signhost.com/api/transaction/b10ae331-af78-4e79-a39e-5b64693b6b68/file/file1" + } + ], + "DisplayName":"Sample File" + } + }, + "Seal":true, + "Signers":[ + { + "Id":"fa95495d-6c59-48e0-962a-a4552f8d6b85", + "Expires":null, + "Email":"user@example.com", + "Verifications":[ + { + "Type":"PhoneNumber", + "Number":"+31612345678" + }, + { + "Type":"Scribble", + "RequireHandsignature":false, + "ScribbleNameFixed":false, + "ScribbleName":"John Doe" + }, + { + "Type":"IPAddress", + "IPAddress":"1.2.3.4" + } + ], + "Mobile":"+31612345678", + "Iban":null, + "BSN":null, + "RequireScribbleName":false, + "RequireScribble":true, + "RequireEmailVerification":true, + "RequireSmsVerification":true, + "RequireIdealVerification":false, + "RequireDigidVerification":false, + "RequireKennisnetVerification":false, + "RequireSurfnetVerification":false, + "SendSignRequest":true, + "SendSignConfirmation":null, + "SignRequestMessage":"Hello, could you please sign this document? Best regards, John Doe", + "DaysToRemind":15, + "Language":"en-US", + "ScribbleName":"John Doe", + "ScribbleNameFixed":false, + "Reference":"Client #123", + "ReturnUrl":"https://signhost.com", + "Activities":[ + { + "Id":"bcba44a9-c201-4494-9920-2c1f7baebcf0", + "Code":103, + "Activity":"Opened", + "CreatedDateTime":"2016-06-15T23:33:04.1965465+02:00" + }, + { + "Id":"7aacf96a-5c2f-475d-98a5-726e41bfc5d3", + "Code":105, + "Activity":"DocumentOpened", + "Info":"file1", + "CreatedDateTime":"2020-01-30T16:31:05.6679583+01:00" + }, + { + "Id":"de94cf6e-e1a3-4c33-93bf-2013b036daaf", + "Code":203, + "Activity":"Signed", + "CreatedDateTime":"2016-06-15T23:38:04.1965465+02:00" + } + ], + "RejectReason":null, + "SignUrl":"https://view.signhost.com/sign/d3c93bd6-f1ce-48e7-8c9c-c2babfdd4034", + "SignedDateTime":null, + "RejectDateTime":null, + "CreatedDateTime":"2016-06-15T23:33:04.1965465+02:00", + "ModifiedDateTime":"2016-06-15T23:33:04.1965465+02:00", + "Context":null + } + ], + "Receivers":[ + { + "Id":"97ed6b54-b6d1-46ed-88c1-79779c3b47b1", + "Name":"John Doe", + "Email":"user@example.com", + "Language":"en-US", + "Message":"Hello, please find enclosed the digital signed document. Best regards, John Doe", + "Reference":null, + "Activities":null, + "CreatedDateTime":"2016-06-15T23:33:04.1965465+02:00", + "ModifiedDateTime":"2016-06-15T23:33:04.1965465+02:00", + "Context":null + } + ], + "Reference":"Contract #123", + "PostbackUrl":"https://example.com/postback.php", + "SignRequestMode":2, + "DaysToExpire":30, + "SendEmailNotifications":true, + "CreatedDateTime":"2016-08-31T21:22:56.2467731+02:00", + "ModifiedDateTime":"2016-08-31T21:22:56.2467731+02:00", + "CanceledDateTime":null, + "Context":null, + "Checksum":"cdc09eee2ed6df2846dcc193aedfef59f2834f8d" +} diff --git a/src/SignhostAPIClient.Tests/JSON/TransactionSingleSignerJson.json b/src/SignhostAPIClient.Tests/JSON/TransactionSingleSignerJson.json new file mode 100644 index 0000000..07d2fdb --- /dev/null +++ b/src/SignhostAPIClient.Tests/JSON/TransactionSingleSignerJson.json @@ -0,0 +1,46 @@ +{ + "Id":"50262c3f-9744-45bf-a4c6-8a3whatever", + "Status":5, + "CanceledDateTime":"2017-01-01T15:00:00.0000000+01:00", + "Files":{}, + "Seal":false, + "Signers":[ + { + "Id":"Signer1", + "Email":"test1@example.com", + "Verifications":[ + { + "Type":"PhoneNumber", + "Number":"+31615123456" + } + ], + "Mobile":"+31615087075", + "SignRequestMessage":"Hello 1st signer", + "Language":"nl-NL", + "Activities":[ + { + "Id":"Activity1", + "Code":103, + "CreatedDateTime":"2017-05-31T22:15:17.6409005+02:00" + }, + { + "Id":"Activity2", + "Code":105, + "Info":"592f2448347cd", + "CreatedDateTime":"2017-05-31T22:15:20.3284659+02:00" + }, + { + "Id":"25dd4131-f1c4-4e4c-a407-c4164cfe4096", + "Code":105, + "Info":"592f244834807", + "CreatedDateTime":"2017-05-31T22:15:24.4379773+02:00" + } + ] + } + ], + "Receivers":[], + "Reference":"Contract #123", + "SignRequestMode":2, + "DaysToExpire":14, + "Context":null +} diff --git a/src/SignhostAPIClient.Tests/LevelEnumConverterTests.cs b/src/SignhostAPIClient.Tests/LevelEnumConverterTests.cs index 67e491d..534d548 100644 --- a/src/SignhostAPIClient.Tests/LevelEnumConverterTests.cs +++ b/src/SignhostAPIClient.Tests/LevelEnumConverterTests.cs @@ -1,5 +1,5 @@ using FluentAssertions; -using Newtonsoft.Json; +using System.Text.Json; using Signhost.APIClient.Rest.DataObjects; using System; using System.Collections; @@ -17,7 +17,7 @@ public void when_Level_is_null_should_deserialize_to_null() const string json = "{\"Type\":\"eIDAS Login\",\"Level\":null}"; // Act - var eidasLogin = JsonConvert.DeserializeObject(json); + var eidasLogin = JsonSerializer.Deserialize(json, SignhostJsonSerializerOptions.Default); // Assert eidasLogin.Level.Should().Be(null); @@ -30,7 +30,7 @@ public void when_Level_is_not_supplied_should_deserialize_to_null() const string json = "{\"Type\":\"eIDAS Login\"}"; // Act - var eidasLogin = JsonConvert.DeserializeObject(json); + var eidasLogin = JsonSerializer.Deserialize(json, SignhostJsonSerializerOptions.Default); // Assert eidasLogin.Level.Should().Be(null); @@ -43,7 +43,7 @@ public void when_Level_is_unknown_should_deserialize_to_Unknown_Level() const string json = "{\"Type\":\"eIDAS Login\",\"Level\":\"foobar\"}"; // Act - var eidasLogin = JsonConvert.DeserializeObject(json); + var eidasLogin = JsonSerializer.Deserialize(json, SignhostJsonSerializerOptions.Default); // Assert eidasLogin.Level.Should().Be(Level.Unknown); @@ -57,7 +57,7 @@ public void when_Level_is_valid_should_deserialize_to_correct_value(Level level) string json = $"{{\"Type\":\"eIDAS Login\",\"Level\":\"{level}\"}}"; // Act - var eidasLogin = JsonConvert.DeserializeObject(json); + var eidasLogin = JsonSerializer.Deserialize(json, SignhostJsonSerializerOptions.Default); // Assert eidasLogin.Level.Should().Be(level); diff --git a/src/SignhostAPIClient.Tests/PostbackTests.cs b/src/SignhostAPIClient.Tests/PostbackTests.cs index a472472..7a00595 100644 --- a/src/SignhostAPIClient.Tests/PostbackTests.cs +++ b/src/SignhostAPIClient.Tests/PostbackTests.cs @@ -1,8 +1,9 @@ using System; using System.Linq; +using System.Text.Json; using FluentAssertions; -using Newtonsoft.Json; using Signhost.APIClient.Rest.DataObjects; +using SignhostAPIClient.Tests.JSON; using Xunit; namespace Signhost.APIClient.Rest.Tests @@ -12,8 +13,8 @@ public class PostbackTests [Fact] public void PostbackTransaction_should_get_serialized_correctly() { - string json = RequestBodies.MockPostbackValid; - var postbackTransaction = JsonConvert.DeserializeObject(json); + string json = JsonResources.MockPostbackValid; + var postbackTransaction = JsonSerializer.Deserialize(json, SignhostJsonSerializerOptions.Default); postbackTransaction.Id .Should().Be("b10ae331-af78-4e79-a39e-5b64693b6b68"); postbackTransaction.Status .Should().Be(TransactionStatus.InProgress); @@ -24,7 +25,7 @@ public void PostbackTransaction_should_get_serialized_correctly() postbackTransaction.DaysToExpire .Should().Be(30); postbackTransaction.SendEmailNotifications.Should().BeTrue(); postbackTransaction.CreatedDateTime .Should().Be(DateTimeOffset.Parse("2016-08-31T21:22:56.2467731+02:00")); - postbackTransaction.CancelledDateTime .Should().BeNull(); + postbackTransaction.CanceledDateTime .Should().BeNull(); (postbackTransaction.Context is null) .Should().BeTrue(); postbackTransaction.Checksum .Should().Be("cdc09eee2ed6df2846dcc193aedfef59f2834f8d"); @@ -53,19 +54,16 @@ public void PostbackTransaction_should_get_serialized_correctly() var phoneNumberVerification = verifications[0] as PhoneNumberVerification; phoneNumberVerification .Should().NotBeNull(); - phoneNumberVerification.Type .Should().Be("PhoneNumber"); phoneNumberVerification.Number.Should().Be("+31612345678"); var scribbleVerification = verifications[1] as ScribbleVerification; scribbleVerification .Should().NotBeNull(); - scribbleVerification.Type .Should().Be("Scribble"); scribbleVerification.RequireHandsignature.Should().BeFalse(); scribbleVerification.ScribbleNameFixed .Should().BeFalse(); scribbleVerification.ScribbleName .Should().Be("John Doe"); var ipAddressVerification = verifications[2] as IPAddressVerification; ipAddressVerification .Should().NotBeNull(); - ipAddressVerification.Type .Should().Be("IPAddress"); ipAddressVerification.IPAddress.Should().Be("1.2.3.4"); var activities = signer.Activities; diff --git a/src/SignhostAPIClient.Tests/RequestBodies.Designer.cs b/src/SignhostAPIClient.Tests/RequestBodies.Designer.cs deleted file mode 100644 index 55227f0..0000000 --- a/src/SignhostAPIClient.Tests/RequestBodies.Designer.cs +++ /dev/null @@ -1,114 +0,0 @@ -//------------------------------------------------------------------------------ -// -// This code was generated by a tool. -// Runtime Version:4.0.30319.42000 -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -//------------------------------------------------------------------------------ - -namespace Signhost.APIClient.Rest.Tests { - using System; - - - /// - /// A strongly-typed resource class, for looking up localized strings, etc. - /// - // This class was auto-generated by the StronglyTypedResourceBuilder - // class via a tool like ResGen or Visual Studio. - // To add or remove a member, edit your .ResX file then rerun ResGen - // with the /str option, or rebuild your VS project. - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] - [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] - [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] - internal class RequestBodies { - - private static global::System.Resources.ResourceManager resourceMan; - - private static global::System.Globalization.CultureInfo resourceCulture; - - [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] - internal RequestBodies() { - } - - /// - /// Returns the cached ResourceManager instance used by this class. - /// - [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Resources.ResourceManager ResourceManager { - get { - if (object.ReferenceEquals(resourceMan, null)) { - global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Signhost.APIClient.Rest.Tests.RequestBodies", typeof(RequestBodies).Assembly); - resourceMan = temp; - } - return resourceMan; - } - } - - /// - /// Overrides the current thread's CurrentUICulture property for all - /// resource lookups using this strongly typed resource class. - /// - [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Globalization.CultureInfo Culture { - get { - return resourceCulture; - } - set { - resourceCulture = value; - } - } - - /// - /// Looks up a localized string similar to {"DisplayOrder":null,"DisplayName":null,"Description":null,"Signers":{"someSignerId":{"FormSets":["SampleFormSet"]}},"FormSets":{"SampleFormSet":{"SampleCheck":{"Type":"Check","Value":"I agree","Location":{"Search":"test","Occurence":null,"Top":null,"Right":null,"Bottom":null,"Left":null,"Width":null,"Height":null,"PageNumber":null}}}}}. - /// - internal static string AddOrReplaceFileMetaToTransaction { - get { - return ResourceManager.GetString("AddOrReplaceFileMetaToTransaction", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to { - /// "Id": "50262c3f-9744-45bf-a4c6-8a3whatever", - /// "Status": 5, - /// "CanceledDateTime": "2017-01-01 15:00", - /// "Files": {}, - /// "Seal": false, - /// "Signers": [ - /// { - /// "Id": "Signer1", - /// "Email": "test1@example.com", - /// "Verifications": [ - /// { - /// "Type": "PhoneNumber", - /// "Number": "+31615123456" - /// } - /// ], - /// "Mobile": "+31615087075", - /// "SignRequestMessage": "Hello 1st signer", - /// "Language": "nl-NL", - /// "Activities": [ - /// { - /// "I [rest of string was truncated]";. - /// - internal static string TransactionSingleSignerJson { - get { - return ResourceManager.GetString("TransactionSingleSignerJson", resourceCulture); - } - } - - internal static string MockPostbackValid { - get { - return ResourceManager.GetString("MockPostbackValid", resourceCulture); - } - } - - internal static string MockPostbackInvalid { - get { - return ResourceManager.GetString("MockPostbackInvalid", resourceCulture); - } - } - } -} diff --git a/src/SignhostAPIClient.Tests/RequestBodies.resx b/src/SignhostAPIClient.Tests/RequestBodies.resx deleted file mode 100644 index 62d2094..0000000 --- a/src/SignhostAPIClient.Tests/RequestBodies.resx +++ /dev/null @@ -1,355 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - text/microsoft-resx - - - 2.0 - - - System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - {"DisplayOrder":null,"DisplayName":null,"Description":null,"Signers":{"someSignerId":{"FormSets":["SampleFormSet"]}},"FormSets":{"SampleFormSet":{"SampleCheck":{"Type":"Check","Value":"I agree","Location":{"Search":"test","Occurence":null,"Top":null,"Right":null,"Bottom":null,"Left":null,"Width":null,"Height":null,"PageNumber":null}}}}} - - - { - "Id": "50262c3f-9744-45bf-a4c6-8a3whatever", - "Status": 5, - "CanceledDateTime": "2017-01-01 15:00", - "Files": {}, - "Seal": false, - "Signers": [ - { - "Id": "Signer1", - "Email": "test1@example.com", - "Verifications": [ - { - "Type": "PhoneNumber", - "Number": "+31615123456" - } - ], - "Mobile": "+31615087075", - "SignRequestMessage": "Hello 1st signer", - "Language": "nl-NL", - "Activities": [ - { - "Id": "Activity1", - "Code": 103, - "CreatedDateTime": "2017-05-31T22:15:17.6409005+02:00" - }, - { - "Id": "Activity2", - "Code": 105, - "Info": "592f2448347cd", - "CreatedDateTime": "2017-05-31T22:15:20.3284659+02:00" - }, - { - "Id": "25dd4131-f1c4-4e4c-a407-c4164cfe4096", - "Code": 105, - "Info": "592f244834807", - "CreatedDateTime": "2017-05-31T22:15:24.4379773+02:00" - } - ] - } - ], - "Receivers": [], - "Reference": "Contract #123", - "SignRequestMode": 2, - "DaysToExpire": 14, - "Context": null -} - - - -{ - "Id": "b10ae331-af78-4e79-a39e-5b64693b6b68", - "Status": 20, - "Files": { - "file1": { - "Links": [ - { - "Rel": "file", - "Type": "application/pdf", - "Link": "https://api.signhost.com/api/transaction/b10ae331-af78-4e79-a39e-5b64693b6b68/file/file1" - } - ], - "DisplayName": "Sample File" - } - }, - "Seal": true, - "Signers": - [{ - "Id": "fa95495d-6c59-48e0-962a-a4552f8d6b85", - "Expires": null, - "Email": "user@example.com", - "Verifications": [ - { - "Type": "PhoneNumber", - "Number": "+31612345678" - }, - { - "Type": "Scribble", - "RequireHandsignature": false, - "ScribbleNameFixed": false, - "ScribbleName": "John Doe" - }, - { - "Type": "IPAddress", - "IPAddress": "1.2.3.4" - } - ], - "Mobile": "+31612345678", - "Iban": null, - "BSN": null, - "RequireScribbleName": false, - "RequireScribble": true, - "RequireEmailVerification": true, - "RequireSmsVerification": true, - "RequireIdealVerification": false, - "RequireDigidVerification": false, - "RequireKennisnetVerification": false, - "RequireSurfnetVerification": false, - "SendSignRequest": true, - "SendSignConfirmation": null, - "SignRequestMessage": "Hello, could you please sign this document? Best regards, John Doe", - "DaysToRemind": 15, - "Language": "en-US", - "ScribbleName": "John Doe", - "ScribbleNameFixed": false, - "Reference": "Client #123", - "ReturnUrl": "https://signhost.com", - "Activities": - [{ - "Id": "bcba44a9-c201-4494-9920-2c1f7baebcf0", - "Code": 103, - "Activity": "Opened", - "CreatedDateTime": "2016-06-15T23:33:04.1965465+02:00"}, - { - "Id": "7aacf96a-5c2f-475d-98a5-726e41bfc5d3", - "Code": 105, - "Activity": "DocumentOpened", - "Info": "file1", - "CreatedDateTime": "2020-01-30T16:31:05.6679583+01:00" - }, - { - "Id": "de94cf6e-e1a3-4c33-93bf-2013b036daaf", - "Code": 203,"Activity": "Signed", - "CreatedDateTime": "2016-06-15T23:38:04.1965465+02:00" - }], - "RejectReason": null, - "SignUrl": "https://view.signhost.com/sign/d3c93bd6-f1ce-48e7-8c9c-c2babfdd4034", - "SignedDateTime": null, - "RejectDateTime": null, - "CreatedDateTime": "2016-06-15T23:33:04.1965465+02:00", - "ModifiedDateTime": "2016-06-15T23:33:04.1965465+02:00", - "Context": null}], - "Receivers": - [{ - "Id": "97ed6b54-b6d1-46ed-88c1-79779c3b47b1", - "Name": "John Doe", - "Email": "user@example.com", - "Language": "en-US", - "Message": "Hello, please find enclosed the digital signed document. Best regards, John Doe", - "Reference": null, - "Activities": null, - "CreatedDateTime": "2016-06-15T23:33:04.1965465+02:00", - "ModifiedDateTime": "2016-06-15T23:33:04.1965465+02:00", - "Context": null - }], - "Reference": "Contract #123", - "PostbackUrl": "https://example.com/postback.php", - "SignRequestMode": 2, - "DaysToExpire": 30, - "SendEmailNotifications": true, - "CreatedDateTime": "2016-08-31T21:22:56.2467731+02:00", - "ModifiedDateTime": "2016-08-31T21:22:56.2467731+02:00", - "CanceledDateTime": null, - "Context": null, - "Checksum": "cdc09eee2ed6df2846dcc193aedfef59f2834f8d" -} - - - - -{ - "Id": "b10ae331-af78-4e79-a39e-5b64693b6b68", - "Status": 20, - "Seal": true, - "Signers": - [{ - "Id": "fa95495d-6c59-48e0-962a-a4552f8d6b85", - "Expires": null, - "Email": "user@example.com", - "Mobile": "+31612345678", - "Iban": null, - "BSN": null, - "RequireScribbleName": false, - "RequireScribble": true, - "RequireEmailVerification": true, - "RequireSmsVerification": true, - "RequireIdealVerification": false, - "RequireDigidVerification": false, - "RequireKennisnetVerification": false, - "RequireSurfnetVerification": false, - "SendSignRequest": true, - "SendSignConfirmation": null, - "SignRequestMessage": "Hello, could you please sign this document? Best regards, John Doe", - "DaysToRemind": 15, - "Language": "en-US", - "ScribbleName": "John Doe", - "ScribbleNameFixed": false, - "Reference": "Client #123", - "ReturnUrl": "https://signhost.com", - "Activities": - [{ - "Id": "bcba44a9-c201-4494-9920-2c1f7baebcf0", - "Code": 103, - "Activity": "Opened", - "CreatedDateTime": "2016-06-15T23:33:04.1965465+02:00"}, - { - "Id": "de94cf6e-e1a3-4c33-93bf-2013b036daaf", - "Code": 203,"Activity": "Signed", - "CreatedDateTime": "2016-06-15T23:38:04.1965465+02:00" - }], - "RejectReason": null, - "SignUrl": "https://view.signhost.com/sign/d3c93bd6-f1ce-48e7-8c9c-c2babfdd4034", - "SignedDateTime": null, - "RejectDateTime": null, - "CreatedDateTime": "2016-06-15T23:33:04.1965465+02:00", - "ModifiedDateTime": "2016-06-15T23:33:04.1965465+02:00", - "Context": null}], - "Receivers": - [{ - "Id": "97ed6b54-b6d1-46ed-88c1-79779c3b47b1", - "Name": "John Doe", - "Email": "user@example.com", - "Language": "en-US", - "Message": "Hello, please find enclosed the digital signed document. Best regards, John Doe", - "Reference": null, - "Activities": null, - "CreatedDateTime": "2016-06-15T23:33:04.1965465+02:00", - "ModifiedDateTime": "2016-06-15T23:33:04.1965465+02:00", - "Context": null - }], - "Reference": "Contract #123", - "PostbackUrl": "https://example.com/postback.php", - "SignRequestMode": 2, - "DaysToExpire": 30, - "SendEmailNotifications": true, - "CreatedDateTime": "2016-08-31T21:22:56.2467731+02:00", - "ModifiedDateTime": "2016-08-31T21:22:56.2467731+02:00", - "CanceledDateTime": null, - "Context": null, -} - - - diff --git a/src/SignhostAPIClient.Tests/SignhostAPIClient.Tests.csproj b/src/SignhostAPIClient.Tests/SignhostAPIClient.Tests.csproj index fbe586e..d7aa909 100644 --- a/src/SignhostAPIClient.Tests/SignhostAPIClient.Tests.csproj +++ b/src/SignhostAPIClient.Tests/SignhostAPIClient.Tests.csproj @@ -10,7 +10,6 @@ - @@ -22,23 +21,11 @@ - - Signhost.APIClient.Rest.Tests - - - - ResXFileCodeGenerator - - - ResXFileCodeGenerator - RequestBodies.Designer.cs - + - - - RequestBodies.resx - - + + Signhost.APIClient.Rest.Tests + diff --git a/src/SignhostAPIClient.Tests/SignhostApiClientTests.cs b/src/SignhostAPIClient.Tests/SignhostApiClientTests.cs index 041a40f..0e5f1e9 100644 --- a/src/SignhostAPIClient.Tests/SignhostApiClientTests.cs +++ b/src/SignhostAPIClient.Tests/SignhostApiClientTests.cs @@ -8,16 +8,18 @@ using System.Collections.Generic; using RichardSzalay.MockHttp; using System.Net; +using SignhostAPIClient.Tests.JSON; +using System.Text.Json.Serialization; namespace Signhost.APIClient.Rest.Tests { - public class SignHostApiClientTests + public class SignhostApiClientTests { - private readonly SignHostApiClientSettings settings = new("AppKey", "Usertoken") { + private readonly SignhostApiClientSettings settings = new("AppKey", "Usertoken") { Endpoint = "http://localhost/api/" }; - private readonly SignHostApiClientSettings oauthSettings = new("AppKey") { + private readonly SignhostApiClientSettings oauthSettings = new("AppKey") { Endpoint = "http://localhost/api/" }; @@ -27,11 +29,11 @@ public async Task when_AddOrReplaceFileMetaToTransaction_is_called_then_the_requ var mockHttp = new MockHttpMessageHandler(); mockHttp.Expect(HttpMethod.Put, "http://localhost/api/transaction/transactionId/file/fileId") - .WithContent(RequestBodies.AddOrReplaceFileMetaToTransaction) + .WithContent(JsonResources.AddOrReplaceFileMetaToTransaction) .Respond(HttpStatusCode.OK); using (var httpClient = mockHttp.ToHttpClient()) { - var signhostApiClient = new SignHostApiClient(settings, httpClient); + var signhostApiClient = new SignhostApiClient(settings, httpClient); var fileSignerMeta = new FileSignerMeta { @@ -76,11 +78,11 @@ public async Task when_a_GetTransaction_is_called_then_we_should_have_called_the var mockHttp = new MockHttpMessageHandler(); mockHttp .Expect(HttpMethod.Get, "http://localhost/api/transaction/transaction Id") - .Respond(HttpStatusCode.OK, new StringContent(APIResponses.GetTransaction)); + .Respond(HttpStatusCode.OK, new StringContent(JsonResources.GetTransaction)); using (var httpClient = mockHttp.ToHttpClient()) { - var signhostApiClient = new SignHostApiClient(settings, httpClient); + var signhostApiClient = new SignhostApiClient(settings, httpClient); var result = await signhostApiClient.GetTransactionAsync("transaction Id"); result.Id.Should().Be("c487be92-0255-40c7-bd7d-20805a65e7d9"); @@ -95,11 +97,15 @@ public async Task when_GetTransaction_is_called_and_the_authorization_is_bad_the var mockHttp = new MockHttpMessageHandler(); mockHttp .Expect(HttpMethod.Get, "http://localhost/api/transaction/transaction Id") - .Respond(HttpStatusCode.Unauthorized, new StringContent("{'message': 'unauthorized' }")); + .Respond(HttpStatusCode.Unauthorized, new StringContent(""" + { + "message": "unauthorized" + } + """)); using (var httpClient = mockHttp.ToHttpClient()) { - var signhostApiClient = new SignHostApiClient(settings, httpClient); + var signhostApiClient = new SignhostApiClient(settings, httpClient); Func getTransaction = () => signhostApiClient.GetTransactionAsync("transaction Id"); await getTransaction.Should().ThrowAsync(); @@ -114,11 +120,15 @@ public async Task when_GetTransaction_is_called_and_request_is_bad_then_we_shoul var mockHttp = new MockHttpMessageHandler(); mockHttp .Expect(HttpMethod.Get, "http://localhost/api/transaction/transaction Id") - .Respond(HttpStatusCode.BadRequest, new StringContent("{ 'message': 'Bad Request' }")); + .Respond(HttpStatusCode.BadRequest, new StringContent(""" + { + "message": "Bad Request" + } + """)); using (var httpClient = mockHttp.ToHttpClient()) { - var signhostApiClient = new SignHostApiClient(settings, httpClient); + var signhostApiClient = new SignhostApiClient(settings, httpClient); Func getTransaction = () => signhostApiClient.GetTransactionAsync("transaction Id"); await getTransaction.Should().ThrowAsync(); @@ -133,11 +143,15 @@ public async Task when_GetTransaction_is_called_and_credits_have_run_out_then_we var mockHttp = new MockHttpMessageHandler(); mockHttp .Expect(HttpMethod.Get, "http://localhost/api/transaction/transaction Id") - .Respond(HttpStatusCode.PaymentRequired, new StringContent("{ 'type': 'https://api.signhost.com/problem/subscription/out-of-credits' }")); + .Respond(HttpStatusCode.PaymentRequired, new StringContent(""" + { + "type": "https://api.signhost.com/problem/subscription/out-of-credits" + } + """)); using (var httpClient = mockHttp.ToHttpClient()) { - var signhostApiClient = new SignHostApiClient(settings, httpClient); + var signhostApiClient = new SignhostApiClient(settings, httpClient); Func getTransaction = () => signhostApiClient.GetTransactionAsync("transaction Id"); await getTransaction.Should().ThrowAsync(); @@ -152,11 +166,15 @@ public async Task when_GetTransaction_is_called_and_not_found_then_we_should_get var mockHttp = new MockHttpMessageHandler(); mockHttp .Expect(HttpMethod.Get, "http://localhost/api/transaction/transaction Id") - .Respond(HttpStatusCode.NotFound, new StringContent("{ 'Message': 'Not Found' }")); + .Respond(HttpStatusCode.NotFound, new StringContent(""" + { + "message": "Not Found" + } + """)); using (var httpClient = mockHttp.ToHttpClient()) { - var signhostApiClient = new SignHostApiClient(settings, httpClient); + var signhostApiClient = new SignhostApiClient(settings, httpClient); Func getTransaction = () => signhostApiClient.GetTransactionAsync("transaction Id"); @@ -172,11 +190,15 @@ public async Task when_GetTransaction_is_called_and_unkownerror_like_418_occures var mockHttp = new MockHttpMessageHandler(); mockHttp .Expect(HttpMethod.Get, "http://localhost/api/transaction/transaction Id") - .Respond((HttpStatusCode)418, new StringContent("{ 'message': '418 I\\'m a teapot' }")); + .Respond((HttpStatusCode)418, new StringContent(""" + { + "message": "418 I'm a teapot" + } + """)); using (var httpClient = mockHttp.ToHttpClient()) { - var signhostApiClient = new SignHostApiClient(settings, httpClient); + var signhostApiClient = new SignhostApiClient(settings, httpClient); Func getTransaction = () => signhostApiClient.GetTransactionAsync("transaction Id"); await getTransaction.Should().ThrowAsync() @@ -192,11 +214,15 @@ public async Task when_GetTransaction_is_called_and_there_is_an_InternalServerEr var mockHttp = new MockHttpMessageHandler(); mockHttp .Expect(HttpMethod.Get, "http://localhost/api/transaction/transaction Id") - .Respond(HttpStatusCode.InternalServerError, new StringContent("{ 'message': 'Internal Server Error' }")); + .Respond(HttpStatusCode.InternalServerError, new StringContent(""" + { + "message": "Internal Server Error" + } + """)); using (var httpClient = mockHttp.ToHttpClient()) { - var signhostApiClient = new SignHostApiClient(settings, httpClient); + var signhostApiClient = new SignhostApiClient(settings, httpClient); Func getTransaction = () => signhostApiClient.GetTransactionAsync("transaction Id"); await getTransaction.Should().ThrowAsync(); @@ -211,10 +237,10 @@ public async Task When_GetTransaction_is_called_on_gone_transaction_we_shoud_get var mockHttp = new MockHttpMessageHandler(); mockHttp .Expect(HttpMethod.Get, "http://localhost/api/transaction/transaction Id") - .Respond(HttpStatusCode.Gone, new StringContent(APIResponses.GetTransaction)); + .Respond(HttpStatusCode.Gone, new StringContent(JsonResources.GetTransaction)); using (var httpClient = mockHttp.ToHttpClient()) { - var signhostApiClient = new SignHostApiClient(settings, httpClient); + var signhostApiClient = new SignhostApiClient(settings, httpClient); Func getTransaction = () => signhostApiClient.GetTransactionAsync("transaction Id"); await getTransaction.Should().ThrowAsync>(); @@ -229,10 +255,10 @@ public async Task When_GetTransaction_is_called_and_gone_is_expected_we_should_g var mockHttp = new MockHttpMessageHandler(); mockHttp .Expect(HttpMethod.Get, "http://localhost/api/transaction/transaction Id") - .Respond(HttpStatusCode.Gone, new StringContent(APIResponses.GetTransaction)); + .Respond(HttpStatusCode.Gone, new StringContent(JsonResources.GetTransaction)); using (var httpClient = mockHttp.ToHttpClient()) { - var signhostApiClient = new SignHostApiClient(settings, httpClient); + var signhostApiClient = new SignhostApiClient(settings, httpClient); Func getTransaction = () => signhostApiClient.GetTransactionResponseAsync("transaction Id"); await getTransaction.Should().NotThrowAsync(); @@ -247,10 +273,10 @@ public async Task when_a_CreateTransaction_is_called_then_we_should_have_called_ var mockHttp = new MockHttpMessageHandler(); mockHttp .Expect(HttpMethod.Post, "http://localhost/api/transaction") - .Respond(HttpStatusCode.OK, new StringContent(APIResponses.AddTransaction)); + .Respond(HttpStatusCode.OK, new StringContent(JsonResources.AddTransaction)); using (var httpClient = mockHttp.ToHttpClient()) { - var signhostApiClient = new SignHostApiClient(settings, httpClient); + var signhostApiClient = new SignhostApiClient(settings, httpClient); Signer testSigner = new Signer(); testSigner.Email = "firstname.lastname@gmail.com"; @@ -273,12 +299,12 @@ public async Task when_a_CreateTransaction_is_called_we_can_add_custom_http_head .Expect(HttpMethod.Post, "http://localhost/api/transaction") .WithHeaders("X-Forwarded-For", "localhost") .With(matcher => matcher.Headers.UserAgent.ToString().Contains("SignhostClientLibrary")) - .Respond(HttpStatusCode.OK, new StringContent(APIResponses.AddTransaction)); + .Respond(HttpStatusCode.OK, new StringContent(JsonResources.AddTransaction)); using (var httpClient = mockHttp.ToHttpClient()) { settings.AddHeader = (AddHeaders a) => a("X-Forwarded-For", "localhost"); - var signhostApiClient = new SignHostApiClient(settings, httpClient); + var signhostApiClient = new SignhostApiClient(settings, httpClient); Transaction testTransaction = new Transaction(); @@ -295,10 +321,14 @@ public async Task when_CreateTransaction_is_called_with_invalid_email_then_we_sh mockHttp .Expect(HttpMethod.Post, "http://localhost/api/transaction") .WithHeaders("Content-Type", "application/json") - .Respond(HttpStatusCode.BadRequest, new StringContent(" { 'message': 'Bad Request' }")); + .Respond(HttpStatusCode.BadRequest, new StringContent(""" + { + "message": "Bad Request" + } + """)); using (var httpClient = mockHttp.ToHttpClient()) { - var signhostApiClient = new SignHostApiClient(settings, httpClient); + var signhostApiClient = new SignhostApiClient(settings, httpClient); Signer testSigner = new Signer(); testSigner.Email = "firstname.lastnamegmail.com"; @@ -319,10 +349,14 @@ public async Task when_a_function_is_called_with_a_wrong_endpoint_we_should_get_ var mockHttp = new MockHttpMessageHandler(); mockHttp .Expect(HttpMethod.Get, "http://localhost/api/transaction/transaction Id") - .Respond(HttpStatusCode.BadGateway, new StringContent(" { 'Message': 'Bad Gateway' }")); + .Respond(HttpStatusCode.BadGateway, new StringContent(""" + { + "message": "Bad Gateway" + } + """)); using (var httpClient = mockHttp.ToHttpClient()) { - var signhostApiClient = new SignHostApiClient(settings, httpClient); + var signhostApiClient = new SignhostApiClient(settings, httpClient); Func getTransaction = () => signhostApiClient.GetTransactionAsync("transaction Id"); await getTransaction.Should().ThrowAsync() @@ -338,10 +372,10 @@ public async Task when_a_DeleteTransaction_is_called_then_we_should_have_called_ var mockHttp = new MockHttpMessageHandler(); mockHttp .Expect(HttpMethod.Delete, "http://localhost/api/transaction/transaction Id") - .Respond(HttpStatusCode.OK, new StringContent(APIResponses.DeleteTransaction)); + .Respond(HttpStatusCode.OK, new StringContent(JsonResources.DeleteTransaction)); using (var httpClient = mockHttp.ToHttpClient()) { - var signhostApiClient = new SignHostApiClient(settings, httpClient); + var signhostApiClient = new SignhostApiClient(settings, httpClient); await signhostApiClient.DeleteTransactionAsync("transaction Id"); } @@ -357,10 +391,10 @@ public async Task when_a_DeleteTransaction_with_notification_is_called_then_we_s .Expect(HttpMethod.Delete, "http://localhost/api/transaction/transaction Id") .WithHeaders("Content-Type", "application/json") //.With(matcher => matcher.Content.ToString().Contains("'SendNotifications': true")) - .Respond(HttpStatusCode.OK, new StringContent(APIResponses.DeleteTransaction)); + .Respond(HttpStatusCode.OK, new StringContent(JsonResources.DeleteTransaction)); using (var httpClient = mockHttp.ToHttpClient()) { - var signhostApiClient = new SignHostApiClient(settings, httpClient); + var signhostApiClient = new SignhostApiClient(settings, httpClient); await signhostApiClient.DeleteTransactionAsync( "transaction Id", @@ -381,7 +415,7 @@ public async Task when_AddOrReplaceFileToTransaction_is_called_then_we_should_ha .Respond(HttpStatusCode.OK); using (var httpClient = mockHttp.ToHttpClient()) { - var signhostApiClient = new SignHostApiClient(settings, httpClient); + var signhostApiClient = new SignhostApiClient(settings, httpClient); // Create a 0 sized file using (Stream file = System.IO.File.Create("unittestdocument.pdf")) { @@ -402,7 +436,7 @@ public async Task when_AddOrReplaceFileToTransaction_is_called_default_digest_is .Respond(HttpStatusCode.OK); using (var httpClient = mockHttp.ToHttpClient()) { - var signhostApiClient = new SignHostApiClient(settings, httpClient); + var signhostApiClient = new SignhostApiClient(settings, httpClient); await signhostApiClient.AddOrReplaceFileToTransaction(new MemoryStream(), "transaction Id", "file Id"); } @@ -420,7 +454,7 @@ public async Task when_AddOrReplaceFileToTransaction_with_sha512_is_called_defau .Respond(HttpStatusCode.OK); using (var httpClient = mockHttp.ToHttpClient()) { - var signhostApiClient = new SignHostApiClient(settings, httpClient); + var signhostApiClient = new SignhostApiClient(settings, httpClient); await signhostApiClient.AddOrReplaceFileToTransactionAsync( new MemoryStream(), @@ -447,7 +481,7 @@ public async Task when_AddOrReplaceFileToTransaction_with_digest_value_is_used_a .Respond(HttpStatusCode.OK); using (var httpClient = mockHttp.ToHttpClient()) { - var signhostApiClient = new SignHostApiClient(settings, httpClient); + var signhostApiClient = new SignhostApiClient(settings, httpClient); await signhostApiClient.AddOrReplaceFileToTransactionAsync( new MemoryStream(), @@ -475,7 +509,7 @@ public async Task when_StartTransaction_is_called_then_we_should_have_called_the using (var httpClient = mockHttp.ToHttpClient()) { - var signhostApiClient = new SignHostApiClient(settings, httpClient); + var signhostApiClient = new SignhostApiClient(settings, httpClient); await signhostApiClient.StartTransactionAsync("transaction Id"); } @@ -491,7 +525,7 @@ public async Task when_GetReceipt_is_called_then_we_should_have_called_the_filer .Respond(HttpStatusCode.OK); using (var httpClient = mockHttp.ToHttpClient()) { - var signhostApiClient = new SignHostApiClient(settings, httpClient); + var signhostApiClient = new SignhostApiClient(settings, httpClient); var receipt = await signhostApiClient.GetReceiptAsync("transaction ID"); } @@ -508,7 +542,7 @@ public async Task when_GetDocument_is_called_then_we_should_have_called_the_file using (var httpClient = mockHttp.ToHttpClient()) { - var signhostApiClient = new SignHostApiClient(settings, httpClient); + var signhostApiClient = new SignhostApiClient(settings, httpClient); var document = await signhostApiClient.GetDocumentAsync("transaction Id", "file Id"); } @@ -521,11 +555,11 @@ public async Task When_a_transaction_json_is_returned_it_is_deserialized_correct { var mockHttp = new MockHttpMessageHandler(); mockHttp.Expect(HttpMethod.Post, "http://localhost/api/transaction") - .Respond(HttpStatusCode.OK, new StringContent(RequestBodies.TransactionSingleSignerJson)); + .Respond(HttpStatusCode.OK, new StringContent(JsonResources.TransactionSingleSignerJson)); using (var httpClient = mockHttp.ToHttpClient()) { - var signhostApiClient = new SignHostApiClient(settings, httpClient); + var signhostApiClient = new SignhostApiClient(settings, httpClient); var result = await signhostApiClient.CreateTransactionAsync(new Transaction { @@ -544,7 +578,7 @@ public async Task When_a_transaction_json_is_returned_it_is_deserialized_correct }); result.Id.Should().Be("50262c3f-9744-45bf-a4c6-8a3whatever"); - result.CancelledDateTime.Should().HaveYear(2017); + result.CanceledDateTime.Should().HaveYear(2017); result.Status.Should().Be(TransactionStatus.WaitingForDocument); result.Signers.Should().HaveCount(1); result.Receivers.Should().HaveCount(0); @@ -589,9 +623,9 @@ MockedRequest AddHeaders(MockedRequest request) var mockHttp = new MockHttpMessageHandler(); AddHeaders(mockHttp.Expect(HttpMethod.Post, "http://localhost/api/transaction")) - .Respond(new StringContent(RequestBodies.TransactionSingleSignerJson)); + .Respond(new StringContent(JsonResources.TransactionSingleSignerJson)); AddHeaders(mockHttp.Expect(HttpMethod.Put, "http://localhost/api/transaction/*/file/somefileid")) - .Respond(HttpStatusCode.Accepted, new StringContent(RequestBodies.AddOrReplaceFileMetaToTransaction)); + .Respond(HttpStatusCode.Accepted, new StringContent(JsonResources.AddOrReplaceFileMetaToTransaction)); AddHeaders(mockHttp.Expect(HttpMethod.Put, "http://localhost/api/transaction/*/file/somefileid")) .Respond(HttpStatusCode.Created); AddHeaders(mockHttp.Expect(HttpMethod.Put, "http://localhost/api/transaction/*/start")) @@ -600,7 +634,7 @@ MockedRequest AddHeaders(MockedRequest request) using (var httpClient = mockHttp.ToHttpClient()) { var clientSettings = isOauth ? oauthSettings : settings; clientSettings.AddHeader = add => add("X-Custom", "test"); - var signhostApiClient = new SignHostApiClient(clientSettings, httpClient); + var signhostApiClient = new SignhostApiClient(clientSettings, httpClient); var result = await signhostApiClient.CreateTransactionAsync(new Transaction()); await signhostApiClient.AddOrReplaceFileMetaToTransactionAsync(new FileMeta(), result.Id, "somefileid"); @@ -614,43 +648,17 @@ MockedRequest AddHeaders(MockedRequest request) mockHttp.VerifyNoOutstandingRequest(); } - [Fact] - public async Task When_a_custom_verificationtype_is_provided_it_is_deserialized_correctly() - { - var mockHttp = new MockHttpMessageHandler(); - mockHttp - .Expect(HttpMethod.Get, "http://localhost/api/transaction/c487be92-0255-40c7-bd7d-20805a65e7d9") - .Respond(new StringContent(APIResponses.GetTransactionCustomVerificationType)); - - SignHostApiClient.RegisterVerification(); - - using (var httpClient = mockHttp.ToHttpClient()) { - var signhostApiClient = new SignHostApiClient(settings, httpClient); - - var result = await signhostApiClient.GetTransactionAsync("c487be92-0255-40c7-bd7d-20805a65e7d9"); - - result.Signers[0].Verifications.Should().HaveCount(3); - result.Signers[0].Verifications[0].Should().BeOfType(); - result.Signers[0].Verifications[1].Should().BeOfType() - .Which.IPAddress.Should().Be("127.0.0.33"); - result.Signers[0].Verifications[2].Should().BeOfType() - .Which.Number.Should().Be("123"); - } - } - [Fact] public async Task When_a_minimal_response_is_retrieved_list_and_dictionaries_are_not_null() { var mockHttp = new MockHttpMessageHandler(); mockHttp .Expect(HttpMethod.Get, "http://localhost/api/transaction/c487be92-0255-40c7-bd7d-20805a65e7d9") - .Respond(new StringContent(APIResponses.MinimalTransactionResponse)); - - SignHostApiClient.RegisterVerification(); + .Respond(new StringContent(JsonResources.MinimalTransactionResponse)); using (var httpClient = mockHttp.ToHttpClient()) { - var signhostApiClient = new SignHostApiClient(settings, httpClient); + var signhostApiClient = new SignhostApiClient(settings, httpClient); var result = await signhostApiClient.GetTransactionAsync("c487be92-0255-40c7-bd7d-20805a65e7d9"); @@ -659,11 +667,5 @@ public async Task When_a_minimal_response_is_retrieved_list_and_dictionaries_are result.Files.Should().BeEmpty(); } } - - public class CustomVerification - : IVerification - { - public string Type => "CustomVerificationType"; - } } } diff --git a/src/SignhostAPIClient.Tests/SignhostApiReceiverTests.cs b/src/SignhostAPIClient.Tests/SignhostApiReceiverTests.cs index 79099cc..ba6dd32 100644 --- a/src/SignhostAPIClient.Tests/SignhostApiReceiverTests.cs +++ b/src/SignhostAPIClient.Tests/SignhostApiReceiverTests.cs @@ -8,6 +8,7 @@ using System.Collections.Generic; using RichardSzalay.MockHttp; using System.Net; +using SignhostAPIClient.Tests.JSON; namespace Signhost.APIClient.Rest.Tests { @@ -20,7 +21,7 @@ public void when_IsPostbackChecksumValid_is_called_with_valid_postback_in_body_t { // Arrange IDictionary headers = new Dictionary { { "Content-Type", new[] { "application/json" } } }; - string body = RequestBodies.MockPostbackValid; + string body = JsonResources.MockPostbackValid; // Act SignhostApiReceiver signhostApiReceiver = new SignhostApiReceiver(receiverSettings); @@ -35,7 +36,7 @@ public void when_IsPostbackChecksumValid_is_called_with_invalid_postback_in_body { // Arrange IDictionary headers = new Dictionary { { "Content-Type", new[] { "application/json" } } }; - string body = RequestBodies.MockPostbackInvalid; + string body = JsonResources.MockPostbackInvalid; // Act SignhostApiReceiver signhostApiReceiver = new SignhostApiReceiver(receiverSettings); @@ -53,7 +54,7 @@ public void when_IsPostbackChecksumValid_is_called_with_valid_postback_in_header { "Content-Type", new[] { "application/json" }}, {"Checksum", new[] {"cdc09eee2ed6df2846dcc193aedfef59f2834f8d"}} }; - string body = RequestBodies.MockPostbackValid; + string body = JsonResources.MockPostbackValid; // Act SignhostApiReceiver signhostApiReceiver = new SignhostApiReceiver(receiverSettings); @@ -71,7 +72,7 @@ public void when_IsPostbackChecksumValid_is_called_with_invalid_postback_in_head { "Content-Type", new[] { "application/json" }}, {"Checksum", new[] {"70dda90616f744797972c0d2f787f86643a60c83"}} }; - string body = RequestBodies.MockPostbackValid; + string body = JsonResources.MockPostbackValid; // Act SignhostApiReceiver signhostApiReceiver = new SignhostApiReceiver(receiverSettings); diff --git a/src/SignhostAPIClient.Tests/app.config b/src/SignhostAPIClient.Tests/app.config deleted file mode 100644 index de5386a..0000000 --- a/src/SignhostAPIClient.Tests/app.config +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - \ No newline at end of file diff --git a/src/SignhostAPIClient/Rest/DataObjects/Activity.cs b/src/SignhostAPIClient/Rest/DataObjects/Activity.cs index 31a703c..517d255 100644 --- a/src/SignhostAPIClient/Rest/DataObjects/Activity.cs +++ b/src/SignhostAPIClient/Rest/DataObjects/Activity.cs @@ -1,5 +1,5 @@ using System; -using Newtonsoft.Json; +using System.Text.Json.Serialization; namespace Signhost.APIClient.Rest.DataObjects { @@ -9,7 +9,7 @@ public class Activity public ActivityType Code { get; set; } - [JsonProperty("Activity")] + [JsonPropertyName("Activity")] public string ActivityValue { get; set; } public string Info { get; set; } diff --git a/src/SignhostAPIClient/Rest/DataObjects/ConsentVerification.cs b/src/SignhostAPIClient/Rest/DataObjects/ConsentVerification.cs index 18c1cfd..ec51d08 100644 --- a/src/SignhostAPIClient/Rest/DataObjects/ConsentVerification.cs +++ b/src/SignhostAPIClient/Rest/DataObjects/ConsentVerification.cs @@ -10,9 +10,5 @@ namespace Signhost.APIClient.Rest.DataObjects public class ConsentVerification : IVerification { - /// - /// Gets the . - /// - public string Type { get; } = "Consent"; } } diff --git a/src/SignhostAPIClient/Rest/DataObjects/CscVerification.cs b/src/SignhostAPIClient/Rest/DataObjects/CscVerification.cs index 109074e..2ceb0de 100644 --- a/src/SignhostAPIClient/Rest/DataObjects/CscVerification.cs +++ b/src/SignhostAPIClient/Rest/DataObjects/CscVerification.cs @@ -8,11 +8,6 @@ namespace Signhost.APIClient.Rest.DataObjects public class CscVerification : IVerification { - /// - /// Gets the . - /// - public string Type => "CSC Qualified"; - /// /// Gets or sets the provider identifier. /// diff --git a/src/SignhostAPIClient/Rest/DataObjects/DigidVerification.cs b/src/SignhostAPIClient/Rest/DataObjects/DigidVerification.cs index c5be757..bfb8b9f 100644 --- a/src/SignhostAPIClient/Rest/DataObjects/DigidVerification.cs +++ b/src/SignhostAPIClient/Rest/DataObjects/DigidVerification.cs @@ -3,8 +3,6 @@ public class DigidVerification : IVerification { - public string Type => "DigiD"; - public string Bsn { get; set; } public bool? SecureDownload { get; set; } diff --git a/src/SignhostAPIClient/Rest/DataObjects/EherkenningVerification.cs b/src/SignhostAPIClient/Rest/DataObjects/EherkenningVerification.cs index adee19d..647b1c0 100644 --- a/src/SignhostAPIClient/Rest/DataObjects/EherkenningVerification.cs +++ b/src/SignhostAPIClient/Rest/DataObjects/EherkenningVerification.cs @@ -3,11 +3,6 @@ namespace Signhost.APIClient.Rest.DataObjects public class EherkenningVerification : IVerification { - /// - /// Gets the . - /// - public string Type => "eHerkenning"; - /// /// Gets or sets the Uid. /// diff --git a/src/SignhostAPIClient/Rest/DataObjects/EidasLoginVerification.cs b/src/SignhostAPIClient/Rest/DataObjects/EidasLoginVerification.cs index 72deeac..18ac4af 100644 --- a/src/SignhostAPIClient/Rest/DataObjects/EidasLoginVerification.cs +++ b/src/SignhostAPIClient/Rest/DataObjects/EidasLoginVerification.cs @@ -1,5 +1,7 @@ using System; using System.Collections.Generic; +using System.Text.Json.Serialization; +using Signhost.APIClient.Rest.JsonConverters; namespace Signhost.APIClient.Rest.DataObjects { @@ -9,11 +11,6 @@ namespace Signhost.APIClient.Rest.DataObjects public class EidasLoginVerification : IVerification { - /// - /// Gets the . - /// - public string Type { get; } = "eIDAS Login"; - /// /// Gets or sets the uid. /// @@ -22,6 +19,7 @@ public class EidasLoginVerification /// /// Gets or sets the level. /// + [JsonConverter(typeof(LevelEnumConverter))] public Level? Level { get; set; } /// diff --git a/src/SignhostAPIClient/Rest/DataObjects/FileMeta.cs b/src/SignhostAPIClient/Rest/DataObjects/FileMeta.cs index 4c0f2eb..6cbda5a 100644 --- a/src/SignhostAPIClient/Rest/DataObjects/FileMeta.cs +++ b/src/SignhostAPIClient/Rest/DataObjects/FileMeta.cs @@ -1,5 +1,5 @@ using System.Collections.Generic; -using Newtonsoft.Json; +using System.Text.Json.Serialization; namespace Signhost.APIClient.Rest.DataObjects { @@ -21,7 +21,7 @@ public class FileMeta /// Don't use this setting unless you are really sure this is what you /// want and know the side-effects. /// - [JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] public bool? SetParaph { get; set; } } } diff --git a/src/SignhostAPIClient/Rest/DataObjects/IPAddressVerification.cs b/src/SignhostAPIClient/Rest/DataObjects/IPAddressVerification.cs index ea2f0eb..5e78954 100644 --- a/src/SignhostAPIClient/Rest/DataObjects/IPAddressVerification.cs +++ b/src/SignhostAPIClient/Rest/DataObjects/IPAddressVerification.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using System.Text; -using Newtonsoft.Json; namespace Signhost.APIClient.Rest.DataObjects { @@ -11,20 +10,6 @@ namespace Signhost.APIClient.Rest.DataObjects public class IPAddressVerification : IVerification { - /// - /// Initializes a new instance of the class. - /// Do not use! - /// - [Obsolete("This constructor is for internal usage only!")] - public IPAddressVerification() - { - } - - /// - /// Gets the . - /// - public string Type { get; } = "IPAddress"; - /// /// Gets or sets the IP Address used by the signer while signing the documents. /// diff --git a/src/SignhostAPIClient/Rest/DataObjects/IVerification.cs b/src/SignhostAPIClient/Rest/DataObjects/IVerification.cs index 5ab6b93..bbab8ea 100644 --- a/src/SignhostAPIClient/Rest/DataObjects/IVerification.cs +++ b/src/SignhostAPIClient/Rest/DataObjects/IVerification.cs @@ -1,12 +1,24 @@ -using Newtonsoft.Json; -using Signhost.APIClient.Rest.JsonConverters; +using System.Text.Json.Serialization; namespace Signhost.APIClient.Rest.DataObjects { // TO-DO: Split to verification and authentication in v5 - [JsonConverter(typeof(JsonVerificationConverter))] + [JsonPolymorphic(TypeDiscriminatorPropertyName = "Type")] + [JsonDerivedType(typeof(ConsentVerification), "Consent")] + [JsonDerivedType(typeof(DigidVerification), "DigiD")] + [JsonDerivedType(typeof(EidasLoginVerification), "eIDAS Login")] + [JsonDerivedType(typeof(IdealVerification), "iDeal")] + [JsonDerivedType(typeof(IdinVerification), "iDIN")] + [JsonDerivedType(typeof(IPAddressVerification), "IPAddress")] + [JsonDerivedType(typeof(ItsmeIdentificationVerification), "itsme Identification")] + [JsonDerivedType(typeof(PhoneNumberVerification), "PhoneNumber")] + [JsonDerivedType(typeof(ScribbleVerification), "Scribble")] + [JsonDerivedType(typeof(SurfnetVerification), "SURFnet")] + [JsonDerivedType(typeof(CscVerification), "CSC Qualified")] + [JsonDerivedType(typeof(EherkenningVerification), "eHerkenning")] + [JsonDerivedType(typeof(OidcVerification), "OpenID Providers")] + [JsonDerivedType(typeof(OnfidoVerification), "Onfido")] public interface IVerification { - string Type { get; } } } diff --git a/src/SignhostAPIClient/Rest/DataObjects/IdealVerification.cs b/src/SignhostAPIClient/Rest/DataObjects/IdealVerification.cs index 459e159..5267f28 100644 --- a/src/SignhostAPIClient/Rest/DataObjects/IdealVerification.cs +++ b/src/SignhostAPIClient/Rest/DataObjects/IdealVerification.cs @@ -3,8 +3,6 @@ public class IdealVerification : IVerification { - public string Type => "iDeal"; - public string Iban { get; set; } public string AccountHolderName { get; set; } diff --git a/src/SignhostAPIClient/Rest/DataObjects/IdinVerification.cs b/src/SignhostAPIClient/Rest/DataObjects/IdinVerification.cs index 684438c..38adcd9 100644 --- a/src/SignhostAPIClient/Rest/DataObjects/IdinVerification.cs +++ b/src/SignhostAPIClient/Rest/DataObjects/IdinVerification.cs @@ -6,8 +6,6 @@ namespace Signhost.APIClient.Rest.DataObjects public class IdinVerification : IVerification { - public string Type { get; } = "iDIN"; - public string AccountHolderName { get; set; } public string AccountHolderAddress1 { get; set; } diff --git a/src/SignhostAPIClient/Rest/DataObjects/ItsmeIdentificationVerification.cs b/src/SignhostAPIClient/Rest/DataObjects/ItsmeIdentificationVerification.cs index c2c069e..eeae90b 100644 --- a/src/SignhostAPIClient/Rest/DataObjects/ItsmeIdentificationVerification.cs +++ b/src/SignhostAPIClient/Rest/DataObjects/ItsmeIdentificationVerification.cs @@ -6,11 +6,6 @@ namespace Signhost.APIClient.Rest.DataObjects public class ItsmeIdentificationVerification : IVerification { - /// - /// Gets the . - /// - public string Type => "itsme Identification"; - /// /// Gets or sets the phonenumber. /// diff --git a/src/SignhostAPIClient/Rest/DataObjects/ItsmeSignVerification.cs b/src/SignhostAPIClient/Rest/DataObjects/ItsmeSignVerification.cs deleted file mode 100644 index 2069988..0000000 --- a/src/SignhostAPIClient/Rest/DataObjects/ItsmeSignVerification.cs +++ /dev/null @@ -1,14 +0,0 @@ -namespace Signhost.APIClient.Rest.DataObjects -{ - /// - /// Verification object for itsme sign. - /// - public class ItsmeSignVerification - : IVerification - { - /// - /// Gets the . - /// - public string Type => "itsme sign"; - } -} diff --git a/src/SignhostAPIClient/Rest/DataObjects/KennisnetVerification.cs b/src/SignhostAPIClient/Rest/DataObjects/KennisnetVerification.cs deleted file mode 100644 index 7254659..0000000 --- a/src/SignhostAPIClient/Rest/DataObjects/KennisnetVerification.cs +++ /dev/null @@ -1,11 +0,0 @@ -using System; - -namespace Signhost.APIClient.Rest.DataObjects -{ - [Obsolete("This verification is no longer supported and will be removed in SemVer 4.")] - public class KennisnetVerification - : IVerification - { - public string Type => "Kennisnet"; - } -} diff --git a/src/SignhostAPIClient/Rest/DataObjects/Level.cs b/src/SignhostAPIClient/Rest/DataObjects/Level.cs index 40b016c..93f223b 100644 --- a/src/SignhostAPIClient/Rest/DataObjects/Level.cs +++ b/src/SignhostAPIClient/Rest/DataObjects/Level.cs @@ -1,12 +1,8 @@ -using Newtonsoft.Json; -using Signhost.APIClient.Rest.JsonConverters; - namespace Signhost.APIClient.Rest.DataObjects { /// /// Level of Assurance. /// - [JsonConverter(typeof(LevelEnumConverter))] public enum Level { /// diff --git a/src/SignhostAPIClient/Rest/DataObjects/OidcVerification.cs b/src/SignhostAPIClient/Rest/DataObjects/OidcVerification.cs index 208b012..57507de 100644 --- a/src/SignhostAPIClient/Rest/DataObjects/OidcVerification.cs +++ b/src/SignhostAPIClient/Rest/DataObjects/OidcVerification.cs @@ -6,11 +6,6 @@ namespace Signhost.APIClient.Rest.DataObjects public class OidcVerification : IVerification { - /// - /// Gets the . - /// - public string Type => "OpenID Providers"; - /// /// Gets or sets the OIDC provider name. /// diff --git a/src/SignhostAPIClient/Rest/DataObjects/OnfidoVerification.cs b/src/SignhostAPIClient/Rest/DataObjects/OnfidoVerification.cs index 7a9d166..ae2a6a3 100644 --- a/src/SignhostAPIClient/Rest/DataObjects/OnfidoVerification.cs +++ b/src/SignhostAPIClient/Rest/DataObjects/OnfidoVerification.cs @@ -9,11 +9,6 @@ namespace Signhost.APIClient.Rest.DataObjects public class OnfidoVerification : IVerification { - /// - /// Gets the . - /// - public string Type => "Onfido"; - /// /// Gets or sets the Onfido workflow identifier. /// diff --git a/src/SignhostAPIClient/Rest/DataObjects/PhoneNumberVerification.cs b/src/SignhostAPIClient/Rest/DataObjects/PhoneNumberVerification.cs index 14b3870..6881543 100644 --- a/src/SignhostAPIClient/Rest/DataObjects/PhoneNumberVerification.cs +++ b/src/SignhostAPIClient/Rest/DataObjects/PhoneNumberVerification.cs @@ -3,8 +3,6 @@ public class PhoneNumberVerification : IVerification { - public string Type => "PhoneNumber"; - public string Number { get; set; } public bool? SecureDownload { get; set; } diff --git a/src/SignhostAPIClient/Rest/DataObjects/PostbackTransaction.cs b/src/SignhostAPIClient/Rest/DataObjects/PostbackTransaction.cs index 4c5db85..76a2726 100644 --- a/src/SignhostAPIClient/Rest/DataObjects/PostbackTransaction.cs +++ b/src/SignhostAPIClient/Rest/DataObjects/PostbackTransaction.cs @@ -1,27 +1,13 @@ using System; using System.Collections.Generic; -using Newtonsoft.Json; +using System.Text.Json.Serialization; namespace Signhost.APIClient.Rest.DataObjects { public class PostbackTransaction : Transaction { - public PostbackTransaction() - { - } - - [JsonConstructor] - private PostbackTransaction( - IReadOnlyDictionary files, - DateTimeOffset? createdDateTime, - DateTimeOffset? canceledDateTime, - string cancelationReason) - : base(files, createdDateTime, canceledDateTime, cancelationReason) - { - } - - [JsonProperty("Checksum")] + [JsonPropertyName("Checksum")] public string Checksum { get; set; } } } diff --git a/src/SignhostAPIClient/Rest/DataObjects/Receiver.cs b/src/SignhostAPIClient/Rest/DataObjects/Receiver.cs index 5f446c7..be82050 100644 --- a/src/SignhostAPIClient/Rest/DataObjects/Receiver.cs +++ b/src/SignhostAPIClient/Rest/DataObjects/Receiver.cs @@ -1,6 +1,6 @@ -using System; +using System; using System.Collections.Generic; -using Newtonsoft.Json; +using System.Text.Json.Serialization; namespace Signhost.APIClient.Rest.DataObjects { diff --git a/src/SignhostAPIClient/Rest/DataObjects/ScribbleVerification.cs b/src/SignhostAPIClient/Rest/DataObjects/ScribbleVerification.cs index 64f18a7..fadb57a 100644 --- a/src/SignhostAPIClient/Rest/DataObjects/ScribbleVerification.cs +++ b/src/SignhostAPIClient/Rest/DataObjects/ScribbleVerification.cs @@ -3,8 +3,6 @@ public class ScribbleVerification : IVerification { - public string Type => "Scribble"; - public bool RequireHandsignature { get; set; } public bool ScribbleNameFixed { get; set; } diff --git a/src/SignhostAPIClient/Rest/DataObjects/Signer.cs b/src/SignhostAPIClient/Rest/DataObjects/Signer.cs index e3c8870..5d61f23 100644 --- a/src/SignhostAPIClient/Rest/DataObjects/Signer.cs +++ b/src/SignhostAPIClient/Rest/DataObjects/Signer.cs @@ -1,6 +1,6 @@ using System; using System.Collections.Generic; -using Newtonsoft.Json; +using System.Text.Json.Serialization; namespace Signhost.APIClient.Rest.DataObjects { diff --git a/src/SignhostAPIClient/Rest/DataObjects/SigningCertificateVerification.cs b/src/SignhostAPIClient/Rest/DataObjects/SigningCertificateVerification.cs deleted file mode 100644 index 98673fd..0000000 --- a/src/SignhostAPIClient/Rest/DataObjects/SigningCertificateVerification.cs +++ /dev/null @@ -1,13 +0,0 @@ -namespace Signhost.APIClient.Rest.DataObjects -{ - /// - /// Represents a verification method using a signers signing certificate - /// for example a qualified certificate. - /// - public class SigningCertificateVerification - : IVerification - { - /// - public string Type => "SigningCertificate"; - } -} diff --git a/src/SignhostAPIClient/Rest/DataObjects/SurfnetVerification.cs b/src/SignhostAPIClient/Rest/DataObjects/SurfnetVerification.cs index c65632f..dfef6fd 100644 --- a/src/SignhostAPIClient/Rest/DataObjects/SurfnetVerification.cs +++ b/src/SignhostAPIClient/Rest/DataObjects/SurfnetVerification.cs @@ -5,8 +5,6 @@ namespace Signhost.APIClient.Rest.DataObjects public class SurfnetVerification : IVerification { - public string Type => "SURFnet"; - public string Uid { get; set; } public IDictionary Attributes { get; set; } diff --git a/src/SignhostAPIClient/Rest/DataObjects/Transaction.cs b/src/SignhostAPIClient/Rest/DataObjects/Transaction.cs index 534d31e..35dbb13 100644 --- a/src/SignhostAPIClient/Rest/DataObjects/Transaction.cs +++ b/src/SignhostAPIClient/Rest/DataObjects/Transaction.cs @@ -1,55 +1,41 @@ using System; using System.Collections.Generic; -using Newtonsoft.Json; +using System.Text.Json.Serialization; namespace Signhost.APIClient.Rest.DataObjects { public class Transaction { - public Transaction() - { - } - - [JsonConstructor] - protected Transaction( - IReadOnlyDictionary files, - DateTimeOffset? createdDateTime, - DateTimeOffset? canceledDateTime, - string cancelationReason) - { - Files = files ?? new Dictionary(); - CreatedDateTime = createdDateTime; - CancelledDateTime = canceledDateTime; - CancellationReason = cancelationReason; - } - public string Id { get; set; } /// /// Gets the when the was created. /// - public DateTimeOffset? CreatedDateTime { get; } + public DateTimeOffset? CreatedDateTime { get; set; } /// /// Gets the when the was cancelled. /// Returns null if the transaction was not cancelled. /// - public DateTimeOffset? CancelledDateTime { get; } + public DateTimeOffset? CanceledDateTime { get; set; } /// /// Gets the cancellation reason when the was cancelled. /// - public string CancellationReason { get; } + public string CancellationReason { get; set; } - public IReadOnlyDictionary Files { get; private set; } + public IReadOnlyDictionary Files { get; set; } = + new Dictionary(); public TransactionStatus Status { get; set; } public bool Seal { get; set; } - public IList Signers { get; set; } = new List(); + public IList Signers { get; set; } + = new List(); - public IList Receivers { get; set; } = new List(); + public IList Receivers { get; set; } + = new List(); public string Reference { get; set; } diff --git a/src/SignhostAPIClient/Rest/DataObjects/UnknownVerification.cs b/src/SignhostAPIClient/Rest/DataObjects/UnknownVerification.cs deleted file mode 100644 index 69c17ba..0000000 --- a/src/SignhostAPIClient/Rest/DataObjects/UnknownVerification.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace Signhost.APIClient.Rest.DataObjects -{ - public class UnknownVerification - : IVerification - { - public string Type { get; set; } - } -} diff --git a/src/SignhostAPIClient/Rest/ErrorHandling/HttpResponseMessageErrorHandlingExtensions.cs b/src/SignhostAPIClient/Rest/ErrorHandling/HttpResponseMessageErrorHandlingExtensions.cs index 3ad5df5..55e09ec 100644 --- a/src/SignhostAPIClient/Rest/ErrorHandling/HttpResponseMessageErrorHandlingExtensions.cs +++ b/src/SignhostAPIClient/Rest/ErrorHandling/HttpResponseMessageErrorHandlingExtensions.cs @@ -2,8 +2,9 @@ using System.Linq; using System.Net; using System.Net.Http; +using System.Text.Json; +using System.Text.Json.Serialization; using System.Threading.Tasks; -using Newtonsoft.Json; namespace Signhost.APIClient.Rest.ErrorHandling { @@ -64,15 +65,10 @@ public static async Task EnsureSignhostSuccessStatusCodeAsy responseBody = await response.Content.ReadAsStringAsync() .ConfigureAwait(false); - var error = JsonConvert.DeserializeAnonymousType( - responseBody, - new { - Type = string.Empty, - Message = string.Empty, - }); + var error = JsonSerializer.Deserialize(responseBody); - errorType = error.Type; - errorMessage = error.Message; + errorType = error?.Type ?? string.Empty; + errorMessage = error?.Message ?? "Unknown Signhost error"; } // TO-DO: Use switch pattern in v5 @@ -116,5 +112,14 @@ public static async Task EnsureSignhostSuccessStatusCodeAsy throw exception; } + + private class ErrorResponse + { + [JsonPropertyName("type")] + public string Type { get; set; } + + [JsonPropertyName("message")] + public string Message { get; set; } + } } } diff --git a/src/SignhostAPIClient/Rest/HttpContentJsonExtensions.cs b/src/SignhostAPIClient/Rest/HttpContentJsonExtensions.cs index e205e41..b7e2836 100644 --- a/src/SignhostAPIClient/Rest/HttpContentJsonExtensions.cs +++ b/src/SignhostAPIClient/Rest/HttpContentJsonExtensions.cs @@ -1,6 +1,6 @@ using System.Net.Http; +using System.Text.Json; using System.Threading.Tasks; -using Newtonsoft.Json; namespace Signhost.APIClient.Rest { @@ -25,7 +25,7 @@ internal static async Task FromJsonAsync( var json = await httpContent.ReadAsStringAsync() .ConfigureAwait(false); - return JsonConvert.DeserializeObject(json); + return JsonSerializer.Deserialize(json, SignhostJsonSerializerOptions.Default); } } } diff --git a/src/SignhostAPIClient/Rest/ISignHostApiClient.cs b/src/SignhostAPIClient/Rest/ISignHostApiClient.cs index f4546fd..79c5a88 100644 --- a/src/SignhostAPIClient/Rest/ISignHostApiClient.cs +++ b/src/SignhostAPIClient/Rest/ISignHostApiClient.cs @@ -8,7 +8,7 @@ namespace Signhost.APIClient.Rest /// /// Interface abstracting the available Signhost API calls. /// - public interface ISignHostApiClient + public interface ISignhostApiClient { /// /// Creates a new transaction. diff --git a/src/SignhostAPIClient/Rest/ISignhostApiClientSettings.cs b/src/SignhostAPIClient/Rest/ISignhostApiClientSettings.cs index 439dc12..34c7c92 100644 --- a/src/SignhostAPIClient/Rest/ISignhostApiClientSettings.cs +++ b/src/SignhostAPIClient/Rest/ISignhostApiClientSettings.cs @@ -4,7 +4,7 @@ namespace Signhost.APIClient.Rest { public delegate void AddHeaders(string name, string value); - public interface ISignHostApiClientSettings + public interface ISignhostApiClientSettings { /// /// Gets the usertoken identifying an authorized user. diff --git a/src/SignhostAPIClient/Rest/JsonContent.cs b/src/SignhostAPIClient/Rest/JsonContent.cs index 54e72b4..295054a 100644 --- a/src/SignhostAPIClient/Rest/JsonContent.cs +++ b/src/SignhostAPIClient/Rest/JsonContent.cs @@ -1,6 +1,6 @@ using System.Net.Http; using System.Net.Http.Headers; -using Newtonsoft.Json; +using System.Text.Json; namespace Signhost.APIClient.Rest { @@ -40,7 +40,7 @@ public JsonContent(T value) private static string ToJson(T value) { - return JsonConvert.SerializeObject(value); + return JsonSerializer.Serialize(value, SignhostJsonSerializerOptions.Default); } } } diff --git a/src/SignhostAPIClient/Rest/JsonConverters/JsonBaseConverter.cs b/src/SignhostAPIClient/Rest/JsonConverters/JsonBaseConverter.cs deleted file mode 100644 index ff5642b..0000000 --- a/src/SignhostAPIClient/Rest/JsonConverters/JsonBaseConverter.cs +++ /dev/null @@ -1,33 +0,0 @@ -using System; -using System.Reflection; -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; - -namespace Signhost.APIClient.Rest.JsonConverters -{ - public abstract class JsonBaseConverter - : JsonConverter - { - public override bool CanConvert(Type objectType) - => typeof(T) -#if TYPEINFO - .GetTypeInfo().IsAssignableFrom(objectType.GetTypeInfo()); -#else - .IsAssignableFrom(objectType); -#endif - - public override object ReadJson( - JsonReader reader, - Type objectType, - object existingValue, - JsonSerializer serializer) - { - var jsonObject = JObject.Load(reader); - var target = Create(objectType, jsonObject); - serializer.Populate(jsonObject.CreateReader(), target); - return target; - } - - protected abstract T Create(Type objectType, JObject jsonObject); - } -} diff --git a/src/SignhostAPIClient/Rest/JsonConverters/JsonVerificationConverter.cs b/src/SignhostAPIClient/Rest/JsonConverters/JsonVerificationConverter.cs deleted file mode 100644 index 4e1f9fa..0000000 --- a/src/SignhostAPIClient/Rest/JsonConverters/JsonVerificationConverter.cs +++ /dev/null @@ -1,93 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Reflection; -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; -using Signhost.APIClient.Rest.DataObjects; - -namespace Signhost.APIClient.Rest.JsonConverters -{ - internal class JsonVerificationConverter - : JsonBaseConverter - { -#if TYPEINFO - private static readonly IDictionary VerificationTypes = -#else - private static readonly IDictionary VerificationTypes = -#endif - CreateVerificationTypeMap(); - - public override bool CanWrite - => false; - - public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) - => new NotImplementedException(); - - /// - /// Adds an additional verification type to the - /// map. - /// - /// - internal static void RegisterVerification() - where T : IVerification - { - var verification = (IVerification)Activator.CreateInstance(typeof(T)); - - VerificationTypes[verification.Type] = -#if TYPEINFO - typeof(T).GetTypeInfo(); -#else - typeof(T); -#endif - } - - protected override IVerification Create( - Type objectType, - JObject jsonObject) - { - var typeName = jsonObject["Type"]?.ToString(); - - if (VerificationTypes.TryGetValue(typeName, out var verificationType)) { -#if TYPEINFO - return (IVerification)Activator.CreateInstance(verificationType.AsType()); -#else - return (IVerification)Activator.CreateInstance(verificationType); -#endif - } - - return new UnknownVerification(); - } - -#if TYPEINFO - private static Dictionary CreateVerificationTypeMap() - { - return typeof(JsonVerificationConverter).GetTypeInfo().Assembly.ExportedTypes - .Select(t => t.GetTypeInfo()) - .Where(t => typeof(IVerification).GetTypeInfo().IsAssignableFrom(t)) - .Where(t => !t.IsInterface && !t.IsAbstract) -#pragma warning disable SA1008 // Opening parenthesis must be spaced correctly - .Select(t => ( - typeInfo: t, - instance: (IVerification)Activator.CreateInstance(t.AsType()))) -#pragma warning restore SA1008 // Opening parenthesis must be spaced correctly - .Where(t => t.instance.Type != null) - .ToDictionary(t => t.instance.Type, t => t.typeInfo); - } -#else - private static Dictionary CreateVerificationTypeMap() - { - return typeof(JsonVerificationConverter).Assembly.ExportedTypes - .Where(t => typeof(IVerification).IsAssignableFrom(t)) - .Where(t => !t.IsInterface && !t.IsAbstract) -#pragma warning disable SA1008 // Opening parenthesis must be spaced correctly - .Select(t => ( - type: t, - instance: (IVerification)Activator.CreateInstance(t))) -#pragma warning restore SA1008 // Opening parenthesis must be spaced correctly - .Where(t => t.instance.Type is not null) - .ToDictionary(t => t.instance.Type, t => t.type); - } -#endif - } -} diff --git a/src/SignhostAPIClient/Rest/JsonConverters/LevelEnumConverter.cs b/src/SignhostAPIClient/Rest/JsonConverters/LevelEnumConverter.cs index e0e5aee..07c2031 100644 --- a/src/SignhostAPIClient/Rest/JsonConverters/LevelEnumConverter.cs +++ b/src/SignhostAPIClient/Rest/JsonConverters/LevelEnumConverter.cs @@ -1,69 +1,56 @@ using System; -using System.Reflection; -using Newtonsoft.Json; +using System.Text.Json; +using System.Text.Json.Serialization; using Signhost.APIClient.Rest.DataObjects; namespace Signhost.APIClient.Rest.JsonConverters { /// - /// JSON converter for converting the enum. + /// JSON converter factory for converting the enum. /// Invalid values are mapped to . /// internal class LevelEnumConverter - : JsonConverter + : JsonConverter { - /// - public override bool CanWrite => false; - - /// - public override bool CanConvert(Type objectType) - => IsLevelEnum(GetUnderlyingType(objectType)); - - /// - public override object ReadJson( - JsonReader reader, - Type objectType, - object existingValue, - JsonSerializer serializer) + public override Level? Read( + ref Utf8JsonReader reader, + Type typeToConvert, + JsonSerializerOptions options) { - var value = reader.Value as string; + if (reader.TokenType == JsonTokenType.Null) { + return null; + } - if (value != null) { - if (Enum.TryParse(value, out Level level)) { + if (reader.TokenType == JsonTokenType.String) { + var value = reader.GetString() ?? string.Empty; + if (Enum.TryParse(value, out var level)) { return level; } return Level.Unknown; } - return null; - } - - /// - public override void WriteJson( - JsonWriter writer, - object value, - JsonSerializer serializer) - => throw new NotImplementedException(); + if (reader.TokenType == JsonTokenType.Number) { + int value = reader.GetInt32(); + if (Enum.IsDefined(typeof(Level), value)) { + return (Level)value; + } + } - private static Type GetUnderlyingType(Type type) - => -#if TYPEINFO - type.GetTypeInfo().IsGenericType && -#else - type.IsGenericType && -#endif - type.GetGenericTypeDefinition() == typeof(Nullable<>) - ? Nullable.GetUnderlyingType(type) - : type; + return Level.Unknown; + } - private static bool IsLevelEnum(Type type) - => -#if TYPEINFO - type.GetTypeInfo().IsEnum && -#else - type.IsEnum && -#endif - type == typeof(Level); + public override void Write( + Utf8JsonWriter writer, + Level? value, + JsonSerializerOptions options) + { + if (value is null) { + writer.WriteNullValue(); + } + else { + writer.WriteStringValue(value.ToString()); + } + } } } diff --git a/src/SignhostAPIClient/Rest/SignHostApiClient.cs b/src/SignhostAPIClient/Rest/SignHostApiClient.cs index dd2e3a8..780aa1b 100644 --- a/src/SignhostAPIClient/Rest/SignHostApiClient.cs +++ b/src/SignhostAPIClient/Rest/SignHostApiClient.cs @@ -12,43 +12,43 @@ namespace Signhost.APIClient.Rest { /// - /// Implements the interface which provides + /// Implements the interface which provides /// an signhost api client implementation. /// - public class SignHostApiClient - : ISignHostApiClient + public class SignhostApiClient + : ISignhostApiClient , IDisposable { private const string ApiVersion = "v1"; - private static readonly string Version = typeof(SignHostApiClient) + private static readonly string Version = typeof(SignhostApiClient) #if TYPEINFO .GetTypeInfo() #endif .Assembly.GetCustomAttribute() .Version; - private readonly ISignHostApiClientSettings settings; + private readonly ISignhostApiClientSettings settings; private readonly HttpClient client; /// - /// Initializes a new instance of the class. - /// Set your usertoken and APPKey by creating a . + /// Initializes a new instance of the class. + /// Set your usertoken and APPKey by creating a . /// - /// - public SignHostApiClient(ISignHostApiClientSettings settings) + /// + public SignhostApiClient(ISignhostApiClientSettings settings) : this(settings, new HttpClient()) { } /// - /// Initializes a new instance of the class. - /// Set your usertoken and APPKey by creating a . + /// Initializes a new instance of the class. + /// Set your usertoken and APPKey by creating a . /// - /// + /// /// to use for all http calls. - public SignHostApiClient( - ISignHostApiClientSettings settings, + public SignhostApiClient( + ISignhostApiClientSettings settings, HttpClient httpClient) { this.settings = settings; @@ -75,16 +75,6 @@ private string ApplicationHeader private string AuthorizationHeader => $"APIKey {settings.UserToken}"; - /// - /// Globally register an additional verification type. - /// - /// to - public static void RegisterVerification() - where T : IVerification - { - JsonConverters.JsonVerificationConverter.RegisterVerification(); - } - /// public async Task CreateTransactionAsync( Transaction transaction) diff --git a/src/SignhostAPIClient/Rest/SignHostApiClientSettings.cs b/src/SignhostAPIClient/Rest/SignHostApiClientSettings.cs index f93a6c9..b4afed6 100644 --- a/src/SignhostAPIClient/Rest/SignHostApiClientSettings.cs +++ b/src/SignhostAPIClient/Rest/SignHostApiClientSettings.cs @@ -2,18 +2,18 @@ namespace Signhost.APIClient.Rest { - public class SignHostApiClientSettings - : ISignHostApiClientSettings + public class SignhostApiClientSettings + : ISignhostApiClientSettings { public const string DefaultEndpoint = "https://api.signhost.com/api/"; - public SignHostApiClientSettings(string appkey, string userToken) + public SignhostApiClientSettings(string appkey, string userToken) { APPKey = appkey; UserToken = userToken; } - public SignHostApiClientSettings(string appkey) + public SignhostApiClientSettings(string appkey) { APPKey = appkey; } diff --git a/src/SignhostAPIClient/Rest/SignhostApiReceiver.cs b/src/SignhostAPIClient/Rest/SignhostApiReceiver.cs index a3bfde6..bdffe93 100644 --- a/src/SignhostAPIClient/Rest/SignhostApiReceiver.cs +++ b/src/SignhostAPIClient/Rest/SignhostApiReceiver.cs @@ -3,7 +3,7 @@ using System.Linq; using System.Security.Cryptography; using System.Text; -using Newtonsoft.Json; +using System.Text.Json; using Signhost.APIClient.Rest; using Signhost.APIClient.Rest.DataObjects; @@ -64,12 +64,10 @@ private string CalculateChecksumFromPostback(PostbackTransaction postback) } } - private PostbackTransaction DeserializeToPostbackTransaction(string body) - { - return JsonConvert.DeserializeObject(body); - } - - private string GetChecksumFromHeadersOrPostback( + private PostbackTransaction DeserializeToPostbackTransaction(string body) + { + return JsonSerializer.Deserialize(body, SignhostJsonSerializerOptions.Default); + } private string GetChecksumFromHeadersOrPostback( IDictionary headers, PostbackTransaction postback) { diff --git a/src/SignhostAPIClient/Rest/SignhostJsonSerializerOptions.cs b/src/SignhostAPIClient/Rest/SignhostJsonSerializerOptions.cs new file mode 100644 index 0000000..3784502 --- /dev/null +++ b/src/SignhostAPIClient/Rest/SignhostJsonSerializerOptions.cs @@ -0,0 +1,23 @@ +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace Signhost.APIClient.Rest +{ + /// + /// Centralized JSON serialization options for Signhost API. + /// + public static class SignhostJsonSerializerOptions + { + /// + /// Gets the default JSON serializer options. + /// + public static JsonSerializerOptions Default { get; } = new JsonSerializerOptions + { + PropertyNameCaseInsensitive = true, + DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull, + Converters = { + new JsonStringEnumConverter(), + }, + }; + } +} diff --git a/src/SignhostAPIClient/SignhostAPIClient.csproj b/src/SignhostAPIClient/SignhostAPIClient.csproj index 8c6a83a..7309d33 100644 --- a/src/SignhostAPIClient/SignhostAPIClient.csproj +++ b/src/SignhostAPIClient/SignhostAPIClient.csproj @@ -42,7 +42,7 @@ - + From 00e4a42b46c007baa598fe4c86dec9181a5a5a08 Mon Sep 17 00:00:00 2001 From: Anthony Timmers Date: Fri, 21 Nov 2025 18:55:04 +0100 Subject: [PATCH 07/28] Remove NCrunch --- .gitattributes | 3 -- .gitignore | 5 ---- .../SignhostAPIClient.Tests.v2.ncrunchproject | 26 ----------------- src/SignhostAPIClient.v2.ncrunchsolution | 14 --------- src/SignhostAPIClient.v3.ncrunchsolution | 6 ---- .../SignhostAPIClient.v2.ncrunchproject | 29 ------------------- 6 files changed, 83 deletions(-) delete mode 100644 src/SignhostAPIClient.Tests/SignhostAPIClient.Tests.v2.ncrunchproject delete mode 100644 src/SignhostAPIClient.v2.ncrunchsolution delete mode 100644 src/SignhostAPIClient.v3.ncrunchsolution delete mode 100644 src/SignhostAPIClient/SignhostAPIClient.v2.ncrunchproject diff --git a/.gitattributes b/.gitattributes index 4eb59c0..48d3a28 100644 --- a/.gitattributes +++ b/.gitattributes @@ -16,9 +16,6 @@ *.edmx text *.resx text -*.ncrunchproject text -*.ncrunchsolution text - # Custom for Visual Studio *.sln text eol=crlf merge=union *.csproj text diff --git a/.gitignore b/.gitignore index 7494d73..0e70063 100644 --- a/.gitignore +++ b/.gitignore @@ -104,11 +104,6 @@ _TeamCity* # DotCover is a Code Coverage Tool *.dotCover -# NCrunch -_NCrunch_* -.*crunch*.local.xml -*.v3.ncrunchsolution.user - # MightyMoose *.mm.* AutoTest.Net/ diff --git a/src/SignhostAPIClient.Tests/SignhostAPIClient.Tests.v2.ncrunchproject b/src/SignhostAPIClient.Tests/SignhostAPIClient.Tests.v2.ncrunchproject deleted file mode 100644 index 1f00ab7..0000000 --- a/src/SignhostAPIClient.Tests/SignhostAPIClient.Tests.v2.ncrunchproject +++ /dev/null @@ -1,26 +0,0 @@ - - true - 1000 - false - false - false - true - false - false - false - false - false - true - true - false - true - true - true - 60000 - - - - AutoDetect - STA - x86 - \ No newline at end of file diff --git a/src/SignhostAPIClient.v2.ncrunchsolution b/src/SignhostAPIClient.v2.ncrunchsolution deleted file mode 100644 index b98737f..0000000 --- a/src/SignhostAPIClient.v2.ncrunchsolution +++ /dev/null @@ -1,14 +0,0 @@ - - 1 - false - false - true - UseDynamicAnalysis - UseStaticAnalysis - UseStaticAnalysis - UseStaticAnalysis - UseDynamicAnalysis - - - - \ No newline at end of file diff --git a/src/SignhostAPIClient.v3.ncrunchsolution b/src/SignhostAPIClient.v3.ncrunchsolution deleted file mode 100644 index ff7af7e..0000000 --- a/src/SignhostAPIClient.v3.ncrunchsolution +++ /dev/null @@ -1,6 +0,0 @@ - - - True - True - - diff --git a/src/SignhostAPIClient/SignhostAPIClient.v2.ncrunchproject b/src/SignhostAPIClient/SignhostAPIClient.v2.ncrunchproject deleted file mode 100644 index 3f48b96..0000000 --- a/src/SignhostAPIClient/SignhostAPIClient.v2.ncrunchproject +++ /dev/null @@ -1,29 +0,0 @@ - - true - 1000 - false - false - false - true - false - false - false - false - false - true - true - false - true - true - true - 60000 - - - - AutoDetect - STA - x86 - - - - \ No newline at end of file From b84019635530c37a9477bcbcec6d9df26974d366 Mon Sep 17 00:00:00 2001 From: Anthony Timmers Date: Fri, 21 Nov 2025 18:57:58 +0100 Subject: [PATCH 08/28] Remove unused using directives --- src/SignhostAPIClient.Tests/SignhostApiClientTests.cs | 1 - src/SignhostAPIClient.Tests/SignhostApiReceiverTests.cs | 8 +------- src/SignhostAPIClient/Rest/ApiResponse.cs | 5 +---- src/SignhostAPIClient/Rest/DataObjects/ActivityType.cs | 6 +----- .../Rest/DataObjects/ConsentVerification.cs | 6 +----- .../Rest/DataObjects/IPAddressVerification.cs | 4 ---- .../Rest/DataObjects/PostbackTransaction.cs | 2 -- src/SignhostAPIClient/Rest/DataObjects/Transaction.cs | 1 - .../Rest/ErrorHandling/BadAuthorizationException.cs | 5 ----- .../Rest/ErrorHandling/BadRequestException.cs | 1 - .../Rest/ErrorHandling/DefaultSignhostException.cs | 1 - src/SignhostAPIClient/Rest/ErrorHandling/GoneException.cs | 1 - .../Rest/ErrorHandling/InternalServerErrorException.cs | 1 - .../Rest/ErrorHandling/NotFoundException.cs | 1 - .../Rest/ErrorHandling/SignhostException.cs | 1 - .../Rest/ErrorHandling/SignhostRestApiClientException.cs | 1 - src/SignhostAPIClient/Rest/FileDigestOptions.cs | 6 +----- src/SignhostAPIClient/Rest/ISignhostApiReceiver.cs | 3 --- src/SignhostAPIClient/Rest/SignhostApiReceiverSettings.cs | 4 +--- 19 files changed, 6 insertions(+), 52 deletions(-) diff --git a/src/SignhostAPIClient.Tests/SignhostApiClientTests.cs b/src/SignhostAPIClient.Tests/SignhostApiClientTests.cs index 0e5f1e9..fc3c085 100644 --- a/src/SignhostAPIClient.Tests/SignhostApiClientTests.cs +++ b/src/SignhostAPIClient.Tests/SignhostApiClientTests.cs @@ -9,7 +9,6 @@ using RichardSzalay.MockHttp; using System.Net; using SignhostAPIClient.Tests.JSON; -using System.Text.Json.Serialization; namespace Signhost.APIClient.Rest.Tests { diff --git a/src/SignhostAPIClient.Tests/SignhostApiReceiverTests.cs b/src/SignhostAPIClient.Tests/SignhostApiReceiverTests.cs index ba6dd32..198f95b 100644 --- a/src/SignhostAPIClient.Tests/SignhostApiReceiverTests.cs +++ b/src/SignhostAPIClient.Tests/SignhostApiReceiverTests.cs @@ -1,13 +1,7 @@ -using System; -using System.Threading.Tasks; -using System.Net.Http; -using System.IO; -using Xunit; +using Xunit; using Signhost.APIClient.Rest.DataObjects; using FluentAssertions; using System.Collections.Generic; -using RichardSzalay.MockHttp; -using System.Net; using SignhostAPIClient.Tests.JSON; namespace Signhost.APIClient.Rest.Tests diff --git a/src/SignhostAPIClient/Rest/ApiResponse.cs b/src/SignhostAPIClient/Rest/ApiResponse.cs index 880dcd3..42e2134 100644 --- a/src/SignhostAPIClient/Rest/ApiResponse.cs +++ b/src/SignhostAPIClient/Rest/ApiResponse.cs @@ -1,8 +1,5 @@ -using System; -using System.Collections.Generic; -using System.Net; +using System.Net; using System.Net.Http; -using System.Text; namespace Signhost.APIClient.Rest { diff --git a/src/SignhostAPIClient/Rest/DataObjects/ActivityType.cs b/src/SignhostAPIClient/Rest/DataObjects/ActivityType.cs index 4b85586..576f9eb 100644 --- a/src/SignhostAPIClient/Rest/DataObjects/ActivityType.cs +++ b/src/SignhostAPIClient/Rest/DataObjects/ActivityType.cs @@ -1,8 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Text; - -namespace Signhost.APIClient.Rest.DataObjects +namespace Signhost.APIClient.Rest.DataObjects { /// /// type. diff --git a/src/SignhostAPIClient/Rest/DataObjects/ConsentVerification.cs b/src/SignhostAPIClient/Rest/DataObjects/ConsentVerification.cs index ec51d08..0028961 100644 --- a/src/SignhostAPIClient/Rest/DataObjects/ConsentVerification.cs +++ b/src/SignhostAPIClient/Rest/DataObjects/ConsentVerification.cs @@ -1,8 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Text; - -namespace Signhost.APIClient.Rest.DataObjects +namespace Signhost.APIClient.Rest.DataObjects { /// /// Adds a consent verification screen diff --git a/src/SignhostAPIClient/Rest/DataObjects/IPAddressVerification.cs b/src/SignhostAPIClient/Rest/DataObjects/IPAddressVerification.cs index 5e78954..cb68fcd 100644 --- a/src/SignhostAPIClient/Rest/DataObjects/IPAddressVerification.cs +++ b/src/SignhostAPIClient/Rest/DataObjects/IPAddressVerification.cs @@ -1,7 +1,3 @@ -using System; -using System.Collections.Generic; -using System.Text; - namespace Signhost.APIClient.Rest.DataObjects { /// diff --git a/src/SignhostAPIClient/Rest/DataObjects/PostbackTransaction.cs b/src/SignhostAPIClient/Rest/DataObjects/PostbackTransaction.cs index 76a2726..ea57648 100644 --- a/src/SignhostAPIClient/Rest/DataObjects/PostbackTransaction.cs +++ b/src/SignhostAPIClient/Rest/DataObjects/PostbackTransaction.cs @@ -1,5 +1,3 @@ -using System; -using System.Collections.Generic; using System.Text.Json.Serialization; namespace Signhost.APIClient.Rest.DataObjects diff --git a/src/SignhostAPIClient/Rest/DataObjects/Transaction.cs b/src/SignhostAPIClient/Rest/DataObjects/Transaction.cs index 35dbb13..6616633 100644 --- a/src/SignhostAPIClient/Rest/DataObjects/Transaction.cs +++ b/src/SignhostAPIClient/Rest/DataObjects/Transaction.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.Text.Json.Serialization; namespace Signhost.APIClient.Rest.DataObjects { diff --git a/src/SignhostAPIClient/Rest/ErrorHandling/BadAuthorizationException.cs b/src/SignhostAPIClient/Rest/ErrorHandling/BadAuthorizationException.cs index 7dc3a19..63dde05 100644 --- a/src/SignhostAPIClient/Rest/ErrorHandling/BadAuthorizationException.cs +++ b/src/SignhostAPIClient/Rest/ErrorHandling/BadAuthorizationException.cs @@ -1,9 +1,4 @@ using System; -using System.Collections.Generic; -using System.Linq; -using System.Runtime.Serialization; -using System.Text; -using System.Threading.Tasks; namespace Signhost.APIClient.Rest.ErrorHandling { diff --git a/src/SignhostAPIClient/Rest/ErrorHandling/BadRequestException.cs b/src/SignhostAPIClient/Rest/ErrorHandling/BadRequestException.cs index 8fb0d52..d6041ab 100644 --- a/src/SignhostAPIClient/Rest/ErrorHandling/BadRequestException.cs +++ b/src/SignhostAPIClient/Rest/ErrorHandling/BadRequestException.cs @@ -1,5 +1,4 @@ using System; -using System.Runtime.Serialization; namespace Signhost.APIClient.Rest.ErrorHandling { diff --git a/src/SignhostAPIClient/Rest/ErrorHandling/DefaultSignhostException.cs b/src/SignhostAPIClient/Rest/ErrorHandling/DefaultSignhostException.cs index ca6fb12..8c9a054 100644 --- a/src/SignhostAPIClient/Rest/ErrorHandling/DefaultSignhostException.cs +++ b/src/SignhostAPIClient/Rest/ErrorHandling/DefaultSignhostException.cs @@ -1,5 +1,4 @@ using System; -using System.Runtime.Serialization; namespace Signhost.APIClient.Rest.ErrorHandling { diff --git a/src/SignhostAPIClient/Rest/ErrorHandling/GoneException.cs b/src/SignhostAPIClient/Rest/ErrorHandling/GoneException.cs index b902fe6..08e8ce8 100644 --- a/src/SignhostAPIClient/Rest/ErrorHandling/GoneException.cs +++ b/src/SignhostAPIClient/Rest/ErrorHandling/GoneException.cs @@ -1,5 +1,4 @@ using System; -using System.Runtime.Serialization; namespace Signhost.APIClient.Rest.ErrorHandling { diff --git a/src/SignhostAPIClient/Rest/ErrorHandling/InternalServerErrorException.cs b/src/SignhostAPIClient/Rest/ErrorHandling/InternalServerErrorException.cs index 927c49f..0100e31 100644 --- a/src/SignhostAPIClient/Rest/ErrorHandling/InternalServerErrorException.cs +++ b/src/SignhostAPIClient/Rest/ErrorHandling/InternalServerErrorException.cs @@ -1,6 +1,5 @@ using System; using System.Net.Http.Headers; -using System.Runtime.Serialization; namespace Signhost.APIClient.Rest.ErrorHandling { diff --git a/src/SignhostAPIClient/Rest/ErrorHandling/NotFoundException.cs b/src/SignhostAPIClient/Rest/ErrorHandling/NotFoundException.cs index ff21de4..6c9eda4 100644 --- a/src/SignhostAPIClient/Rest/ErrorHandling/NotFoundException.cs +++ b/src/SignhostAPIClient/Rest/ErrorHandling/NotFoundException.cs @@ -1,5 +1,4 @@ using System; -using System.Runtime.Serialization; namespace Signhost.APIClient.Rest.ErrorHandling { diff --git a/src/SignhostAPIClient/Rest/ErrorHandling/SignhostException.cs b/src/SignhostAPIClient/Rest/ErrorHandling/SignhostException.cs index a0e7bcf..2e5db3d 100644 --- a/src/SignhostAPIClient/Rest/ErrorHandling/SignhostException.cs +++ b/src/SignhostAPIClient/Rest/ErrorHandling/SignhostException.cs @@ -1,5 +1,4 @@ using System; -using System.Runtime.Serialization; namespace Signhost.APIClient.Rest.ErrorHandling { diff --git a/src/SignhostAPIClient/Rest/ErrorHandling/SignhostRestApiClientException.cs b/src/SignhostAPIClient/Rest/ErrorHandling/SignhostRestApiClientException.cs index ff620e0..e437036 100644 --- a/src/SignhostAPIClient/Rest/ErrorHandling/SignhostRestApiClientException.cs +++ b/src/SignhostAPIClient/Rest/ErrorHandling/SignhostRestApiClientException.cs @@ -1,5 +1,4 @@ using System; -using System.Runtime.Serialization; namespace Signhost.APIClient.Rest.ErrorHandling { diff --git a/src/SignhostAPIClient/Rest/FileDigestOptions.cs b/src/SignhostAPIClient/Rest/FileDigestOptions.cs index 02d4248..b71e0eb 100644 --- a/src/SignhostAPIClient/Rest/FileDigestOptions.cs +++ b/src/SignhostAPIClient/Rest/FileDigestOptions.cs @@ -1,8 +1,4 @@ -using System; -using System.IO; -using System.Security.Cryptography; - -namespace Signhost.APIClient.Rest +namespace Signhost.APIClient.Rest { /// /// File digest options for file uploads diff --git a/src/SignhostAPIClient/Rest/ISignhostApiReceiver.cs b/src/SignhostAPIClient/Rest/ISignhostApiReceiver.cs index ac8441a..7778deb 100644 --- a/src/SignhostAPIClient/Rest/ISignhostApiReceiver.cs +++ b/src/SignhostAPIClient/Rest/ISignhostApiReceiver.cs @@ -1,7 +1,4 @@ -using System; using System.Collections.Generic; -using System.Threading.Tasks; -using Signhost.APIClient.Rest; using Signhost.APIClient.Rest.DataObjects; namespace Signhost.APIClient.Rest diff --git a/src/SignhostAPIClient/Rest/SignhostApiReceiverSettings.cs b/src/SignhostAPIClient/Rest/SignhostApiReceiverSettings.cs index cb8fd62..d3d962a 100644 --- a/src/SignhostAPIClient/Rest/SignhostApiReceiverSettings.cs +++ b/src/SignhostAPIClient/Rest/SignhostApiReceiverSettings.cs @@ -1,6 +1,4 @@ -using System; - -namespace Signhost.APIClient.Rest +namespace Signhost.APIClient.Rest { /// /// Registers the necessary settings for the class. From 0a0ec911f741ccd451405835a29bb25d6a694493 Mon Sep 17 00:00:00 2001 From: Anthony Timmers Date: Fri, 21 Nov 2025 19:06:48 +0100 Subject: [PATCH 09/28] Fix or suppress StyleCop Analyzer warnings --- src/SignhostAPIClient/Rest/JsonContent.cs | 10 +++++----- src/SignhostAPIClient/Rest/SignHostApiClient.cs | 5 +++-- src/SignhostAPIClient/Rest/SignhostApiReceiver.cs | 14 +++++++++----- src/signhost.ruleset | 4 ++++ 4 files changed, 21 insertions(+), 12 deletions(-) diff --git a/src/SignhostAPIClient/Rest/JsonContent.cs b/src/SignhostAPIClient/Rest/JsonContent.cs index 295054a..837d627 100644 --- a/src/SignhostAPIClient/Rest/JsonContent.cs +++ b/src/SignhostAPIClient/Rest/JsonContent.cs @@ -5,16 +5,16 @@ namespace Signhost.APIClient.Rest { /// - /// Helper class + /// See the helper class. /// internal static class JsonContent { /// /// Creates a new . /// - /// Type to serialize - /// Value to serialize - /// + /// Type to serialize. + /// Value to serialize. + /// . internal static JsonContent From(T value) { return new JsonContent(value); @@ -31,7 +31,7 @@ internal class JsonContent /// /// Initializes a new instance of the class. /// - /// Value to serialize + /// Value to serialize. public JsonContent(T value) : base(ToJson(value)) { diff --git a/src/SignhostAPIClient/Rest/SignHostApiClient.cs b/src/SignhostAPIClient/Rest/SignHostApiClient.cs index 780aa1b..a453f2c 100644 --- a/src/SignhostAPIClient/Rest/SignHostApiClient.cs +++ b/src/SignhostAPIClient/Rest/SignHostApiClient.cs @@ -35,7 +35,7 @@ public class SignhostApiClient /// Initializes a new instance of the class. /// Set your usertoken and APPKey by creating a . /// - /// + /// . public SignhostApiClient(ISignhostApiClientSettings settings) : this(settings, new HttpClient()) { @@ -45,7 +45,7 @@ public SignhostApiClient(ISignhostApiClientSettings settings) /// Initializes a new instance of the class. /// Set your usertoken and APPKey by creating a . /// - /// + /// . /// to use for all http calls. public SignhostApiClient( ISignhostApiClientSettings settings, @@ -153,6 +153,7 @@ public async Task GetTransactionAsync( return response.Value; } + /// public async Task DeleteTransactionAsync( string transactionId, CancellationToken cancellationToken = default) diff --git a/src/SignhostAPIClient/Rest/SignhostApiReceiver.cs b/src/SignhostAPIClient/Rest/SignhostApiReceiver.cs index bdffe93..da6c189 100644 --- a/src/SignhostAPIClient/Rest/SignhostApiReceiver.cs +++ b/src/SignhostAPIClient/Rest/SignhostApiReceiver.cs @@ -22,7 +22,9 @@ public class SignhostApiReceiver /// Initializes a new instance of the class. /// Set your SharedSecret by creating a . /// - /// + /// + /// Settings for the receiver. + /// public SignhostApiReceiver(SignhostApiReceiverSettings receiverSettings) { this.settings = receiverSettings; @@ -64,10 +66,12 @@ private string CalculateChecksumFromPostback(PostbackTransaction postback) } } - private PostbackTransaction DeserializeToPostbackTransaction(string body) - { - return JsonSerializer.Deserialize(body, SignhostJsonSerializerOptions.Default); - } private string GetChecksumFromHeadersOrPostback( + private PostbackTransaction DeserializeToPostbackTransaction(string body) + { + return JsonSerializer.Deserialize(body, SignhostJsonSerializerOptions.Default); + } + + private string GetChecksumFromHeadersOrPostback( IDictionary headers, PostbackTransaction postback) { diff --git a/src/signhost.ruleset b/src/signhost.ruleset index 6d703aa..51c9a98 100644 --- a/src/signhost.ruleset +++ b/src/signhost.ruleset @@ -6,7 +6,11 @@ + + + + From 2164fc83a6f1b8d880e9d72e7f5da6ad89e8d28e Mon Sep 17 00:00:00 2001 From: Anthony Timmers Date: Thu, 27 Nov 2025 19:43:57 +0100 Subject: [PATCH 10/28] Remove obsolete exception --- .../Rest/ErrorHandling/SignhostException.cs | 36 ------------------- 1 file changed, 36 deletions(-) delete mode 100644 src/SignhostAPIClient/Rest/ErrorHandling/SignhostException.cs diff --git a/src/SignhostAPIClient/Rest/ErrorHandling/SignhostException.cs b/src/SignhostAPIClient/Rest/ErrorHandling/SignhostException.cs deleted file mode 100644 index 2e5db3d..0000000 --- a/src/SignhostAPIClient/Rest/ErrorHandling/SignhostException.cs +++ /dev/null @@ -1,36 +0,0 @@ -using System; - -namespace Signhost.APIClient.Rest.ErrorHandling -{ - // TO-DO: Remove in v5 - [Serializable] - [Obsolete("Unused will be removed")] - public class SignhostException - : SignhostRestApiClientException - { - public SignhostException() - : base() - { - } - - public SignhostException(string message) - : base(message) - { - } - - public SignhostException(string message, Exception innerException) - : base(message, innerException) - { - HelpLink = "https://api.signhost.com/Help"; - } - -#if SERIALIZABLE - protected SignhostException( - SerializationInfo info, - StreamingContext context) - : base(info, context) - { - } -#endif - } -} From a1fd012ea309c7b64b10c861b1c422329120fe6d Mon Sep 17 00:00:00 2001 From: Anthony Timmers Date: Fri, 21 Nov 2025 19:07:22 +0100 Subject: [PATCH 11/28] Reformat use file-scoped namespaces --- .../LevelEnumConverterTests.cs | 103 +- src/SignhostAPIClient.Tests/PostbackTests.cs | 209 ++-- .../SignhostApiClientTests.cs | 1003 ++++++++--------- .../SignhostApiReceiverTests.cs | 115 +- src/SignhostAPIClient/Rest/ApiResponse.cs | 49 +- .../Rest/DataObjects/Activity.cs | 19 +- .../Rest/DataObjects/ActivityType.cs | 265 +++-- .../Rest/DataObjects/ConsentVerification.cs | 15 +- .../Rest/DataObjects/CscVerification.cs | 51 +- .../DataObjects/DeleteTransactionOptions.cs | 25 +- .../Rest/DataObjects/DigidVerification.cs | 13 +- .../DataObjects/EherkenningVerification.cs | 25 +- .../DataObjects/EidasLoginVerification.cs | 61 +- .../Rest/DataObjects/Field.cs | 17 +- .../Rest/DataObjects/FileEntry.cs | 11 +- .../Rest/DataObjects/FileLink.cs | 13 +- .../Rest/DataObjects/FileMeta.cs | 33 +- .../Rest/DataObjects/FileSignerMeta.cs | 9 +- .../Rest/DataObjects/IPAddressVerification.cs | 19 +- .../Rest/DataObjects/IVerification.cs | 38 +- .../Rest/DataObjects/IdealVerification.cs | 15 +- .../Rest/DataObjects/IdinVerification.cs | 19 +- .../ItsmeIdentificationVerification.cs | 19 +- .../Rest/DataObjects/Level.cs | 41 +- .../Rest/DataObjects/Location.cs | 25 +- .../Rest/DataObjects/OidcVerification.cs | 19 +- .../Rest/DataObjects/OnfidoVerification.cs | 43 +- .../DataObjects/PhoneNumberVerification.cs | 13 +- .../Rest/DataObjects/PostbackTransaction.cs | 13 +- .../Rest/DataObjects/Receiver.cs | 45 +- .../Rest/DataObjects/ScribbleVerification.cs | 15 +- .../Rest/DataObjects/Signer.cs | 91 +- .../Rest/DataObjects/SurfnetVerification.cs | 13 +- .../Rest/DataObjects/Transaction.cs | 65 +- .../Rest/DataObjects/TransactionStatus.cs | 89 +- .../BadAuthorizationException.cs | 53 +- .../Rest/ErrorHandling/BadRequestException.cs | 51 +- .../ErrorHandling/DefaultSignhostException.cs | 41 +- .../Rest/ErrorHandling/GoneException.cs | 95 +- ...pResponseMessageErrorHandlingExtensions.cs | 213 ++-- .../InternalServerErrorException.cs | 63 +- .../Rest/ErrorHandling/NotFoundException.cs | 47 +- .../ErrorHandling/OutOfCreditsException.cs | 25 +- .../SignhostRestApiClientException.cs | 47 +- .../Rest/FileDigestOptions.cs | 41 +- .../Rest/FileUploadOptions.cs | 19 +- .../Rest/HttpContentJsonExtensions.cs | 39 +- .../Rest/ISignHostApiClient.cs | 483 ++++---- .../Rest/ISignhostApiClientSettings.cs | 37 +- .../Rest/ISignhostApiReceiver.cs | 31 +- src/SignhostAPIClient/Rest/JsonContent.cs | 59 +- .../Rest/JsonConverters/LevelEnumConverter.cs | 77 +- .../Rest/SignHostApiClient.cs | 809 +++++++------ .../Rest/SignHostApiClientSettings.cs | 37 +- .../Rest/SignhostApiReceiver.cs | 131 ++- .../Rest/SignhostApiReceiverSettings.cs | 29 +- .../Rest/SignhostJsonSerializerOptions.cs | 29 +- .../StreamContentDigestOptionsExtensions.cs | 135 ++- .../Rest/UriPathExtensions.cs | 31 +- 59 files changed, 2590 insertions(+), 2650 deletions(-) diff --git a/src/SignhostAPIClient.Tests/LevelEnumConverterTests.cs b/src/SignhostAPIClient.Tests/LevelEnumConverterTests.cs index 534d548..92ca645 100644 --- a/src/SignhostAPIClient.Tests/LevelEnumConverterTests.cs +++ b/src/SignhostAPIClient.Tests/LevelEnumConverterTests.cs @@ -6,74 +6,73 @@ using System.Collections.Generic; using Xunit; -namespace Signhost.APIClient.Rest.Tests +namespace Signhost.APIClient.Rest.Tests; + +public class LevelEnumConverterTests { - public class LevelEnumConverterTests + [Fact] + public void when_Level_is_null_should_deserialize_to_null() { - [Fact] - public void when_Level_is_null_should_deserialize_to_null() - { - // Arrange - const string json = "{\"Type\":\"eIDAS Login\",\"Level\":null}"; + // Arrange + const string json = "{\"Type\":\"eIDAS Login\",\"Level\":null}"; - // Act - var eidasLogin = JsonSerializer.Deserialize(json, SignhostJsonSerializerOptions.Default); + // Act + var eidasLogin = JsonSerializer.Deserialize(json, SignhostJsonSerializerOptions.Default); - // Assert - eidasLogin.Level.Should().Be(null); - } + // Assert + eidasLogin.Level.Should().Be(null); + } - [Fact] - public void when_Level_is_not_supplied_should_deserialize_to_null() - { - // Arrange - const string json = "{\"Type\":\"eIDAS Login\"}"; + [Fact] + public void when_Level_is_not_supplied_should_deserialize_to_null() + { + // Arrange + const string json = "{\"Type\":\"eIDAS Login\"}"; - // Act - var eidasLogin = JsonSerializer.Deserialize(json, SignhostJsonSerializerOptions.Default); + // Act + var eidasLogin = JsonSerializer.Deserialize(json, SignhostJsonSerializerOptions.Default); - // Assert - eidasLogin.Level.Should().Be(null); - } + // Assert + eidasLogin.Level.Should().Be(null); + } - [Fact] - public void when_Level_is_unknown_should_deserialize_to_Unknown_Level() - { - // Arrange - const string json = "{\"Type\":\"eIDAS Login\",\"Level\":\"foobar\"}"; + [Fact] + public void when_Level_is_unknown_should_deserialize_to_Unknown_Level() + { + // Arrange + const string json = "{\"Type\":\"eIDAS Login\",\"Level\":\"foobar\"}"; - // Act - var eidasLogin = JsonSerializer.Deserialize(json, SignhostJsonSerializerOptions.Default); + // Act + var eidasLogin = JsonSerializer.Deserialize(json, SignhostJsonSerializerOptions.Default); - // Assert - eidasLogin.Level.Should().Be(Level.Unknown); - } + // Assert + eidasLogin.Level.Should().Be(Level.Unknown); + } - [Theory] - [ClassData(typeof(LevelTestData))] - public void when_Level_is_valid_should_deserialize_to_correct_value(Level level) - { - // Arrange - string json = $"{{\"Type\":\"eIDAS Login\",\"Level\":\"{level}\"}}"; + [Theory] + [ClassData(typeof(LevelTestData))] + public void when_Level_is_valid_should_deserialize_to_correct_value(Level level) + { + // Arrange + string json = $"{{\"Type\":\"eIDAS Login\",\"Level\":\"{level}\"}}"; - // Act - var eidasLogin = JsonSerializer.Deserialize(json, SignhostJsonSerializerOptions.Default); + // Act + var eidasLogin = JsonSerializer.Deserialize(json, SignhostJsonSerializerOptions.Default); - // Assert - eidasLogin.Level.Should().Be(level); - } + // Assert + eidasLogin.Level.Should().Be(level); + } - private class LevelTestData - : IEnumerable + private class LevelTestData + : IEnumerable + { + public IEnumerator GetEnumerator() { - public IEnumerator GetEnumerator() - { - foreach (var value in Enum.GetValues(typeof(Level))) { - yield return new[] { value }; - } + foreach (var value in Enum.GetValues(typeof(Level))) { + yield return new[] { value }; } - - IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); } + + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); } } diff --git a/src/SignhostAPIClient.Tests/PostbackTests.cs b/src/SignhostAPIClient.Tests/PostbackTests.cs index 7a00595..8d3314c 100644 --- a/src/SignhostAPIClient.Tests/PostbackTests.cs +++ b/src/SignhostAPIClient.Tests/PostbackTests.cs @@ -6,112 +6,111 @@ using SignhostAPIClient.Tests.JSON; using Xunit; -namespace Signhost.APIClient.Rest.Tests +namespace Signhost.APIClient.Rest.Tests; + +public class PostbackTests { - public class PostbackTests + [Fact] + public void PostbackTransaction_should_get_serialized_correctly() { - [Fact] - public void PostbackTransaction_should_get_serialized_correctly() - { - string json = JsonResources.MockPostbackValid; - var postbackTransaction = JsonSerializer.Deserialize(json, SignhostJsonSerializerOptions.Default); - - postbackTransaction.Id .Should().Be("b10ae331-af78-4e79-a39e-5b64693b6b68"); - postbackTransaction.Status .Should().Be(TransactionStatus.InProgress); - postbackTransaction.Seal .Should().BeTrue(); - postbackTransaction.Reference .Should().Be("Contract #123"); - postbackTransaction.PostbackUrl .Should().Be("https://example.com/postback.php"); - postbackTransaction.SignRequestMode .Should().Be(2); - postbackTransaction.DaysToExpire .Should().Be(30); - postbackTransaction.SendEmailNotifications.Should().BeTrue(); - postbackTransaction.CreatedDateTime .Should().Be(DateTimeOffset.Parse("2016-08-31T21:22:56.2467731+02:00")); - postbackTransaction.CanceledDateTime .Should().BeNull(); - (postbackTransaction.Context is null) .Should().BeTrue(); - postbackTransaction.Checksum .Should().Be("cdc09eee2ed6df2846dcc193aedfef59f2834f8d"); - - var signers = postbackTransaction.Signers; - signers.Should().HaveCount(1); - - var signer = signers.Single(); - signer.Id .Should().Be("fa95495d-6c59-48e0-962a-a4552f8d6b85"); - signer.Expires .Should().BeNull(); - signer.Email .Should().Be("user@example.com"); - signer.SendSignRequest .Should().BeTrue(); - signer.SendSignConfirmation.Should().BeNull(); - signer.SignRequestMessage .Should().Be("Hello, could you please sign this document? Best regards, John Doe"); - signer.DaysToRemind .Should().Be(15); - signer.Language .Should().Be("en-US"); - signer.ScribbleName .Should().Be("John Doe"); - signer.ScribbleNameFixed .Should().BeFalse(); - signer.Reference .Should().Be("Client #123"); - signer.ReturnUrl .Should().Be("https://signhost.com"); - signer.RejectReason .Should().BeNull(); - signer.SignUrl .Should().Be("https://view.signhost.com/sign/d3c93bd6-f1ce-48e7-8c9c-c2babfdd4034"); - (signer.Context is null) .Should().BeTrue(); - - var verifications = signer.Verifications; - verifications.Should().HaveCount(3); - - var phoneNumberVerification = verifications[0] as PhoneNumberVerification; - phoneNumberVerification .Should().NotBeNull(); - phoneNumberVerification.Number.Should().Be("+31612345678"); - - var scribbleVerification = verifications[1] as ScribbleVerification; - scribbleVerification .Should().NotBeNull(); - scribbleVerification.RequireHandsignature.Should().BeFalse(); - scribbleVerification.ScribbleNameFixed .Should().BeFalse(); - scribbleVerification.ScribbleName .Should().Be("John Doe"); - - var ipAddressVerification = verifications[2] as IPAddressVerification; - ipAddressVerification .Should().NotBeNull(); - ipAddressVerification.IPAddress.Should().Be("1.2.3.4"); - - var activities = signer.Activities; - activities.Should().HaveCount(3); - - var openedActivity = activities[0]; - openedActivity.Id .Should().Be("bcba44a9-c201-4494-9920-2c1f7baebcf0"); - openedActivity.Code .Should().Be(ActivityType.Opened); - openedActivity.Info .Should().BeNull(); - openedActivity.CreatedDateTime.Should().Be(DateTimeOffset.Parse("2016-06-15T23:33:04.1965465+02:00")); - - var documentOpenedActivity = activities[1]; - documentOpenedActivity.Id .Should().Be("7aacf96a-5c2f-475d-98a5-726e41bfc5d3"); - documentOpenedActivity.Code .Should().Be(ActivityType.DocumentOpened); - documentOpenedActivity.Info .Should().Be("file1"); - documentOpenedActivity.CreatedDateTime.Should().Be(DateTimeOffset.Parse("2020-01-30T16:31:05.6679583+01:00")); - - var signedActivity = activities[2]; - signedActivity.Id .Should().Be("de94cf6e-e1a3-4c33-93bf-2013b036daaf"); - signedActivity.Code .Should().Be(ActivityType.Signed); - signedActivity.Info .Should().BeNull(); - signedActivity.CreatedDateTime.Should().Be(DateTimeOffset.Parse("2016-06-15T23:38:04.1965465+02:00")); - - var receivers = postbackTransaction.Receivers; - receivers.Should().HaveCount(1); - - var receiver = receivers.Single(); - receiver.Name .Should().Be("John Doe"); - receiver.Email .Should().Be("user@example.com"); - receiver.Language .Should().Be("en-US"); - receiver.Message .Should().Be("Hello, please find enclosed the digital signed document. Best regards, John Doe"); - receiver.Reference .Should().BeNull(); - receiver.Activities .Should().BeNull(); - (receiver.Context is null).Should().BeTrue(); - - var files = postbackTransaction.Files; - files.Should().HaveCount(1); - - var file = files["file1"]; - file.DisplayName.Should().Be("Sample File"); - - var links = file.Links; - links.Should().HaveCount(1); - - var link = links.Single(); - link.Rel .Should().Be("file"); - link.Type.Should().Be("application/pdf"); - link.Link.Should().Be("https://api.signhost.com/api/transaction/b10ae331-af78-4e79-a39e-5b64693b6b68/file/file1"); - } + string json = JsonResources.MockPostbackValid; + var postbackTransaction = JsonSerializer.Deserialize(json, SignhostJsonSerializerOptions.Default); + + postbackTransaction.Id .Should().Be("b10ae331-af78-4e79-a39e-5b64693b6b68"); + postbackTransaction.Status .Should().Be(TransactionStatus.InProgress); + postbackTransaction.Seal .Should().BeTrue(); + postbackTransaction.Reference .Should().Be("Contract #123"); + postbackTransaction.PostbackUrl .Should().Be("https://example.com/postback.php"); + postbackTransaction.SignRequestMode .Should().Be(2); + postbackTransaction.DaysToExpire .Should().Be(30); + postbackTransaction.SendEmailNotifications.Should().BeTrue(); + postbackTransaction.CreatedDateTime .Should().Be(DateTimeOffset.Parse("2016-08-31T21:22:56.2467731+02:00")); + postbackTransaction.CanceledDateTime .Should().BeNull(); + (postbackTransaction.Context is null) .Should().BeTrue(); + postbackTransaction.Checksum .Should().Be("cdc09eee2ed6df2846dcc193aedfef59f2834f8d"); + + var signers = postbackTransaction.Signers; + signers.Should().HaveCount(1); + + var signer = signers.Single(); + signer.Id .Should().Be("fa95495d-6c59-48e0-962a-a4552f8d6b85"); + signer.Expires .Should().BeNull(); + signer.Email .Should().Be("user@example.com"); + signer.SendSignRequest .Should().BeTrue(); + signer.SendSignConfirmation.Should().BeNull(); + signer.SignRequestMessage .Should().Be("Hello, could you please sign this document? Best regards, John Doe"); + signer.DaysToRemind .Should().Be(15); + signer.Language .Should().Be("en-US"); + signer.ScribbleName .Should().Be("John Doe"); + signer.ScribbleNameFixed .Should().BeFalse(); + signer.Reference .Should().Be("Client #123"); + signer.ReturnUrl .Should().Be("https://signhost.com"); + signer.RejectReason .Should().BeNull(); + signer.SignUrl .Should().Be("https://view.signhost.com/sign/d3c93bd6-f1ce-48e7-8c9c-c2babfdd4034"); + (signer.Context is null) .Should().BeTrue(); + + var verifications = signer.Verifications; + verifications.Should().HaveCount(3); + + var phoneNumberVerification = verifications[0] as PhoneNumberVerification; + phoneNumberVerification .Should().NotBeNull(); + phoneNumberVerification.Number.Should().Be("+31612345678"); + + var scribbleVerification = verifications[1] as ScribbleVerification; + scribbleVerification .Should().NotBeNull(); + scribbleVerification.RequireHandsignature.Should().BeFalse(); + scribbleVerification.ScribbleNameFixed .Should().BeFalse(); + scribbleVerification.ScribbleName .Should().Be("John Doe"); + + var ipAddressVerification = verifications[2] as IPAddressVerification; + ipAddressVerification .Should().NotBeNull(); + ipAddressVerification.IPAddress.Should().Be("1.2.3.4"); + + var activities = signer.Activities; + activities.Should().HaveCount(3); + + var openedActivity = activities[0]; + openedActivity.Id .Should().Be("bcba44a9-c201-4494-9920-2c1f7baebcf0"); + openedActivity.Code .Should().Be(ActivityType.Opened); + openedActivity.Info .Should().BeNull(); + openedActivity.CreatedDateTime.Should().Be(DateTimeOffset.Parse("2016-06-15T23:33:04.1965465+02:00")); + + var documentOpenedActivity = activities[1]; + documentOpenedActivity.Id .Should().Be("7aacf96a-5c2f-475d-98a5-726e41bfc5d3"); + documentOpenedActivity.Code .Should().Be(ActivityType.DocumentOpened); + documentOpenedActivity.Info .Should().Be("file1"); + documentOpenedActivity.CreatedDateTime.Should().Be(DateTimeOffset.Parse("2020-01-30T16:31:05.6679583+01:00")); + + var signedActivity = activities[2]; + signedActivity.Id .Should().Be("de94cf6e-e1a3-4c33-93bf-2013b036daaf"); + signedActivity.Code .Should().Be(ActivityType.Signed); + signedActivity.Info .Should().BeNull(); + signedActivity.CreatedDateTime.Should().Be(DateTimeOffset.Parse("2016-06-15T23:38:04.1965465+02:00")); + + var receivers = postbackTransaction.Receivers; + receivers.Should().HaveCount(1); + + var receiver = receivers.Single(); + receiver.Name .Should().Be("John Doe"); + receiver.Email .Should().Be("user@example.com"); + receiver.Language .Should().Be("en-US"); + receiver.Message .Should().Be("Hello, please find enclosed the digital signed document. Best regards, John Doe"); + receiver.Reference .Should().BeNull(); + receiver.Activities .Should().BeNull(); + (receiver.Context is null).Should().BeTrue(); + + var files = postbackTransaction.Files; + files.Should().HaveCount(1); + + var file = files["file1"]; + file.DisplayName.Should().Be("Sample File"); + + var links = file.Links; + links.Should().HaveCount(1); + + var link = links.Single(); + link.Rel .Should().Be("file"); + link.Type.Should().Be("application/pdf"); + link.Link.Should().Be("https://api.signhost.com/api/transaction/b10ae331-af78-4e79-a39e-5b64693b6b68/file/file1"); } } diff --git a/src/SignhostAPIClient.Tests/SignhostApiClientTests.cs b/src/SignhostAPIClient.Tests/SignhostApiClientTests.cs index fc3c085..aee6ccf 100644 --- a/src/SignhostAPIClient.Tests/SignhostApiClientTests.cs +++ b/src/SignhostAPIClient.Tests/SignhostApiClientTests.cs @@ -10,661 +10,660 @@ using System.Net; using SignhostAPIClient.Tests.JSON; -namespace Signhost.APIClient.Rest.Tests +namespace Signhost.APIClient.Rest.Tests; + +public class SignhostApiClientTests { - public class SignhostApiClientTests - { - private readonly SignhostApiClientSettings settings = new("AppKey", "Usertoken") { - Endpoint = "http://localhost/api/" - }; + private readonly SignhostApiClientSettings settings = new("AppKey", "Usertoken") { + Endpoint = "http://localhost/api/" + }; - private readonly SignhostApiClientSettings oauthSettings = new("AppKey") { - Endpoint = "http://localhost/api/" - }; + private readonly SignhostApiClientSettings oauthSettings = new("AppKey") { + Endpoint = "http://localhost/api/" + }; - [Fact] - public async Task when_AddOrReplaceFileMetaToTransaction_is_called_then_the_request_body_should_contain_the_serialized_file_meta() - { - var mockHttp = new MockHttpMessageHandler(); + [Fact] + public async Task when_AddOrReplaceFileMetaToTransaction_is_called_then_the_request_body_should_contain_the_serialized_file_meta() + { + var mockHttp = new MockHttpMessageHandler(); - mockHttp.Expect(HttpMethod.Put, "http://localhost/api/transaction/transactionId/file/fileId") - .WithContent(JsonResources.AddOrReplaceFileMetaToTransaction) - .Respond(HttpStatusCode.OK); + mockHttp.Expect(HttpMethod.Put, "http://localhost/api/transaction/transactionId/file/fileId") + .WithContent(JsonResources.AddOrReplaceFileMetaToTransaction) + .Respond(HttpStatusCode.OK); - using (var httpClient = mockHttp.ToHttpClient()) { - var signhostApiClient = new SignhostApiClient(settings, httpClient); + using (var httpClient = mockHttp.ToHttpClient()) { + var signhostApiClient = new SignhostApiClient(settings, httpClient); - var fileSignerMeta = new FileSignerMeta - { - FormSets = new string[] { "SampleFormSet" } - }; + var fileSignerMeta = new FileSignerMeta + { + FormSets = new string[] { "SampleFormSet" } + }; - var field = new Field + var field = new Field + { + Type = "Check", + Value = "I agree", + Location = new Location { - Type = "Check", - Value = "I agree", - Location = new Location - { - Search = "test" - } - }; + Search = "test" + } + }; - FileMeta fileMeta = new FileMeta + FileMeta fileMeta = new FileMeta + { + Signers = new Dictionary { - Signers = new Dictionary - { - { "someSignerId", fileSignerMeta } - }, - FormSets = new Dictionary> - { - { "SampleFormSet", new Dictionary - { - { "SampleCheck", field } - } + { "someSignerId", fileSignerMeta } + }, + FormSets = new Dictionary> + { + { "SampleFormSet", new Dictionary + { + { "SampleCheck", field } } } - }; - - await signhostApiClient.AddOrReplaceFileMetaToTransactionAsync(fileMeta, "transactionId", "fileId"); - } + } + }; - mockHttp.VerifyNoOutstandingExpectation(); + await signhostApiClient.AddOrReplaceFileMetaToTransactionAsync(fileMeta, "transactionId", "fileId"); } - [Fact] - public async Task when_a_GetTransaction_is_called_then_we_should_have_called_the_transaction_get_once() - { - var mockHttp = new MockHttpMessageHandler(); - mockHttp - .Expect(HttpMethod.Get, "http://localhost/api/transaction/transaction Id") - .Respond(HttpStatusCode.OK, new StringContent(JsonResources.GetTransaction)); + mockHttp.VerifyNoOutstandingExpectation(); + } - using (var httpClient = mockHttp.ToHttpClient()) { + [Fact] + public async Task when_a_GetTransaction_is_called_then_we_should_have_called_the_transaction_get_once() + { + var mockHttp = new MockHttpMessageHandler(); + mockHttp + .Expect(HttpMethod.Get, "http://localhost/api/transaction/transaction Id") + .Respond(HttpStatusCode.OK, new StringContent(JsonResources.GetTransaction)); - var signhostApiClient = new SignhostApiClient(settings, httpClient); + using (var httpClient = mockHttp.ToHttpClient()) { - var result = await signhostApiClient.GetTransactionAsync("transaction Id"); - result.Id.Should().Be("c487be92-0255-40c7-bd7d-20805a65e7d9"); - } + var signhostApiClient = new SignhostApiClient(settings, httpClient); - mockHttp.VerifyNoOutstandingExpectation(); + var result = await signhostApiClient.GetTransactionAsync("transaction Id"); + result.Id.Should().Be("c487be92-0255-40c7-bd7d-20805a65e7d9"); } - [Fact] - public async Task when_GetTransaction_is_called_and_the_authorization_is_bad_then_we_should_get_a_BadAuthorizationException() - { - var mockHttp = new MockHttpMessageHandler(); - mockHttp - .Expect(HttpMethod.Get, "http://localhost/api/transaction/transaction Id") - .Respond(HttpStatusCode.Unauthorized, new StringContent(""" - { - "message": "unauthorized" - } - """)); + mockHttp.VerifyNoOutstandingExpectation(); + } - using (var httpClient = mockHttp.ToHttpClient()) { + [Fact] + public async Task when_GetTransaction_is_called_and_the_authorization_is_bad_then_we_should_get_a_BadAuthorizationException() + { + var mockHttp = new MockHttpMessageHandler(); + mockHttp + .Expect(HttpMethod.Get, "http://localhost/api/transaction/transaction Id") + .Respond(HttpStatusCode.Unauthorized, new StringContent(""" + { + "message": "unauthorized" + } + """)); - var signhostApiClient = new SignhostApiClient(settings, httpClient); + using (var httpClient = mockHttp.ToHttpClient()) { - Func getTransaction = () => signhostApiClient.GetTransactionAsync("transaction Id"); - await getTransaction.Should().ThrowAsync(); - } + var signhostApiClient = new SignhostApiClient(settings, httpClient); - mockHttp.VerifyNoOutstandingExpectation(); + Func getTransaction = () => signhostApiClient.GetTransactionAsync("transaction Id"); + await getTransaction.Should().ThrowAsync(); } - [Fact] - public async Task when_GetTransaction_is_called_and_request_is_bad_then_we_should_get_a_BadRequestException() - { - var mockHttp = new MockHttpMessageHandler(); - mockHttp - .Expect(HttpMethod.Get, "http://localhost/api/transaction/transaction Id") - .Respond(HttpStatusCode.BadRequest, new StringContent(""" - { - "message": "Bad Request" - } - """)); + mockHttp.VerifyNoOutstandingExpectation(); + } - using (var httpClient = mockHttp.ToHttpClient()) { + [Fact] + public async Task when_GetTransaction_is_called_and_request_is_bad_then_we_should_get_a_BadRequestException() + { + var mockHttp = new MockHttpMessageHandler(); + mockHttp + .Expect(HttpMethod.Get, "http://localhost/api/transaction/transaction Id") + .Respond(HttpStatusCode.BadRequest, new StringContent(""" + { + "message": "Bad Request" + } + """)); - var signhostApiClient = new SignhostApiClient(settings, httpClient); + using (var httpClient = mockHttp.ToHttpClient()) { - Func getTransaction = () => signhostApiClient.GetTransactionAsync("transaction Id"); - await getTransaction.Should().ThrowAsync(); - } + var signhostApiClient = new SignhostApiClient(settings, httpClient); - mockHttp.VerifyNoOutstandingExpectation(); + Func getTransaction = () => signhostApiClient.GetTransactionAsync("transaction Id"); + await getTransaction.Should().ThrowAsync(); } - [Fact] - public async Task when_GetTransaction_is_called_and_credits_have_run_out_then_we_should_get_a_OutOfCreditsException() - { - var mockHttp = new MockHttpMessageHandler(); - mockHttp - .Expect(HttpMethod.Get, "http://localhost/api/transaction/transaction Id") - .Respond(HttpStatusCode.PaymentRequired, new StringContent(""" - { - "type": "https://api.signhost.com/problem/subscription/out-of-credits" - } - """)); + mockHttp.VerifyNoOutstandingExpectation(); + } - using (var httpClient = mockHttp.ToHttpClient()) { + [Fact] + public async Task when_GetTransaction_is_called_and_credits_have_run_out_then_we_should_get_a_OutOfCreditsException() + { + var mockHttp = new MockHttpMessageHandler(); + mockHttp + .Expect(HttpMethod.Get, "http://localhost/api/transaction/transaction Id") + .Respond(HttpStatusCode.PaymentRequired, new StringContent(""" + { + "type": "https://api.signhost.com/problem/subscription/out-of-credits" + } + """)); - var signhostApiClient = new SignhostApiClient(settings, httpClient); + using (var httpClient = mockHttp.ToHttpClient()) { - Func getTransaction = () => signhostApiClient.GetTransactionAsync("transaction Id"); - await getTransaction.Should().ThrowAsync(); - } + var signhostApiClient = new SignhostApiClient(settings, httpClient); - mockHttp.VerifyNoOutstandingExpectation(); + Func getTransaction = () => signhostApiClient.GetTransactionAsync("transaction Id"); + await getTransaction.Should().ThrowAsync(); } - [Fact] - public async Task when_GetTransaction_is_called_and_not_found_then_we_should_get_a_NotFoundException() - { - var mockHttp = new MockHttpMessageHandler(); - mockHttp - .Expect(HttpMethod.Get, "http://localhost/api/transaction/transaction Id") - .Respond(HttpStatusCode.NotFound, new StringContent(""" - { - "message": "Not Found" - } - """)); + mockHttp.VerifyNoOutstandingExpectation(); + } - using (var httpClient = mockHttp.ToHttpClient()) { + [Fact] + public async Task when_GetTransaction_is_called_and_not_found_then_we_should_get_a_NotFoundException() + { + var mockHttp = new MockHttpMessageHandler(); + mockHttp + .Expect(HttpMethod.Get, "http://localhost/api/transaction/transaction Id") + .Respond(HttpStatusCode.NotFound, new StringContent(""" + { + "message": "Not Found" + } + """)); - var signhostApiClient = new SignhostApiClient(settings, httpClient); + using (var httpClient = mockHttp.ToHttpClient()) { - Func getTransaction = () => signhostApiClient.GetTransactionAsync("transaction Id"); + var signhostApiClient = new SignhostApiClient(settings, httpClient); - await getTransaction.Should().ThrowAsync().WithMessage("Not Found"); - } + Func getTransaction = () => signhostApiClient.GetTransactionAsync("transaction Id"); - mockHttp.VerifyNoOutstandingExpectation(); + await getTransaction.Should().ThrowAsync().WithMessage("Not Found"); } - [Fact] - public async Task when_GetTransaction_is_called_and_unkownerror_like_418_occures_then_we_should_get_a_SignhostException() - { - var mockHttp = new MockHttpMessageHandler(); - mockHttp - .Expect(HttpMethod.Get, "http://localhost/api/transaction/transaction Id") - .Respond((HttpStatusCode)418, new StringContent(""" - { - "message": "418 I'm a teapot" - } - """)); + mockHttp.VerifyNoOutstandingExpectation(); + } - using (var httpClient = mockHttp.ToHttpClient()) { + [Fact] + public async Task when_GetTransaction_is_called_and_unkownerror_like_418_occures_then_we_should_get_a_SignhostException() + { + var mockHttp = new MockHttpMessageHandler(); + mockHttp + .Expect(HttpMethod.Get, "http://localhost/api/transaction/transaction Id") + .Respond((HttpStatusCode)418, new StringContent(""" + { + "message": "418 I'm a teapot" + } + """)); - var signhostApiClient = new SignhostApiClient(settings, httpClient); + using (var httpClient = mockHttp.ToHttpClient()) { - Func getTransaction = () => signhostApiClient.GetTransactionAsync("transaction Id"); - await getTransaction.Should().ThrowAsync() - .WithMessage("*418*"); - } + var signhostApiClient = new SignhostApiClient(settings, httpClient); - mockHttp.VerifyNoOutstandingExpectation(); + Func getTransaction = () => signhostApiClient.GetTransactionAsync("transaction Id"); + await getTransaction.Should().ThrowAsync() + .WithMessage("*418*"); } - [Fact] - public async Task when_GetTransaction_is_called_and_there_is_an_InternalServerError_then_we_should_get_a_InternalServerErrorException() - { - var mockHttp = new MockHttpMessageHandler(); - mockHttp - .Expect(HttpMethod.Get, "http://localhost/api/transaction/transaction Id") - .Respond(HttpStatusCode.InternalServerError, new StringContent(""" - { - "message": "Internal Server Error" - } - """)); + mockHttp.VerifyNoOutstandingExpectation(); + } - using (var httpClient = mockHttp.ToHttpClient()) { + [Fact] + public async Task when_GetTransaction_is_called_and_there_is_an_InternalServerError_then_we_should_get_a_InternalServerErrorException() + { + var mockHttp = new MockHttpMessageHandler(); + mockHttp + .Expect(HttpMethod.Get, "http://localhost/api/transaction/transaction Id") + .Respond(HttpStatusCode.InternalServerError, new StringContent(""" + { + "message": "Internal Server Error" + } + """)); - var signhostApiClient = new SignhostApiClient(settings, httpClient); + using (var httpClient = mockHttp.ToHttpClient()) { - Func getTransaction = () => signhostApiClient.GetTransactionAsync("transaction Id"); - await getTransaction.Should().ThrowAsync(); - } + var signhostApiClient = new SignhostApiClient(settings, httpClient); - mockHttp.VerifyNoOutstandingExpectation(); + Func getTransaction = () => signhostApiClient.GetTransactionAsync("transaction Id"); + await getTransaction.Should().ThrowAsync(); } - [Fact] - public async Task When_GetTransaction_is_called_on_gone_transaction_we_shoud_get_a_GoneException() - { - var mockHttp = new MockHttpMessageHandler(); - mockHttp - .Expect(HttpMethod.Get, "http://localhost/api/transaction/transaction Id") - .Respond(HttpStatusCode.Gone, new StringContent(JsonResources.GetTransaction)); + mockHttp.VerifyNoOutstandingExpectation(); + } - using (var httpClient = mockHttp.ToHttpClient()) { - var signhostApiClient = new SignhostApiClient(settings, httpClient); + [Fact] + public async Task When_GetTransaction_is_called_on_gone_transaction_we_shoud_get_a_GoneException() + { + var mockHttp = new MockHttpMessageHandler(); + mockHttp + .Expect(HttpMethod.Get, "http://localhost/api/transaction/transaction Id") + .Respond(HttpStatusCode.Gone, new StringContent(JsonResources.GetTransaction)); - Func getTransaction = () => signhostApiClient.GetTransactionAsync("transaction Id"); - await getTransaction.Should().ThrowAsync>(); - } + using (var httpClient = mockHttp.ToHttpClient()) { + var signhostApiClient = new SignhostApiClient(settings, httpClient); - mockHttp.VerifyNoOutstandingExpectation(); + Func getTransaction = () => signhostApiClient.GetTransactionAsync("transaction Id"); + await getTransaction.Should().ThrowAsync>(); } - [Fact] - public async Task When_GetTransaction_is_called_and_gone_is_expected_we_should_get_a_transaction() - { - var mockHttp = new MockHttpMessageHandler(); - mockHttp - .Expect(HttpMethod.Get, "http://localhost/api/transaction/transaction Id") - .Respond(HttpStatusCode.Gone, new StringContent(JsonResources.GetTransaction)); + mockHttp.VerifyNoOutstandingExpectation(); + } - using (var httpClient = mockHttp.ToHttpClient()) { - var signhostApiClient = new SignhostApiClient(settings, httpClient); + [Fact] + public async Task When_GetTransaction_is_called_and_gone_is_expected_we_should_get_a_transaction() + { + var mockHttp = new MockHttpMessageHandler(); + mockHttp + .Expect(HttpMethod.Get, "http://localhost/api/transaction/transaction Id") + .Respond(HttpStatusCode.Gone, new StringContent(JsonResources.GetTransaction)); - Func getTransaction = () => signhostApiClient.GetTransactionResponseAsync("transaction Id"); - await getTransaction.Should().NotThrowAsync(); - } + using (var httpClient = mockHttp.ToHttpClient()) { + var signhostApiClient = new SignhostApiClient(settings, httpClient); - mockHttp.VerifyNoOutstandingExpectation(); + Func getTransaction = () => signhostApiClient.GetTransactionResponseAsync("transaction Id"); + await getTransaction.Should().NotThrowAsync(); } - [Fact] - public async Task when_a_CreateTransaction_is_called_then_we_should_have_called_the_transaction_Post_once() - { - var mockHttp = new MockHttpMessageHandler(); - mockHttp - .Expect(HttpMethod.Post, "http://localhost/api/transaction") - .Respond(HttpStatusCode.OK, new StringContent(JsonResources.AddTransaction)); + mockHttp.VerifyNoOutstandingExpectation(); + } - using (var httpClient = mockHttp.ToHttpClient()) { - var signhostApiClient = new SignhostApiClient(settings, httpClient); + [Fact] + public async Task when_a_CreateTransaction_is_called_then_we_should_have_called_the_transaction_Post_once() + { + var mockHttp = new MockHttpMessageHandler(); + mockHttp + .Expect(HttpMethod.Post, "http://localhost/api/transaction") + .Respond(HttpStatusCode.OK, new StringContent(JsonResources.AddTransaction)); - Signer testSigner = new Signer(); - testSigner.Email = "firstname.lastname@gmail.com"; + using (var httpClient = mockHttp.ToHttpClient()) { + var signhostApiClient = new SignhostApiClient(settings, httpClient); - Transaction testTransaction = new Transaction(); - testTransaction.Signers.Add(testSigner); + Signer testSigner = new Signer(); + testSigner.Email = "firstname.lastname@gmail.com"; - var result = await signhostApiClient.CreateTransactionAsync(testTransaction); - result.Id.Should().Be("c487be92-0255-40c7-bd7d-20805a65e7d9"); - } + Transaction testTransaction = new Transaction(); + testTransaction.Signers.Add(testSigner); - mockHttp.VerifyNoOutstandingExpectation(); + var result = await signhostApiClient.CreateTransactionAsync(testTransaction); + result.Id.Should().Be("c487be92-0255-40c7-bd7d-20805a65e7d9"); } - [Fact] - public async Task when_a_CreateTransaction_is_called_we_can_add_custom_http_headers() - { - var mockHttp = new MockHttpMessageHandler(); - mockHttp - .Expect(HttpMethod.Post, "http://localhost/api/transaction") - .WithHeaders("X-Forwarded-For", "localhost") - .With(matcher => matcher.Headers.UserAgent.ToString().Contains("SignhostClientLibrary")) - .Respond(HttpStatusCode.OK, new StringContent(JsonResources.AddTransaction)); + mockHttp.VerifyNoOutstandingExpectation(); + } - using (var httpClient = mockHttp.ToHttpClient()) { - settings.AddHeader = (AddHeaders a) => a("X-Forwarded-For", "localhost"); + [Fact] + public async Task when_a_CreateTransaction_is_called_we_can_add_custom_http_headers() + { + var mockHttp = new MockHttpMessageHandler(); + mockHttp + .Expect(HttpMethod.Post, "http://localhost/api/transaction") + .WithHeaders("X-Forwarded-For", "localhost") + .With(matcher => matcher.Headers.UserAgent.ToString().Contains("SignhostClientLibrary")) + .Respond(HttpStatusCode.OK, new StringContent(JsonResources.AddTransaction)); - var signhostApiClient = new SignhostApiClient(settings, httpClient); + using (var httpClient = mockHttp.ToHttpClient()) { + settings.AddHeader = (AddHeaders a) => a("X-Forwarded-For", "localhost"); - Transaction testTransaction = new Transaction(); + var signhostApiClient = new SignhostApiClient(settings, httpClient); - var result = await signhostApiClient.CreateTransactionAsync(testTransaction); - } + Transaction testTransaction = new Transaction(); - mockHttp.VerifyNoOutstandingExpectation(); + var result = await signhostApiClient.CreateTransactionAsync(testTransaction); } - [Fact] - public async Task when_CreateTransaction_is_called_with_invalid_email_then_we_should_get_a_BadRequestException() - { - var mockHttp = new MockHttpMessageHandler(); - mockHttp - .Expect(HttpMethod.Post, "http://localhost/api/transaction") - .WithHeaders("Content-Type", "application/json") - .Respond(HttpStatusCode.BadRequest, new StringContent(""" - { - "message": "Bad Request" - } - """)); + mockHttp.VerifyNoOutstandingExpectation(); + } - using (var httpClient = mockHttp.ToHttpClient()) { - var signhostApiClient = new SignhostApiClient(settings, httpClient); + [Fact] + public async Task when_CreateTransaction_is_called_with_invalid_email_then_we_should_get_a_BadRequestException() + { + var mockHttp = new MockHttpMessageHandler(); + mockHttp + .Expect(HttpMethod.Post, "http://localhost/api/transaction") + .WithHeaders("Content-Type", "application/json") + .Respond(HttpStatusCode.BadRequest, new StringContent(""" + { + "message": "Bad Request" + } + """)); - Signer testSigner = new Signer(); - testSigner.Email = "firstname.lastnamegmail.com"; + using (var httpClient = mockHttp.ToHttpClient()) { + var signhostApiClient = new SignhostApiClient(settings, httpClient); - Transaction testTransaction = new Transaction(); - testTransaction.Signers.Add(testSigner); + Signer testSigner = new Signer(); + testSigner.Email = "firstname.lastnamegmail.com"; - Func getTransaction = () => signhostApiClient.CreateTransactionAsync(testTransaction); - await getTransaction.Should().ThrowAsync(); - } + Transaction testTransaction = new Transaction(); + testTransaction.Signers.Add(testSigner); - mockHttp.VerifyNoOutstandingExpectation(); + Func getTransaction = () => signhostApiClient.CreateTransactionAsync(testTransaction); + await getTransaction.Should().ThrowAsync(); } - [Fact] - public async Task when_a_function_is_called_with_a_wrong_endpoint_we_should_get_a_SignhostRestApiClientException() - { - var mockHttp = new MockHttpMessageHandler(); - mockHttp - .Expect(HttpMethod.Get, "http://localhost/api/transaction/transaction Id") - .Respond(HttpStatusCode.BadGateway, new StringContent(""" - { - "message": "Bad Gateway" - } - """)); - - using (var httpClient = mockHttp.ToHttpClient()) { - var signhostApiClient = new SignhostApiClient(settings, httpClient); + mockHttp.VerifyNoOutstandingExpectation(); + } - Func getTransaction = () => signhostApiClient.GetTransactionAsync("transaction Id"); - await getTransaction.Should().ThrowAsync() - .WithMessage("Bad Gateway"); + [Fact] + public async Task when_a_function_is_called_with_a_wrong_endpoint_we_should_get_a_SignhostRestApiClientException() + { + var mockHttp = new MockHttpMessageHandler(); + mockHttp + .Expect(HttpMethod.Get, "http://localhost/api/transaction/transaction Id") + .Respond(HttpStatusCode.BadGateway, new StringContent(""" + { + "message": "Bad Gateway" } + """)); - mockHttp.VerifyNoOutstandingExpectation(); + using (var httpClient = mockHttp.ToHttpClient()) { + var signhostApiClient = new SignhostApiClient(settings, httpClient); + + Func getTransaction = () => signhostApiClient.GetTransactionAsync("transaction Id"); + await getTransaction.Should().ThrowAsync() + .WithMessage("Bad Gateway"); } - [Fact] - public async Task when_a_DeleteTransaction_is_called_then_we_should_have_called_the_transaction_delete_once() - { - var mockHttp = new MockHttpMessageHandler(); - mockHttp - .Expect(HttpMethod.Delete, "http://localhost/api/transaction/transaction Id") - .Respond(HttpStatusCode.OK, new StringContent(JsonResources.DeleteTransaction)); + mockHttp.VerifyNoOutstandingExpectation(); + } - using (var httpClient = mockHttp.ToHttpClient()) { - var signhostApiClient = new SignhostApiClient(settings, httpClient); + [Fact] + public async Task when_a_DeleteTransaction_is_called_then_we_should_have_called_the_transaction_delete_once() + { + var mockHttp = new MockHttpMessageHandler(); + mockHttp + .Expect(HttpMethod.Delete, "http://localhost/api/transaction/transaction Id") + .Respond(HttpStatusCode.OK, new StringContent(JsonResources.DeleteTransaction)); - await signhostApiClient.DeleteTransactionAsync("transaction Id"); - } + using (var httpClient = mockHttp.ToHttpClient()) { + var signhostApiClient = new SignhostApiClient(settings, httpClient); - mockHttp.VerifyNoOutstandingExpectation(); + await signhostApiClient.DeleteTransactionAsync("transaction Id"); } - [Fact] - public async Task when_a_DeleteTransaction_with_notification_is_called_then_we_should_have_called_the_transaction_delete_once() - { - var mockHttp = new MockHttpMessageHandler(); - mockHttp - .Expect(HttpMethod.Delete, "http://localhost/api/transaction/transaction Id") - .WithHeaders("Content-Type", "application/json") - //.With(matcher => matcher.Content.ToString().Contains("'SendNotifications': true")) - .Respond(HttpStatusCode.OK, new StringContent(JsonResources.DeleteTransaction)); - - using (var httpClient = mockHttp.ToHttpClient()) { - var signhostApiClient = new SignhostApiClient(settings, httpClient); - - await signhostApiClient.DeleteTransactionAsync( - "transaction Id", - new DeleteTransactionOptions { SendNotifications = true }); - } + mockHttp.VerifyNoOutstandingExpectation(); + } - mockHttp.VerifyNoOutstandingExpectation(); + [Fact] + public async Task when_a_DeleteTransaction_with_notification_is_called_then_we_should_have_called_the_transaction_delete_once() + { + var mockHttp = new MockHttpMessageHandler(); + mockHttp + .Expect(HttpMethod.Delete, "http://localhost/api/transaction/transaction Id") + .WithHeaders("Content-Type", "application/json") + //.With(matcher => matcher.Content.ToString().Contains("'SendNotifications': true")) + .Respond(HttpStatusCode.OK, new StringContent(JsonResources.DeleteTransaction)); + + using (var httpClient = mockHttp.ToHttpClient()) { + var signhostApiClient = new SignhostApiClient(settings, httpClient); + + await signhostApiClient.DeleteTransactionAsync( + "transaction Id", + new DeleteTransactionOptions { SendNotifications = true }); } - [Fact] - public async Task when_AddOrReplaceFileToTransaction_is_called_then_we_should_have_called_the_file_put_once() - { - var mockHttp = new MockHttpMessageHandler(); - mockHttp - .Expect(HttpMethod.Put, "http://localhost/api/transaction/transaction Id/file/file Id") - .WithHeaders("Content-Type", "application/pdf") - .WithHeaders("Digest", "SHA-256=47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU=") - .Respond(HttpStatusCode.OK); - - using (var httpClient = mockHttp.ToHttpClient()) { - var signhostApiClient = new SignhostApiClient(settings, httpClient); - - // Create a 0 sized file - using (Stream file = System.IO.File.Create("unittestdocument.pdf")) { - await signhostApiClient.AddOrReplaceFileToTransaction(file, "transaction Id", "file Id"); - } - } + mockHttp.VerifyNoOutstandingExpectation(); + } - mockHttp.VerifyNoOutstandingExpectation(); + [Fact] + public async Task when_AddOrReplaceFileToTransaction_is_called_then_we_should_have_called_the_file_put_once() + { + var mockHttp = new MockHttpMessageHandler(); + mockHttp + .Expect(HttpMethod.Put, "http://localhost/api/transaction/transaction Id/file/file Id") + .WithHeaders("Content-Type", "application/pdf") + .WithHeaders("Digest", "SHA-256=47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU=") + .Respond(HttpStatusCode.OK); + + using (var httpClient = mockHttp.ToHttpClient()) { + var signhostApiClient = new SignhostApiClient(settings, httpClient); + + // Create a 0 sized file + using (Stream file = System.IO.File.Create("unittestdocument.pdf")) { + await signhostApiClient.AddOrReplaceFileToTransaction(file, "transaction Id", "file Id"); + } } - [Fact] - public async Task when_AddOrReplaceFileToTransaction_is_called_default_digest_is_sha256() - { - var mockHttp = new MockHttpMessageHandler(); - mockHttp - .Expect(HttpMethod.Put, "http://localhost/api/transaction/transaction Id/file/file Id") - .WithHeaders("Digest", "SHA-256=47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU=") - .Respond(HttpStatusCode.OK); + mockHttp.VerifyNoOutstandingExpectation(); + } - using (var httpClient = mockHttp.ToHttpClient()) { - var signhostApiClient = new SignhostApiClient(settings, httpClient); + [Fact] + public async Task when_AddOrReplaceFileToTransaction_is_called_default_digest_is_sha256() + { + var mockHttp = new MockHttpMessageHandler(); + mockHttp + .Expect(HttpMethod.Put, "http://localhost/api/transaction/transaction Id/file/file Id") + .WithHeaders("Digest", "SHA-256=47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU=") + .Respond(HttpStatusCode.OK); - await signhostApiClient.AddOrReplaceFileToTransaction(new MemoryStream(), "transaction Id", "file Id"); - } + using (var httpClient = mockHttp.ToHttpClient()) { + var signhostApiClient = new SignhostApiClient(settings, httpClient); - mockHttp.VerifyNoOutstandingExpectation(); + await signhostApiClient.AddOrReplaceFileToTransaction(new MemoryStream(), "transaction Id", "file Id"); } - [Fact] - public async Task when_AddOrReplaceFileToTransaction_with_sha512_is_called_default_digest_is_sha512() - { - var mockHttp = new MockHttpMessageHandler(); - mockHttp - .Expect(HttpMethod.Put, "http://localhost/api/transaction/transaction Id/file/file Id") - .WithHeaders("Digest", "SHA-512=z4PhNX7vuL3xVChQ1m2AB9Yg5AULVxXcg/SpIdNs6c5H0NE8XYXysP+DGNKHfuwvY7kxvUdBeoGlODJ6+SfaPg==") - .Respond(HttpStatusCode.OK); - - using (var httpClient = mockHttp.ToHttpClient()) { - var signhostApiClient = new SignhostApiClient(settings, httpClient); - - await signhostApiClient.AddOrReplaceFileToTransactionAsync( - new MemoryStream(), - "transaction Id", - "file Id", - new FileUploadOptions{ - DigestOptions = new FileDigestOptions - { - DigestHashAlgorithm = "SHA-512" - } - }); - } + mockHttp.VerifyNoOutstandingExpectation(); + } - mockHttp.VerifyNoOutstandingExpectation(); + [Fact] + public async Task when_AddOrReplaceFileToTransaction_with_sha512_is_called_default_digest_is_sha512() + { + var mockHttp = new MockHttpMessageHandler(); + mockHttp + .Expect(HttpMethod.Put, "http://localhost/api/transaction/transaction Id/file/file Id") + .WithHeaders("Digest", "SHA-512=z4PhNX7vuL3xVChQ1m2AB9Yg5AULVxXcg/SpIdNs6c5H0NE8XYXysP+DGNKHfuwvY7kxvUdBeoGlODJ6+SfaPg==") + .Respond(HttpStatusCode.OK); + + using (var httpClient = mockHttp.ToHttpClient()) { + var signhostApiClient = new SignhostApiClient(settings, httpClient); + + await signhostApiClient.AddOrReplaceFileToTransactionAsync( + new MemoryStream(), + "transaction Id", + "file Id", + new FileUploadOptions{ + DigestOptions = new FileDigestOptions + { + DigestHashAlgorithm = "SHA-512" + } + }); } - [Fact] - public async Task when_AddOrReplaceFileToTransaction_with_digest_value_is_used_as_is() - { - var mockHttp = new MockHttpMessageHandler(); - mockHttp - .Expect(HttpMethod.Put, "http://localhost/api/transaction/transaction Id/file/file Id") - .WithHeaders("Digest", "SHA-1=AAEC") - .Respond(HttpStatusCode.OK); - - using (var httpClient = mockHttp.ToHttpClient()) { - var signhostApiClient = new SignhostApiClient(settings, httpClient); - - await signhostApiClient.AddOrReplaceFileToTransactionAsync( - new MemoryStream(), - "transaction Id", - "file Id", - new FileUploadOptions - { - DigestOptions = new FileDigestOptions - { - DigestHashAlgorithm = "SHA-1", - DigestHashValue = new byte[] { 0x00, 0x01, 0x02 } - } - }); - } + mockHttp.VerifyNoOutstandingExpectation(); + } - mockHttp.VerifyNoOutstandingExpectation(); + [Fact] + public async Task when_AddOrReplaceFileToTransaction_with_digest_value_is_used_as_is() + { + var mockHttp = new MockHttpMessageHandler(); + mockHttp + .Expect(HttpMethod.Put, "http://localhost/api/transaction/transaction Id/file/file Id") + .WithHeaders("Digest", "SHA-1=AAEC") + .Respond(HttpStatusCode.OK); + + using (var httpClient = mockHttp.ToHttpClient()) { + var signhostApiClient = new SignhostApiClient(settings, httpClient); + + await signhostApiClient.AddOrReplaceFileToTransactionAsync( + new MemoryStream(), + "transaction Id", + "file Id", + new FileUploadOptions + { + DigestOptions = new FileDigestOptions + { + DigestHashAlgorithm = "SHA-1", + DigestHashValue = new byte[] { 0x00, 0x01, 0x02 } + } + }); } - [Fact] - public async Task when_StartTransaction_is_called_then_we_should_have_called_the_transaction_put_once() - { - var mockHttp = new MockHttpMessageHandler(); - mockHttp.Expect("http://localhost/api/transaction/transaction Id/start") - .Respond(HttpStatusCode.NoContent); + mockHttp.VerifyNoOutstandingExpectation(); + } - using (var httpClient = mockHttp.ToHttpClient()) { + [Fact] + public async Task when_StartTransaction_is_called_then_we_should_have_called_the_transaction_put_once() + { + var mockHttp = new MockHttpMessageHandler(); + mockHttp.Expect("http://localhost/api/transaction/transaction Id/start") + .Respond(HttpStatusCode.NoContent); - var signhostApiClient = new SignhostApiClient(settings, httpClient); + using (var httpClient = mockHttp.ToHttpClient()) { - await signhostApiClient.StartTransactionAsync("transaction Id"); - } + var signhostApiClient = new SignhostApiClient(settings, httpClient); - mockHttp.VerifyNoOutstandingExpectation(); + await signhostApiClient.StartTransactionAsync("transaction Id"); } - [Fact] - public async Task when_GetReceipt_is_called_then_we_should_have_called_the_filereceipt_get_once() - { - var mockHttp = new MockHttpMessageHandler(); - mockHttp.Expect("http://localhost/api/file/receipt/transaction ID") - .Respond(HttpStatusCode.OK); + mockHttp.VerifyNoOutstandingExpectation(); + } - using (var httpClient = mockHttp.ToHttpClient()) { - var signhostApiClient = new SignhostApiClient(settings, httpClient); + [Fact] + public async Task when_GetReceipt_is_called_then_we_should_have_called_the_filereceipt_get_once() + { + var mockHttp = new MockHttpMessageHandler(); + mockHttp.Expect("http://localhost/api/file/receipt/transaction ID") + .Respond(HttpStatusCode.OK); - var receipt = await signhostApiClient.GetReceiptAsync("transaction ID"); - } + using (var httpClient = mockHttp.ToHttpClient()) { + var signhostApiClient = new SignhostApiClient(settings, httpClient); - mockHttp.VerifyNoOutstandingExpectation(); + var receipt = await signhostApiClient.GetReceiptAsync("transaction ID"); } - [Fact] - public async Task when_GetDocument_is_called_then_we_should_have_called_the_file_get_once() - { - var mockHttp = new MockHttpMessageHandler(); - mockHttp.Expect(HttpMethod.Get, "http://localhost/api/transaction/*/file/file Id") - .Respond(HttpStatusCode.OK, new StringContent(string.Empty)); + mockHttp.VerifyNoOutstandingExpectation(); + } - using (var httpClient = mockHttp.ToHttpClient()) { + [Fact] + public async Task when_GetDocument_is_called_then_we_should_have_called_the_file_get_once() + { + var mockHttp = new MockHttpMessageHandler(); + mockHttp.Expect(HttpMethod.Get, "http://localhost/api/transaction/*/file/file Id") + .Respond(HttpStatusCode.OK, new StringContent(string.Empty)); - var signhostApiClient = new SignhostApiClient(settings, httpClient); + using (var httpClient = mockHttp.ToHttpClient()) { - var document = await signhostApiClient.GetDocumentAsync("transaction Id", "file Id"); - } + var signhostApiClient = new SignhostApiClient(settings, httpClient); - mockHttp.VerifyNoOutstandingExpectation(); + var document = await signhostApiClient.GetDocumentAsync("transaction Id", "file Id"); } - [Fact] - public async Task When_a_transaction_json_is_returned_it_is_deserialized_correctly() - { - var mockHttp = new MockHttpMessageHandler(); - mockHttp.Expect(HttpMethod.Post, "http://localhost/api/transaction") - .Respond(HttpStatusCode.OK, new StringContent(JsonResources.TransactionSingleSignerJson)); + mockHttp.VerifyNoOutstandingExpectation(); + } - using (var httpClient = mockHttp.ToHttpClient()) { + [Fact] + public async Task When_a_transaction_json_is_returned_it_is_deserialized_correctly() + { + var mockHttp = new MockHttpMessageHandler(); + mockHttp.Expect(HttpMethod.Post, "http://localhost/api/transaction") + .Respond(HttpStatusCode.OK, new StringContent(JsonResources.TransactionSingleSignerJson)); - var signhostApiClient = new SignhostApiClient(settings, httpClient); + using (var httpClient = mockHttp.ToHttpClient()) { - var result = await signhostApiClient.CreateTransactionAsync(new Transaction - { - Signers = new List{ - new Signer + var signhostApiClient = new SignhostApiClient(settings, httpClient); + + var result = await signhostApiClient.CreateTransactionAsync(new Transaction + { + Signers = new List{ + new Signer + { + Verifications = new List { - Verifications = new List + new PhoneNumberVerification { - new PhoneNumberVerification - { - Number = "31615087075" - } + Number = "31615087075" } } } - }); + } + }); + + result.Id.Should().Be("50262c3f-9744-45bf-a4c6-8a3whatever"); + result.CanceledDateTime.Should().HaveYear(2017); + result.Status.Should().Be(TransactionStatus.WaitingForDocument); + result.Signers.Should().HaveCount(1); + result.Receivers.Should().HaveCount(0); + result.Reference.Should().Be("Contract #123"); + result.SignRequestMode.Should().Be(2); + result.DaysToExpire.Should().Be(14); + result.Signers[0].Id.Should().Be("Signer1"); + result.Signers[0].Email.Should().Be("test1@example.com"); + result.Signers[0].Verifications.Should().HaveCount(1); + result.Signers[0].Verifications[0].Should().BeOfType() + .And.Subject.Should().BeEquivalentTo(new PhoneNumberVerification { + Number = "+31615123456" + }); + result.Signers[0].Activities.Should().HaveCount(3); + result.Signers[0].Activities[0].Should().BeEquivalentTo(new Activity + { + Id = "Activity1", + Code = ActivityType.Opened, + CreatedDateTime = DateTimeOffset.Parse("2017-05-31T22:15:17.6409005+02:00") + }); + } - result.Id.Should().Be("50262c3f-9744-45bf-a4c6-8a3whatever"); - result.CanceledDateTime.Should().HaveYear(2017); - result.Status.Should().Be(TransactionStatus.WaitingForDocument); - result.Signers.Should().HaveCount(1); - result.Receivers.Should().HaveCount(0); - result.Reference.Should().Be("Contract #123"); - result.SignRequestMode.Should().Be(2); - result.DaysToExpire.Should().Be(14); - result.Signers[0].Id.Should().Be("Signer1"); - result.Signers[0].Email.Should().Be("test1@example.com"); - result.Signers[0].Verifications.Should().HaveCount(1); - result.Signers[0].Verifications[0].Should().BeOfType() - .And.Subject.Should().BeEquivalentTo(new PhoneNumberVerification { - Number = "+31615123456" - }); - result.Signers[0].Activities.Should().HaveCount(3); - result.Signers[0].Activities[0].Should().BeEquivalentTo(new Activity - { - Id = "Activity1", - Code = ActivityType.Opened, - CreatedDateTime = DateTimeOffset.Parse("2017-05-31T22:15:17.6409005+02:00") - }); + mockHttp.VerifyNoOutstandingExpectation(); + } + + [Theory] + [InlineData(true)] + [InlineData(false)] + public async Task When_a_complete_transaction_flow_is_created_headers_are_not_set_multiple_times( + bool isOauth) + { + MockedRequest AddHeaders(MockedRequest request) + { + if (!isOauth) { + request = request.WithHeaders("Authorization", "APIKey Usertoken"); } - mockHttp.VerifyNoOutstandingExpectation(); + return request + .WithHeaders("Application", "APPKey AppKey") + .WithHeaders("X-Custom", "test"); } - [Theory] - [InlineData(true)] - [InlineData(false)] - public async Task When_a_complete_transaction_flow_is_created_headers_are_not_set_multiple_times( - bool isOauth) - { - MockedRequest AddHeaders(MockedRequest request) - { - if (!isOauth) { - request = request.WithHeaders("Authorization", "APIKey Usertoken"); - } - - return request - .WithHeaders("Application", "APPKey AppKey") - .WithHeaders("X-Custom", "test"); + var mockHttp = new MockHttpMessageHandler(); + AddHeaders(mockHttp.Expect(HttpMethod.Post, "http://localhost/api/transaction")) + .Respond(new StringContent(JsonResources.TransactionSingleSignerJson)); + AddHeaders(mockHttp.Expect(HttpMethod.Put, "http://localhost/api/transaction/*/file/somefileid")) + .Respond(HttpStatusCode.Accepted, new StringContent(JsonResources.AddOrReplaceFileMetaToTransaction)); + AddHeaders(mockHttp.Expect(HttpMethod.Put, "http://localhost/api/transaction/*/file/somefileid")) + .Respond(HttpStatusCode.Created); + AddHeaders(mockHttp.Expect(HttpMethod.Put, "http://localhost/api/transaction/*/start")) + .Respond(HttpStatusCode.NoContent); + + using (var httpClient = mockHttp.ToHttpClient()) { + var clientSettings = isOauth ? oauthSettings : settings; + clientSettings.AddHeader = add => add("X-Custom", "test"); + var signhostApiClient = new SignhostApiClient(clientSettings, httpClient); + + var result = await signhostApiClient.CreateTransactionAsync(new Transaction()); + await signhostApiClient.AddOrReplaceFileMetaToTransactionAsync(new FileMeta(), result.Id, "somefileid"); + using (Stream file = System.IO.File.Create("unittestdocument.pdf")) { + await signhostApiClient.AddOrReplaceFileToTransaction(file, result.Id, "somefileid"); } + await signhostApiClient.StartTransactionAsync(result.Id); + } - var mockHttp = new MockHttpMessageHandler(); - AddHeaders(mockHttp.Expect(HttpMethod.Post, "http://localhost/api/transaction")) - .Respond(new StringContent(JsonResources.TransactionSingleSignerJson)); - AddHeaders(mockHttp.Expect(HttpMethod.Put, "http://localhost/api/transaction/*/file/somefileid")) - .Respond(HttpStatusCode.Accepted, new StringContent(JsonResources.AddOrReplaceFileMetaToTransaction)); - AddHeaders(mockHttp.Expect(HttpMethod.Put, "http://localhost/api/transaction/*/file/somefileid")) - .Respond(HttpStatusCode.Created); - AddHeaders(mockHttp.Expect(HttpMethod.Put, "http://localhost/api/transaction/*/start")) - .Respond(HttpStatusCode.NoContent); - - using (var httpClient = mockHttp.ToHttpClient()) { - var clientSettings = isOauth ? oauthSettings : settings; - clientSettings.AddHeader = add => add("X-Custom", "test"); - var signhostApiClient = new SignhostApiClient(clientSettings, httpClient); - - var result = await signhostApiClient.CreateTransactionAsync(new Transaction()); - await signhostApiClient.AddOrReplaceFileMetaToTransactionAsync(new FileMeta(), result.Id, "somefileid"); - using (Stream file = System.IO.File.Create("unittestdocument.pdf")) { - await signhostApiClient.AddOrReplaceFileToTransaction(file, result.Id, "somefileid"); - } - await signhostApiClient.StartTransactionAsync(result.Id); - } + mockHttp.VerifyNoOutstandingExpectation(); + mockHttp.VerifyNoOutstandingRequest(); + } - mockHttp.VerifyNoOutstandingExpectation(); - mockHttp.VerifyNoOutstandingRequest(); - } + [Fact] + public async Task When_a_minimal_response_is_retrieved_list_and_dictionaries_are_not_null() + { + var mockHttp = new MockHttpMessageHandler(); + mockHttp + .Expect(HttpMethod.Get, "http://localhost/api/transaction/c487be92-0255-40c7-bd7d-20805a65e7d9") + .Respond(new StringContent(JsonResources.MinimalTransactionResponse)); - [Fact] - public async Task When_a_minimal_response_is_retrieved_list_and_dictionaries_are_not_null() + using (var httpClient = mockHttp.ToHttpClient()) { - var mockHttp = new MockHttpMessageHandler(); - mockHttp - .Expect(HttpMethod.Get, "http://localhost/api/transaction/c487be92-0255-40c7-bd7d-20805a65e7d9") - .Respond(new StringContent(JsonResources.MinimalTransactionResponse)); + var signhostApiClient = new SignhostApiClient(settings, httpClient); - using (var httpClient = mockHttp.ToHttpClient()) - { - var signhostApiClient = new SignhostApiClient(settings, httpClient); + var result = await signhostApiClient.GetTransactionAsync("c487be92-0255-40c7-bd7d-20805a65e7d9"); - var result = await signhostApiClient.GetTransactionAsync("c487be92-0255-40c7-bd7d-20805a65e7d9"); - - result.Signers.Should().BeEmpty(); - result.Receivers.Should().BeEmpty(); - result.Files.Should().BeEmpty(); - } + result.Signers.Should().BeEmpty(); + result.Receivers.Should().BeEmpty(); + result.Files.Should().BeEmpty(); } } } diff --git a/src/SignhostAPIClient.Tests/SignhostApiReceiverTests.cs b/src/SignhostAPIClient.Tests/SignhostApiReceiverTests.cs index 198f95b..4bab87e 100644 --- a/src/SignhostAPIClient.Tests/SignhostApiReceiverTests.cs +++ b/src/SignhostAPIClient.Tests/SignhostApiReceiverTests.cs @@ -4,76 +4,75 @@ using System.Collections.Generic; using SignhostAPIClient.Tests.JSON; -namespace Signhost.APIClient.Rest.Tests +namespace Signhost.APIClient.Rest.Tests; + +public class SignhostApiReceiverTests { - public class SignhostApiReceiverTests - { - private SignhostApiReceiverSettings receiverSettings = new SignhostApiReceiverSettings("SharedSecret"); + private SignhostApiReceiverSettings receiverSettings = new SignhostApiReceiverSettings("SharedSecret"); - [Fact] - public void when_IsPostbackChecksumValid_is_called_with_valid_postback_in_body_then_true_is_returned() - { - // Arrange - IDictionary headers = new Dictionary { { "Content-Type", new[] { "application/json" } } }; - string body = JsonResources.MockPostbackValid; + [Fact] + public void when_IsPostbackChecksumValid_is_called_with_valid_postback_in_body_then_true_is_returned() + { + // Arrange + IDictionary headers = new Dictionary { { "Content-Type", new[] { "application/json" } } }; + string body = JsonResources.MockPostbackValid; - // Act - SignhostApiReceiver signhostApiReceiver = new SignhostApiReceiver(receiverSettings); - bool result = signhostApiReceiver.IsPostbackChecksumValid(headers, body, out Transaction transaction); + // Act + SignhostApiReceiver signhostApiReceiver = new SignhostApiReceiver(receiverSettings); + bool result = signhostApiReceiver.IsPostbackChecksumValid(headers, body, out Transaction transaction); - // Assert - result.Should().BeTrue(); - } + // Assert + result.Should().BeTrue(); + } - [Fact] - public void when_IsPostbackChecksumValid_is_called_with_invalid_postback_in_body_then_false_is_returned() - { - // Arrange - IDictionary headers = new Dictionary { { "Content-Type", new[] { "application/json" } } }; - string body = JsonResources.MockPostbackInvalid; + [Fact] + public void when_IsPostbackChecksumValid_is_called_with_invalid_postback_in_body_then_false_is_returned() + { + // Arrange + IDictionary headers = new Dictionary { { "Content-Type", new[] { "application/json" } } }; + string body = JsonResources.MockPostbackInvalid; - // Act - SignhostApiReceiver signhostApiReceiver = new SignhostApiReceiver(receiverSettings); - bool result = signhostApiReceiver.IsPostbackChecksumValid(headers, body, out Transaction transaction); + // Act + SignhostApiReceiver signhostApiReceiver = new SignhostApiReceiver(receiverSettings); + bool result = signhostApiReceiver.IsPostbackChecksumValid(headers, body, out Transaction transaction); - // Assert - result.Should().BeFalse(); - } + // Assert + result.Should().BeFalse(); + } - [Fact] - public void when_IsPostbackChecksumValid_is_called_with_valid_postback_in_header_then_true_is_returned() - { - // Arrange - IDictionary headers = new Dictionary { - { "Content-Type", new[] { "application/json" }}, - {"Checksum", new[] {"cdc09eee2ed6df2846dcc193aedfef59f2834f8d"}} - }; - string body = JsonResources.MockPostbackValid; + [Fact] + public void when_IsPostbackChecksumValid_is_called_with_valid_postback_in_header_then_true_is_returned() + { + // Arrange + IDictionary headers = new Dictionary { + { "Content-Type", new[] { "application/json" }}, + {"Checksum", new[] {"cdc09eee2ed6df2846dcc193aedfef59f2834f8d"}} + }; + string body = JsonResources.MockPostbackValid; - // Act - SignhostApiReceiver signhostApiReceiver = new SignhostApiReceiver(receiverSettings); - bool result = signhostApiReceiver.IsPostbackChecksumValid(headers, body, out Transaction transaction); + // Act + SignhostApiReceiver signhostApiReceiver = new SignhostApiReceiver(receiverSettings); + bool result = signhostApiReceiver.IsPostbackChecksumValid(headers, body, out Transaction transaction); - // Assert - result.Should().BeTrue(); - } + // Assert + result.Should().BeTrue(); + } - [Fact] - public void when_IsPostbackChecksumValid_is_called_with_invalid_postback_in_header_then_false_is_returned() - { - // Arrange - IDictionary headers = new Dictionary { - { "Content-Type", new[] { "application/json" }}, - {"Checksum", new[] {"70dda90616f744797972c0d2f787f86643a60c83"}} - }; - string body = JsonResources.MockPostbackValid; + [Fact] + public void when_IsPostbackChecksumValid_is_called_with_invalid_postback_in_header_then_false_is_returned() + { + // Arrange + IDictionary headers = new Dictionary { + { "Content-Type", new[] { "application/json" }}, + {"Checksum", new[] {"70dda90616f744797972c0d2f787f86643a60c83"}} + }; + string body = JsonResources.MockPostbackValid; - // Act - SignhostApiReceiver signhostApiReceiver = new SignhostApiReceiver(receiverSettings); - bool result = signhostApiReceiver.IsPostbackChecksumValid(headers, body, out Transaction transaction); + // Act + SignhostApiReceiver signhostApiReceiver = new SignhostApiReceiver(receiverSettings); + bool result = signhostApiReceiver.IsPostbackChecksumValid(headers, body, out Transaction transaction); - // Assert - result.Should().BeFalse(); - } + // Assert + result.Should().BeFalse(); } } diff --git a/src/SignhostAPIClient/Rest/ApiResponse.cs b/src/SignhostAPIClient/Rest/ApiResponse.cs index 42e2134..bdd84e3 100644 --- a/src/SignhostAPIClient/Rest/ApiResponse.cs +++ b/src/SignhostAPIClient/Rest/ApiResponse.cs @@ -1,36 +1,35 @@ using System.Net; using System.Net.Http; -namespace Signhost.APIClient.Rest +namespace Signhost.APIClient.Rest; + +public class ApiResponse { - public class ApiResponse - { - private readonly HttpResponseMessage httpResponse; + private readonly HttpResponseMessage httpResponse; - public ApiResponse(HttpResponseMessage httpResponse, TValue value) - { - this.httpResponse = httpResponse; - this.Value = value; - } + public ApiResponse(HttpResponseMessage httpResponse, TValue value) + { + this.httpResponse = httpResponse; + this.Value = value; + } - public TValue Value { get; private set; } + public TValue Value { get; private set; } - public HttpStatusCode HttpStatusCode => httpResponse.StatusCode; + public HttpStatusCode HttpStatusCode => httpResponse.StatusCode; - public void EnsureAvailableStatusCode() - { - if (HttpStatusCode == HttpStatusCode.Gone) { - throw new ErrorHandling.GoneException( - httpResponse.ReasonPhrase, - Value) - { - // TO-DO: Make async in v5 - ResponseBody = httpResponse.Content.ReadAsStringAsync() - .ConfigureAwait(false) - .GetAwaiter() - .GetResult(), - }; - } + public void EnsureAvailableStatusCode() + { + if (HttpStatusCode == HttpStatusCode.Gone) { + throw new ErrorHandling.GoneException( + httpResponse.ReasonPhrase, + Value) + { + // TO-DO: Make async in v5 + ResponseBody = httpResponse.Content.ReadAsStringAsync() + .ConfigureAwait(false) + .GetAwaiter() + .GetResult(), + }; } } } diff --git a/src/SignhostAPIClient/Rest/DataObjects/Activity.cs b/src/SignhostAPIClient/Rest/DataObjects/Activity.cs index 517d255..37a9811 100644 --- a/src/SignhostAPIClient/Rest/DataObjects/Activity.cs +++ b/src/SignhostAPIClient/Rest/DataObjects/Activity.cs @@ -1,19 +1,18 @@ using System; using System.Text.Json.Serialization; -namespace Signhost.APIClient.Rest.DataObjects +namespace Signhost.APIClient.Rest.DataObjects; + +public class Activity { - public class Activity - { - public string Id { get; set; } + public string Id { get; set; } - public ActivityType Code { get; set; } + public ActivityType Code { get; set; } - [JsonPropertyName("Activity")] - public string ActivityValue { get; set; } + [JsonPropertyName("Activity")] + public string ActivityValue { get; set; } - public string Info { get; set; } + public string Info { get; set; } - public DateTimeOffset CreatedDateTime { get; set; } - } + public DateTimeOffset CreatedDateTime { get; set; } } diff --git a/src/SignhostAPIClient/Rest/DataObjects/ActivityType.cs b/src/SignhostAPIClient/Rest/DataObjects/ActivityType.cs index 576f9eb..223be52 100644 --- a/src/SignhostAPIClient/Rest/DataObjects/ActivityType.cs +++ b/src/SignhostAPIClient/Rest/DataObjects/ActivityType.cs @@ -1,136 +1,135 @@ -namespace Signhost.APIClient.Rest.DataObjects +namespace Signhost.APIClient.Rest.DataObjects; + +/// +/// type. +/// +// TO-DO: Remove unused activity types in v5. +public enum ActivityType { /// - /// type. - /// - // TO-DO: Remove unused activity types in v5. - public enum ActivityType - { - /// - /// The invitation mail was sent. - /// - InvitationSent = 101, - - /// - /// The invitation mail was received. - /// - InvitationReceived = 102, - - /// - /// The sign url was opened. - /// - Opened = 103, - - /// - /// An invitation reminder mail was sent. - /// - InvitationReminderResent = 104, - - /// - /// The document was opened. - /// The contains the fileId of the opened - /// document. - /// - DocumentOpened = 105, - - /// - /// Consumer Signing identity approved. - /// - IdentityApproved = 110, - - /// - /// Consumer Signing identity failed. - /// - IdentityFailed = 111, - - /// - /// Cancelled. - /// - Cancelled = 201, - - /// - /// The signer rejected the sign request. - /// - Rejected = 202, - - /// - /// The signer signed the documents. - /// - Signed = 203, - - /// - /// The signer delegated signing to a different signer. - /// - SignerDelegated = 204, - - /// - /// Signed document sent. - /// - SignedDocumentSent = 301, - - /// - /// Signed document opened. - /// - SignedDocumentOpened = 302, - - /// - /// Signed document downloaded. - /// - SignedDocumentDownloaded = 303, - - /// - /// Receipt sent. - /// - ReceiptSent = 401, - - /// - /// Receipt opened. - /// - ReceiptOpened = 402, - - /// - /// Receipt downloaded. - /// - ReceiptDownloaded = 403, - - /// - /// Finished. - /// - Finished = 500, - - /// - /// Deleted. - /// - Deleted = 600, - - /// - /// Expired. - /// - Expired = 700, - - /// - /// Email bounce - hard. - /// - EmailBounceHard = 901, - - /// - /// Email bounce - soft. - /// - EmailBounceSoft = 902, - - /// - /// Email bounce - blocked. - /// - EmailBounceBlocked = 903, - - /// - /// Email bounce - undetermined. - /// - EmailBounceUndetermined = 904, - - /// - /// Operation failed. - /// - Failed = 999, - } + /// The invitation mail was sent. + /// + InvitationSent = 101, + + /// + /// The invitation mail was received. + /// + InvitationReceived = 102, + + /// + /// The sign url was opened. + /// + Opened = 103, + + /// + /// An invitation reminder mail was sent. + /// + InvitationReminderResent = 104, + + /// + /// The document was opened. + /// The contains the fileId of the opened + /// document. + /// + DocumentOpened = 105, + + /// + /// Consumer Signing identity approved. + /// + IdentityApproved = 110, + + /// + /// Consumer Signing identity failed. + /// + IdentityFailed = 111, + + /// + /// Cancelled. + /// + Cancelled = 201, + + /// + /// The signer rejected the sign request. + /// + Rejected = 202, + + /// + /// The signer signed the documents. + /// + Signed = 203, + + /// + /// The signer delegated signing to a different signer. + /// + SignerDelegated = 204, + + /// + /// Signed document sent. + /// + SignedDocumentSent = 301, + + /// + /// Signed document opened. + /// + SignedDocumentOpened = 302, + + /// + /// Signed document downloaded. + /// + SignedDocumentDownloaded = 303, + + /// + /// Receipt sent. + /// + ReceiptSent = 401, + + /// + /// Receipt opened. + /// + ReceiptOpened = 402, + + /// + /// Receipt downloaded. + /// + ReceiptDownloaded = 403, + + /// + /// Finished. + /// + Finished = 500, + + /// + /// Deleted. + /// + Deleted = 600, + + /// + /// Expired. + /// + Expired = 700, + + /// + /// Email bounce - hard. + /// + EmailBounceHard = 901, + + /// + /// Email bounce - soft. + /// + EmailBounceSoft = 902, + + /// + /// Email bounce - blocked. + /// + EmailBounceBlocked = 903, + + /// + /// Email bounce - undetermined. + /// + EmailBounceUndetermined = 904, + + /// + /// Operation failed. + /// + Failed = 999, } diff --git a/src/SignhostAPIClient/Rest/DataObjects/ConsentVerification.cs b/src/SignhostAPIClient/Rest/DataObjects/ConsentVerification.cs index 0028961..6a045ce 100644 --- a/src/SignhostAPIClient/Rest/DataObjects/ConsentVerification.cs +++ b/src/SignhostAPIClient/Rest/DataObjects/ConsentVerification.cs @@ -1,10 +1,9 @@ -namespace Signhost.APIClient.Rest.DataObjects +namespace Signhost.APIClient.Rest.DataObjects; + +/// +/// Adds a consent verification screen +/// +public class ConsentVerification + : IVerification { - /// - /// Adds a consent verification screen - /// - public class ConsentVerification - : IVerification - { - } } diff --git a/src/SignhostAPIClient/Rest/DataObjects/CscVerification.cs b/src/SignhostAPIClient/Rest/DataObjects/CscVerification.cs index 2ceb0de..3befa8d 100644 --- a/src/SignhostAPIClient/Rest/DataObjects/CscVerification.cs +++ b/src/SignhostAPIClient/Rest/DataObjects/CscVerification.cs @@ -1,36 +1,35 @@ using System.Collections.Generic; -namespace Signhost.APIClient.Rest.DataObjects +namespace Signhost.APIClient.Rest.DataObjects; + +/// +/// Cloud Signature Consortium (CSC) verification. +/// +public class CscVerification + : IVerification { /// - /// Cloud Signature Consortium (CSC) verification. + /// Gets or sets the provider identifier. /// - public class CscVerification - : IVerification - { - /// - /// Gets or sets the provider identifier. - /// - public string Provider { get; set; } + public string Provider { get; set; } - /// - /// Gets or sets the certificate issuer. - /// - public string Issuer { get; set; } + /// + /// Gets or sets the certificate issuer. + /// + public string Issuer { get; set; } - /// - /// Gets or sets the certificate subject. - /// - public string Subject { get; set; } + /// + /// Gets or sets the certificate subject. + /// + public string Subject { get; set; } - /// - /// Gets or sets the certificate thumbprint. - /// - public string Thumbprint { get; set; } + /// + /// Gets or sets the certificate thumbprint. + /// + public string Thumbprint { get; set; } - /// - /// Gets or sets additional user data. - /// - public Dictionary AdditionalUserData { get; set; } - } + /// + /// Gets or sets additional user data. + /// + public Dictionary AdditionalUserData { get; set; } } diff --git a/src/SignhostAPIClient/Rest/DataObjects/DeleteTransactionOptions.cs b/src/SignhostAPIClient/Rest/DataObjects/DeleteTransactionOptions.cs index 19ad295..ae3ecc5 100644 --- a/src/SignhostAPIClient/Rest/DataObjects/DeleteTransactionOptions.cs +++ b/src/SignhostAPIClient/Rest/DataObjects/DeleteTransactionOptions.cs @@ -1,16 +1,15 @@ -namespace Signhost.APIClient.Rest.DataObjects +namespace Signhost.APIClient.Rest.DataObjects; + +public class DeleteTransactionOptions { - public class DeleteTransactionOptions - { - /// - /// Gets or sets a value indicating whether - /// e-mail notifications should be send to the awaiting signers. - /// - public bool SendNotifications { get; set; } + /// + /// Gets or sets a value indicating whether + /// e-mail notifications should be send to the awaiting signers. + /// + public bool SendNotifications { get; set; } - /// - /// Gets or sets the reason of cancellation. - /// - public string Reason { get; set; } - } + /// + /// Gets or sets the reason of cancellation. + /// + public string Reason { get; set; } } diff --git a/src/SignhostAPIClient/Rest/DataObjects/DigidVerification.cs b/src/SignhostAPIClient/Rest/DataObjects/DigidVerification.cs index bfb8b9f..6ec39b9 100644 --- a/src/SignhostAPIClient/Rest/DataObjects/DigidVerification.cs +++ b/src/SignhostAPIClient/Rest/DataObjects/DigidVerification.cs @@ -1,10 +1,9 @@ -namespace Signhost.APIClient.Rest.DataObjects +namespace Signhost.APIClient.Rest.DataObjects; + +public class DigidVerification + : IVerification { - public class DigidVerification - : IVerification - { - public string Bsn { get; set; } + public string Bsn { get; set; } - public bool? SecureDownload { get; set; } - } + public bool? SecureDownload { get; set; } } diff --git a/src/SignhostAPIClient/Rest/DataObjects/EherkenningVerification.cs b/src/SignhostAPIClient/Rest/DataObjects/EherkenningVerification.cs index 647b1c0..838144b 100644 --- a/src/SignhostAPIClient/Rest/DataObjects/EherkenningVerification.cs +++ b/src/SignhostAPIClient/Rest/DataObjects/EherkenningVerification.cs @@ -1,16 +1,15 @@ -namespace Signhost.APIClient.Rest.DataObjects +namespace Signhost.APIClient.Rest.DataObjects; + +public class EherkenningVerification + : IVerification { - public class EherkenningVerification - : IVerification - { - /// - /// Gets or sets the Uid. - /// - public string Uid { get; set; } + /// + /// Gets or sets the Uid. + /// + public string Uid { get; set; } - /// - /// Gets or sets the entity concern ID / KVK number. - /// - public string EntityConcernIdKvkNr { get; set; } - } + /// + /// Gets or sets the entity concern ID / KVK number. + /// + public string EntityConcernIdKvkNr { get; set; } } diff --git a/src/SignhostAPIClient/Rest/DataObjects/EidasLoginVerification.cs b/src/SignhostAPIClient/Rest/DataObjects/EidasLoginVerification.cs index 18ac4af..3d54b0e 100644 --- a/src/SignhostAPIClient/Rest/DataObjects/EidasLoginVerification.cs +++ b/src/SignhostAPIClient/Rest/DataObjects/EidasLoginVerification.cs @@ -3,43 +3,42 @@ using System.Text.Json.Serialization; using Signhost.APIClient.Rest.JsonConverters; -namespace Signhost.APIClient.Rest.DataObjects +namespace Signhost.APIClient.Rest.DataObjects; + +/// +/// Verification object for eIDAS. +/// +public class EidasLoginVerification + : IVerification { /// - /// Verification object for eIDAS. + /// Gets or sets the uid. /// - public class EidasLoginVerification - : IVerification - { - /// - /// Gets or sets the uid. - /// - public string Uid { get; set; } + public string Uid { get; set; } - /// - /// Gets or sets the level. - /// - [JsonConverter(typeof(LevelEnumConverter))] - public Level? Level { get; set; } + /// + /// Gets or sets the level. + /// + [JsonConverter(typeof(LevelEnumConverter))] + public Level? Level { get; set; } - /// - /// Gets or sets the first name. - /// - public string FirstName { get; set; } + /// + /// Gets or sets the first name. + /// + public string FirstName { get; set; } - /// - /// Gets or sets the last name. - /// - public string LastName { get; set; } + /// + /// Gets or sets the last name. + /// + public string LastName { get; set; } - /// - /// Gets or sets the date of birth. - /// - public DateTime? DateOfBirth { get; set; } + /// + /// Gets or sets the date of birth. + /// + public DateTime? DateOfBirth { get; set; } - /// - /// Gets or sets the eIDAS attributes. - /// - public IDictionary Attributes { get; set; } - } + /// + /// Gets or sets the eIDAS attributes. + /// + public IDictionary Attributes { get; set; } } diff --git a/src/SignhostAPIClient/Rest/DataObjects/Field.cs b/src/SignhostAPIClient/Rest/DataObjects/Field.cs index a7453c8..58cd3f9 100644 --- a/src/SignhostAPIClient/Rest/DataObjects/Field.cs +++ b/src/SignhostAPIClient/Rest/DataObjects/Field.cs @@ -1,13 +1,12 @@ -namespace Signhost.APIClient.Rest.DataObjects +namespace Signhost.APIClient.Rest.DataObjects; + +public class Field { - public class Field - { - // TO-DO: Make enum in v5. - public string Type { get; set; } + // TO-DO: Make enum in v5. + public string Type { get; set; } - // TO-DO: Can be boolean, number, string, should be fixed in v5. - public string Value { get; set; } + // TO-DO: Can be boolean, number, string, should be fixed in v5. + public string Value { get; set; } - public Location Location { get; set; } - } + public Location Location { get; set; } } diff --git a/src/SignhostAPIClient/Rest/DataObjects/FileEntry.cs b/src/SignhostAPIClient/Rest/DataObjects/FileEntry.cs index 5a3cc1b..f1565e5 100644 --- a/src/SignhostAPIClient/Rest/DataObjects/FileEntry.cs +++ b/src/SignhostAPIClient/Rest/DataObjects/FileEntry.cs @@ -1,11 +1,10 @@ using System.Collections.Generic; -namespace Signhost.APIClient.Rest.DataObjects +namespace Signhost.APIClient.Rest.DataObjects; + +public class FileEntry { - public class FileEntry - { - public IList Links { get; set; } + public IList Links { get; set; } - public string DisplayName { get; set; } - } + public string DisplayName { get; set; } } diff --git a/src/SignhostAPIClient/Rest/DataObjects/FileLink.cs b/src/SignhostAPIClient/Rest/DataObjects/FileLink.cs index d1fb032..f503d5a 100644 --- a/src/SignhostAPIClient/Rest/DataObjects/FileLink.cs +++ b/src/SignhostAPIClient/Rest/DataObjects/FileLink.cs @@ -1,11 +1,10 @@ -namespace Signhost.APIClient.Rest.DataObjects +namespace Signhost.APIClient.Rest.DataObjects; + +public class FileLink { - public class FileLink - { - public string Rel { get; set; } + public string Rel { get; set; } - public string Type { get; set; } + public string Type { get; set; } - public string Link { get; set; } - } + public string Link { get; set; } } diff --git a/src/SignhostAPIClient/Rest/DataObjects/FileMeta.cs b/src/SignhostAPIClient/Rest/DataObjects/FileMeta.cs index 6cbda5a..b20b72e 100644 --- a/src/SignhostAPIClient/Rest/DataObjects/FileMeta.cs +++ b/src/SignhostAPIClient/Rest/DataObjects/FileMeta.cs @@ -1,27 +1,26 @@ using System.Collections.Generic; using System.Text.Json.Serialization; -namespace Signhost.APIClient.Rest.DataObjects +namespace Signhost.APIClient.Rest.DataObjects; + +public class FileMeta { - public class FileMeta - { - public int? DisplayOrder { get; set; } + public int? DisplayOrder { get; set; } - public string DisplayName { get; set; } + public string DisplayName { get; set; } - public string Description { get; set; } + public string Description { get; set; } - public IDictionary Signers { get; set; } + public IDictionary Signers { get; set; } - public IDictionary> FormSets { get; set; } + public IDictionary> FormSets { get; set; } - /// - /// Gets or sets whether to use the scribble signature as a paraph - /// on each non-signed page. - /// Don't use this setting unless you are really sure this is what you - /// want and know the side-effects. - /// - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - public bool? SetParaph { get; set; } - } + /// + /// Gets or sets whether to use the scribble signature as a paraph + /// on each non-signed page. + /// Don't use this setting unless you are really sure this is what you + /// want and know the side-effects. + /// + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public bool? SetParaph { get; set; } } diff --git a/src/SignhostAPIClient/Rest/DataObjects/FileSignerMeta.cs b/src/SignhostAPIClient/Rest/DataObjects/FileSignerMeta.cs index 1c39aa8..7adfc40 100644 --- a/src/SignhostAPIClient/Rest/DataObjects/FileSignerMeta.cs +++ b/src/SignhostAPIClient/Rest/DataObjects/FileSignerMeta.cs @@ -1,7 +1,6 @@ -namespace Signhost.APIClient.Rest.DataObjects +namespace Signhost.APIClient.Rest.DataObjects; + +public class FileSignerMeta { - public class FileSignerMeta - { - public string[] FormSets { get; set; } - } + public string[] FormSets { get; set; } } diff --git a/src/SignhostAPIClient/Rest/DataObjects/IPAddressVerification.cs b/src/SignhostAPIClient/Rest/DataObjects/IPAddressVerification.cs index cb68fcd..b1fb67f 100644 --- a/src/SignhostAPIClient/Rest/DataObjects/IPAddressVerification.cs +++ b/src/SignhostAPIClient/Rest/DataObjects/IPAddressVerification.cs @@ -1,14 +1,13 @@ -namespace Signhost.APIClient.Rest.DataObjects +namespace Signhost.APIClient.Rest.DataObjects; + +/// +/// Adds a consent verification screen +/// +public class IPAddressVerification + : IVerification { /// - /// Adds a consent verification screen + /// Gets or sets the IP Address used by the signer while signing the documents. /// - public class IPAddressVerification - : IVerification - { - /// - /// Gets or sets the IP Address used by the signer while signing the documents. - /// - public string IPAddress { get; set; } - } + public string IPAddress { get; set; } } diff --git a/src/SignhostAPIClient/Rest/DataObjects/IVerification.cs b/src/SignhostAPIClient/Rest/DataObjects/IVerification.cs index bbab8ea..de6a297 100644 --- a/src/SignhostAPIClient/Rest/DataObjects/IVerification.cs +++ b/src/SignhostAPIClient/Rest/DataObjects/IVerification.cs @@ -1,24 +1,22 @@ using System.Text.Json.Serialization; -namespace Signhost.APIClient.Rest.DataObjects +namespace Signhost.APIClient.Rest.DataObjects; + +[JsonPolymorphic(TypeDiscriminatorPropertyName = "Type")] +[JsonDerivedType(typeof(ConsentVerification), "Consent")] +[JsonDerivedType(typeof(DigidVerification), "DigiD")] +[JsonDerivedType(typeof(EidasLoginVerification), "eIDAS Login")] +[JsonDerivedType(typeof(IdealVerification), "iDeal")] +[JsonDerivedType(typeof(IdinVerification), "iDIN")] +[JsonDerivedType(typeof(IPAddressVerification), "IPAddress")] +[JsonDerivedType(typeof(ItsmeIdentificationVerification), "itsme Identification")] +[JsonDerivedType(typeof(PhoneNumberVerification), "PhoneNumber")] +[JsonDerivedType(typeof(ScribbleVerification), "Scribble")] +[JsonDerivedType(typeof(SurfnetVerification), "SURFnet")] +[JsonDerivedType(typeof(CscVerification), "CSC Qualified")] +[JsonDerivedType(typeof(EherkenningVerification), "eHerkenning")] +[JsonDerivedType(typeof(OidcVerification), "OpenID Providers")] +[JsonDerivedType(typeof(OnfidoVerification), "Onfido")] +public interface IVerification { - // TO-DO: Split to verification and authentication in v5 - [JsonPolymorphic(TypeDiscriminatorPropertyName = "Type")] - [JsonDerivedType(typeof(ConsentVerification), "Consent")] - [JsonDerivedType(typeof(DigidVerification), "DigiD")] - [JsonDerivedType(typeof(EidasLoginVerification), "eIDAS Login")] - [JsonDerivedType(typeof(IdealVerification), "iDeal")] - [JsonDerivedType(typeof(IdinVerification), "iDIN")] - [JsonDerivedType(typeof(IPAddressVerification), "IPAddress")] - [JsonDerivedType(typeof(ItsmeIdentificationVerification), "itsme Identification")] - [JsonDerivedType(typeof(PhoneNumberVerification), "PhoneNumber")] - [JsonDerivedType(typeof(ScribbleVerification), "Scribble")] - [JsonDerivedType(typeof(SurfnetVerification), "SURFnet")] - [JsonDerivedType(typeof(CscVerification), "CSC Qualified")] - [JsonDerivedType(typeof(EherkenningVerification), "eHerkenning")] - [JsonDerivedType(typeof(OidcVerification), "OpenID Providers")] - [JsonDerivedType(typeof(OnfidoVerification), "Onfido")] - public interface IVerification - { - } } diff --git a/src/SignhostAPIClient/Rest/DataObjects/IdealVerification.cs b/src/SignhostAPIClient/Rest/DataObjects/IdealVerification.cs index 5267f28..55f1bc9 100644 --- a/src/SignhostAPIClient/Rest/DataObjects/IdealVerification.cs +++ b/src/SignhostAPIClient/Rest/DataObjects/IdealVerification.cs @@ -1,12 +1,11 @@ -namespace Signhost.APIClient.Rest.DataObjects +namespace Signhost.APIClient.Rest.DataObjects; + +public class IdealVerification + : IVerification { - public class IdealVerification - : IVerification - { - public string Iban { get; set; } + public string Iban { get; set; } - public string AccountHolderName { get; set; } + public string AccountHolderName { get; set; } - public string AccountHolderCity { get; set; } - } + public string AccountHolderCity { get; set; } } diff --git a/src/SignhostAPIClient/Rest/DataObjects/IdinVerification.cs b/src/SignhostAPIClient/Rest/DataObjects/IdinVerification.cs index 38adcd9..150f398 100644 --- a/src/SignhostAPIClient/Rest/DataObjects/IdinVerification.cs +++ b/src/SignhostAPIClient/Rest/DataObjects/IdinVerification.cs @@ -1,19 +1,18 @@ using System; using System.Collections.Generic; -namespace Signhost.APIClient.Rest.DataObjects +namespace Signhost.APIClient.Rest.DataObjects; + +public class IdinVerification + : IVerification { - public class IdinVerification - : IVerification - { - public string AccountHolderName { get; set; } + public string AccountHolderName { get; set; } - public string AccountHolderAddress1 { get; set; } + public string AccountHolderAddress1 { get; set; } - public string AccountHolderAddress2 { get; set; } + public string AccountHolderAddress2 { get; set; } - public DateTime AccountHolderDateOfBirth { get; set; } + public DateTime AccountHolderDateOfBirth { get; set; } - public IDictionary Attributes { get; set; } - } + public IDictionary Attributes { get; set; } } diff --git a/src/SignhostAPIClient/Rest/DataObjects/ItsmeIdentificationVerification.cs b/src/SignhostAPIClient/Rest/DataObjects/ItsmeIdentificationVerification.cs index eeae90b..914665e 100644 --- a/src/SignhostAPIClient/Rest/DataObjects/ItsmeIdentificationVerification.cs +++ b/src/SignhostAPIClient/Rest/DataObjects/ItsmeIdentificationVerification.cs @@ -1,14 +1,13 @@ -namespace Signhost.APIClient.Rest.DataObjects +namespace Signhost.APIClient.Rest.DataObjects; + +/// +/// Verification object for itsme Identification. +/// +public class ItsmeIdentificationVerification + : IVerification { /// - /// Verification object for itsme Identification. + /// Gets or sets the phonenumber. /// - public class ItsmeIdentificationVerification - : IVerification - { - /// - /// Gets or sets the phonenumber. - /// - public string PhoneNumber { get; set; } - } + public string PhoneNumber { get; set; } } diff --git a/src/SignhostAPIClient/Rest/DataObjects/Level.cs b/src/SignhostAPIClient/Rest/DataObjects/Level.cs index 93f223b..a96cd7f 100644 --- a/src/SignhostAPIClient/Rest/DataObjects/Level.cs +++ b/src/SignhostAPIClient/Rest/DataObjects/Level.cs @@ -1,28 +1,27 @@ -namespace Signhost.APIClient.Rest.DataObjects +namespace Signhost.APIClient.Rest.DataObjects; + +/// +/// Level of Assurance. +/// +public enum Level { /// - /// Level of Assurance. + /// Unknown. /// - public enum Level - { - /// - /// Unknown. - /// - Unknown = 0, + Unknown = 0, - /// - /// Low. - /// - Low, + /// + /// Low. + /// + Low, - /// - /// Substantial. - /// - Substantial, + /// + /// Substantial. + /// + Substantial, - /// - /// High. - /// - High, - } + /// + /// High. + /// + High, } diff --git a/src/SignhostAPIClient/Rest/DataObjects/Location.cs b/src/SignhostAPIClient/Rest/DataObjects/Location.cs index d7288d9..1799ad6 100644 --- a/src/SignhostAPIClient/Rest/DataObjects/Location.cs +++ b/src/SignhostAPIClient/Rest/DataObjects/Location.cs @@ -1,23 +1,22 @@ -namespace Signhost.APIClient.Rest.DataObjects +namespace Signhost.APIClient.Rest.DataObjects; + +public class Location { - public class Location - { - public string Search { get; set; } + public string Search { get; set; } - public int? Occurence { get; set; } + public int? Occurence { get; set; } - public int? Top { get; set; } + public int? Top { get; set; } - public int? Right { get; set; } + public int? Right { get; set; } - public int? Bottom { get; set; } + public int? Bottom { get; set; } - public int? Left { get; set; } + public int? Left { get; set; } - public int? Width { get; set; } + public int? Width { get; set; } - public int? Height { get; set; } + public int? Height { get; set; } - public int? PageNumber { get; set; } - } + public int? PageNumber { get; set; } } diff --git a/src/SignhostAPIClient/Rest/DataObjects/OidcVerification.cs b/src/SignhostAPIClient/Rest/DataObjects/OidcVerification.cs index 57507de..608f9df 100644 --- a/src/SignhostAPIClient/Rest/DataObjects/OidcVerification.cs +++ b/src/SignhostAPIClient/Rest/DataObjects/OidcVerification.cs @@ -1,14 +1,13 @@ -namespace Signhost.APIClient.Rest.DataObjects +namespace Signhost.APIClient.Rest.DataObjects; + +/// +/// OpenID Connect identification. +/// +public class OidcVerification + : IVerification { /// - /// OpenID Connect identification. + /// Gets or sets the OIDC provider name. /// - public class OidcVerification - : IVerification - { - /// - /// Gets or sets the OIDC provider name. - /// - public string ProviderName { get; set; } - } + public string ProviderName { get; set; } } diff --git a/src/SignhostAPIClient/Rest/DataObjects/OnfidoVerification.cs b/src/SignhostAPIClient/Rest/DataObjects/OnfidoVerification.cs index ae2a6a3..c980c25 100644 --- a/src/SignhostAPIClient/Rest/DataObjects/OnfidoVerification.cs +++ b/src/SignhostAPIClient/Rest/DataObjects/OnfidoVerification.cs @@ -1,32 +1,31 @@ using System; using System.Collections.Generic; -namespace Signhost.APIClient.Rest.DataObjects +namespace Signhost.APIClient.Rest.DataObjects; + +/// +/// Onfido identity verification. +/// +public class OnfidoVerification + : IVerification { /// - /// Onfido identity verification. + /// Gets or sets the Onfido workflow identifier. /// - public class OnfidoVerification - : IVerification - { - /// - /// Gets or sets the Onfido workflow identifier. - /// - public Guid? WorkflowId { get; set; } + public Guid? WorkflowId { get; set; } - /// - /// Gets or sets the Onfido workflow run identifier. - /// - public Guid? WorkflowRunId { get; set; } + /// + /// Gets or sets the Onfido workflow run identifier. + /// + public Guid? WorkflowRunId { get; set; } - /// - /// Gets or sets the Onfido API version. - /// - public int? Version { get; set; } + /// + /// Gets or sets the Onfido API version. + /// + public int? Version { get; set; } - /// - /// Gets or sets raw Onfido attributes (availability not guaranteed). - /// - public Dictionary Attributes { get; set; } - } + /// + /// Gets or sets raw Onfido attributes (availability not guaranteed). + /// + public Dictionary? Attributes { get; set; } } diff --git a/src/SignhostAPIClient/Rest/DataObjects/PhoneNumberVerification.cs b/src/SignhostAPIClient/Rest/DataObjects/PhoneNumberVerification.cs index 6881543..38640a1 100644 --- a/src/SignhostAPIClient/Rest/DataObjects/PhoneNumberVerification.cs +++ b/src/SignhostAPIClient/Rest/DataObjects/PhoneNumberVerification.cs @@ -1,10 +1,9 @@ -namespace Signhost.APIClient.Rest.DataObjects +namespace Signhost.APIClient.Rest.DataObjects; + +public class PhoneNumberVerification + : IVerification { - public class PhoneNumberVerification - : IVerification - { - public string Number { get; set; } + public string Number { get; set; } - public bool? SecureDownload { get; set; } - } + public bool? SecureDownload { get; set; } } diff --git a/src/SignhostAPIClient/Rest/DataObjects/PostbackTransaction.cs b/src/SignhostAPIClient/Rest/DataObjects/PostbackTransaction.cs index ea57648..cffc6eb 100644 --- a/src/SignhostAPIClient/Rest/DataObjects/PostbackTransaction.cs +++ b/src/SignhostAPIClient/Rest/DataObjects/PostbackTransaction.cs @@ -1,11 +1,10 @@ using System.Text.Json.Serialization; -namespace Signhost.APIClient.Rest.DataObjects +namespace Signhost.APIClient.Rest.DataObjects; + +public class PostbackTransaction + : Transaction { - public class PostbackTransaction - : Transaction - { - [JsonPropertyName("Checksum")] - public string Checksum { get; set; } - } + [JsonPropertyName("Checksum")] + public string Checksum { get; set; } } diff --git a/src/SignhostAPIClient/Rest/DataObjects/Receiver.cs b/src/SignhostAPIClient/Rest/DataObjects/Receiver.cs index be82050..b044b37 100644 --- a/src/SignhostAPIClient/Rest/DataObjects/Receiver.cs +++ b/src/SignhostAPIClient/Rest/DataObjects/Receiver.cs @@ -2,41 +2,40 @@ using System.Collections.Generic; using System.Text.Json.Serialization; -namespace Signhost.APIClient.Rest.DataObjects +namespace Signhost.APIClient.Rest.DataObjects; + +public class Receiver { - public class Receiver + public Receiver() { - public Receiver() - { - } + } - [JsonConstructor] - private Receiver(IReadOnlyList activities) - { - Activities = activities; - } + [JsonConstructor] + private Receiver(IReadOnlyList activities) + { + Activities = activities; + } - public string Id { get; set; } + public string Id { get; set; } - public string Name { get; set; } + public string Name { get; set; } - public string Email { get; set; } + public string Email { get; set; } - public string Language { get; set; } + public string Language { get; set; } - public string Subject { get; set; } + public string Subject { get; set; } - public string Message { get; set; } + public string Message { get; set; } - public string Reference { get; set; } + public string Reference { get; set; } - public DateTimeOffset? CreatedDateTime { get; set; } + public DateTimeOffset? CreatedDateTime { get; set; } - public DateTimeOffset? ModifiedDateTime { get; set; } + public DateTimeOffset? ModifiedDateTime { get; set; } - public IReadOnlyList Activities { get; set; } = - new List().AsReadOnly(); + public IReadOnlyList Activities { get; set; } = + new List().AsReadOnly(); - public dynamic Context { get; set; } - } + public dynamic Context { get; set; } } diff --git a/src/SignhostAPIClient/Rest/DataObjects/ScribbleVerification.cs b/src/SignhostAPIClient/Rest/DataObjects/ScribbleVerification.cs index fadb57a..5556f0b 100644 --- a/src/SignhostAPIClient/Rest/DataObjects/ScribbleVerification.cs +++ b/src/SignhostAPIClient/Rest/DataObjects/ScribbleVerification.cs @@ -1,12 +1,11 @@ -namespace Signhost.APIClient.Rest.DataObjects +namespace Signhost.APIClient.Rest.DataObjects; + +public class ScribbleVerification + : IVerification { - public class ScribbleVerification - : IVerification - { - public bool RequireHandsignature { get; set; } + public bool RequireHandsignature { get; set; } - public bool ScribbleNameFixed { get; set; } + public bool ScribbleNameFixed { get; set; } - public string ScribbleName { get; set; } - } + public string ScribbleName { get; set; } } diff --git a/src/SignhostAPIClient/Rest/DataObjects/Signer.cs b/src/SignhostAPIClient/Rest/DataObjects/Signer.cs index 5d61f23..944c257 100644 --- a/src/SignhostAPIClient/Rest/DataObjects/Signer.cs +++ b/src/SignhostAPIClient/Rest/DataObjects/Signer.cs @@ -2,85 +2,84 @@ using System.Collections.Generic; using System.Text.Json.Serialization; -namespace Signhost.APIClient.Rest.DataObjects +namespace Signhost.APIClient.Rest.DataObjects; + +public class Signer { - public class Signer + public Signer() { - public Signer() - { - } + } - [JsonConstructor] - private Signer(IReadOnlyList activities) - { - Activities = activities; - } + [JsonConstructor] + private Signer(IReadOnlyList activities) + { + Activities = activities; + } - public string Id { get; set; } + public string Id { get; set; } - public DateTimeOffset? Expires { get; set; } + public DateTimeOffset? Expires { get; set; } - public string Email { get; set; } + public string Email { get; set; } - public string IntroText { get; set; } + public string IntroText { get; set; } - public string SignRequestSubject { get; set; } + public string SignRequestSubject { get; set; } - public string SignRequestMessage { get; set; } + public string SignRequestMessage { get; set; } - public IList Authentications { get; set; } - = new List(); + public IList Authentications { get; set; } + = new List(); - public IList Verifications { get; set; } - = new List(); + public IList Verifications { get; set; } + = new List(); - public bool SendSignRequest { get; set; } + public bool SendSignRequest { get; set; } - public bool? SendSignConfirmation { get; set; } + public bool? SendSignConfirmation { get; set; } - public int? DaysToRemind { get; set; } + public int? DaysToRemind { get; set; } - public string Language { get; set; } + public string Language { get; set; } - public string ScribbleName { get; set; } + public string ScribbleName { get; set; } - public bool ScribbleNameFixed { get; set; } + public bool ScribbleNameFixed { get; set; } - public string Reference { get; set; } + public string Reference { get; set; } - public string ReturnUrl { get; set; } + public string ReturnUrl { get; set; } - public string RejectReason { get; set; } + public string RejectReason { get; set; } - public string SignUrl { get; set; } + public string SignUrl { get; set; } - public bool AllowDelegation { get; set; } + public bool AllowDelegation { get; set; } - public string DelegateSignUrl { get; set; } + public string DelegateSignUrl { get; set; } - public string DelegateReason { get; set; } + public string DelegateReason { get; set; } - public string DelegateSignerEmail { get; set; } + public string DelegateSignerEmail { get; set; } - public string DelegateSignerName { get; set; } + public string DelegateSignerName { get; set; } - public DateTimeOffset? SignedDateTime { get; set; } + public DateTimeOffset? SignedDateTime { get; set; } - public DateTimeOffset? RejectDateTime { get; set; } + public DateTimeOffset? RejectDateTime { get; set; } - public DateTimeOffset? CreatedDateTime { get; set; } + public DateTimeOffset? CreatedDateTime { get; set; } - public DateTimeOffset? SignerDelegationDateTime { get; set; } + public DateTimeOffset? SignerDelegationDateTime { get; set; } - public DateTimeOffset? ModifiedDateTime { get; set; } + public DateTimeOffset? ModifiedDateTime { get; set; } - public string ShowUrl { get; set; } + public string ShowUrl { get; set; } - public string ReceiptUrl { get; set; } + public string ReceiptUrl { get; set; } - public IReadOnlyList Activities { get; private set; } = - new List().AsReadOnly(); + public IReadOnlyList Activities { get; private set; } = + new List().AsReadOnly(); - public dynamic Context { get; set; } - } + public dynamic Context { get; set; } } diff --git a/src/SignhostAPIClient/Rest/DataObjects/SurfnetVerification.cs b/src/SignhostAPIClient/Rest/DataObjects/SurfnetVerification.cs index dfef6fd..8523e73 100644 --- a/src/SignhostAPIClient/Rest/DataObjects/SurfnetVerification.cs +++ b/src/SignhostAPIClient/Rest/DataObjects/SurfnetVerification.cs @@ -1,12 +1,11 @@ using System.Collections.Generic; -namespace Signhost.APIClient.Rest.DataObjects +namespace Signhost.APIClient.Rest.DataObjects; + +public class SurfnetVerification + : IVerification { - public class SurfnetVerification - : IVerification - { - public string Uid { get; set; } + public string Uid { get; set; } - public IDictionary Attributes { get; set; } - } + public IDictionary Attributes { get; set; } } diff --git a/src/SignhostAPIClient/Rest/DataObjects/Transaction.cs b/src/SignhostAPIClient/Rest/DataObjects/Transaction.cs index 6616633..7ac6220 100644 --- a/src/SignhostAPIClient/Rest/DataObjects/Transaction.cs +++ b/src/SignhostAPIClient/Rest/DataObjects/Transaction.cs @@ -1,53 +1,52 @@ using System; using System.Collections.Generic; -namespace Signhost.APIClient.Rest.DataObjects +namespace Signhost.APIClient.Rest.DataObjects; + +public class Transaction { - public class Transaction - { - public string Id { get; set; } + public string Id { get; set; } - /// - /// Gets the when the was created. - /// - public DateTimeOffset? CreatedDateTime { get; set; } + /// + /// Gets the when the was created. + /// + public DateTimeOffset? CreatedDateTime { get; set; } - /// - /// Gets the when the was cancelled. - /// Returns null if the transaction was not cancelled. - /// - public DateTimeOffset? CanceledDateTime { get; set; } + /// + /// Gets the when the was cancelled. + /// Returns null if the transaction was not cancelled. + /// + public DateTimeOffset? CanceledDateTime { get; set; } - /// - /// Gets the cancellation reason when the was cancelled. - /// - public string CancellationReason { get; set; } + /// + /// Gets the cancellation reason when the was cancelled. + /// + public string CancellationReason { get; set; } - public IReadOnlyDictionary Files { get; set; } = - new Dictionary(); + public IReadOnlyDictionary Files { get; set; } = + new Dictionary(); - public TransactionStatus Status { get; set; } + public TransactionStatus Status { get; set; } - public bool Seal { get; set; } + public bool Seal { get; set; } - public IList Signers { get; set; } - = new List(); + public IList Signers { get; set; } + = new List(); - public IList Receivers { get; set; } - = new List(); + public IList Receivers { get; set; } + = new List(); - public string Reference { get; set; } + public string Reference { get; set; } - public string PostbackUrl { get; set; } + public string PostbackUrl { get; set; } - public int SignRequestMode { get; set; } + public int SignRequestMode { get; set; } - public int DaysToExpire { get; set; } + public int DaysToExpire { get; set; } - public string Language { get; set; } + public string Language { get; set; } - public bool SendEmailNotifications { get; set; } + public bool SendEmailNotifications { get; set; } - public dynamic Context { get; set; } - } + public dynamic Context { get; set; } } diff --git a/src/SignhostAPIClient/Rest/DataObjects/TransactionStatus.cs b/src/SignhostAPIClient/Rest/DataObjects/TransactionStatus.cs index 2c9b80a..7485bf1 100644 --- a/src/SignhostAPIClient/Rest/DataObjects/TransactionStatus.cs +++ b/src/SignhostAPIClient/Rest/DataObjects/TransactionStatus.cs @@ -1,47 +1,46 @@ -namespace Signhost.APIClient.Rest.DataObjects +namespace Signhost.APIClient.Rest.DataObjects; + +public enum TransactionStatus { - public enum TransactionStatus - { - /// - /// Transaction has not yet been started and is waiting for its - /// documents. - /// - WaitingForDocument = 5, - - /// - /// The transaction was started and is waiting for one or more signers - /// to sign the transaction. - /// - WaitingForSigner = 10, - - /// - /// In progress - /// - InProgress = 20, - - /// - /// The transaction was succesfully completed. - /// - Signed = 30, - - /// - /// The transaction was rejected by one or more of the signers. - /// - Rejected = 40, - - /// - /// The transaction was not signed before it expired. - /// - Expired = 50, - - /// - /// The transaction was cancelled by the sender. - /// - Cancelled = 60, - - /// - /// The transaction could not be completed. - /// - Failed = 70, - } + /// + /// Transaction has not yet been started and is waiting for its + /// documents. + /// + WaitingForDocument = 5, + + /// + /// The transaction was started and is waiting for one or more signers + /// to sign the transaction. + /// + WaitingForSigner = 10, + + /// + /// In progress + /// + InProgress = 20, + + /// + /// The transaction was succesfully completed. + /// + Signed = 30, + + /// + /// The transaction was rejected by one or more of the signers. + /// + Rejected = 40, + + /// + /// The transaction was not signed before it expired. + /// + Expired = 50, + + /// + /// The transaction was cancelled by the sender. + /// + Cancelled = 60, + + /// + /// The transaction could not be completed. + /// + Failed = 70, } diff --git a/src/SignhostAPIClient/Rest/ErrorHandling/BadAuthorizationException.cs b/src/SignhostAPIClient/Rest/ErrorHandling/BadAuthorizationException.cs index 63dde05..d9d8db3 100644 --- a/src/SignhostAPIClient/Rest/ErrorHandling/BadAuthorizationException.cs +++ b/src/SignhostAPIClient/Rest/ErrorHandling/BadAuthorizationException.cs @@ -1,37 +1,36 @@ using System; -namespace Signhost.APIClient.Rest.ErrorHandling +namespace Signhost.APIClient.Rest.ErrorHandling; + +// TO-DO: Use this instead of Unauthorized exception in v5 +[Serializable] +public class BadAuthorizationException + : SignhostRestApiClientException { - // TO-DO: Use this instead of Unauthorized exception in v5 - [Serializable] - public class BadAuthorizationException - : SignhostRestApiClientException + public BadAuthorizationException() + : base("API call returned a 401 error code. Please check your request headers.") { - public BadAuthorizationException() - : base("API call returned a 401 error code. Please check your request headers.") - { - HelpLink = "https://api.signhost.com/Help"; - } + HelpLink = "https://api.signhost.com/Help"; + } - public BadAuthorizationException(string message) - : base(message) - { - } + public BadAuthorizationException(string message) + : base(message) + { + } - public BadAuthorizationException( - string message, - Exception innerException) - : base(message, innerException) - { - } + public BadAuthorizationException( + string message, + Exception innerException) + : base(message, innerException) + { + } #if SERIALIZABLE - protected BadAuthorizationException( - SerializationInfo info, - StreamingContext context) - : base(info, context) - { - } -#endif + protected BadAuthorizationException( + SerializationInfo info, + StreamingContext context) + : base(info, context) + { } +#endif } diff --git a/src/SignhostAPIClient/Rest/ErrorHandling/BadRequestException.cs b/src/SignhostAPIClient/Rest/ErrorHandling/BadRequestException.cs index d6041ab..d449b02 100644 --- a/src/SignhostAPIClient/Rest/ErrorHandling/BadRequestException.cs +++ b/src/SignhostAPIClient/Rest/ErrorHandling/BadRequestException.cs @@ -1,36 +1,35 @@ using System; -namespace Signhost.APIClient.Rest.ErrorHandling +namespace Signhost.APIClient.Rest.ErrorHandling; + +[Serializable] +public class BadRequestException + : SignhostRestApiClientException { - [Serializable] - public class BadRequestException - : SignhostRestApiClientException + public BadRequestException() + : base() { - public BadRequestException() - : base() - { - } + } - public BadRequestException(string message) - : base(message) - { - } + public BadRequestException(string message) + : base(message) + { + } - public BadRequestException( - string message, - Exception innerException) - : base(message, innerException) - { - HelpLink = "https://api.signhost.com/Help"; - } + public BadRequestException( + string message, + Exception innerException) + : base(message, innerException) + { + HelpLink = "https://api.signhost.com/Help"; + } #if SERIALIZABLE - protected BadRequestException( - SerializationInfo info, - StreamingContext context) - : base(info, context) - { - } -#endif + protected BadRequestException( + SerializationInfo info, + StreamingContext context) + : base(info, context) + { } +#endif } diff --git a/src/SignhostAPIClient/Rest/ErrorHandling/DefaultSignhostException.cs b/src/SignhostAPIClient/Rest/ErrorHandling/DefaultSignhostException.cs index 8c9a054..b7db21a 100644 --- a/src/SignhostAPIClient/Rest/ErrorHandling/DefaultSignhostException.cs +++ b/src/SignhostAPIClient/Rest/ErrorHandling/DefaultSignhostException.cs @@ -1,30 +1,29 @@ using System; -namespace Signhost.APIClient.Rest.ErrorHandling +namespace Signhost.APIClient.Rest.ErrorHandling; + +[Serializable] +public class DefaultSignhostException : Exception { - [Serializable] - public class DefaultSignhostException : Exception + public DefaultSignhostException(string message) + : base(message) { - public DefaultSignhostException(string message) - : base(message) - { - HelpLink = "https://api.signhost.com/Help"; - } + HelpLink = "https://api.signhost.com/Help"; + } - public DefaultSignhostException( - string message, - Exception innerException) - : base(message, innerException) - { - } + public DefaultSignhostException( + string message, + Exception innerException) + : base(message, innerException) + { + } #if SERIALIZABLE - protected DefaultSignhostException( - SerializationInfo info, - StreamingContext context) - : base(info, context) - { - } -#endif + protected DefaultSignhostException( + SerializationInfo info, + StreamingContext context) + : base(info, context) + { } +#endif } diff --git a/src/SignhostAPIClient/Rest/ErrorHandling/GoneException.cs b/src/SignhostAPIClient/Rest/ErrorHandling/GoneException.cs index 08e8ce8..5aad375 100644 --- a/src/SignhostAPIClient/Rest/ErrorHandling/GoneException.cs +++ b/src/SignhostAPIClient/Rest/ErrorHandling/GoneException.cs @@ -1,62 +1,61 @@ using System; -namespace Signhost.APIClient.Rest.ErrorHandling +namespace Signhost.APIClient.Rest.ErrorHandling; + +/// +/// Thrown when a transaction is deleted / cancelled. +/// +[Serializable] +public class GoneException + : SignhostRestApiClientException { /// - /// Thrown when a transaction is deleted / cancelled. + /// Initializes a new instance of the class. /// - [Serializable] - public class GoneException - : SignhostRestApiClientException + public GoneException() + : base() { - /// - /// Initializes a new instance of the class. - /// - public GoneException() - : base() - { - } + } - /// - /// Initializes a new instance of the class. - /// - /// Additional information - public GoneException(string message) - : base(message) - { - } + /// + /// Initializes a new instance of the class. + /// + /// Additional information + public GoneException(string message) + : base(message) + { + } - public GoneException(string message, TResult result) - : base(message) - { - Result = result; - } + public GoneException(string message, TResult result) + : base(message) + { + Result = result; + } - /// - /// Initializes a new instance of the class. - /// - /// Additional information - /// Inner exception - public GoneException(string message, Exception innerException) - : base(message, innerException) - { - HelpLink = "https://api.signhost.com/Help"; - } + /// + /// Initializes a new instance of the class. + /// + /// Additional information + /// Inner exception + public GoneException(string message, Exception innerException) + : base(message, innerException) + { + HelpLink = "https://api.signhost.com/Help"; + } #if SERIALIZABLE - protected GoneException( - SerializationInfo info, - StreamingContext context) - : base(info, context) - { - } + protected GoneException( + SerializationInfo info, + StreamingContext context) + : base(info, context) + { + } #endif - /// - /// Gets the api / transaction details which are still available. - /// Please note that this no longer contains the full transaction - /// details as most data is removed. - /// - public TResult Result { get; private set; } - } + /// + /// Gets the api / transaction details which are still available. + /// Please note that this no longer contains the full transaction + /// details as most data is removed. + /// + public TResult Result { get; private set; } } diff --git a/src/SignhostAPIClient/Rest/ErrorHandling/HttpResponseMessageErrorHandlingExtensions.cs b/src/SignhostAPIClient/Rest/ErrorHandling/HttpResponseMessageErrorHandlingExtensions.cs index 55e09ec..21e6b51 100644 --- a/src/SignhostAPIClient/Rest/ErrorHandling/HttpResponseMessageErrorHandlingExtensions.cs +++ b/src/SignhostAPIClient/Rest/ErrorHandling/HttpResponseMessageErrorHandlingExtensions.cs @@ -6,120 +6,119 @@ using System.Text.Json.Serialization; using System.Threading.Tasks; -namespace Signhost.APIClient.Rest.ErrorHandling +namespace Signhost.APIClient.Rest.ErrorHandling; + +/// +/// Error handling around s. +/// +public static class HttpResponseMessageErrorHandlingExtensions { + private const string OutOfCreditsApiProblemType = + "https://api.signhost.com/problem/subscription/out-of-credits"; + /// - /// Error handling around s. + /// Throws an exception if the + /// has an error code. /// - public static class HttpResponseMessageErrorHandlingExtensions + /// + /// Returns if the call is succesful. + /// List of which should + /// not be handled as an error. + /// + /// When the api authentication failed. + /// + /// + /// When the API request was an invalid request for your account. + /// + /// + /// When your organisation has run out of credits. + /// + /// + /// When the request resource (ie transaction id or file id) was not found. + /// + /// + /// When the API was unable to proces the request at the moment, + /// a RetryAfter property is set if available. + /// + /// + /// An other unknown API error occured. + /// + public static async Task EnsureSignhostSuccessStatusCodeAsync( + this Task responseTask, + params HttpStatusCode[] expectedStatusCodes) { - private const string OutOfCreditsApiProblemType = - "https://api.signhost.com/problem/subscription/out-of-credits"; - - /// - /// Throws an exception if the - /// has an error code. - /// - /// - /// Returns if the call is succesful. - /// List of which should - /// not be handled as an error. - /// - /// When the api authentication failed. - /// - /// - /// When the API request was an invalid request for your account. - /// - /// - /// When your organisation has run out of credits. - /// - /// - /// When the request resource (ie transaction id or file id) was not found. - /// - /// - /// When the API was unable to proces the request at the moment, - /// a RetryAfter property is set if available. - /// - /// - /// An other unknown API error occured. - /// - public static async Task EnsureSignhostSuccessStatusCodeAsync( - this Task responseTask, - params HttpStatusCode[] expectedStatusCodes) - { - var response = await responseTask.ConfigureAwait(false); - - if (response.IsSuccessStatusCode) { - return response; - } - - if (expectedStatusCodes.Contains(response.StatusCode)) { - return response; - } - - string errorType = string.Empty; - string errorMessage = "Unknown Signhost error"; - string responseBody = string.Empty; - - if (response.Content != null) { - responseBody = await response.Content.ReadAsStringAsync() - .ConfigureAwait(false); - - var error = JsonSerializer.Deserialize(responseBody); - - errorType = error?.Type ?? string.Empty; - errorMessage = error?.Message ?? "Unknown Signhost error"; - } - - // TO-DO: Use switch pattern in v5 - Exception exception = null; - switch (response.StatusCode) { - case HttpStatusCode.Unauthorized: - exception = new UnauthorizedAccessException( - errorMessage); - break; - - case HttpStatusCode.BadRequest: - exception = new BadRequestException( - errorMessage); - break; - - case HttpStatusCode.PaymentRequired - when errorType == OutOfCreditsApiProblemType: - exception = new OutOfCreditsException( - errorMessage); - break; - - case HttpStatusCode.NotFound: - exception = new NotFoundException( - errorMessage); - break; - - case HttpStatusCode.InternalServerError: - exception = new InternalServerErrorException( - errorMessage, response.Headers.RetryAfter); - break; - - default: - exception = new SignhostRestApiClientException( - errorMessage); - break; - } - - if (exception is SignhostRestApiClientException signhostException) { - signhostException.ResponseBody = responseBody; - } - - throw exception; + var response = await responseTask.ConfigureAwait(false); + + if (response.IsSuccessStatusCode) { + return response; + } + + if (expectedStatusCodes.Contains(response.StatusCode)) { + return response; + } + + string errorType = string.Empty; + string errorMessage = "Unknown Signhost error"; + string responseBody = string.Empty; + + if (response.Content != null) { + responseBody = await response.Content.ReadAsStringAsync() + .ConfigureAwait(false); + + var error = JsonSerializer.Deserialize(responseBody); + + errorType = error?.Type ?? string.Empty; + errorMessage = error?.Message ?? "Unknown Signhost error"; } - private class ErrorResponse - { - [JsonPropertyName("type")] - public string Type { get; set; } + // TO-DO: Use switch pattern in v5 + Exception exception = null; + switch (response.StatusCode) { + case HttpStatusCode.Unauthorized: + exception = new UnauthorizedAccessException( + errorMessage); + break; + + case HttpStatusCode.BadRequest: + exception = new BadRequestException( + errorMessage); + break; + + case HttpStatusCode.PaymentRequired + when errorType == OutOfCreditsApiProblemType: + exception = new OutOfCreditsException( + errorMessage); + break; + + case HttpStatusCode.NotFound: + exception = new NotFoundException( + errorMessage); + break; + + case HttpStatusCode.InternalServerError: + exception = new InternalServerErrorException( + errorMessage, response.Headers.RetryAfter); + break; + + default: + exception = new SignhostRestApiClientException( + errorMessage); + break; + } - [JsonPropertyName("message")] - public string Message { get; set; } + if (exception is SignhostRestApiClientException signhostException) { + signhostException.ResponseBody = responseBody; } + + throw exception; + } + + private class ErrorResponse + { + [JsonPropertyName("type")] + public string Type { get; set; } + + [JsonPropertyName("message")] + public string Message { get; set; } } } diff --git a/src/SignhostAPIClient/Rest/ErrorHandling/InternalServerErrorException.cs b/src/SignhostAPIClient/Rest/ErrorHandling/InternalServerErrorException.cs index 0100e31..265489f 100644 --- a/src/SignhostAPIClient/Rest/ErrorHandling/InternalServerErrorException.cs +++ b/src/SignhostAPIClient/Rest/ErrorHandling/InternalServerErrorException.cs @@ -1,48 +1,47 @@ using System; using System.Net.Http.Headers; -namespace Signhost.APIClient.Rest.ErrorHandling +namespace Signhost.APIClient.Rest.ErrorHandling; + +[Serializable] +public class InternalServerErrorException + : SignhostRestApiClientException { - [Serializable] - public class InternalServerErrorException - : SignhostRestApiClientException + public InternalServerErrorException() + : base() { - public InternalServerErrorException() - : base() - { - } + } - public InternalServerErrorException(string message) - : base(message) - { - } + public InternalServerErrorException(string message) + : base(message) + { + } - public InternalServerErrorException( - string message, RetryConditionHeaderValue retryAfter) - : base(message) - { - HelpLink = "https://api.signhost.com/Help"; + public InternalServerErrorException( + string message, RetryConditionHeaderValue retryAfter) + : base(message) + { + HelpLink = "https://api.signhost.com/Help"; - if (retryAfter != null) { - if (retryAfter.Date != null) { - RetryAfter = retryAfter.Date; - } + if (retryAfter != null) { + if (retryAfter.Date != null) { + RetryAfter = retryAfter.Date; + } - if (retryAfter.Delta != null) { - RetryAfter = DateTime.Now + retryAfter.Delta; - } + if (retryAfter.Delta != null) { + RetryAfter = DateTime.Now + retryAfter.Delta; } } + } #if SERIALIZABLE - protected InternalServerErrorException( - SerializationInfo info, - StreamingContext context) - : base(info, context) - { - } + protected InternalServerErrorException( + SerializationInfo info, + StreamingContext context) + : base(info, context) + { + } #endif - private DateTimeOffset? RetryAfter { get; set; } - } + private DateTimeOffset? RetryAfter { get; set; } } diff --git a/src/SignhostAPIClient/Rest/ErrorHandling/NotFoundException.cs b/src/SignhostAPIClient/Rest/ErrorHandling/NotFoundException.cs index 6c9eda4..0b61c9e 100644 --- a/src/SignhostAPIClient/Rest/ErrorHandling/NotFoundException.cs +++ b/src/SignhostAPIClient/Rest/ErrorHandling/NotFoundException.cs @@ -1,34 +1,33 @@ using System; -namespace Signhost.APIClient.Rest.ErrorHandling +namespace Signhost.APIClient.Rest.ErrorHandling; + +[Serializable] +public class NotFoundException + : SignhostRestApiClientException { - [Serializable] - public class NotFoundException - : SignhostRestApiClientException + public NotFoundException() + : base() { - public NotFoundException() - : base() - { - } + } - public NotFoundException(string message) - : base(message) - { - } + public NotFoundException(string message) + : base(message) + { + } - public NotFoundException(string message, Exception innerException) - : base(message, innerException) - { - HelpLink = "https://api.signhost.com/Help"; - } + public NotFoundException(string message, Exception innerException) + : base(message, innerException) + { + HelpLink = "https://api.signhost.com/Help"; + } #if SERIALIZABLE - protected NotFoundException( - SerializationInfo info, - StreamingContext context) - : base(info, context) - { - } -#endif + protected NotFoundException( + SerializationInfo info, + StreamingContext context) + : base(info, context) + { } +#endif } diff --git a/src/SignhostAPIClient/Rest/ErrorHandling/OutOfCreditsException.cs b/src/SignhostAPIClient/Rest/ErrorHandling/OutOfCreditsException.cs index a5cf42e..7d8c89e 100644 --- a/src/SignhostAPIClient/Rest/ErrorHandling/OutOfCreditsException.cs +++ b/src/SignhostAPIClient/Rest/ErrorHandling/OutOfCreditsException.cs @@ -1,21 +1,20 @@ using System; -namespace Signhost.APIClient.Rest.ErrorHandling +namespace Signhost.APIClient.Rest.ErrorHandling; + +/// +/// An exception which indicates payment is required when an API action is called. +/// +[Serializable] +public class OutOfCreditsException + : SignhostRestApiClientException { /// - /// An exception which indicates payment is required when an API action is called. + /// Initializes a new instance of the class. /// - [Serializable] - public class OutOfCreditsException - : SignhostRestApiClientException + /// The exception message. + public OutOfCreditsException(string message) + : base(message) { - /// - /// Initializes a new instance of the class. - /// - /// The exception message. - public OutOfCreditsException(string message) - : base(message) - { - } } } diff --git a/src/SignhostAPIClient/Rest/ErrorHandling/SignhostRestApiClientException.cs b/src/SignhostAPIClient/Rest/ErrorHandling/SignhostRestApiClientException.cs index e437036..6e30ba8 100644 --- a/src/SignhostAPIClient/Rest/ErrorHandling/SignhostRestApiClientException.cs +++ b/src/SignhostAPIClient/Rest/ErrorHandling/SignhostRestApiClientException.cs @@ -1,28 +1,28 @@ using System; -namespace Signhost.APIClient.Rest.ErrorHandling +namespace Signhost.APIClient.Rest.ErrorHandling; + +[Serializable] +public class SignhostRestApiClientException + : Exception { - [Serializable] - public class SignhostRestApiClientException - : Exception + public SignhostRestApiClientException() + : base() { - public SignhostRestApiClientException() - : base() - { - } + } - public SignhostRestApiClientException(string message) - : base(message) - { - } + public SignhostRestApiClientException(string message) + : base(message) + { + } - public SignhostRestApiClientException( - string message, - Exception innerException) - : base(message, innerException) - { - HelpLink = "https://api.signhost.com/Help"; - } + public SignhostRestApiClientException( + string message, + Exception innerException) + : base(message, innerException) + { + HelpLink = "https://api.signhost.com/Help"; + } #if SERIALIZABLE protected SignhostRestApiClientException( @@ -33,9 +33,8 @@ protected SignhostRestApiClientException( } #endif - /// - /// Gets or sets the response body returned from the Signhost REST API. - /// - public string ResponseBody { get; set; } - } + /// + /// Gets or sets the response body returned from the Signhost REST API. + /// + public string ResponseBody { get; set; } } diff --git a/src/SignhostAPIClient/Rest/FileDigestOptions.cs b/src/SignhostAPIClient/Rest/FileDigestOptions.cs index b71e0eb..bc0218b 100644 --- a/src/SignhostAPIClient/Rest/FileDigestOptions.cs +++ b/src/SignhostAPIClient/Rest/FileDigestOptions.cs @@ -1,27 +1,26 @@ -namespace Signhost.APIClient.Rest +namespace Signhost.APIClient.Rest; + +/// +/// File digest options for file uploads +/// +public class FileDigestOptions { /// - /// File digest options for file uploads + /// Gets or sets whether to use the Digest header with a checksum of + /// the uploaded file. /// - public class FileDigestOptions - { - /// - /// Gets or sets whether to use the Digest header with a checksum of - /// the uploaded file. - /// - public bool UseFileDigesting { get; set; } = true; + public bool UseFileDigesting { get; set; } = true; - /// - /// Gets or sets the digest algorithm to use when calculating - /// the hash value or the digest algorithm that is used - /// to set the . - /// - public string DigestHashAlgorithm { get; set; } = "SHA-256"; + /// + /// Gets or sets the digest algorithm to use when calculating + /// the hash value or the digest algorithm that is used + /// to set the . + /// + public string DigestHashAlgorithm { get; set; } = "SHA-256"; - /// - /// Gets or sets the hash digest value, you can set this yourself - /// if you know the digest value in advance. - /// - public byte[] DigestHashValue { get; set; } - } + /// + /// Gets or sets the hash digest value, you can set this yourself + /// if you know the digest value in advance. + /// + public byte[] DigestHashValue { get; set; } } diff --git a/src/SignhostAPIClient/Rest/FileUploadOptions.cs b/src/SignhostAPIClient/Rest/FileUploadOptions.cs index 340ad1e..4336b6d 100644 --- a/src/SignhostAPIClient/Rest/FileUploadOptions.cs +++ b/src/SignhostAPIClient/Rest/FileUploadOptions.cs @@ -1,14 +1,13 @@ -namespace Signhost.APIClient.Rest +namespace Signhost.APIClient.Rest; + +/// +/// Options to be used during a file upload +/// +public class FileUploadOptions { /// - /// Options to be used during a file upload + /// Gets or sets the . /// - public class FileUploadOptions - { - /// - /// Gets or sets the . - /// - public FileDigestOptions DigestOptions { get; set; } - = new FileDigestOptions(); - } + public FileDigestOptions DigestOptions { get; set; } + = new FileDigestOptions(); } diff --git a/src/SignhostAPIClient/Rest/HttpContentJsonExtensions.cs b/src/SignhostAPIClient/Rest/HttpContentJsonExtensions.cs index b7e2836..3dfa535 100644 --- a/src/SignhostAPIClient/Rest/HttpContentJsonExtensions.cs +++ b/src/SignhostAPIClient/Rest/HttpContentJsonExtensions.cs @@ -2,30 +2,29 @@ using System.Text.Json; using System.Threading.Tasks; -namespace Signhost.APIClient.Rest +namespace Signhost.APIClient.Rest; + +/// +/// Extension methods around JSON Deserialization. +/// +internal static class HttpContentJsonExtensions { /// - /// Extension methods around JSON Deserialization. + /// Reads the JSON content and returns the deserialized value. /// - internal static class HttpContentJsonExtensions + /// Type to deserialize to + /// to read. + /// A deserialized value of + /// or default(T) if no content is available. + internal static async Task FromJsonAsync( + this HttpContent httpContent) { - /// - /// Reads the JSON content and returns the deserialized value. - /// - /// Type to deserialize to - /// to read. - /// A deserialized value of - /// or default(T) if no content is available. - internal static async Task FromJsonAsync( - this HttpContent httpContent) - { - if (httpContent == null) { - return default(T); - } - - var json = await httpContent.ReadAsStringAsync() - .ConfigureAwait(false); - return JsonSerializer.Deserialize(json, SignhostJsonSerializerOptions.Default); + if (httpContent == null) { + return default(T); } + + var json = await httpContent.ReadAsStringAsync() + .ConfigureAwait(false); + return JsonSerializer.Deserialize(json, SignhostJsonSerializerOptions.Default); } } diff --git a/src/SignhostAPIClient/Rest/ISignHostApiClient.cs b/src/SignhostAPIClient/Rest/ISignHostApiClient.cs index 79c5a88..d2ac880 100644 --- a/src/SignhostAPIClient/Rest/ISignHostApiClient.cs +++ b/src/SignhostAPIClient/Rest/ISignHostApiClient.cs @@ -3,248 +3,247 @@ using System.Threading.Tasks; using Signhost.APIClient.Rest.DataObjects; -namespace Signhost.APIClient.Rest +namespace Signhost.APIClient.Rest; + +/// +/// Interface abstracting the available Signhost API calls. +/// +public interface ISignhostApiClient { /// - /// Interface abstracting the available Signhost API calls. - /// - public interface ISignhostApiClient - { - /// - /// Creates a new transaction. - /// - /// A transaction model. - /// A transaction object. - Task CreateTransactionAsync(Transaction transaction); - - /// - /// Creates a new transaction. - /// - /// A transaction model. - /// A cancellation token. - /// A transaction object. - Task CreateTransactionAsync( - Transaction transaction, - CancellationToken cancellationToken = default); - - /// - /// Adds meta data for a file to an existing transaction by providing a - /// file location and a transaction id. - /// - /// Meta data for the file. - /// A valid transaction Id of an existing transaction. - /// An Id for the file. Should be the same as the fileId in the . - /// A task. - /// Make sure to call this method before - /// . - Task AddOrReplaceFileMetaToTransactionAsync( - FileMeta fileMeta, - string transactionId, - string fileId); - - /// - /// Adds meta data for a file to an existing transaction by providing a - /// file location and a transaction id. - /// - /// Meta data for the file. - /// A valid transaction Id of an existing transaction. - /// An Id for the file. Should be the same as the fileId in the . - /// A cancellation token. - /// A task. - /// Make sure to call this method before - /// . - Task AddOrReplaceFileMetaToTransactionAsync( - FileMeta fileMeta, - string transactionId, - string fileId, - CancellationToken cancellationToken = default); - - /// - /// Add a file to a existing transaction by providing a file location - /// and a transaction id. - /// - /// A Stream containing the file to upload. - /// A valid transaction Id of an existing transaction. - /// A Id for the file. Using the file name is recommended. - /// If a file with the same fileId allready exists the file wil be replaced. - /// . - /// A Task. - Task AddOrReplaceFileToTransactionAsync( - Stream fileStream, - string transactionId, - string fileId, - FileUploadOptions uploadOptions); - - /// - /// Add a file to a existing transaction by providing a file location - /// and a transaction id. - /// - /// A Stream containing the file to upload. - /// A valid transaction Id of an existing transaction. - /// A Id for the file. Using the file name is recommended. - /// If a file with the same fileId allready exists the file wil be replaced. - /// . - /// A cancellation token. - /// A Task. - Task AddOrReplaceFileToTransactionAsync( - Stream fileStream, - string transactionId, - string fileId, - FileUploadOptions uploadOptions, - CancellationToken cancellationToken = default); - - /// - /// Add a file to a existing transaction by providing a file location - /// and a transaction id. - /// - /// A string representation of the file path. - /// A valid transaction Id of an existing transaction. - /// A Id for the file. Using the file name is recommended. - /// If a file with the same fileId allready exists the file wil be replaced. - /// Optional . - /// A Task. - Task AddOrReplaceFileToTransactionAsync( - string filePath, - string transactionId, - string fileId, - FileUploadOptions uploadOptions); - - /// - /// Add a file to a existing transaction by providing a file location - /// and a transaction id. - /// - /// A string representation of the file path. - /// A valid transaction Id of an existing transaction. - /// A Id for the file. Using the file name is recommended. - /// If a file with the same fileId allready exists the file wil be replaced. - /// Optional . - /// A cancellation token. - /// A Task. - Task AddOrReplaceFileToTransactionAsync( - string filePath, - string transactionId, - string fileId, - FileUploadOptions uploadOptions, - CancellationToken cancellationToken = default); - - /// - /// start a existing transaction by providing transaction id. - /// - /// A valid transaction Id of an existing transaction. - /// A Task. - Task StartTransactionAsync(string transactionId); - - /// - /// start a existing transaction by providing transaction id. - /// - /// A valid transaction Id of an existing transaction. - /// A cancellation token. - /// A Task. - Task StartTransactionAsync( - string transactionId, - CancellationToken cancellationToken = default); - - /// - /// Gets an exisiting transaction by providing a transaction id. - /// - /// A valid transaction id for an existing transaction. - /// A object. - Task GetTransactionAsync(string transactionId); - - /// - /// Gets an exisiting transaction by providing a transaction id. - /// - /// A valid transaction id for an existing transaction. - /// A cancellation token. - /// A object. - Task GetTransactionAsync( - string transactionId, - CancellationToken cancellationToken = default); - - /// - /// Gets a existing transaction by providing a transaction id. - /// - /// A valid transaction Id of an existing transaction. - /// A object. - Task> GetTransactionResponseAsync(string transactionId); - - /// - /// Gets a existing transaction by providing a transaction id. - /// - /// A valid transaction Id of an existing transaction. - /// A cancellation token. - /// A object. - Task> GetTransactionResponseAsync( - string transactionId, - CancellationToken cancellationToken = default); - - /// - /// Deletes a existing transaction by providing a transaction id. - /// - /// A valid transaction Id of an existing transaction. - /// A cancellation token. - /// A Task. - Task DeleteTransactionAsync( - string transactionId, - CancellationToken cancellationToken = default); - - /// - /// Deletes a existing transaction by providing a transaction id. - /// - /// A valid transaction Id of an existing transaction. - /// Optional . - /// A Task. - Task DeleteTransactionAsync( - string transactionId, - DeleteTransactionOptions options); - - /// - /// Deletes a existing transaction by providing a transaction id. - /// - /// A valid transaction Id of an existing transaction. - /// Optional . - /// A cancellation token. - /// A Task. - Task DeleteTransactionAsync( - string transactionId, - DeleteTransactionOptions options = default, - CancellationToken cancellationToken = default); - - /// - /// Gets the signed document of a finished transaction by providing transaction id. - /// - /// A valid transaction Id of an existing transaction. - /// A valid file Id of a signed document. - /// Returns a stream containing the signed document data. - Task GetDocumentAsync(string transactionId, string fileId); - - /// - /// Gets the signed document of a finished transaction by providing transaction id. - /// - /// A valid transaction Id of an existing transaction. - /// A valid file Id of a signed document. - /// A cancellation token. - /// Returns a stream containing the signed document data. - Task GetDocumentAsync( - string transactionId, - string fileId, - CancellationToken cancellationToken = default); - - /// - /// Gets the receipt of a finished transaction by providing transaction id. - /// - /// A valid transaction Id of an finnished transaction. - /// Returns a stream containing the receipt data. - Task GetReceiptAsync(string transactionId); - - /// - /// Gets the receipt of a finished transaction by providing transaction id. - /// - /// A valid transaction Id of an finnished transaction. - /// A cancellation token. - /// Returns a stream containing the receipt data. - Task GetReceiptAsync( - string transactionId, - CancellationToken cancellationToken = default); - } + /// Creates a new transaction. + /// + /// A transaction model. + /// A transaction object. + Task CreateTransactionAsync(Transaction transaction); + + /// + /// Creates a new transaction. + /// + /// A transaction model. + /// A cancellation token. + /// A transaction object. + Task CreateTransactionAsync( + Transaction transaction, + CancellationToken cancellationToken = default); + + /// + /// Adds meta data for a file to an existing transaction by providing a + /// file location and a transaction id. + /// + /// Meta data for the file. + /// A valid transaction Id of an existing transaction. + /// An Id for the file. Should be the same as the fileId in the . + /// A task. + /// Make sure to call this method before + /// . + Task AddOrReplaceFileMetaToTransactionAsync( + FileMeta fileMeta, + string transactionId, + string fileId); + + /// + /// Adds meta data for a file to an existing transaction by providing a + /// file location and a transaction id. + /// + /// Meta data for the file. + /// A valid transaction Id of an existing transaction. + /// An Id for the file. Should be the same as the fileId in the . + /// A cancellation token. + /// A task. + /// Make sure to call this method before + /// . + Task AddOrReplaceFileMetaToTransactionAsync( + FileMeta fileMeta, + string transactionId, + string fileId, + CancellationToken cancellationToken = default); + + /// + /// Add a file to a existing transaction by providing a file location + /// and a transaction id. + /// + /// A Stream containing the file to upload. + /// A valid transaction Id of an existing transaction. + /// A Id for the file. Using the file name is recommended. + /// If a file with the same fileId allready exists the file wil be replaced. + /// . + /// A Task. + Task AddOrReplaceFileToTransactionAsync( + Stream fileStream, + string transactionId, + string fileId, + FileUploadOptions uploadOptions); + + /// + /// Add a file to a existing transaction by providing a file location + /// and a transaction id. + /// + /// A Stream containing the file to upload. + /// A valid transaction Id of an existing transaction. + /// A Id for the file. Using the file name is recommended. + /// If a file with the same fileId allready exists the file wil be replaced. + /// . + /// A cancellation token. + /// A Task. + Task AddOrReplaceFileToTransactionAsync( + Stream fileStream, + string transactionId, + string fileId, + FileUploadOptions uploadOptions, + CancellationToken cancellationToken = default); + + /// + /// Add a file to a existing transaction by providing a file location + /// and a transaction id. + /// + /// A string representation of the file path. + /// A valid transaction Id of an existing transaction. + /// A Id for the file. Using the file name is recommended. + /// If a file with the same fileId allready exists the file wil be replaced. + /// Optional . + /// A Task. + Task AddOrReplaceFileToTransactionAsync( + string filePath, + string transactionId, + string fileId, + FileUploadOptions uploadOptions); + + /// + /// Add a file to a existing transaction by providing a file location + /// and a transaction id. + /// + /// A string representation of the file path. + /// A valid transaction Id of an existing transaction. + /// A Id for the file. Using the file name is recommended. + /// If a file with the same fileId allready exists the file wil be replaced. + /// Optional . + /// A cancellation token. + /// A Task. + Task AddOrReplaceFileToTransactionAsync( + string filePath, + string transactionId, + string fileId, + FileUploadOptions uploadOptions, + CancellationToken cancellationToken = default); + + /// + /// start a existing transaction by providing transaction id. + /// + /// A valid transaction Id of an existing transaction. + /// A Task. + Task StartTransactionAsync(string transactionId); + + /// + /// start a existing transaction by providing transaction id. + /// + /// A valid transaction Id of an existing transaction. + /// A cancellation token. + /// A Task. + Task StartTransactionAsync( + string transactionId, + CancellationToken cancellationToken = default); + + /// + /// Gets an exisiting transaction by providing a transaction id. + /// + /// A valid transaction id for an existing transaction. + /// A object. + Task GetTransactionAsync(string transactionId); + + /// + /// Gets an exisiting transaction by providing a transaction id. + /// + /// A valid transaction id for an existing transaction. + /// A cancellation token. + /// A object. + Task GetTransactionAsync( + string transactionId, + CancellationToken cancellationToken = default); + + /// + /// Gets a existing transaction by providing a transaction id. + /// + /// A valid transaction Id of an existing transaction. + /// A object. + Task> GetTransactionResponseAsync(string transactionId); + + /// + /// Gets a existing transaction by providing a transaction id. + /// + /// A valid transaction Id of an existing transaction. + /// A cancellation token. + /// A object. + Task> GetTransactionResponseAsync( + string transactionId, + CancellationToken cancellationToken = default); + + /// + /// Deletes a existing transaction by providing a transaction id. + /// + /// A valid transaction Id of an existing transaction. + /// A cancellation token. + /// A Task. + Task DeleteTransactionAsync( + string transactionId, + CancellationToken cancellationToken = default); + + /// + /// Deletes a existing transaction by providing a transaction id. + /// + /// A valid transaction Id of an existing transaction. + /// Optional . + /// A Task. + Task DeleteTransactionAsync( + string transactionId, + DeleteTransactionOptions options); + + /// + /// Deletes a existing transaction by providing a transaction id. + /// + /// A valid transaction Id of an existing transaction. + /// Optional . + /// A cancellation token. + /// A Task. + Task DeleteTransactionAsync( + string transactionId, + DeleteTransactionOptions options = default, + CancellationToken cancellationToken = default); + + /// + /// Gets the signed document of a finished transaction by providing transaction id. + /// + /// A valid transaction Id of an existing transaction. + /// A valid file Id of a signed document. + /// Returns a stream containing the signed document data. + Task GetDocumentAsync(string transactionId, string fileId); + + /// + /// Gets the signed document of a finished transaction by providing transaction id. + /// + /// A valid transaction Id of an existing transaction. + /// A valid file Id of a signed document. + /// A cancellation token. + /// Returns a stream containing the signed document data. + Task GetDocumentAsync( + string transactionId, + string fileId, + CancellationToken cancellationToken = default); + + /// + /// Gets the receipt of a finished transaction by providing transaction id. + /// + /// A valid transaction Id of an finnished transaction. + /// Returns a stream containing the receipt data. + Task GetReceiptAsync(string transactionId); + + /// + /// Gets the receipt of a finished transaction by providing transaction id. + /// + /// A valid transaction Id of an finnished transaction. + /// A cancellation token. + /// Returns a stream containing the receipt data. + Task GetReceiptAsync( + string transactionId, + CancellationToken cancellationToken = default); } diff --git a/src/SignhostAPIClient/Rest/ISignhostApiClientSettings.cs b/src/SignhostAPIClient/Rest/ISignhostApiClientSettings.cs index 34c7c92..22baf84 100644 --- a/src/SignhostAPIClient/Rest/ISignhostApiClientSettings.cs +++ b/src/SignhostAPIClient/Rest/ISignhostApiClientSettings.cs @@ -1,26 +1,25 @@ using System; -namespace Signhost.APIClient.Rest -{ - public delegate void AddHeaders(string name, string value); +namespace Signhost.APIClient.Rest; + +public delegate void AddHeaders(string name, string value); - public interface ISignhostApiClientSettings - { - /// - /// Gets the usertoken identifying an authorized user. - /// - string UserToken { get; } +public interface ISignhostApiClientSettings +{ + /// + /// Gets the usertoken identifying an authorized user. + /// + string UserToken { get; } - /// - /// Gets the app key of your applications. - /// - string APPKey { get; } + /// + /// Gets the app key of your applications. + /// + string APPKey { get; } - /// - /// Gets the signhost API endpoint. - /// - string Endpoint { get; } + /// + /// Gets the signhost API endpoint. + /// + string Endpoint { get; } - Action AddHeader { get; } - } + Action AddHeader { get; } } diff --git a/src/SignhostAPIClient/Rest/ISignhostApiReceiver.cs b/src/SignhostAPIClient/Rest/ISignhostApiReceiver.cs index 7778deb..ba4eb96 100644 --- a/src/SignhostAPIClient/Rest/ISignhostApiReceiver.cs +++ b/src/SignhostAPIClient/Rest/ISignhostApiReceiver.cs @@ -1,23 +1,22 @@ using System.Collections.Generic; using Signhost.APIClient.Rest.DataObjects; -namespace Signhost.APIClient.Rest +namespace Signhost.APIClient.Rest; + +/// +/// Interface abstracting the available Signhost API responses. +/// +public interface ISignhostApiReceiver { /// - /// Interface abstracting the available Signhost API responses. + /// Checks the validity of the postback checksum. /// - public interface ISignhostApiReceiver - { - /// - /// Checks the validity of the postback checksum. - /// - /// true, if postback checksum valid was validated, false otherwise. - /// HTTP response headers. - /// HTTP response body. - /// A transaction object. - bool IsPostbackChecksumValid( - IDictionary headers, - string body, - out Transaction postbackTransaction); - } + /// true, if postback checksum valid was validated, false otherwise. + /// HTTP response headers. + /// HTTP response body. + /// A transaction object. + bool IsPostbackChecksumValid( + IDictionary headers, + string body, + out Transaction postbackTransaction); } diff --git a/src/SignhostAPIClient/Rest/JsonContent.cs b/src/SignhostAPIClient/Rest/JsonContent.cs index 837d627..cb31cea 100644 --- a/src/SignhostAPIClient/Rest/JsonContent.cs +++ b/src/SignhostAPIClient/Rest/JsonContent.cs @@ -2,45 +2,44 @@ using System.Net.Http.Headers; using System.Text.Json; -namespace Signhost.APIClient.Rest +namespace Signhost.APIClient.Rest; + +/// +/// See the helper class. +/// +internal static class JsonContent { /// - /// See the helper class. + /// Creates a new . /// - internal static class JsonContent + /// Type to serialize. + /// Value to serialize. + /// . + internal static JsonContent From(T value) { - /// - /// Creates a new . - /// - /// Type to serialize. - /// Value to serialize. - /// . - internal static JsonContent From(T value) - { - return new JsonContent(value); - } + return new JsonContent(value); } +} +/// +/// A class for application/json. +/// +/// The type to serialize +internal class JsonContent + : StringContent +{ /// - /// A class for application/json. + /// Initializes a new instance of the class. /// - /// The type to serialize - internal class JsonContent - : StringContent + /// Value to serialize. + public JsonContent(T value) + : base(ToJson(value)) { - /// - /// Initializes a new instance of the class. - /// - /// Value to serialize. - public JsonContent(T value) - : base(ToJson(value)) - { - Headers.ContentType = new MediaTypeHeaderValue("application/json"); - } + Headers.ContentType = new MediaTypeHeaderValue("application/json"); + } - private static string ToJson(T value) - { - return JsonSerializer.Serialize(value, SignhostJsonSerializerOptions.Default); - } + private static string ToJson(T value) + { + return JsonSerializer.Serialize(value, SignhostJsonSerializerOptions.Default); } } diff --git a/src/SignhostAPIClient/Rest/JsonConverters/LevelEnumConverter.cs b/src/SignhostAPIClient/Rest/JsonConverters/LevelEnumConverter.cs index 07c2031..06b9c0b 100644 --- a/src/SignhostAPIClient/Rest/JsonConverters/LevelEnumConverter.cs +++ b/src/SignhostAPIClient/Rest/JsonConverters/LevelEnumConverter.cs @@ -3,54 +3,53 @@ using System.Text.Json.Serialization; using Signhost.APIClient.Rest.DataObjects; -namespace Signhost.APIClient.Rest.JsonConverters +namespace Signhost.APIClient.Rest.JsonConverters; + +/// +/// JSON converter factory for converting the enum. +/// Invalid values are mapped to . +/// +internal class LevelEnumConverter + : JsonConverter { - /// - /// JSON converter factory for converting the enum. - /// Invalid values are mapped to . - /// - internal class LevelEnumConverter - : JsonConverter + public override Level? Read( + ref Utf8JsonReader reader, + Type typeToConvert, + JsonSerializerOptions options) { - public override Level? Read( - ref Utf8JsonReader reader, - Type typeToConvert, - JsonSerializerOptions options) - { - if (reader.TokenType == JsonTokenType.Null) { - return null; - } - - if (reader.TokenType == JsonTokenType.String) { - var value = reader.GetString() ?? string.Empty; - if (Enum.TryParse(value, out var level)) { - return level; - } - - return Level.Unknown; - } + if (reader.TokenType == JsonTokenType.Null) { + return null; + } - if (reader.TokenType == JsonTokenType.Number) { - int value = reader.GetInt32(); - if (Enum.IsDefined(typeof(Level), value)) { - return (Level)value; - } + if (reader.TokenType == JsonTokenType.String) { + var value = reader.GetString() ?? string.Empty; + if (Enum.TryParse(value, out var level)) { + return level; } return Level.Unknown; } - public override void Write( - Utf8JsonWriter writer, - Level? value, - JsonSerializerOptions options) - { - if (value is null) { - writer.WriteNullValue(); - } - else { - writer.WriteStringValue(value.ToString()); + if (reader.TokenType == JsonTokenType.Number) { + int value = reader.GetInt32(); + if (Enum.IsDefined(typeof(Level), value)) { + return (Level)value; } } + + return Level.Unknown; + } + + public override void Write( + Utf8JsonWriter writer, + Level? value, + JsonSerializerOptions options) + { + if (value is null) { + writer.WriteNullValue(); + } + else { + writer.WriteStringValue(value.ToString()); + } } } diff --git a/src/SignhostAPIClient/Rest/SignHostApiClient.cs b/src/SignhostAPIClient/Rest/SignHostApiClient.cs index a453f2c..4c7f698 100644 --- a/src/SignhostAPIClient/Rest/SignHostApiClient.cs +++ b/src/SignhostAPIClient/Rest/SignHostApiClient.cs @@ -9,475 +9,474 @@ using Signhost.APIClient.Rest.DataObjects; using Signhost.APIClient.Rest.ErrorHandling; -namespace Signhost.APIClient.Rest +namespace Signhost.APIClient.Rest; + +/// +/// Implements the interface which provides +/// an signhost api client implementation. +/// +public class SignhostApiClient + : ISignhostApiClient + , IDisposable { + private const string ApiVersion = "v1"; + + private static readonly string Version = typeof(SignhostApiClient) +#if TYPEINFO + .GetTypeInfo() +#endif + .Assembly.GetCustomAttribute() + .Version; + + private readonly ISignhostApiClientSettings settings; + private readonly HttpClient client; + /// - /// Implements the interface which provides - /// an signhost api client implementation. + /// Initializes a new instance of the class. + /// Set your usertoken and APPKey by creating a . /// - public class SignhostApiClient - : ISignhostApiClient - , IDisposable + /// . + public SignhostApiClient(ISignhostApiClientSettings settings) + : this(settings, new HttpClient()) { - private const string ApiVersion = "v1"; + } - private static readonly string Version = typeof(SignhostApiClient) -#if TYPEINFO - .GetTypeInfo() -#endif - .Assembly.GetCustomAttribute() - .Version; - - private readonly ISignhostApiClientSettings settings; - private readonly HttpClient client; - - /// - /// Initializes a new instance of the class. - /// Set your usertoken and APPKey by creating a . - /// - /// . - public SignhostApiClient(ISignhostApiClientSettings settings) - : this(settings, new HttpClient()) - { + /// + /// Initializes a new instance of the class. + /// Set your usertoken and APPKey by creating a . + /// + /// . + /// to use for all http calls. + public SignhostApiClient( + ISignhostApiClientSettings settings, + HttpClient httpClient) + { + this.settings = settings; + this.client = httpClient; + this.client.BaseAddress = new Uri( + settings.Endpoint + (settings.Endpoint.EndsWith("/") ? string.Empty : "/")); + this.client.DefaultRequestHeaders.UserAgent.Add( + new System.Net.Http.Headers.ProductInfoHeaderValue( + "SignhostClientLibrary", + Version)); + this.client.DefaultRequestHeaders.Add("Application", ApplicationHeader); + + if (!string.IsNullOrWhiteSpace(settings.UserToken)) { + this.client.DefaultRequestHeaders.Add("Authorization", AuthorizationHeader); } - /// - /// Initializes a new instance of the class. - /// Set your usertoken and APPKey by creating a . - /// - /// . - /// to use for all http calls. - public SignhostApiClient( - ISignhostApiClientSettings settings, - HttpClient httpClient) - { - this.settings = settings; - this.client = httpClient; - this.client.BaseAddress = new Uri( - settings.Endpoint + (settings.Endpoint.EndsWith("/") ? string.Empty : "/")); - this.client.DefaultRequestHeaders.UserAgent.Add( - new System.Net.Http.Headers.ProductInfoHeaderValue( - "SignhostClientLibrary", - Version)); - this.client.DefaultRequestHeaders.Add("Application", ApplicationHeader); - - if (!string.IsNullOrWhiteSpace(settings.UserToken)) { - this.client.DefaultRequestHeaders.Add("Authorization", AuthorizationHeader); - } - - this.client.DefaultRequestHeaders.Accept.Add(MediaTypeWithQualityHeaderValue.Parse($"application/vnd.signhost.{ApiVersion}+json")); - settings.AddHeader?.Invoke(this.client.DefaultRequestHeaders.Add); + this.client.DefaultRequestHeaders.Accept.Add(MediaTypeWithQualityHeaderValue.Parse($"application/vnd.signhost.{ApiVersion}+json")); + settings.AddHeader?.Invoke(this.client.DefaultRequestHeaders.Add); + } + + private string ApplicationHeader + => $"APPKey {settings.APPKey}"; + + private string AuthorizationHeader + => $"APIKey {settings.UserToken}"; + + /// + public async Task CreateTransactionAsync( + Transaction transaction) + => await CreateTransactionAsync(transaction, default) + .ConfigureAwait(false); + + /// + public async Task CreateTransactionAsync( + Transaction transaction, + CancellationToken cancellationToken = default) + { + if (transaction == null) { + throw new ArgumentNullException(nameof(transaction)); } - private string ApplicationHeader - => $"APPKey {settings.APPKey}"; + var result = await client + .PostAsync( + "transaction", + JsonContent.From(transaction), + cancellationToken) + .EnsureSignhostSuccessStatusCodeAsync() + .ConfigureAwait(false); - private string AuthorizationHeader - => $"APIKey {settings.UserToken}"; + return await result.Content.FromJsonAsync() + .ConfigureAwait(false); + } - /// - public async Task CreateTransactionAsync( - Transaction transaction) - => await CreateTransactionAsync(transaction, default) - .ConfigureAwait(false); + /// + public async Task> GetTransactionResponseAsync( + string transactionId) + => await GetTransactionResponseAsync(transactionId, default) + .ConfigureAwait(false); - /// - public async Task CreateTransactionAsync( - Transaction transaction, - CancellationToken cancellationToken = default) - { - if (transaction == null) { - throw new ArgumentNullException(nameof(transaction)); - } - - var result = await client - .PostAsync( - "transaction", - JsonContent.From(transaction), - cancellationToken) - .EnsureSignhostSuccessStatusCodeAsync() - .ConfigureAwait(false); + /// + public async Task> GetTransactionResponseAsync( + string transactionId, + CancellationToken cancellationToken = default) + { + if (transactionId == null) { + throw new ArgumentNullException(nameof(transactionId)); + } - return await result.Content.FromJsonAsync() - .ConfigureAwait(false); + if (string.IsNullOrWhiteSpace(transactionId)) { + throw new ArgumentException("Cannot be empty or contain only whitespaces.", nameof(transactionId)); } - /// - public async Task> GetTransactionResponseAsync( - string transactionId) - => await GetTransactionResponseAsync(transactionId, default) - .ConfigureAwait(false); + var result = await client + .GetAsync( + "transaction".JoinPaths(transactionId), + cancellationToken) + .EnsureSignhostSuccessStatusCodeAsync(HttpStatusCode.Gone) + .ConfigureAwait(false); + var transaction = await result.Content.FromJsonAsync() + .ConfigureAwait(false); - /// - public async Task> GetTransactionResponseAsync( - string transactionId, - CancellationToken cancellationToken = default) - { - if (transactionId == null) { - throw new ArgumentNullException(nameof(transactionId)); - } + return new ApiResponse(result, transaction); + } - if (string.IsNullOrWhiteSpace(transactionId)) { - throw new ArgumentException("Cannot be empty or contain only whitespaces.", nameof(transactionId)); - } + /// + public async Task GetTransactionAsync(string transactionId) + => await GetTransactionAsync(transactionId, default) + .ConfigureAwait(false); - var result = await client - .GetAsync( - "transaction".JoinPaths(transactionId), - cancellationToken) - .EnsureSignhostSuccessStatusCodeAsync(HttpStatusCode.Gone) - .ConfigureAwait(false); - var transaction = await result.Content.FromJsonAsync() - .ConfigureAwait(false); + /// + public async Task GetTransactionAsync( + string transactionId, + CancellationToken cancellationToken = default) + { + var response = await GetTransactionResponseAsync( + transactionId, + cancellationToken) + .ConfigureAwait(false); + + response.EnsureAvailableStatusCode(); + + return response.Value; + } + + /// + public async Task DeleteTransactionAsync( + string transactionId, + CancellationToken cancellationToken = default) + => await DeleteTransactionAsync( + transactionId, + default, + cancellationToken).ConfigureAwait(false); + + /// + public async Task DeleteTransactionAsync( + string transactionId, + DeleteTransactionOptions options) + => await DeleteTransactionAsync( + transactionId, + options, + default).ConfigureAwait(false); + + /// + public async Task DeleteTransactionAsync( + string transactionId, + DeleteTransactionOptions options, + CancellationToken cancellationToken = default) + { + if (transactionId == null) { + throw new ArgumentNullException(nameof(transactionId)); + } - return new ApiResponse(result, transaction); + if (string.IsNullOrWhiteSpace(transactionId)) { + throw new ArgumentException("Cannot be empty or contain only whitespaces.", nameof(transactionId)); } - /// - public async Task GetTransactionAsync(string transactionId) - => await GetTransactionAsync(transactionId, default) - .ConfigureAwait(false); + if (options == null) { + options = new DeleteTransactionOptions(); + } - /// - public async Task GetTransactionAsync( - string transactionId, - CancellationToken cancellationToken = default) - { - var response = await GetTransactionResponseAsync( - transactionId, + var request = new HttpRequestMessage(HttpMethod.Delete, "transaction".JoinPaths(transactionId)); + request.Content = JsonContent.From(options); + await client + .SendAsync( + request, cancellationToken) - .ConfigureAwait(false); + .EnsureSignhostSuccessStatusCodeAsync() + .ConfigureAwait(false); + } - response.EnsureAvailableStatusCode(); + /// + public async Task AddOrReplaceFileMetaToTransactionAsync( + FileMeta fileMeta, + string transactionId, + string fileId) + => await AddOrReplaceFileMetaToTransactionAsync( + fileMeta, + transactionId, + fileId, + default).ConfigureAwait(false); + + /// + public async Task AddOrReplaceFileMetaToTransactionAsync( + FileMeta fileMeta, + string transactionId, + string fileId, + CancellationToken cancellationToken = default) + { + if (fileMeta == null) { + throw new ArgumentNullException("fileMeta"); + } - return response.Value; + if (transactionId == null) { + throw new ArgumentNullException(nameof(transactionId)); } - /// - public async Task DeleteTransactionAsync( - string transactionId, - CancellationToken cancellationToken = default) - => await DeleteTransactionAsync( - transactionId, - default, - cancellationToken).ConfigureAwait(false); - - /// - public async Task DeleteTransactionAsync( - string transactionId, - DeleteTransactionOptions options) - => await DeleteTransactionAsync( - transactionId, - options, - default).ConfigureAwait(false); - - /// - public async Task DeleteTransactionAsync( - string transactionId, - DeleteTransactionOptions options, - CancellationToken cancellationToken = default) - { - if (transactionId == null) { - throw new ArgumentNullException(nameof(transactionId)); - } - - if (string.IsNullOrWhiteSpace(transactionId)) { - throw new ArgumentException("Cannot be empty or contain only whitespaces.", nameof(transactionId)); - } - - if (options == null) { - options = new DeleteTransactionOptions(); - } - - var request = new HttpRequestMessage(HttpMethod.Delete, "transaction".JoinPaths(transactionId)); - request.Content = JsonContent.From(options); - await client - .SendAsync( - request, - cancellationToken) - .EnsureSignhostSuccessStatusCodeAsync() - .ConfigureAwait(false); + if (string.IsNullOrWhiteSpace(transactionId)) { + throw new ArgumentException("Cannot be empty or contain only whitespaces.", nameof(transactionId)); } - /// - public async Task AddOrReplaceFileMetaToTransactionAsync( - FileMeta fileMeta, - string transactionId, - string fileId) - => await AddOrReplaceFileMetaToTransactionAsync( - fileMeta, - transactionId, - fileId, - default).ConfigureAwait(false); - - /// - public async Task AddOrReplaceFileMetaToTransactionAsync( - FileMeta fileMeta, - string transactionId, - string fileId, - CancellationToken cancellationToken = default) - { - if (fileMeta == null) { - throw new ArgumentNullException("fileMeta"); - } - - if (transactionId == null) { - throw new ArgumentNullException(nameof(transactionId)); - } - - if (string.IsNullOrWhiteSpace(transactionId)) { - throw new ArgumentException("Cannot be empty or contain only whitespaces.", nameof(transactionId)); - } - - if (fileId == null) { - throw new ArgumentNullException(nameof(fileId)); - } - - if (string.IsNullOrWhiteSpace(fileId)) { - throw new ArgumentException("Cannot be empty or contain only whitespaces.", nameof(fileId)); - } - - await client - .PutAsync( - "transaction".JoinPaths(transactionId, "file", fileId), - JsonContent.From(fileMeta), - cancellationToken) - .EnsureSignhostSuccessStatusCodeAsync() - .ConfigureAwait(false); + if (fileId == null) { + throw new ArgumentNullException(nameof(fileId)); + } + + if (string.IsNullOrWhiteSpace(fileId)) { + throw new ArgumentException("Cannot be empty or contain only whitespaces.", nameof(fileId)); } - /// - public async Task AddOrReplaceFileToTransactionAsync( + await client + .PutAsync( + "transaction".JoinPaths(transactionId, "file", fileId), + JsonContent.From(fileMeta), + cancellationToken) + .EnsureSignhostSuccessStatusCodeAsync() + .ConfigureAwait(false); + } + + /// + public async Task AddOrReplaceFileToTransactionAsync( + Stream fileStream, + string transactionId, + string fileId, + FileUploadOptions uploadOptions) + => await AddOrReplaceFileToTransactionAsync( + fileStream, + transactionId, + fileId, + uploadOptions, + default).ConfigureAwait(false); + + /// + public async Task AddOrReplaceFileToTransactionAsync( Stream fileStream, string transactionId, string fileId, - FileUploadOptions uploadOptions) - => await AddOrReplaceFileToTransactionAsync( - fileStream, - transactionId, - fileId, - uploadOptions, - default).ConfigureAwait(false); - - /// - public async Task AddOrReplaceFileToTransactionAsync( - Stream fileStream, - string transactionId, - string fileId, - FileUploadOptions uploadOptions, - CancellationToken cancellationToken = default) - { - if (fileStream == null) { - throw new ArgumentNullException(nameof(fileStream)); - } - - if (transactionId == null) { - throw new ArgumentNullException(nameof(transactionId)); - } - - if (string.IsNullOrWhiteSpace(transactionId)) { - throw new ArgumentException("Cannot be empty or contain only whitespaces.", nameof(transactionId)); - } - - if (fileId == null) { - throw new ArgumentNullException(nameof(fileId)); - } - - if (string.IsNullOrWhiteSpace(fileId)) { - throw new ArgumentException("Cannot be empty or contain only whitespaces.", nameof(fileId)); - } - - if (uploadOptions == null) { - uploadOptions = new FileUploadOptions(); - } - - var content = new StreamContent(fileStream) - .WithDigest(fileStream, uploadOptions.DigestOptions); - content.Headers.ContentType = new MediaTypeHeaderValue("application/pdf"); - - await client - .PutAsync( - "transaction".JoinPaths(transactionId, "file", fileId), - content, - cancellationToken) - .EnsureSignhostSuccessStatusCodeAsync() - .ConfigureAwait(false); + FileUploadOptions uploadOptions, + CancellationToken cancellationToken = default) + { + if (fileStream == null) { + throw new ArgumentNullException(nameof(fileStream)); } - /// - public Task AddOrReplaceFileToTransaction( - Stream fileStream, - string transactionId, - string fileId) - { - return AddOrReplaceFileToTransactionAsync( - fileStream, - transactionId, - fileId, - null); + if (transactionId == null) { + throw new ArgumentNullException(nameof(transactionId)); } - /// - public async Task AddOrReplaceFileToTransactionAsync( - string filePath, - string transactionId, - string fileId, - FileUploadOptions uploadOptions) - => await AddOrReplaceFileToTransactionAsync( - filePath, - transactionId, - fileId, - uploadOptions, - default).ConfigureAwait(false); - - /// - public async Task AddOrReplaceFileToTransactionAsync( - string filePath, - string transactionId, - string fileId, - FileUploadOptions uploadOptions, - CancellationToken cancellationToken = default) - { - if (filePath == null) { - throw new ArgumentNullException(nameof(filePath)); - } - - using (Stream fileStream = System.IO.File.Open( - filePath, - FileMode.Open, - FileAccess.Read, - FileShare.Delete | FileShare.Read)) - { - await AddOrReplaceFileToTransactionAsync( - fileStream, - transactionId, - fileId, - uploadOptions, - cancellationToken) - .ConfigureAwait(false); - } + if (string.IsNullOrWhiteSpace(transactionId)) { + throw new ArgumentException("Cannot be empty or contain only whitespaces.", nameof(transactionId)); } - /// - public Task AddOrReplaceFileToTransaction( - string filePath, - string transactionId, - string fileId) - { - return AddOrReplaceFileToTransactionAsync( - filePath, - transactionId, - fileId, - null); + if (fileId == null) { + throw new ArgumentNullException(nameof(fileId)); } - /// - public async Task StartTransactionAsync( - string transactionId) - => await StartTransactionAsync(transactionId, default) - .ConfigureAwait(false); + if (string.IsNullOrWhiteSpace(fileId)) { + throw new ArgumentException("Cannot be empty or contain only whitespaces.", nameof(fileId)); + } + + if (uploadOptions == null) { + uploadOptions = new FileUploadOptions(); + } - /// - public async Task StartTransactionAsync( - string transactionId, - CancellationToken cancellationToken = default) + var content = new StreamContent(fileStream) + .WithDigest(fileStream, uploadOptions.DigestOptions); + content.Headers.ContentType = new MediaTypeHeaderValue("application/pdf"); + + await client + .PutAsync( + "transaction".JoinPaths(transactionId, "file", fileId), + content, + cancellationToken) + .EnsureSignhostSuccessStatusCodeAsync() + .ConfigureAwait(false); + } + + /// + public Task AddOrReplaceFileToTransaction( + Stream fileStream, + string transactionId, + string fileId) + { + return AddOrReplaceFileToTransactionAsync( + fileStream, + transactionId, + fileId, + null); + } + + /// + public async Task AddOrReplaceFileToTransactionAsync( + string filePath, + string transactionId, + string fileId, + FileUploadOptions uploadOptions) + => await AddOrReplaceFileToTransactionAsync( + filePath, + transactionId, + fileId, + uploadOptions, + default).ConfigureAwait(false); + + /// + public async Task AddOrReplaceFileToTransactionAsync( + string filePath, + string transactionId, + string fileId, + FileUploadOptions uploadOptions, + CancellationToken cancellationToken = default) + { + if (filePath == null) { + throw new ArgumentNullException(nameof(filePath)); + } + + using (Stream fileStream = System.IO.File.Open( + filePath, + FileMode.Open, + FileAccess.Read, + FileShare.Delete | FileShare.Read)) { - if (transactionId == null) { - throw new ArgumentNullException(nameof(transactionId)); - } - - if (string.IsNullOrWhiteSpace(transactionId)) { - throw new ArgumentException("Cannot be empty or contain only whitespaces.", nameof(transactionId)); - } - - await client - .PutAsync( - "transaction".JoinPaths(transactionId, "start"), - null, + await AddOrReplaceFileToTransactionAsync( + fileStream, + transactionId, + fileId, + uploadOptions, cancellationToken) - .EnsureSignhostSuccessStatusCodeAsync() .ConfigureAwait(false); } + } - /// - public async Task GetReceiptAsync(string transactionId) - => await GetReceiptAsync(transactionId, default) - .ConfigureAwait(false); + /// + public Task AddOrReplaceFileToTransaction( + string filePath, + string transactionId, + string fileId) + { + return AddOrReplaceFileToTransactionAsync( + filePath, + transactionId, + fileId, + null); + } - /// - public async Task GetReceiptAsync( - string transactionId, - CancellationToken cancellationToken = default) - { - if (transactionId == null) { - throw new ArgumentNullException(nameof(transactionId)); - } + /// + public async Task StartTransactionAsync( + string transactionId) + => await StartTransactionAsync(transactionId, default) + .ConfigureAwait(false); - if (string.IsNullOrWhiteSpace(transactionId)) { - throw new ArgumentException("Cannot be empty or contain only whitespaces.", nameof(transactionId)); - } + /// + public async Task StartTransactionAsync( + string transactionId, + CancellationToken cancellationToken = default) + { + if (transactionId == null) { + throw new ArgumentNullException(nameof(transactionId)); + } - var result = await client - .GetStreamAsync( - "file".JoinPaths("receipt", transactionId)) - .ConfigureAwait(false); + if (string.IsNullOrWhiteSpace(transactionId)) { + throw new ArgumentException("Cannot be empty or contain only whitespaces.", nameof(transactionId)); + } + + await client + .PutAsync( + "transaction".JoinPaths(transactionId, "start"), + null, + cancellationToken) + .EnsureSignhostSuccessStatusCodeAsync() + .ConfigureAwait(false); + } - return result; + /// + public async Task GetReceiptAsync(string transactionId) + => await GetReceiptAsync(transactionId, default) + .ConfigureAwait(false); + + /// + public async Task GetReceiptAsync( + string transactionId, + CancellationToken cancellationToken = default) + { + if (transactionId == null) { + throw new ArgumentNullException(nameof(transactionId)); } - /// - public async Task GetDocumentAsync( - string transactionId, - string fileId) - => await GetDocumentAsync(transactionId, fileId, default) - .ConfigureAwait(false); + if (string.IsNullOrWhiteSpace(transactionId)) { + throw new ArgumentException("Cannot be empty or contain only whitespaces.", nameof(transactionId)); + } - /// - public async Task GetDocumentAsync( - string transactionId, - string fileId, - CancellationToken cancellationToken = default) - { - if (transactionId == null) { - throw new ArgumentNullException(nameof(transactionId)); - } + var result = await client + .GetStreamAsync( + "file".JoinPaths("receipt", transactionId)) + .ConfigureAwait(false); - if (string.IsNullOrWhiteSpace(transactionId)) { - throw new ArgumentException("Cannot be empty or contain only whitespaces.", nameof(transactionId)); - } + return result; + } - if (fileId == null) { - throw new ArgumentNullException(nameof(fileId)); - } + /// + public async Task GetDocumentAsync( + string transactionId, + string fileId) + => await GetDocumentAsync(transactionId, fileId, default) + .ConfigureAwait(false); - if (string.IsNullOrWhiteSpace(fileId)) { - throw new ArgumentException("Cannot be empty or contain only whitespaces.", nameof(fileId)); - } + /// + public async Task GetDocumentAsync( + string transactionId, + string fileId, + CancellationToken cancellationToken = default) + { + if (transactionId == null) { + throw new ArgumentNullException(nameof(transactionId)); + } - var result = await client - .GetStreamAsync( - "transaction".JoinPaths(transactionId, "file", fileId)) - .ConfigureAwait(false); + if (string.IsNullOrWhiteSpace(transactionId)) { + throw new ArgumentException("Cannot be empty or contain only whitespaces.", nameof(transactionId)); + } - return result; + if (fileId == null) { + throw new ArgumentNullException(nameof(fileId)); } - /// - public void Dispose() - { - Dispose(true); - GC.SuppressFinalize(this); + if (string.IsNullOrWhiteSpace(fileId)) { + throw new ArgumentException("Cannot be empty or contain only whitespaces.", nameof(fileId)); } - /// - /// Disposes the instance. - /// - /// Is callled. - protected virtual void Dispose(bool disposing) - { - if (disposing) { - client?.Dispose(); - } + var result = await client + .GetStreamAsync( + "transaction".JoinPaths(transactionId, "file", fileId)) + .ConfigureAwait(false); + + return result; + } + + /// + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + /// + /// Disposes the instance. + /// + /// Is callled. + protected virtual void Dispose(bool disposing) + { + if (disposing) { + client?.Dispose(); } } } diff --git a/src/SignhostAPIClient/Rest/SignHostApiClientSettings.cs b/src/SignhostAPIClient/Rest/SignHostApiClientSettings.cs index b4afed6..c96340d 100644 --- a/src/SignhostAPIClient/Rest/SignHostApiClientSettings.cs +++ b/src/SignhostAPIClient/Rest/SignHostApiClientSettings.cs @@ -1,29 +1,28 @@ using System; -namespace Signhost.APIClient.Rest +namespace Signhost.APIClient.Rest; + +public class SignhostApiClientSettings + : ISignhostApiClientSettings { - public class SignhostApiClientSettings - : ISignhostApiClientSettings - { - public const string DefaultEndpoint = "https://api.signhost.com/api/"; + public const string DefaultEndpoint = "https://api.signhost.com/api/"; - public SignhostApiClientSettings(string appkey, string userToken) - { - APPKey = appkey; - UserToken = userToken; - } + public SignhostApiClientSettings(string appkey, string userToken) + { + APPKey = appkey; + UserToken = userToken; + } - public SignhostApiClientSettings(string appkey) - { - APPKey = appkey; - } + public SignhostApiClientSettings(string appkey) + { + APPKey = appkey; + } - public string UserToken { get; set; } + public string UserToken { get; set; } - public string APPKey { get; private set; } + public string APPKey { get; private set; } - public string Endpoint { get; set; } = DefaultEndpoint; + public string Endpoint { get; set; } = DefaultEndpoint; - public Action AddHeader { get; set; } - } + public Action AddHeader { get; set; } } diff --git a/src/SignhostAPIClient/Rest/SignhostApiReceiver.cs b/src/SignhostAPIClient/Rest/SignhostApiReceiver.cs index da6c189..d4fb3bf 100644 --- a/src/SignhostAPIClient/Rest/SignhostApiReceiver.cs +++ b/src/SignhostAPIClient/Rest/SignhostApiReceiver.cs @@ -7,86 +7,85 @@ using Signhost.APIClient.Rest; using Signhost.APIClient.Rest.DataObjects; -namespace Signhost.APIClient +namespace Signhost.APIClient; + +/// +/// Implements the interface which provides +/// a Signhost API receiver implementation. +/// +public class SignhostApiReceiver + : ISignhostApiReceiver { + private readonly SignhostApiReceiverSettings settings; + /// - /// Implements the interface which provides - /// a Signhost API receiver implementation. + /// Initializes a new instance of the class. + /// Set your SharedSecret by creating a . /// - public class SignhostApiReceiver - : ISignhostApiReceiver + /// + /// Settings for the receiver. + /// + public SignhostApiReceiver(SignhostApiReceiverSettings receiverSettings) { - private readonly SignhostApiReceiverSettings settings; + this.settings = receiverSettings; + } - /// - /// Initializes a new instance of the class. - /// Set your SharedSecret by creating a . - /// - /// - /// Settings for the receiver. - /// - public SignhostApiReceiver(SignhostApiReceiverSettings receiverSettings) - { - this.settings = receiverSettings; - } + /// + public bool IsPostbackChecksumValid( + IDictionary headers, + string body, + out Transaction postbackTransaction) + { + postbackTransaction = null; + string postbackChecksum; + string calculatedChecksum; + PostbackTransaction postback; - /// - public bool IsPostbackChecksumValid( - IDictionary headers, - string body, - out Transaction postbackTransaction) - { - postbackTransaction = null; - string postbackChecksum; - string calculatedChecksum; - PostbackTransaction postback; + postback = DeserializeToPostbackTransaction(body); + postbackChecksum = GetChecksumFromHeadersOrPostback(headers, postback); + bool parametersAreValid = HasValidChecksumProperties(postbackChecksum, postback); - postback = DeserializeToPostbackTransaction(body); - postbackChecksum = GetChecksumFromHeadersOrPostback(headers, postback); - bool parametersAreValid = HasValidChecksumProperties(postbackChecksum, postback); + if (parametersAreValid) { + calculatedChecksum = CalculateChecksumFromPostback(postback); + postbackTransaction = postback; + } else { + return false; + } - if (parametersAreValid) { - calculatedChecksum = CalculateChecksumFromPostback(postback); - postbackTransaction = postback; - } else { - return false; - } + return Equals(calculatedChecksum, postbackChecksum); + } - return Equals(calculatedChecksum, postbackChecksum); + private string CalculateChecksumFromPostback(PostbackTransaction postback) + { + using (var sha1 = SHA1.Create()) { + var checksumBytes = sha1.ComputeHash(Encoding.UTF8.GetBytes( + $"{postback.Id}||{(int)postback.Status}|{settings.SharedSecret}")); + return BitConverter.ToString(checksumBytes) + .Replace("-", string.Empty) + .ToLower(); } + } - private string CalculateChecksumFromPostback(PostbackTransaction postback) - { - using (var sha1 = SHA1.Create()) { - var checksumBytes = sha1.ComputeHash(Encoding.UTF8.GetBytes( - $"{postback.Id}||{(int)postback.Status}|{settings.SharedSecret}")); - return BitConverter.ToString(checksumBytes) - .Replace("-", string.Empty) - .ToLower(); - } - } + private PostbackTransaction DeserializeToPostbackTransaction(string body) + { + return JsonSerializer.Deserialize(body, SignhostJsonSerializerOptions.Default); + } - private PostbackTransaction DeserializeToPostbackTransaction(string body) - { - return JsonSerializer.Deserialize(body, SignhostJsonSerializerOptions.Default); + private string GetChecksumFromHeadersOrPostback( + IDictionary headers, + PostbackTransaction postback) + { + string[] postbackChecksumArray; + if (headers.TryGetValue("Checksum", out postbackChecksumArray)) { + return postbackChecksumArray.First(); } - - private string GetChecksumFromHeadersOrPostback( - IDictionary headers, - PostbackTransaction postback) - { - string[] postbackChecksumArray; - if (headers.TryGetValue("Checksum", out postbackChecksumArray)) { - return postbackChecksumArray.First(); - } - else { - return postback.Checksum; - } + else { + return postback.Checksum; } + } - private bool HasValidChecksumProperties(string postbackChecksum, PostbackTransaction postback) - { - return !string.IsNullOrWhiteSpace(postbackChecksum) && !string.IsNullOrWhiteSpace(postback.Id); - } + private bool HasValidChecksumProperties(string postbackChecksum, PostbackTransaction postback) + { + return !string.IsNullOrWhiteSpace(postbackChecksum) && !string.IsNullOrWhiteSpace(postback.Id); } } diff --git a/src/SignhostAPIClient/Rest/SignhostApiReceiverSettings.cs b/src/SignhostAPIClient/Rest/SignhostApiReceiverSettings.cs index d3d962a..c1b2bbc 100644 --- a/src/SignhostAPIClient/Rest/SignhostApiReceiverSettings.cs +++ b/src/SignhostAPIClient/Rest/SignhostApiReceiverSettings.cs @@ -1,19 +1,18 @@ -namespace Signhost.APIClient.Rest +namespace Signhost.APIClient.Rest; + +/// +/// Registers the necessary settings for the class. +/// +public class SignhostApiReceiverSettings { - /// - /// Registers the necessary settings for the class. - /// - public class SignhostApiReceiverSettings + public SignhostApiReceiverSettings(string sharedsecret) { - public SignhostApiReceiverSettings(string sharedsecret) - { - SharedSecret = sharedsecret; - } - - /// - /// Gets the shared secret. - /// - /// The shared secret key issued by Signhost.com. - public string SharedSecret { get; private set; } + SharedSecret = sharedsecret; } + + /// + /// Gets the shared secret. + /// + /// The shared secret key issued by Signhost.com. + public string SharedSecret { get; private set; } } diff --git a/src/SignhostAPIClient/Rest/SignhostJsonSerializerOptions.cs b/src/SignhostAPIClient/Rest/SignhostJsonSerializerOptions.cs index 3784502..22c3454 100644 --- a/src/SignhostAPIClient/Rest/SignhostJsonSerializerOptions.cs +++ b/src/SignhostAPIClient/Rest/SignhostJsonSerializerOptions.cs @@ -1,23 +1,22 @@ using System.Text.Json; using System.Text.Json.Serialization; -namespace Signhost.APIClient.Rest +namespace Signhost.APIClient.Rest; + +/// +/// Centralized JSON serialization options for Signhost API. +/// +public static class SignhostJsonSerializerOptions { /// - /// Centralized JSON serialization options for Signhost API. + /// Gets the default JSON serializer options. /// - public static class SignhostJsonSerializerOptions + public static JsonSerializerOptions Default { get; } = new JsonSerializerOptions { - /// - /// Gets the default JSON serializer options. - /// - public static JsonSerializerOptions Default { get; } = new JsonSerializerOptions - { - PropertyNameCaseInsensitive = true, - DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull, - Converters = { - new JsonStringEnumConverter(), - }, - }; - } + PropertyNameCaseInsensitive = true, + DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull, + Converters = { + new JsonStringEnumConverter(), + }, + }; } diff --git a/src/SignhostAPIClient/Rest/StreamContentDigestOptionsExtensions.cs b/src/SignhostAPIClient/Rest/StreamContentDigestOptionsExtensions.cs index 3ae2389..eb75441 100644 --- a/src/SignhostAPIClient/Rest/StreamContentDigestOptionsExtensions.cs +++ b/src/SignhostAPIClient/Rest/StreamContentDigestOptionsExtensions.cs @@ -3,95 +3,94 @@ using System.Net.Http; using System.Security.Cryptography; -namespace Signhost.APIClient.Rest +namespace Signhost.APIClient.Rest; + +/// +/// digest extensions. +/// +public static class StreamContentDigestOptionsExtensions { /// - /// digest extensions. + /// Digest extension method on the . /// - public static class StreamContentDigestOptionsExtensions + /// + /// of the filestream. + /// No digest is calculated if the stream is not . + /// digest options to use. + /// . + public static StreamContent WithDigest( + this StreamContent content, + Stream fileStream, + FileDigestOptions options) { - /// - /// Digest extension method on the . - /// - /// - /// of the filestream. - /// No digest is calculated if the stream is not . - /// digest options to use. - /// . - public static StreamContent WithDigest( - this StreamContent content, - Stream fileStream, - FileDigestOptions options) - { - if (!options.UseFileDigesting || options.DigestHashAlgorithm == null) { - return content; - } - - SetHashValue(fileStream, options); - - string base64Digest = Convert.ToBase64String(options.DigestHashValue); - - content.Headers.Add("Digest", $"{options.DigestHashAlgorithm}={base64Digest}"); - + if (!options.UseFileDigesting || options.DigestHashAlgorithm == null) { return content; } - private static void SetHashValue( - Stream fileStream, - FileDigestOptions options) - { - if (options.DigestHashValue != null) { - return; - } + SetHashValue(fileStream, options); - if (!fileStream.CanSeek) { - return; - } + string base64Digest = Convert.ToBase64String(options.DigestHashValue); - long position = fileStream.Position; + content.Headers.Add("Digest", $"{options.DigestHashAlgorithm}={base64Digest}"); - using (var algo = HashAlgorithmCreate(options)) { - options.DigestHashValue = algo.ComputeHash(fileStream); - } + return content; + } - fileStream.Position = position; + private static void SetHashValue( + Stream fileStream, + FileDigestOptions options) + { + if (options.DigestHashValue != null) { + return; } - private static HashAlgorithm HashAlgorithmCreate( - FileDigestOptions options) - { - string algorithmName = options.DigestHashAlgorithm; - HashAlgorithm algorithm = null; + if (!fileStream.CanSeek) { + return; + } + + long position = fileStream.Position; + + using (var algo = HashAlgorithmCreate(options)) { + options.DigestHashValue = algo.ComputeHash(fileStream); + } + + fileStream.Position = position; + } + + private static HashAlgorithm HashAlgorithmCreate( + FileDigestOptions options) + { + string algorithmName = options.DigestHashAlgorithm; + HashAlgorithm algorithm = null; #if NET462 - algorithm = HashAlgorithm.Create(algorithmName); + algorithm = HashAlgorithm.Create(algorithmName); #else - algorithm = algorithmName switch { - "SHA1" or "SHA-1" => SHA1.Create(), - "SHA256" or "SHA-256" => SHA256.Create(), - "SHA384" or "SHA-384" => SHA384.Create(), - "SHA512" or "SHA-512" => SHA512.Create(), - - _ => null, - }; + algorithm = algorithmName switch { + "SHA1" or "SHA-1" => SHA1.Create(), + "SHA256" or "SHA-256" => SHA256.Create(), + "SHA384" or "SHA-384" => SHA384.Create(), + "SHA512" or "SHA-512" => SHA512.Create(), + + _ => null, + }; #endif - if (algorithm == null && options.DigestHashValue == null) { - algorithm = DefaultHashAlgorithm(); - options.DigestHashAlgorithm = algorithm.GetType().Name; - } - - if (algorithm == null) { - throw new InvalidOperationException($"No hash algorithm for '{algorithmName}'"); - } + if (algorithm == null && options.DigestHashValue == null) { + algorithm = DefaultHashAlgorithm(); + options.DigestHashAlgorithm = algorithm.GetType().Name; + } - return algorithm; + if (algorithm == null) { + throw new InvalidOperationException($"No hash algorithm for '{algorithmName}'"); } - private static HashAlgorithm DefaultHashAlgorithm() => + return algorithm; + } + + private static HashAlgorithm DefaultHashAlgorithm() => #if NET462 - HashAlgorithm.Create(); + HashAlgorithm.Create(); #else - SHA256.Create(); + SHA256.Create(); #endif - } } diff --git a/src/SignhostAPIClient/Rest/UriPathExtensions.cs b/src/SignhostAPIClient/Rest/UriPathExtensions.cs index 39fb97f..9f13c52 100644 --- a/src/SignhostAPIClient/Rest/UriPathExtensions.cs +++ b/src/SignhostAPIClient/Rest/UriPathExtensions.cs @@ -1,25 +1,24 @@ using System; using System.Linq; -namespace Signhost.APIClient.Rest +namespace Signhost.APIClient.Rest; + +internal static class UriPathExtensions { - internal static class UriPathExtensions + internal static UriBuilder AppendPathSegment(this string url) { - internal static UriBuilder AppendPathSegment(this string url) - { - var builder = new UriBuilder(url); + var builder = new UriBuilder(url); - return builder; - } + return builder; + } - internal static Uri JoinPaths( - this string url, - params string[] segments) - { - var segmentList = segments.ToList(); - segmentList.Insert(0, url); - var escaped = segmentList.Select(seg => Uri.EscapeDataString(seg)); - return new Uri(string.Join("/", escaped), UriKind.Relative); - } + internal static Uri JoinPaths( + this string url, + params string[] segments) + { + var segmentList = segments.ToList(); + segmentList.Insert(0, url); + var escaped = segmentList.Select(seg => Uri.EscapeDataString(seg)); + return new Uri(string.Join("/", escaped), UriKind.Relative); } } From 535a658fda0a1978077651ca65367d7b7de75b4a Mon Sep 17 00:00:00 2001 From: Anthony Timmers Date: Fri, 26 Sep 2025 19:28:30 +0200 Subject: [PATCH 12/28] Remove support weak digest hash algos Also refactor file digest options to use enum for hash algorithms and update related tests --- .../SignhostApiClientTests.cs | 6 +- .../Rest/DigestHashAlgorithmNames.cs | 30 ++++++++++ .../Rest/FileDigestOptions.cs | 4 +- .../StreamContentDigestOptionsExtensions.cs | 55 ++++++++----------- 4 files changed, 59 insertions(+), 36 deletions(-) create mode 100644 src/SignhostAPIClient/Rest/DigestHashAlgorithmNames.cs diff --git a/src/SignhostAPIClient.Tests/SignhostApiClientTests.cs b/src/SignhostAPIClient.Tests/SignhostApiClientTests.cs index aee6ccf..80e0fbb 100644 --- a/src/SignhostAPIClient.Tests/SignhostApiClientTests.cs +++ b/src/SignhostAPIClient.Tests/SignhostApiClientTests.cs @@ -462,7 +462,7 @@ await signhostApiClient.AddOrReplaceFileToTransactionAsync( new FileUploadOptions{ DigestOptions = new FileDigestOptions { - DigestHashAlgorithm = "SHA-512" + DigestHashAlgorithm = DigestHashAlgorithm.SHA512 } }); } @@ -476,7 +476,7 @@ public async Task when_AddOrReplaceFileToTransaction_with_digest_value_is_used_a var mockHttp = new MockHttpMessageHandler(); mockHttp .Expect(HttpMethod.Put, "http://localhost/api/transaction/transaction Id/file/file Id") - .WithHeaders("Digest", "SHA-1=AAEC") + .WithHeaders("Digest", "SHA-256=AAEC") .Respond(HttpStatusCode.OK); using (var httpClient = mockHttp.ToHttpClient()) { @@ -490,7 +490,7 @@ await signhostApiClient.AddOrReplaceFileToTransactionAsync( { DigestOptions = new FileDigestOptions { - DigestHashAlgorithm = "SHA-1", + DigestHashAlgorithm = DigestHashAlgorithm.SHA256, DigestHashValue = new byte[] { 0x00, 0x01, 0x02 } } }); diff --git a/src/SignhostAPIClient/Rest/DigestHashAlgorithmNames.cs b/src/SignhostAPIClient/Rest/DigestHashAlgorithmNames.cs new file mode 100644 index 0000000..c41d87e --- /dev/null +++ b/src/SignhostAPIClient/Rest/DigestHashAlgorithmNames.cs @@ -0,0 +1,30 @@ +namespace Signhost.APIClient.Rest; + +/// +/// Provides constants for hash algorithm names used in HTTP Digest headers, +/// following the naming conventions specified in RFC 3230 (Instance Digests in HTTP) +/// and RFC 5843 (Additional Hash Algorithms for HTTP Instance Digests). +/// +/// These names are in accordance with the Digest header in HTTP requests, +/// where the header specifies the algorithm used to create the digest of the resource. +/// +/// For more information: +/// https://evidos.github.io/endpoints/##/paths//api/transaction/%7BtransactionId%7D/file/%7BfileId%7D/put +/// +public enum DigestHashAlgorithm +{ + /// + /// Use no digest. + /// + None = 0, + + /// + /// SHA-256 hash algorithm, as specified in RFC 5843. + /// + SHA256, + + /// + /// SHA-512 hash algorithm, as specified in RFC 5843. + /// + SHA512, +} diff --git a/src/SignhostAPIClient/Rest/FileDigestOptions.cs b/src/SignhostAPIClient/Rest/FileDigestOptions.cs index bc0218b..c5580a2 100644 --- a/src/SignhostAPIClient/Rest/FileDigestOptions.cs +++ b/src/SignhostAPIClient/Rest/FileDigestOptions.cs @@ -1,7 +1,7 @@ namespace Signhost.APIClient.Rest; /// -/// File digest options for file uploads +/// File digest options for file uploads. /// public class FileDigestOptions { @@ -16,7 +16,7 @@ public class FileDigestOptions /// the hash value or the digest algorithm that is used /// to set the . /// - public string DigestHashAlgorithm { get; set; } = "SHA-256"; + public DigestHashAlgorithm DigestHashAlgorithm { get; set; } = DigestHashAlgorithm.SHA256; /// /// Gets or sets the hash digest value, you can set this yourself diff --git a/src/SignhostAPIClient/Rest/StreamContentDigestOptionsExtensions.cs b/src/SignhostAPIClient/Rest/StreamContentDigestOptionsExtensions.cs index eb75441..32e27d4 100644 --- a/src/SignhostAPIClient/Rest/StreamContentDigestOptionsExtensions.cs +++ b/src/SignhostAPIClient/Rest/StreamContentDigestOptionsExtensions.cs @@ -23,7 +23,10 @@ public static StreamContent WithDigest( Stream fileStream, FileDigestOptions options) { - if (!options.UseFileDigesting || options.DigestHashAlgorithm == null) { + if ( + !options.UseFileDigesting || + options.DigestHashAlgorithm == DigestHashAlgorithm.None + ) { return content; } @@ -31,11 +34,22 @@ public static StreamContent WithDigest( string base64Digest = Convert.ToBase64String(options.DigestHashValue); - content.Headers.Add("Digest", $"{options.DigestHashAlgorithm}={base64Digest}"); + content.Headers.Add("Digest", $"{GetDigestHashAlgorithmName(options)}={base64Digest}"); return content; } + private static string GetDigestHashAlgorithmName(FileDigestOptions options) + { + return options.DigestHashAlgorithm switch { + DigestHashAlgorithm.SHA256 => "SHA-256", + DigestHashAlgorithm.SHA512 => "SHA-512", + + _ => throw new InvalidOperationException( + $"No hash algorithm name for '{options.DigestHashAlgorithm}'"), + }; + } + private static void SetHashValue( Stream fileStream, FileDigestOptions options) @@ -60,37 +74,16 @@ private static void SetHashValue( private static HashAlgorithm HashAlgorithmCreate( FileDigestOptions options) { - string algorithmName = options.DigestHashAlgorithm; - HashAlgorithm algorithm = null; - + return options.DigestHashAlgorithm switch { #if NET462 - algorithm = HashAlgorithm.Create(algorithmName); + DigestHashAlgorithm.SHA256 => HashAlgorithm.Create("SHA256"), + DigestHashAlgorithm.SHA512 => HashAlgorithm.Create("SHA512"), #else - algorithm = algorithmName switch { - "SHA1" or "SHA-1" => SHA1.Create(), - "SHA256" or "SHA-256" => SHA256.Create(), - "SHA384" or "SHA-384" => SHA384.Create(), - "SHA512" or "SHA-512" => SHA512.Create(), - - _ => null, - }; + DigestHashAlgorithm.SHA256 => SHA256.Create(), + DigestHashAlgorithm.SHA512 => SHA512.Create(), #endif - if (algorithm == null && options.DigestHashValue == null) { - algorithm = DefaultHashAlgorithm(); - options.DigestHashAlgorithm = algorithm.GetType().Name; - } - - if (algorithm == null) { - throw new InvalidOperationException($"No hash algorithm for '{algorithmName}'"); - } - - return algorithm; + _ => throw new InvalidOperationException( + $"No hash algorithm for '{options.DigestHashAlgorithm}'"), + }; } - - private static HashAlgorithm DefaultHashAlgorithm() => -#if NET462 - HashAlgorithm.Create(); -#else - SHA256.Create(); -#endif } From a1d7da9e5cc67036d2af272e12e8345be3c7abf8 Mon Sep 17 00:00:00 2001 From: Anthony Timmers Date: Thu, 27 Nov 2025 21:14:08 +0100 Subject: [PATCH 13/28] Make remove documentation warnings --- src/signhost.ruleset | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/signhost.ruleset b/src/signhost.ruleset index 51c9a98..ec914e6 100644 --- a/src/signhost.ruleset +++ b/src/signhost.ruleset @@ -9,9 +9,15 @@ + + + + + + From 30805d26fcc1f2dca417912a0d10c1ce9dcbf4c2 Mon Sep 17 00:00:00 2001 From: Anthony Timmers Date: Thu, 27 Nov 2025 21:14:42 +0100 Subject: [PATCH 14/28] Refactor to nullable enable --- .../TransactionTests.cs | 1 - src/SignhostAPIClient.Tests/PostbackTests.cs | 2 - src/SignhostAPIClient/NotNullWhenAttribute.cs | 18 +++++ src/SignhostAPIClient/Rest/ApiResponse.cs | 6 +- .../Rest/DataObjects/Activity.cs | 6 +- .../DataObjects/DeleteTransactionOptions.cs | 2 +- .../Rest/DataObjects/FileEntry.cs | 4 +- .../Rest/DataObjects/FileLink.cs | 6 +- .../DataObjects/{ => FileMetaData}/Field.cs | 6 +- .../{ => FileMetaData}/FileMeta.cs | 10 +-- .../{ => FileMetaData}/FileSignerMeta.cs | 2 +- .../{ => FileMetaData}/Location.cs | 2 +- .../Rest/DataObjects/IdealVerification.cs | 11 ---- .../Rest/DataObjects/IdinVerification.cs | 18 ----- .../Rest/DataObjects/PostbackTransaction.cs | 2 +- .../Rest/DataObjects/Receiver.cs | 30 +++------ .../Rest/DataObjects/Signer.cs | 59 ++++++----------- .../Rest/DataObjects/Transaction.cs | 16 ++--- .../ConsentVerification.cs | 0 .../{ => Verifications}/CscVerification.cs | 10 +-- .../{ => Verifications}/DigidVerification.cs | 4 +- .../EherkenningVerification.cs | 4 +- .../EidasLoginVerification.cs | 8 +-- .../IPAddressVerification.cs | 2 +- .../{ => Verifications}/IVerification.cs | 0 .../Verifications/IdealVerification.cs | 11 ++++ .../Verifications/IdinVerification.cs | 17 +++++ .../ItsmeIdentificationVerification.cs | 6 +- .../DataObjects/{ => Verifications}/Level.cs | 2 +- .../{ => Verifications}/OidcVerification.cs | 2 +- .../{ => Verifications}/OnfidoVerification.cs | 0 .../PhoneNumberVerification.cs | 2 +- .../ScribbleVerification.cs | 2 +- .../SurfnetVerification.cs | 4 +- .../Rest/ErrorHandling/GoneException.cs | 4 +- ...pResponseMessageErrorHandlingExtensions.cs | 53 +++++---------- .../InternalServerErrorException.cs | 10 +-- .../SignhostRestApiClientException.cs | 2 +- .../Rest/FileDigestOptions.cs | 2 +- .../Rest/HttpContentJsonExtensions.cs | 6 +- .../Rest/ISignHostApiClient.cs | 2 +- .../Rest/ISignhostApiClientSettings.cs | 4 +- .../Rest/ISignhostApiReceiver.cs | 3 +- .../Rest/SignHostApiClient.cs | 65 ++++++++++--------- .../Rest/SignHostApiClientSettings.cs | 4 +- .../Rest/SignhostApiReceiver.cs | 22 ++++--- .../StreamContentDigestOptionsExtensions.cs | 6 +- .../SignhostAPIClient.csproj | 1 + 48 files changed, 225 insertions(+), 234 deletions(-) create mode 100644 src/SignhostAPIClient/NotNullWhenAttribute.cs rename src/SignhostAPIClient/Rest/DataObjects/{ => FileMetaData}/Field.cs (55%) rename src/SignhostAPIClient/Rest/DataObjects/{ => FileMetaData}/FileMeta.cs (76%) rename src/SignhostAPIClient/Rest/DataObjects/{ => FileMetaData}/FileSignerMeta.cs (61%) rename src/SignhostAPIClient/Rest/DataObjects/{ => FileMetaData}/Location.cs (90%) delete mode 100644 src/SignhostAPIClient/Rest/DataObjects/IdealVerification.cs delete mode 100644 src/SignhostAPIClient/Rest/DataObjects/IdinVerification.cs rename src/SignhostAPIClient/Rest/DataObjects/{ => Verifications}/ConsentVerification.cs (100%) rename src/SignhostAPIClient/Rest/DataObjects/{ => Verifications}/CscVerification.cs (72%) rename src/SignhostAPIClient/Rest/DataObjects/{ => Verifications}/DigidVerification.cs (62%) rename src/SignhostAPIClient/Rest/DataObjects/{ => Verifications}/EherkenningVerification.cs (74%) rename src/SignhostAPIClient/Rest/DataObjects/{ => Verifications}/EidasLoginVerification.cs (82%) rename src/SignhostAPIClient/Rest/DataObjects/{ => Verifications}/IPAddressVerification.cs (87%) rename src/SignhostAPIClient/Rest/DataObjects/{ => Verifications}/IVerification.cs (100%) create mode 100644 src/SignhostAPIClient/Rest/DataObjects/Verifications/IdealVerification.cs create mode 100644 src/SignhostAPIClient/Rest/DataObjects/Verifications/IdinVerification.cs rename src/SignhostAPIClient/Rest/DataObjects/{ => Verifications}/ItsmeIdentificationVerification.cs (63%) rename src/SignhostAPIClient/Rest/DataObjects/{ => Verifications}/Level.cs (86%) rename src/SignhostAPIClient/Rest/DataObjects/{ => Verifications}/OidcVerification.cs (84%) rename src/SignhostAPIClient/Rest/DataObjects/{ => Verifications}/OnfidoVerification.cs (100%) rename src/SignhostAPIClient/Rest/DataObjects/{ => Verifications}/PhoneNumberVerification.cs (76%) rename src/SignhostAPIClient/Rest/DataObjects/{ => Verifications}/ScribbleVerification.cs (82%) rename src/SignhostAPIClient/Rest/DataObjects/{ => Verifications}/SurfnetVerification.cs (59%) diff --git a/src/SignhostAPIClient.IntegrationTests/TransactionTests.cs b/src/SignhostAPIClient.IntegrationTests/TransactionTests.cs index 0e3f630..3bcfb80 100644 --- a/src/SignhostAPIClient.IntegrationTests/TransactionTests.cs +++ b/src/SignhostAPIClient.IntegrationTests/TransactionTests.cs @@ -121,7 +121,6 @@ public async Task Given_complex_transaction_When_created_and_started_Then_all_pr createdTransaction.SendEmailNotifications.Should().BeFalse(); createdTransaction.SignRequestMode.Should().Be(2); createdTransaction.Language.Should().Be("en-US"); - createdTransaction.CreatedDateTime.Should().HaveValue(); createdTransaction.CreatedDateTime.Should().BeCloseTo(DateTimeOffset.UtcNow, TimeSpan.FromMinutes(1)); createdTransaction.CanceledDateTime.Should().BeNull(); createdTransaction.CancellationReason.Should().BeNull(); diff --git a/src/SignhostAPIClient.Tests/PostbackTests.cs b/src/SignhostAPIClient.Tests/PostbackTests.cs index 8d3314c..c469638 100644 --- a/src/SignhostAPIClient.Tests/PostbackTests.cs +++ b/src/SignhostAPIClient.Tests/PostbackTests.cs @@ -41,8 +41,6 @@ public void PostbackTransaction_should_get_serialized_correctly() signer.SignRequestMessage .Should().Be("Hello, could you please sign this document? Best regards, John Doe"); signer.DaysToRemind .Should().Be(15); signer.Language .Should().Be("en-US"); - signer.ScribbleName .Should().Be("John Doe"); - signer.ScribbleNameFixed .Should().BeFalse(); signer.Reference .Should().Be("Client #123"); signer.ReturnUrl .Should().Be("https://signhost.com"); signer.RejectReason .Should().BeNull(); diff --git a/src/SignhostAPIClient/NotNullWhenAttribute.cs b/src/SignhostAPIClient/NotNullWhenAttribute.cs new file mode 100644 index 0000000..c222fdb --- /dev/null +++ b/src/SignhostAPIClient/NotNullWhenAttribute.cs @@ -0,0 +1,18 @@ +#if NETFRAMEWORK || NETSTANDARD2_0 +using System; + +namespace Signhost.APIClient.Rest; + +public sealed class NotNullWhenAttribute + : Attribute +{ + /// Initializes the attribute with the specified return value condition. + /// + /// The return value condition. If the method returns this value, the associated parameter will not be null. + /// + public NotNullWhenAttribute(bool returnValue) => ReturnValue = returnValue; + + /// Gets the return value condition. + public bool ReturnValue { get; } +} +#endif diff --git a/src/SignhostAPIClient/Rest/ApiResponse.cs b/src/SignhostAPIClient/Rest/ApiResponse.cs index bdd84e3..27a6234 100644 --- a/src/SignhostAPIClient/Rest/ApiResponse.cs +++ b/src/SignhostAPIClient/Rest/ApiResponse.cs @@ -7,13 +7,13 @@ public class ApiResponse { private readonly HttpResponseMessage httpResponse; - public ApiResponse(HttpResponseMessage httpResponse, TValue value) + public ApiResponse(HttpResponseMessage httpResponse, TValue? value) { this.httpResponse = httpResponse; this.Value = value; } - public TValue Value { get; private set; } + public TValue? Value { get; private set; } public HttpStatusCode HttpStatusCode => httpResponse.StatusCode; @@ -21,7 +21,7 @@ public void EnsureAvailableStatusCode() { if (HttpStatusCode == HttpStatusCode.Gone) { throw new ErrorHandling.GoneException( - httpResponse.ReasonPhrase, + httpResponse.ReasonPhrase ?? "No reason phrase provided", Value) { // TO-DO: Make async in v5 diff --git a/src/SignhostAPIClient/Rest/DataObjects/Activity.cs b/src/SignhostAPIClient/Rest/DataObjects/Activity.cs index 37a9811..c550a2b 100644 --- a/src/SignhostAPIClient/Rest/DataObjects/Activity.cs +++ b/src/SignhostAPIClient/Rest/DataObjects/Activity.cs @@ -5,14 +5,14 @@ namespace Signhost.APIClient.Rest.DataObjects; public class Activity { - public string Id { get; set; } + public string Id { get; set; } = default!; public ActivityType Code { get; set; } [JsonPropertyName("Activity")] - public string ActivityValue { get; set; } + public string? ActivityValue { get; set; } - public string Info { get; set; } + public string? Info { get; set; } public DateTimeOffset CreatedDateTime { get; set; } } diff --git a/src/SignhostAPIClient/Rest/DataObjects/DeleteTransactionOptions.cs b/src/SignhostAPIClient/Rest/DataObjects/DeleteTransactionOptions.cs index ae3ecc5..5defee9 100644 --- a/src/SignhostAPIClient/Rest/DataObjects/DeleteTransactionOptions.cs +++ b/src/SignhostAPIClient/Rest/DataObjects/DeleteTransactionOptions.cs @@ -11,5 +11,5 @@ public class DeleteTransactionOptions /// /// Gets or sets the reason of cancellation. /// - public string Reason { get; set; } + public string? Reason { get; set; } } diff --git a/src/SignhostAPIClient/Rest/DataObjects/FileEntry.cs b/src/SignhostAPIClient/Rest/DataObjects/FileEntry.cs index f1565e5..208c070 100644 --- a/src/SignhostAPIClient/Rest/DataObjects/FileEntry.cs +++ b/src/SignhostAPIClient/Rest/DataObjects/FileEntry.cs @@ -4,7 +4,7 @@ namespace Signhost.APIClient.Rest.DataObjects; public class FileEntry { - public IList Links { get; set; } + public IList Links { get; set; } = default!; - public string DisplayName { get; set; } + public string? DisplayName { get; set; } } diff --git a/src/SignhostAPIClient/Rest/DataObjects/FileLink.cs b/src/SignhostAPIClient/Rest/DataObjects/FileLink.cs index f503d5a..6fe1ad1 100644 --- a/src/SignhostAPIClient/Rest/DataObjects/FileLink.cs +++ b/src/SignhostAPIClient/Rest/DataObjects/FileLink.cs @@ -2,9 +2,9 @@ public class FileLink { - public string Rel { get; set; } + public string Rel { get; set; } = default!; - public string Type { get; set; } + public string Type { get; set; } = default!; - public string Link { get; set; } + public string Link { get; set; } = default!; } diff --git a/src/SignhostAPIClient/Rest/DataObjects/Field.cs b/src/SignhostAPIClient/Rest/DataObjects/FileMetaData/Field.cs similarity index 55% rename from src/SignhostAPIClient/Rest/DataObjects/Field.cs rename to src/SignhostAPIClient/Rest/DataObjects/FileMetaData/Field.cs index 58cd3f9..62f40b0 100644 --- a/src/SignhostAPIClient/Rest/DataObjects/Field.cs +++ b/src/SignhostAPIClient/Rest/DataObjects/FileMetaData/Field.cs @@ -3,10 +3,10 @@ public class Field { // TO-DO: Make enum in v5. - public string Type { get; set; } + public string Type { get; set; } = default!; // TO-DO: Can be boolean, number, string, should be fixed in v5. - public string Value { get; set; } + public string? Value { get; set; } - public Location Location { get; set; } + public Location Location { get; set; } = default!; } diff --git a/src/SignhostAPIClient/Rest/DataObjects/FileMeta.cs b/src/SignhostAPIClient/Rest/DataObjects/FileMetaData/FileMeta.cs similarity index 76% rename from src/SignhostAPIClient/Rest/DataObjects/FileMeta.cs rename to src/SignhostAPIClient/Rest/DataObjects/FileMetaData/FileMeta.cs index b20b72e..93ab26c 100644 --- a/src/SignhostAPIClient/Rest/DataObjects/FileMeta.cs +++ b/src/SignhostAPIClient/Rest/DataObjects/FileMetaData/FileMeta.cs @@ -7,13 +7,15 @@ public class FileMeta { public int? DisplayOrder { get; set; } - public string DisplayName { get; set; } + public string? DisplayName { get; set; } - public string Description { get; set; } + public string? Description { get; set; } - public IDictionary Signers { get; set; } + public IDictionary Signers { get; set; } = + new Dictionary(); - public IDictionary> FormSets { get; set; } + public IDictionary> FormSets { get; set; } = + new Dictionary>(); /// /// Gets or sets whether to use the scribble signature as a paraph diff --git a/src/SignhostAPIClient/Rest/DataObjects/FileSignerMeta.cs b/src/SignhostAPIClient/Rest/DataObjects/FileMetaData/FileSignerMeta.cs similarity index 61% rename from src/SignhostAPIClient/Rest/DataObjects/FileSignerMeta.cs rename to src/SignhostAPIClient/Rest/DataObjects/FileMetaData/FileSignerMeta.cs index 7adfc40..6ef1c7f 100644 --- a/src/SignhostAPIClient/Rest/DataObjects/FileSignerMeta.cs +++ b/src/SignhostAPIClient/Rest/DataObjects/FileMetaData/FileSignerMeta.cs @@ -2,5 +2,5 @@ public class FileSignerMeta { - public string[] FormSets { get; set; } + public string[] FormSets { get; set; } = default!; } diff --git a/src/SignhostAPIClient/Rest/DataObjects/Location.cs b/src/SignhostAPIClient/Rest/DataObjects/FileMetaData/Location.cs similarity index 90% rename from src/SignhostAPIClient/Rest/DataObjects/Location.cs rename to src/SignhostAPIClient/Rest/DataObjects/FileMetaData/Location.cs index 1799ad6..4492636 100644 --- a/src/SignhostAPIClient/Rest/DataObjects/Location.cs +++ b/src/SignhostAPIClient/Rest/DataObjects/FileMetaData/Location.cs @@ -2,7 +2,7 @@ public class Location { - public string Search { get; set; } + public string? Search { get; set; } public int? Occurence { get; set; } diff --git a/src/SignhostAPIClient/Rest/DataObjects/IdealVerification.cs b/src/SignhostAPIClient/Rest/DataObjects/IdealVerification.cs deleted file mode 100644 index 55f1bc9..0000000 --- a/src/SignhostAPIClient/Rest/DataObjects/IdealVerification.cs +++ /dev/null @@ -1,11 +0,0 @@ -namespace Signhost.APIClient.Rest.DataObjects; - -public class IdealVerification - : IVerification -{ - public string Iban { get; set; } - - public string AccountHolderName { get; set; } - - public string AccountHolderCity { get; set; } -} diff --git a/src/SignhostAPIClient/Rest/DataObjects/IdinVerification.cs b/src/SignhostAPIClient/Rest/DataObjects/IdinVerification.cs deleted file mode 100644 index 150f398..0000000 --- a/src/SignhostAPIClient/Rest/DataObjects/IdinVerification.cs +++ /dev/null @@ -1,18 +0,0 @@ -using System; -using System.Collections.Generic; - -namespace Signhost.APIClient.Rest.DataObjects; - -public class IdinVerification - : IVerification -{ - public string AccountHolderName { get; set; } - - public string AccountHolderAddress1 { get; set; } - - public string AccountHolderAddress2 { get; set; } - - public DateTime AccountHolderDateOfBirth { get; set; } - - public IDictionary Attributes { get; set; } -} diff --git a/src/SignhostAPIClient/Rest/DataObjects/PostbackTransaction.cs b/src/SignhostAPIClient/Rest/DataObjects/PostbackTransaction.cs index cffc6eb..5ae74e5 100644 --- a/src/SignhostAPIClient/Rest/DataObjects/PostbackTransaction.cs +++ b/src/SignhostAPIClient/Rest/DataObjects/PostbackTransaction.cs @@ -6,5 +6,5 @@ public class PostbackTransaction : Transaction { [JsonPropertyName("Checksum")] - public string Checksum { get; set; } + public string Checksum { get; set; } = default!; } diff --git a/src/SignhostAPIClient/Rest/DataObjects/Receiver.cs b/src/SignhostAPIClient/Rest/DataObjects/Receiver.cs index b044b37..32937d6 100644 --- a/src/SignhostAPIClient/Rest/DataObjects/Receiver.cs +++ b/src/SignhostAPIClient/Rest/DataObjects/Receiver.cs @@ -1,41 +1,29 @@ using System; using System.Collections.Generic; -using System.Text.Json.Serialization; namespace Signhost.APIClient.Rest.DataObjects; public class Receiver { - public Receiver() - { - } + public string? Id { get; set; } - [JsonConstructor] - private Receiver(IReadOnlyList activities) - { - Activities = activities; - } + public string? Name { get; set; } - public string Id { get; set; } + public string? Email { get; set; } - public string Name { get; set; } + public string? Language { get; set; } - public string Email { get; set; } + public string? Subject { get; set; } - public string Language { get; set; } + public string? Message { get; set; } - public string Subject { get; set; } - - public string Message { get; set; } - - public string Reference { get; set; } + public string? Reference { get; set; } public DateTimeOffset? CreatedDateTime { get; set; } public DateTimeOffset? ModifiedDateTime { get; set; } - public IReadOnlyList Activities { get; set; } = - new List().AsReadOnly(); + public IList? Activities { get; set; } - public dynamic Context { get; set; } + public dynamic? Context { get; set; } } diff --git a/src/SignhostAPIClient/Rest/DataObjects/Signer.cs b/src/SignhostAPIClient/Rest/DataObjects/Signer.cs index 944c257..9b32182 100644 --- a/src/SignhostAPIClient/Rest/DataObjects/Signer.cs +++ b/src/SignhostAPIClient/Rest/DataObjects/Signer.cs @@ -1,38 +1,25 @@ using System; using System.Collections.Generic; -using System.Text.Json.Serialization; namespace Signhost.APIClient.Rest.DataObjects; public class Signer { - public Signer() - { - } - - [JsonConstructor] - private Signer(IReadOnlyList activities) - { - Activities = activities; - } - - public string Id { get; set; } + public string? Id { get; set; } public DateTimeOffset? Expires { get; set; } - public string Email { get; set; } + public string Email { get; set; } = default!; - public string IntroText { get; set; } + public string? IntroText { get; set; } - public string SignRequestSubject { get; set; } + public string? SignRequestSubject { get; set; } - public string SignRequestMessage { get; set; } + public string? SignRequestMessage { get; set; } - public IList Authentications { get; set; } - = new List(); + public IList Authentications { get; set; } = default!; - public IList Verifications { get; set; } - = new List(); + public IList Verifications { get; set; } = default!; public bool SendSignRequest { get; set; } @@ -40,29 +27,25 @@ private Signer(IReadOnlyList activities) public int? DaysToRemind { get; set; } - public string Language { get; set; } - - public string ScribbleName { get; set; } - - public bool ScribbleNameFixed { get; set; } + public string? Language { get; set; } - public string Reference { get; set; } + public string? Reference { get; set; } - public string ReturnUrl { get; set; } + public string? ReturnUrl { get; set; } - public string RejectReason { get; set; } + public string? RejectReason { get; set; } - public string SignUrl { get; set; } + public string SignUrl { get; set; } = default!; public bool AllowDelegation { get; set; } - public string DelegateSignUrl { get; set; } + public string? DelegateSignUrl { get; set; } - public string DelegateReason { get; set; } + public string? DelegateReason { get; set; } - public string DelegateSignerEmail { get; set; } + public string? DelegateSignerEmail { get; set; } - public string DelegateSignerName { get; set; } + public string? DelegateSignerName { get; set; } public DateTimeOffset? SignedDateTime { get; set; } @@ -74,12 +57,12 @@ private Signer(IReadOnlyList activities) public DateTimeOffset? ModifiedDateTime { get; set; } - public string ShowUrl { get; set; } + public string ShowUrl { get; set; } = default!; - public string ReceiptUrl { get; set; } + public string ReceiptUrl { get; set; } = default!; - public IReadOnlyList Activities { get; private set; } = - new List().AsReadOnly(); + public IList Activities { get; set; } = + new List(); - public dynamic Context { get; set; } + public dynamic? Context { get; set; } } diff --git a/src/SignhostAPIClient/Rest/DataObjects/Transaction.cs b/src/SignhostAPIClient/Rest/DataObjects/Transaction.cs index 7ac6220..e395370 100644 --- a/src/SignhostAPIClient/Rest/DataObjects/Transaction.cs +++ b/src/SignhostAPIClient/Rest/DataObjects/Transaction.cs @@ -5,12 +5,12 @@ namespace Signhost.APIClient.Rest.DataObjects; public class Transaction { - public string Id { get; set; } + public string Id { get; set; } = default!; /// /// Gets the when the was created. /// - public DateTimeOffset? CreatedDateTime { get; set; } + public DateTimeOffset CreatedDateTime { get; set; } /// /// Gets the when the was cancelled. @@ -21,9 +21,9 @@ public class Transaction /// /// Gets the cancellation reason when the was cancelled. /// - public string CancellationReason { get; set; } + public string? CancellationReason { get; set; } - public IReadOnlyDictionary Files { get; set; } = + public IDictionary Files { get; set; } = new Dictionary(); public TransactionStatus Status { get; set; } @@ -36,17 +36,17 @@ public class Transaction public IList Receivers { get; set; } = new List(); - public string Reference { get; set; } + public string? Reference { get; set; } - public string PostbackUrl { get; set; } + public string? PostbackUrl { get; set; } public int SignRequestMode { get; set; } public int DaysToExpire { get; set; } - public string Language { get; set; } + public string? Language { get; set; } public bool SendEmailNotifications { get; set; } - public dynamic Context { get; set; } + public dynamic? Context { get; set; } } diff --git a/src/SignhostAPIClient/Rest/DataObjects/ConsentVerification.cs b/src/SignhostAPIClient/Rest/DataObjects/Verifications/ConsentVerification.cs similarity index 100% rename from src/SignhostAPIClient/Rest/DataObjects/ConsentVerification.cs rename to src/SignhostAPIClient/Rest/DataObjects/Verifications/ConsentVerification.cs diff --git a/src/SignhostAPIClient/Rest/DataObjects/CscVerification.cs b/src/SignhostAPIClient/Rest/DataObjects/Verifications/CscVerification.cs similarity index 72% rename from src/SignhostAPIClient/Rest/DataObjects/CscVerification.cs rename to src/SignhostAPIClient/Rest/DataObjects/Verifications/CscVerification.cs index 3befa8d..6a17c85 100644 --- a/src/SignhostAPIClient/Rest/DataObjects/CscVerification.cs +++ b/src/SignhostAPIClient/Rest/DataObjects/Verifications/CscVerification.cs @@ -11,25 +11,25 @@ public class CscVerification /// /// Gets or sets the provider identifier. /// - public string Provider { get; set; } + public string? Provider { get; set; } /// /// Gets or sets the certificate issuer. /// - public string Issuer { get; set; } + public string? Issuer { get; set; } /// /// Gets or sets the certificate subject. /// - public string Subject { get; set; } + public string? Subject { get; set; } /// /// Gets or sets the certificate thumbprint. /// - public string Thumbprint { get; set; } + public string? Thumbprint { get; set; } /// /// Gets or sets additional user data. /// - public Dictionary AdditionalUserData { get; set; } + public Dictionary? AdditionalUserData { get; set; } } diff --git a/src/SignhostAPIClient/Rest/DataObjects/DigidVerification.cs b/src/SignhostAPIClient/Rest/DataObjects/Verifications/DigidVerification.cs similarity index 62% rename from src/SignhostAPIClient/Rest/DataObjects/DigidVerification.cs rename to src/SignhostAPIClient/Rest/DataObjects/Verifications/DigidVerification.cs index 6ec39b9..d0bb405 100644 --- a/src/SignhostAPIClient/Rest/DataObjects/DigidVerification.cs +++ b/src/SignhostAPIClient/Rest/DataObjects/Verifications/DigidVerification.cs @@ -3,7 +3,9 @@ public class DigidVerification : IVerification { - public string Bsn { get; set; } + public string? Bsn { get; set; } + + public string? Betrouwbaarheidsniveau { get; set; } public bool? SecureDownload { get; set; } } diff --git a/src/SignhostAPIClient/Rest/DataObjects/EherkenningVerification.cs b/src/SignhostAPIClient/Rest/DataObjects/Verifications/EherkenningVerification.cs similarity index 74% rename from src/SignhostAPIClient/Rest/DataObjects/EherkenningVerification.cs rename to src/SignhostAPIClient/Rest/DataObjects/Verifications/EherkenningVerification.cs index 838144b..b579921 100644 --- a/src/SignhostAPIClient/Rest/DataObjects/EherkenningVerification.cs +++ b/src/SignhostAPIClient/Rest/DataObjects/Verifications/EherkenningVerification.cs @@ -6,10 +6,10 @@ public class EherkenningVerification /// /// Gets or sets the Uid. /// - public string Uid { get; set; } + public string? Uid { get; set; } /// /// Gets or sets the entity concern ID / KVK number. /// - public string EntityConcernIdKvkNr { get; set; } + public string? EntityConcernIdKvkNr { get; set; } } diff --git a/src/SignhostAPIClient/Rest/DataObjects/EidasLoginVerification.cs b/src/SignhostAPIClient/Rest/DataObjects/Verifications/EidasLoginVerification.cs similarity index 82% rename from src/SignhostAPIClient/Rest/DataObjects/EidasLoginVerification.cs rename to src/SignhostAPIClient/Rest/DataObjects/Verifications/EidasLoginVerification.cs index 3d54b0e..13dd4d5 100644 --- a/src/SignhostAPIClient/Rest/DataObjects/EidasLoginVerification.cs +++ b/src/SignhostAPIClient/Rest/DataObjects/Verifications/EidasLoginVerification.cs @@ -14,7 +14,7 @@ public class EidasLoginVerification /// /// Gets or sets the uid. /// - public string Uid { get; set; } + public string? Uid { get; set; } /// /// Gets or sets the level. @@ -25,12 +25,12 @@ public class EidasLoginVerification /// /// Gets or sets the first name. /// - public string FirstName { get; set; } + public string? FirstName { get; set; } /// /// Gets or sets the last name. /// - public string LastName { get; set; } + public string? LastName { get; set; } /// /// Gets or sets the date of birth. @@ -40,5 +40,5 @@ public class EidasLoginVerification /// /// Gets or sets the eIDAS attributes. /// - public IDictionary Attributes { get; set; } + public IDictionary? Attributes { get; set; } } diff --git a/src/SignhostAPIClient/Rest/DataObjects/IPAddressVerification.cs b/src/SignhostAPIClient/Rest/DataObjects/Verifications/IPAddressVerification.cs similarity index 87% rename from src/SignhostAPIClient/Rest/DataObjects/IPAddressVerification.cs rename to src/SignhostAPIClient/Rest/DataObjects/Verifications/IPAddressVerification.cs index b1fb67f..f3141b4 100644 --- a/src/SignhostAPIClient/Rest/DataObjects/IPAddressVerification.cs +++ b/src/SignhostAPIClient/Rest/DataObjects/Verifications/IPAddressVerification.cs @@ -9,5 +9,5 @@ public class IPAddressVerification /// /// Gets or sets the IP Address used by the signer while signing the documents. /// - public string IPAddress { get; set; } + public string? IPAddress { get; set; } } diff --git a/src/SignhostAPIClient/Rest/DataObjects/IVerification.cs b/src/SignhostAPIClient/Rest/DataObjects/Verifications/IVerification.cs similarity index 100% rename from src/SignhostAPIClient/Rest/DataObjects/IVerification.cs rename to src/SignhostAPIClient/Rest/DataObjects/Verifications/IVerification.cs diff --git a/src/SignhostAPIClient/Rest/DataObjects/Verifications/IdealVerification.cs b/src/SignhostAPIClient/Rest/DataObjects/Verifications/IdealVerification.cs new file mode 100644 index 0000000..53600a4 --- /dev/null +++ b/src/SignhostAPIClient/Rest/DataObjects/Verifications/IdealVerification.cs @@ -0,0 +1,11 @@ +namespace Signhost.APIClient.Rest.DataObjects; + +public class IdealVerification + : IVerification +{ + public string Iban { get; set; } = default!; + + public string? AccountHolderName { get; set; } + + public string? AccountHolderCity { get; set; } +} diff --git a/src/SignhostAPIClient/Rest/DataObjects/Verifications/IdinVerification.cs b/src/SignhostAPIClient/Rest/DataObjects/Verifications/IdinVerification.cs new file mode 100644 index 0000000..f753810 --- /dev/null +++ b/src/SignhostAPIClient/Rest/DataObjects/Verifications/IdinVerification.cs @@ -0,0 +1,17 @@ +using System.Collections.Generic; + +namespace Signhost.APIClient.Rest.DataObjects; + +public class IdinVerification + : IVerification +{ + public string? AccountHolderName { get; set; } + + public string? AccountHolderAddress1 { get; set; } + + public string? AccountHolderAddress2 { get; set; } + + public string? AccountHolderDateOfBirth { get; set; } + + public IDictionary? Attributes { get; set; } +} diff --git a/src/SignhostAPIClient/Rest/DataObjects/ItsmeIdentificationVerification.cs b/src/SignhostAPIClient/Rest/DataObjects/Verifications/ItsmeIdentificationVerification.cs similarity index 63% rename from src/SignhostAPIClient/Rest/DataObjects/ItsmeIdentificationVerification.cs rename to src/SignhostAPIClient/Rest/DataObjects/Verifications/ItsmeIdentificationVerification.cs index 914665e..76bd582 100644 --- a/src/SignhostAPIClient/Rest/DataObjects/ItsmeIdentificationVerification.cs +++ b/src/SignhostAPIClient/Rest/DataObjects/Verifications/ItsmeIdentificationVerification.cs @@ -1,3 +1,5 @@ +using System.Collections.Generic; + namespace Signhost.APIClient.Rest.DataObjects; /// @@ -9,5 +11,7 @@ public class ItsmeIdentificationVerification /// /// Gets or sets the phonenumber. /// - public string PhoneNumber { get; set; } + public string PhoneNumber { get; set; } = default!; + + public IDictionary? Attributes { get; set; } } diff --git a/src/SignhostAPIClient/Rest/DataObjects/Level.cs b/src/SignhostAPIClient/Rest/DataObjects/Verifications/Level.cs similarity index 86% rename from src/SignhostAPIClient/Rest/DataObjects/Level.cs rename to src/SignhostAPIClient/Rest/DataObjects/Verifications/Level.cs index a96cd7f..cf4dcd9 100644 --- a/src/SignhostAPIClient/Rest/DataObjects/Level.cs +++ b/src/SignhostAPIClient/Rest/DataObjects/Verifications/Level.cs @@ -1,7 +1,7 @@ namespace Signhost.APIClient.Rest.DataObjects; /// -/// Level of Assurance. +/// Level of Assurance of eIDAS Login verification. /// public enum Level { diff --git a/src/SignhostAPIClient/Rest/DataObjects/OidcVerification.cs b/src/SignhostAPIClient/Rest/DataObjects/Verifications/OidcVerification.cs similarity index 84% rename from src/SignhostAPIClient/Rest/DataObjects/OidcVerification.cs rename to src/SignhostAPIClient/Rest/DataObjects/Verifications/OidcVerification.cs index 608f9df..eea7aec 100644 --- a/src/SignhostAPIClient/Rest/DataObjects/OidcVerification.cs +++ b/src/SignhostAPIClient/Rest/DataObjects/Verifications/OidcVerification.cs @@ -9,5 +9,5 @@ public class OidcVerification /// /// Gets or sets the OIDC provider name. /// - public string ProviderName { get; set; } + public string? ProviderName { get; set; } } diff --git a/src/SignhostAPIClient/Rest/DataObjects/OnfidoVerification.cs b/src/SignhostAPIClient/Rest/DataObjects/Verifications/OnfidoVerification.cs similarity index 100% rename from src/SignhostAPIClient/Rest/DataObjects/OnfidoVerification.cs rename to src/SignhostAPIClient/Rest/DataObjects/Verifications/OnfidoVerification.cs diff --git a/src/SignhostAPIClient/Rest/DataObjects/PhoneNumberVerification.cs b/src/SignhostAPIClient/Rest/DataObjects/Verifications/PhoneNumberVerification.cs similarity index 76% rename from src/SignhostAPIClient/Rest/DataObjects/PhoneNumberVerification.cs rename to src/SignhostAPIClient/Rest/DataObjects/Verifications/PhoneNumberVerification.cs index 38640a1..b1c6867 100644 --- a/src/SignhostAPIClient/Rest/DataObjects/PhoneNumberVerification.cs +++ b/src/SignhostAPIClient/Rest/DataObjects/Verifications/PhoneNumberVerification.cs @@ -3,7 +3,7 @@ public class PhoneNumberVerification : IVerification { - public string Number { get; set; } + public string Number { get; set; } = default!; public bool? SecureDownload { get; set; } } diff --git a/src/SignhostAPIClient/Rest/DataObjects/ScribbleVerification.cs b/src/SignhostAPIClient/Rest/DataObjects/Verifications/ScribbleVerification.cs similarity index 82% rename from src/SignhostAPIClient/Rest/DataObjects/ScribbleVerification.cs rename to src/SignhostAPIClient/Rest/DataObjects/Verifications/ScribbleVerification.cs index 5556f0b..4cb1ee2 100644 --- a/src/SignhostAPIClient/Rest/DataObjects/ScribbleVerification.cs +++ b/src/SignhostAPIClient/Rest/DataObjects/Verifications/ScribbleVerification.cs @@ -7,5 +7,5 @@ public class ScribbleVerification public bool ScribbleNameFixed { get; set; } - public string ScribbleName { get; set; } + public string? ScribbleName { get; set; } } diff --git a/src/SignhostAPIClient/Rest/DataObjects/SurfnetVerification.cs b/src/SignhostAPIClient/Rest/DataObjects/Verifications/SurfnetVerification.cs similarity index 59% rename from src/SignhostAPIClient/Rest/DataObjects/SurfnetVerification.cs rename to src/SignhostAPIClient/Rest/DataObjects/Verifications/SurfnetVerification.cs index 8523e73..a34adc4 100644 --- a/src/SignhostAPIClient/Rest/DataObjects/SurfnetVerification.cs +++ b/src/SignhostAPIClient/Rest/DataObjects/Verifications/SurfnetVerification.cs @@ -5,7 +5,7 @@ namespace Signhost.APIClient.Rest.DataObjects; public class SurfnetVerification : IVerification { - public string Uid { get; set; } + public string? Uid { get; set; } - public IDictionary Attributes { get; set; } + public IDictionary? Attributes { get; set; } } diff --git a/src/SignhostAPIClient/Rest/ErrorHandling/GoneException.cs b/src/SignhostAPIClient/Rest/ErrorHandling/GoneException.cs index 5aad375..077a34d 100644 --- a/src/SignhostAPIClient/Rest/ErrorHandling/GoneException.cs +++ b/src/SignhostAPIClient/Rest/ErrorHandling/GoneException.cs @@ -26,7 +26,7 @@ public GoneException(string message) { } - public GoneException(string message, TResult result) + public GoneException(string message, TResult? result) : base(message) { Result = result; @@ -57,5 +57,5 @@ protected GoneException( /// Please note that this no longer contains the full transaction /// details as most data is removed. /// - public TResult Result { get; private set; } + public TResult? Result { get; private set; } } diff --git a/src/SignhostAPIClient/Rest/ErrorHandling/HttpResponseMessageErrorHandlingExtensions.cs b/src/SignhostAPIClient/Rest/ErrorHandling/HttpResponseMessageErrorHandlingExtensions.cs index 21e6b51..d43b951 100644 --- a/src/SignhostAPIClient/Rest/ErrorHandling/HttpResponseMessageErrorHandlingExtensions.cs +++ b/src/SignhostAPIClient/Rest/ErrorHandling/HttpResponseMessageErrorHandlingExtensions.cs @@ -61,7 +61,7 @@ public static async Task EnsureSignhostSuccessStatusCodeAsy string errorMessage = "Unknown Signhost error"; string responseBody = string.Empty; - if (response.Content != null) { + if (response.Content is not null) { responseBody = await response.Content.ReadAsStringAsync() .ConfigureAwait(false); @@ -72,39 +72,20 @@ public static async Task EnsureSignhostSuccessStatusCodeAsy } // TO-DO: Use switch pattern in v5 - Exception exception = null; - switch (response.StatusCode) { - case HttpStatusCode.Unauthorized: - exception = new UnauthorizedAccessException( - errorMessage); - break; - - case HttpStatusCode.BadRequest: - exception = new BadRequestException( - errorMessage); - break; - - case HttpStatusCode.PaymentRequired - when errorType == OutOfCreditsApiProblemType: - exception = new OutOfCreditsException( - errorMessage); - break; - - case HttpStatusCode.NotFound: - exception = new NotFoundException( - errorMessage); - break; - - case HttpStatusCode.InternalServerError: - exception = new InternalServerErrorException( - errorMessage, response.Headers.RetryAfter); - break; - - default: - exception = new SignhostRestApiClientException( - errorMessage); - break; - } + Exception exception = response.StatusCode switch { + HttpStatusCode.Unauthorized => new UnauthorizedAccessException(errorMessage), + HttpStatusCode.BadRequest => new BadRequestException(errorMessage), + HttpStatusCode.NotFound => new NotFoundException(errorMessage), + + HttpStatusCode.InternalServerError => + new InternalServerErrorException(errorMessage, response.Headers.RetryAfter), + + HttpStatusCode.PaymentRequired + when errorType == OutOfCreditsApiProblemType => + new OutOfCreditsException(errorMessage), + + _ => new SignhostRestApiClientException(errorMessage), + }; if (exception is SignhostRestApiClientException signhostException) { signhostException.ResponseBody = responseBody; @@ -116,9 +97,9 @@ public static async Task EnsureSignhostSuccessStatusCodeAsy private class ErrorResponse { [JsonPropertyName("type")] - public string Type { get; set; } + public string Type { get; set; } = string.Empty; [JsonPropertyName("message")] - public string Message { get; set; } + public string Message { get; set; } = string.Empty; } } diff --git a/src/SignhostAPIClient/Rest/ErrorHandling/InternalServerErrorException.cs b/src/SignhostAPIClient/Rest/ErrorHandling/InternalServerErrorException.cs index 265489f..ff54e52 100644 --- a/src/SignhostAPIClient/Rest/ErrorHandling/InternalServerErrorException.cs +++ b/src/SignhostAPIClient/Rest/ErrorHandling/InternalServerErrorException.cs @@ -18,18 +18,18 @@ public InternalServerErrorException(string message) } public InternalServerErrorException( - string message, RetryConditionHeaderValue retryAfter) + string message, RetryConditionHeaderValue? retryAfter) : base(message) { HelpLink = "https://api.signhost.com/Help"; - if (retryAfter != null) { - if (retryAfter.Date != null) { + if (retryAfter is not null) { + if (retryAfter.Date is not null) { RetryAfter = retryAfter.Date; } - if (retryAfter.Delta != null) { - RetryAfter = DateTime.Now + retryAfter.Delta; + if (retryAfter.Delta is not null) { + RetryAfter = DateTimeOffset.Now + retryAfter.Delta; } } } diff --git a/src/SignhostAPIClient/Rest/ErrorHandling/SignhostRestApiClientException.cs b/src/SignhostAPIClient/Rest/ErrorHandling/SignhostRestApiClientException.cs index 6e30ba8..d7e1296 100644 --- a/src/SignhostAPIClient/Rest/ErrorHandling/SignhostRestApiClientException.cs +++ b/src/SignhostAPIClient/Rest/ErrorHandling/SignhostRestApiClientException.cs @@ -36,5 +36,5 @@ protected SignhostRestApiClientException( /// /// Gets or sets the response body returned from the Signhost REST API. /// - public string ResponseBody { get; set; } + public string? ResponseBody { get; set; } } diff --git a/src/SignhostAPIClient/Rest/FileDigestOptions.cs b/src/SignhostAPIClient/Rest/FileDigestOptions.cs index c5580a2..bb5d770 100644 --- a/src/SignhostAPIClient/Rest/FileDigestOptions.cs +++ b/src/SignhostAPIClient/Rest/FileDigestOptions.cs @@ -22,5 +22,5 @@ public class FileDigestOptions /// Gets or sets the hash digest value, you can set this yourself /// if you know the digest value in advance. /// - public byte[] DigestHashValue { get; set; } + public byte[]? DigestHashValue { get; set; } } diff --git a/src/SignhostAPIClient/Rest/HttpContentJsonExtensions.cs b/src/SignhostAPIClient/Rest/HttpContentJsonExtensions.cs index 3dfa535..f875998 100644 --- a/src/SignhostAPIClient/Rest/HttpContentJsonExtensions.cs +++ b/src/SignhostAPIClient/Rest/HttpContentJsonExtensions.cs @@ -16,11 +16,11 @@ internal static class HttpContentJsonExtensions /// to read. /// A deserialized value of /// or default(T) if no content is available. - internal static async Task FromJsonAsync( + internal static async Task FromJsonAsync( this HttpContent httpContent) { - if (httpContent == null) { - return default(T); + if (httpContent is null) { + return default; } var json = await httpContent.ReadAsStringAsync() diff --git a/src/SignhostAPIClient/Rest/ISignHostApiClient.cs b/src/SignhostAPIClient/Rest/ISignHostApiClient.cs index d2ac880..ee2fa04 100644 --- a/src/SignhostAPIClient/Rest/ISignHostApiClient.cs +++ b/src/SignhostAPIClient/Rest/ISignHostApiClient.cs @@ -207,7 +207,7 @@ Task DeleteTransactionAsync( /// A Task. Task DeleteTransactionAsync( string transactionId, - DeleteTransactionOptions options = default, + DeleteTransactionOptions? options = default, CancellationToken cancellationToken = default); /// diff --git a/src/SignhostAPIClient/Rest/ISignhostApiClientSettings.cs b/src/SignhostAPIClient/Rest/ISignhostApiClientSettings.cs index 22baf84..f017de5 100644 --- a/src/SignhostAPIClient/Rest/ISignhostApiClientSettings.cs +++ b/src/SignhostAPIClient/Rest/ISignhostApiClientSettings.cs @@ -9,7 +9,7 @@ public interface ISignhostApiClientSettings /// /// Gets the usertoken identifying an authorized user. /// - string UserToken { get; } + string? UserToken { get; } /// /// Gets the app key of your applications. @@ -21,5 +21,5 @@ public interface ISignhostApiClientSettings /// string Endpoint { get; } - Action AddHeader { get; } + Action? AddHeader { get; } } diff --git a/src/SignhostAPIClient/Rest/ISignhostApiReceiver.cs b/src/SignhostAPIClient/Rest/ISignhostApiReceiver.cs index ba4eb96..8b29752 100644 --- a/src/SignhostAPIClient/Rest/ISignhostApiReceiver.cs +++ b/src/SignhostAPIClient/Rest/ISignhostApiReceiver.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using Signhost.APIClient.Rest.DataObjects; namespace Signhost.APIClient.Rest; @@ -18,5 +19,5 @@ public interface ISignhostApiReceiver bool IsPostbackChecksumValid( IDictionary headers, string body, - out Transaction postbackTransaction); + [NotNullWhen(true)] out Transaction? postbackTransaction); } diff --git a/src/SignhostAPIClient/Rest/SignHostApiClient.cs b/src/SignhostAPIClient/Rest/SignHostApiClient.cs index 4c7f698..09adc32 100644 --- a/src/SignhostAPIClient/Rest/SignHostApiClient.cs +++ b/src/SignhostAPIClient/Rest/SignHostApiClient.cs @@ -21,12 +21,8 @@ public class SignhostApiClient { private const string ApiVersion = "v1"; - private static readonly string Version = typeof(SignhostApiClient) -#if TYPEINFO - .GetTypeInfo() -#endif - .Assembly.GetCustomAttribute() - .Version; + private static readonly string Version = GetVersion() + ?? throw new InvalidOperationException("Unknown assembly version."); private readonly ISignhostApiClientSettings settings; private readonly HttpClient client; @@ -86,7 +82,7 @@ public async Task CreateTransactionAsync( Transaction transaction, CancellationToken cancellationToken = default) { - if (transaction == null) { + if (transaction is null) { throw new ArgumentNullException(nameof(transaction)); } @@ -99,7 +95,9 @@ public async Task CreateTransactionAsync( .ConfigureAwait(false); return await result.Content.FromJsonAsync() - .ConfigureAwait(false); + .ConfigureAwait(false) + ?? throw new InvalidOperationException( + "Failed to deserialize the transaction."); } /// @@ -113,7 +111,7 @@ public async Task> GetTransactionResponseAsync( string transactionId, CancellationToken cancellationToken = default) { - if (transactionId == null) { + if (transactionId is null) { throw new ArgumentNullException(nameof(transactionId)); } @@ -150,7 +148,8 @@ public async Task GetTransactionAsync( response.EnsureAvailableStatusCode(); - return response.Value; + return response.Value ?? throw new InvalidOperationException( + "Failed to deserialize the transaction."); } /// @@ -174,10 +173,10 @@ public async Task DeleteTransactionAsync( /// public async Task DeleteTransactionAsync( string transactionId, - DeleteTransactionOptions options, + DeleteTransactionOptions? options, CancellationToken cancellationToken = default) { - if (transactionId == null) { + if (transactionId is null) { throw new ArgumentNullException(nameof(transactionId)); } @@ -185,7 +184,7 @@ public async Task DeleteTransactionAsync( throw new ArgumentException("Cannot be empty or contain only whitespaces.", nameof(transactionId)); } - if (options == null) { + if (options is null) { options = new DeleteTransactionOptions(); } @@ -217,11 +216,11 @@ public async Task AddOrReplaceFileMetaToTransactionAsync( string fileId, CancellationToken cancellationToken = default) { - if (fileMeta == null) { + if (fileMeta is null) { throw new ArgumentNullException("fileMeta"); } - if (transactionId == null) { + if (transactionId is null) { throw new ArgumentNullException(nameof(transactionId)); } @@ -229,7 +228,7 @@ public async Task AddOrReplaceFileMetaToTransactionAsync( throw new ArgumentException("Cannot be empty or contain only whitespaces.", nameof(transactionId)); } - if (fileId == null) { + if (fileId is null) { throw new ArgumentNullException(nameof(fileId)); } @@ -251,7 +250,7 @@ public async Task AddOrReplaceFileToTransactionAsync( Stream fileStream, string transactionId, string fileId, - FileUploadOptions uploadOptions) + FileUploadOptions? uploadOptions) => await AddOrReplaceFileToTransactionAsync( fileStream, transactionId, @@ -264,14 +263,14 @@ public async Task AddOrReplaceFileToTransactionAsync( Stream fileStream, string transactionId, string fileId, - FileUploadOptions uploadOptions, + FileUploadOptions? uploadOptions, CancellationToken cancellationToken = default) { - if (fileStream == null) { + if (fileStream is null) { throw new ArgumentNullException(nameof(fileStream)); } - if (transactionId == null) { + if (transactionId is null) { throw new ArgumentNullException(nameof(transactionId)); } @@ -279,7 +278,7 @@ public async Task AddOrReplaceFileToTransactionAsync( throw new ArgumentException("Cannot be empty or contain only whitespaces.", nameof(transactionId)); } - if (fileId == null) { + if (fileId is null) { throw new ArgumentNullException(nameof(fileId)); } @@ -287,7 +286,7 @@ public async Task AddOrReplaceFileToTransactionAsync( throw new ArgumentException("Cannot be empty or contain only whitespaces.", nameof(fileId)); } - if (uploadOptions == null) { + if (uploadOptions is null) { uploadOptions = new FileUploadOptions(); } @@ -322,7 +321,7 @@ public async Task AddOrReplaceFileToTransactionAsync( string filePath, string transactionId, string fileId, - FileUploadOptions uploadOptions) + FileUploadOptions? uploadOptions) => await AddOrReplaceFileToTransactionAsync( filePath, transactionId, @@ -335,10 +334,10 @@ public async Task AddOrReplaceFileToTransactionAsync( string filePath, string transactionId, string fileId, - FileUploadOptions uploadOptions, + FileUploadOptions? uploadOptions, CancellationToken cancellationToken = default) { - if (filePath == null) { + if (filePath is null) { throw new ArgumentNullException(nameof(filePath)); } @@ -382,7 +381,7 @@ public async Task StartTransactionAsync( string transactionId, CancellationToken cancellationToken = default) { - if (transactionId == null) { + if (transactionId is null) { throw new ArgumentNullException(nameof(transactionId)); } @@ -409,7 +408,7 @@ public async Task GetReceiptAsync( string transactionId, CancellationToken cancellationToken = default) { - if (transactionId == null) { + if (transactionId is null) { throw new ArgumentNullException(nameof(transactionId)); } @@ -438,7 +437,7 @@ public async Task GetDocumentAsync( string fileId, CancellationToken cancellationToken = default) { - if (transactionId == null) { + if (transactionId is null) { throw new ArgumentNullException(nameof(transactionId)); } @@ -446,7 +445,7 @@ public async Task GetDocumentAsync( throw new ArgumentException("Cannot be empty or contain only whitespaces.", nameof(transactionId)); } - if (fileId == null) { + if (fileId is null) { throw new ArgumentNullException(nameof(fileId)); } @@ -479,4 +478,12 @@ protected virtual void Dispose(bool disposing) client?.Dispose(); } } + + private static string? GetVersion() + => typeof(SignhostApiClient) +#if TYPEINFO + .GetTypeInfo() +#endif + ?.Assembly?.GetCustomAttribute() + ?.Version; } diff --git a/src/SignhostAPIClient/Rest/SignHostApiClientSettings.cs b/src/SignhostAPIClient/Rest/SignHostApiClientSettings.cs index c96340d..15cd1df 100644 --- a/src/SignhostAPIClient/Rest/SignHostApiClientSettings.cs +++ b/src/SignhostAPIClient/Rest/SignHostApiClientSettings.cs @@ -18,11 +18,11 @@ public SignhostApiClientSettings(string appkey) APPKey = appkey; } - public string UserToken { get; set; } + public string? UserToken { get; set; } public string APPKey { get; private set; } public string Endpoint { get; set; } = DefaultEndpoint; - public Action AddHeader { get; set; } + public Action? AddHeader { get; set; } } diff --git a/src/SignhostAPIClient/Rest/SignhostApiReceiver.cs b/src/SignhostAPIClient/Rest/SignhostApiReceiver.cs index d4fb3bf..aae8071 100644 --- a/src/SignhostAPIClient/Rest/SignhostApiReceiver.cs +++ b/src/SignhostAPIClient/Rest/SignhostApiReceiver.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Security.Cryptography; using System.Text; @@ -34,17 +35,18 @@ public SignhostApiReceiver(SignhostApiReceiverSettings receiverSettings) public bool IsPostbackChecksumValid( IDictionary headers, string body, - out Transaction postbackTransaction) + [NotNullWhen(true)] out Transaction? postbackTransaction) { postbackTransaction = null; - string postbackChecksum; - string calculatedChecksum; - PostbackTransaction postback; + var postback = DeserializeToPostbackTransaction(body); + if (postback is null) { + return false; + } - postback = DeserializeToPostbackTransaction(body); - postbackChecksum = GetChecksumFromHeadersOrPostback(headers, postback); + string postbackChecksum = GetChecksumFromHeadersOrPostback(headers, postback); bool parametersAreValid = HasValidChecksumProperties(postbackChecksum, postback); + string calculatedChecksum; if (parametersAreValid) { calculatedChecksum = CalculateChecksumFromPostback(postback); postbackTransaction = postback; @@ -66,7 +68,7 @@ private string CalculateChecksumFromPostback(PostbackTransaction postback) } } - private PostbackTransaction DeserializeToPostbackTransaction(string body) + private PostbackTransaction? DeserializeToPostbackTransaction(string body) { return JsonSerializer.Deserialize(body, SignhostJsonSerializerOptions.Default); } @@ -75,8 +77,10 @@ private string GetChecksumFromHeadersOrPostback( IDictionary headers, PostbackTransaction postback) { - string[] postbackChecksumArray; - if (headers.TryGetValue("Checksum", out postbackChecksumArray)) { + if ( + headers.TryGetValue("Checksum", out var postbackChecksumArray) && + postbackChecksumArray is not null + ) { return postbackChecksumArray.First(); } else { diff --git a/src/SignhostAPIClient/Rest/StreamContentDigestOptionsExtensions.cs b/src/SignhostAPIClient/Rest/StreamContentDigestOptionsExtensions.cs index 32e27d4..8867a49 100644 --- a/src/SignhostAPIClient/Rest/StreamContentDigestOptionsExtensions.cs +++ b/src/SignhostAPIClient/Rest/StreamContentDigestOptionsExtensions.cs @@ -31,6 +31,10 @@ public static StreamContent WithDigest( } SetHashValue(fileStream, options); + if (options.DigestHashValue is null) { + throw new InvalidOperationException( + "Digest hash value is not set after calculating it."); + } string base64Digest = Convert.ToBase64String(options.DigestHashValue); @@ -54,7 +58,7 @@ private static void SetHashValue( Stream fileStream, FileDigestOptions options) { - if (options.DigestHashValue != null) { + if (options.DigestHashValue is not null) { return; } diff --git a/src/SignhostAPIClient/SignhostAPIClient.csproj b/src/SignhostAPIClient/SignhostAPIClient.csproj index 7309d33..677e13d 100644 --- a/src/SignhostAPIClient/SignhostAPIClient.csproj +++ b/src/SignhostAPIClient/SignhostAPIClient.csproj @@ -5,6 +5,7 @@ ../signhost.ruleset TYPEINFO Signhost.APIClient + enable From 63f1fc01b5f49a1298c0d3735351075accca4ef7 Mon Sep 17 00:00:00 2001 From: Anthony Timmers Date: Thu, 27 Nov 2025 21:16:45 +0100 Subject: [PATCH 15/28] Refactor EnsureAvailableStatusCode to be async --- src/SignhostAPIClient/Rest/ApiResponse.cs | 17 +++++++++++------ src/SignhostAPIClient/Rest/SignHostApiClient.cs | 3 ++- 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/src/SignhostAPIClient/Rest/ApiResponse.cs b/src/SignhostAPIClient/Rest/ApiResponse.cs index 27a6234..ac54a92 100644 --- a/src/SignhostAPIClient/Rest/ApiResponse.cs +++ b/src/SignhostAPIClient/Rest/ApiResponse.cs @@ -1,5 +1,7 @@ using System.Net; using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; namespace Signhost.APIClient.Rest; @@ -17,18 +19,21 @@ public ApiResponse(HttpResponseMessage httpResponse, TValue? value) public HttpStatusCode HttpStatusCode => httpResponse.StatusCode; - public void EnsureAvailableStatusCode() + public async Task EnsureAvailableStatusCodeAsync( + CancellationToken cancellationToken = default) { if (HttpStatusCode == HttpStatusCode.Gone) { throw new ErrorHandling.GoneException( httpResponse.ReasonPhrase ?? "No reason phrase provided", Value) { - // TO-DO: Make async in v5 - ResponseBody = httpResponse.Content.ReadAsStringAsync() - .ConfigureAwait(false) - .GetAwaiter() - .GetResult(), + ResponseBody = await httpResponse.Content +#if NETFRAMEWORK || NETSTANDARD2_0 + .ReadAsStringAsync() +#else + .ReadAsStringAsync(cancellationToken) +#endif + .ConfigureAwait(false), }; } } diff --git a/src/SignhostAPIClient/Rest/SignHostApiClient.cs b/src/SignhostAPIClient/Rest/SignHostApiClient.cs index 09adc32..d06b718 100644 --- a/src/SignhostAPIClient/Rest/SignHostApiClient.cs +++ b/src/SignhostAPIClient/Rest/SignHostApiClient.cs @@ -146,7 +146,8 @@ public async Task GetTransactionAsync( cancellationToken) .ConfigureAwait(false); - response.EnsureAvailableStatusCode(); + await response.EnsureAvailableStatusCodeAsync(cancellationToken) + .ConfigureAwait(false); return response.Value ?? throw new InvalidOperationException( "Failed to deserialize the transaction."); From 2f1b316881ee45b09a9ff473003e6cbc49db8310 Mon Sep 17 00:00:00 2001 From: Anthony Timmers Date: Thu, 27 Nov 2025 21:30:01 +0100 Subject: [PATCH 16/28] Add FileFieldType enum --- .../FileFieldTypeTests.cs | 53 +++++++++++++++++++ .../SignhostApiClientTests.cs | 2 +- .../Rest/DataObjects/FileMetaData/Field.cs | 3 +- .../DataObjects/FileMetaData/FileFieldType.cs | 15 ++++++ 4 files changed, 70 insertions(+), 3 deletions(-) create mode 100644 src/SignhostAPIClient.Tests/FileFieldTypeTests.cs create mode 100644 src/SignhostAPIClient/Rest/DataObjects/FileMetaData/FileFieldType.cs diff --git a/src/SignhostAPIClient.Tests/FileFieldTypeTests.cs b/src/SignhostAPIClient.Tests/FileFieldTypeTests.cs new file mode 100644 index 0000000..f3afd47 --- /dev/null +++ b/src/SignhostAPIClient.Tests/FileFieldTypeTests.cs @@ -0,0 +1,53 @@ +using System.Text.Json; +using FluentAssertions; +using Signhost.APIClient.Rest.DataObjects; +using Xunit; + +namespace Signhost.APIClient.Rest.Tests; + +public class FileFieldTypeTests +{ + [Theory] + [InlineData(FileFieldType.Seal, "Seal")] + [InlineData(FileFieldType.Signature, "Signature")] + [InlineData(FileFieldType.Check, "Check")] + [InlineData(FileFieldType.Radio, "Radio")] + [InlineData(FileFieldType.SingleLine, "SingleLine")] + [InlineData(FileFieldType.Number, "Number")] + [InlineData(FileFieldType.Date, "Date")] + public void Given_a_field_with_specific_type_When_serialized_to_json_Then_type_is_string_not_numeric(FileFieldType fieldType, string expectedString) + { + // Arrange + var field = new Field + { + Type = fieldType, + Value = "test", + Location = new Location { PageNumber = 1 } + }; + + // Act + var json = JsonSerializer.Serialize(field); + + // Assert + json.Should().Contain($"\"Type\":\"{expectedString}\""); + json.Should().NotContain($"\"Type\":{(int)fieldType}"); + } + + [Fact] + public void Given_json_with_string_field_type_When_deserialized_Then_field_type_enum_is_correctly_parsed() + { + // Arrange + var json = @"{ + ""Type"": ""Signature"", + ""Value"": ""test"", + ""Location"": { ""PageNumber"": 1 } + }"; + + // Act + var field = JsonSerializer.Deserialize(json); + + // Assert + field.Should().NotBeNull(); + field!.Type.Should().Be(FileFieldType.Signature); + } +} diff --git a/src/SignhostAPIClient.Tests/SignhostApiClientTests.cs b/src/SignhostAPIClient.Tests/SignhostApiClientTests.cs index 80e0fbb..972b5e7 100644 --- a/src/SignhostAPIClient.Tests/SignhostApiClientTests.cs +++ b/src/SignhostAPIClient.Tests/SignhostApiClientTests.cs @@ -41,7 +41,7 @@ public async Task when_AddOrReplaceFileMetaToTransaction_is_called_then_the_requ var field = new Field { - Type = "Check", + Type = FileFieldType.Check, Value = "I agree", Location = new Location { diff --git a/src/SignhostAPIClient/Rest/DataObjects/FileMetaData/Field.cs b/src/SignhostAPIClient/Rest/DataObjects/FileMetaData/Field.cs index 62f40b0..755e504 100644 --- a/src/SignhostAPIClient/Rest/DataObjects/FileMetaData/Field.cs +++ b/src/SignhostAPIClient/Rest/DataObjects/FileMetaData/Field.cs @@ -2,8 +2,7 @@ public class Field { - // TO-DO: Make enum in v5. - public string Type { get; set; } = default!; + public FileFieldType Type { get; set; } // TO-DO: Can be boolean, number, string, should be fixed in v5. public string? Value { get; set; } diff --git a/src/SignhostAPIClient/Rest/DataObjects/FileMetaData/FileFieldType.cs b/src/SignhostAPIClient/Rest/DataObjects/FileMetaData/FileFieldType.cs new file mode 100644 index 0000000..c5812cb --- /dev/null +++ b/src/SignhostAPIClient/Rest/DataObjects/FileMetaData/FileFieldType.cs @@ -0,0 +1,15 @@ +using System.Text.Json.Serialization; + +namespace Signhost.APIClient.Rest.DataObjects; + +[JsonConverter(typeof(JsonStringEnumConverter))] +public enum FileFieldType +{ + Seal, + Signature, + Check, + Radio, + SingleLine, + Number, + Date, +} From cd54588c23ce533a5651f6983a00437c9a28dd82 Mon Sep 17 00:00:00 2001 From: Anthony Timmers Date: Thu, 27 Nov 2025 21:30:23 +0100 Subject: [PATCH 17/28] Add converter for Field value --- .../FieldValueTests.cs | 274 ++++++++++++++++++ .../Rest/DataObjects/FileMetaData/Field.cs | 12 +- .../JsonConverters/JsonObjectConverter.cs | 55 ++++ 3 files changed, 338 insertions(+), 3 deletions(-) create mode 100644 src/SignhostAPIClient.Tests/FieldValueTests.cs create mode 100644 src/SignhostAPIClient/Rest/JsonConverters/JsonObjectConverter.cs diff --git a/src/SignhostAPIClient.Tests/FieldValueTests.cs b/src/SignhostAPIClient.Tests/FieldValueTests.cs new file mode 100644 index 0000000..4690a69 --- /dev/null +++ b/src/SignhostAPIClient.Tests/FieldValueTests.cs @@ -0,0 +1,274 @@ +using System.Text.Json; +using FluentAssertions; +using Signhost.APIClient.Rest.DataObjects; +using Xunit; + +namespace Signhost.APIClient.Rest.Tests; + +public class FieldValueTests +{ + [Fact] + public void Given_a_field_with_string_value_When_serialized_Then_value_is_json_string() + { + // Arrange + var field = new Field { + Type = FileFieldType.SingleLine, + Value = "John Smith", + Location = new Location { PageNumber = 1 } + }; + + // Act + var json = JsonSerializer.Serialize(field); + + // Assert + json.Should().Contain("\"Value\":\"John Smith\""); + } + + [Fact] + public void Given_a_field_with_numeric_integer_value_When_serialized_Then_value_is_json_number() + { + // Arrange + var field = new Field { + Type = FileFieldType.Number, + Value = 42, + Location = new Location { PageNumber = 1 } + }; + + // Act + var json = JsonSerializer.Serialize(field); + + // Assert + json.Should().Contain("\"Value\":42"); + json.Should().NotContain("\"Value\":\"42\""); + } + + [Fact] + public void Given_a_field_with_numeric_double_value_When_serialized_Then_value_is_json_number() + { + // Arrange + var field = new Field { + Type = FileFieldType.Number, + Value = 3.14, + Location = new Location { PageNumber = 1 } + }; + + // Act + var json = JsonSerializer.Serialize(field); + + // Assert + json.Should().Contain("\"Value\":3.14"); + json.Should().NotContain("\"Value\":\"3.14\""); + } + + [Fact] + public void Given_a_field_with_boolean_true_value_When_serialized_Then_value_is_json_boolean() + { + // Arrange + var field = new Field { + Type = FileFieldType.Check, + Value = true, + Location = new Location { PageNumber = 1 } + }; + + // Act + var json = JsonSerializer.Serialize(field); + + // Assert + json.Should().Contain("\"Value\":true"); + json.Should().NotContain("\"Value\":\"true\""); + } + + [Fact] + public void Given_a_field_with_boolean_false_value_When_serialized_Then_value_is_json_boolean() + { + // Arrange + var field = new Field { + Type = FileFieldType.Check, + Value = false, + Location = new Location { PageNumber = 1 } + }; + + // Act + var json = JsonSerializer.Serialize(field); + + // Assert + json.Should().Contain("\"Value\":false"); + json.Should().NotContain("\"Value\":\"false\""); + } + + [Fact] + public void Given_a_field_with_null_value_When_serialized_Then_value_is_json_null() + { + // Arrange + var field = new Field { + Type = FileFieldType.Signature, + Value = null, + Location = new Location { PageNumber = 1 } + }; + + // Act + var json = JsonSerializer.Serialize(field); + + // Assert + json.Should().Contain("\"Value\":null"); + } + + [Fact] + public void Given_json_with_string_value_When_deserialized_Then_field_value_is_string() + { + // Arrange + var json = @"{ + ""Type"": ""SingleLine"", + ""Value"": ""Test Name"", + ""Location"": { ""PageNumber"": 1 } + }"; + + // Act + var field = JsonSerializer.Deserialize(json); + + // Assert + field.Should().NotBeNull(); + field!.Value.Should().BeOfType().Which.Should().Be("Test Name"); + } + + [Fact] + public void Given_json_with_number_integer_value_When_deserialized_Then_field_value_is_numeric() + { + // Arrange + var json = @"{ + ""Type"": ""Number"", + ""Value"": 123, + ""Location"": { ""PageNumber"": 1 } + }"; + + // Act + var field = JsonSerializer.Deserialize(json); + + // Assert + field.Should().NotBeNull(); + field!.Value.Should().BeOneOf(123L, 123.0); + } + + [Fact] + public void Given_json_with_number_decimal_value_When_deserialized_Then_field_value_is_double() + { + // Arrange + var json = @"{ + ""Type"": ""Number"", + ""Value"": 45.67, + ""Location"": { ""PageNumber"": 1 } + }"; + + // Act + var field = JsonSerializer.Deserialize(json); + + // Assert + field.Should().NotBeNull(); + field!.Value.Should().BeOfType().Which.Should().Be(45.67); + } + + [Fact] + public void Given_json_with_boolean_true_value_When_deserialized_Then_field_value_is_boolean_true() + { + // Arrange + var json = @"{ + ""Type"": ""Check"", + ""Value"": true, + ""Location"": { ""PageNumber"": 1 } + }"; + + // Act + var field = JsonSerializer.Deserialize(json); + + // Assert + field.Should().NotBeNull(); + field!.Value.Should().BeOfType().Which.Should().BeTrue(); + } + + [Fact] + public void Given_json_with_boolean_false_value_When_deserialized_Then_field_value_is_boolean_false() + { + // Arrange + var json = @"{ + ""Type"": ""Check"", + ""Value"": false, + ""Location"": { ""PageNumber"": 1 } + }"; + + // Act + var field = JsonSerializer.Deserialize(json); + + // Assert + field.Should().NotBeNull(); + field!.Value.Should().BeOfType().Which.Should().BeFalse(); + } + + [Fact] + public void Given_json_with_null_value_When_deserialized_Then_field_value_is_null() + { + // Arrange + var json = @"{ + ""Type"": ""Signature"", + ""Value"": null, + ""Location"": { ""PageNumber"": 1 } + }"; + + // Act + var field = JsonSerializer.Deserialize(json); + + // Assert + field.Should().NotBeNull(); + field!.Value.Should().BeNull(); + } + + [Fact] + public void Given_a_field_with_invalid_object_value_When_serialized_Then_throws_json_exception() + { + // Arrange + var field = new Field { + Type = FileFieldType.SingleLine, + Value = new { Name = "Test" }, + Location = new Location { PageNumber = 1 } + }; + + // Act + var act = () => JsonSerializer.Serialize(field); + + // Assert + act.Should().Throw(); + } + + [Fact] + public void Given_json_with_object_value_When_deserialized_Then_throws_json_exception() + { + // Arrange + var json = @"{ + ""Type"": ""SingleLine"", + ""Value"": { ""nested"": ""object"" }, + ""Location"": { ""PageNumber"": 1 } + }"; + + // Act + var act = () => JsonSerializer.Deserialize(json); + + // Assert + act.Should().Throw(); + } + + [Fact] + public void Given_json_with_array_value_When_deserialized_Then_throws_json_exception() + { + // Arrange + var json = @"{ + ""Type"": ""SingleLine"", + ""Value"": [1, 2, 3], + ""Location"": { ""PageNumber"": 1 } + }"; + + // Act + var act = () => JsonSerializer.Deserialize(json); + + // Assert + act.Should().Throw(); + } +} diff --git a/src/SignhostAPIClient/Rest/DataObjects/FileMetaData/Field.cs b/src/SignhostAPIClient/Rest/DataObjects/FileMetaData/Field.cs index 755e504..f8ed5bd 100644 --- a/src/SignhostAPIClient/Rest/DataObjects/FileMetaData/Field.cs +++ b/src/SignhostAPIClient/Rest/DataObjects/FileMetaData/Field.cs @@ -1,11 +1,17 @@ -namespace Signhost.APIClient.Rest.DataObjects; +using System.Text.Json.Serialization; +using Signhost.APIClient.Rest.JsonConverters; + +namespace Signhost.APIClient.Rest.DataObjects; public class Field { public FileFieldType Type { get; set; } - // TO-DO: Can be boolean, number, string, should be fixed in v5. - public string? Value { get; set; } + /// + /// The value content for the field. Can be a string, number, or boolean. + /// + [JsonConverter(typeof(JsonObjectConverter))] + public object? Value { get; set; } public Location Location { get; set; } = default!; } diff --git a/src/SignhostAPIClient/Rest/JsonConverters/JsonObjectConverter.cs b/src/SignhostAPIClient/Rest/JsonConverters/JsonObjectConverter.cs new file mode 100644 index 0000000..ceaa0e4 --- /dev/null +++ b/src/SignhostAPIClient/Rest/JsonConverters/JsonObjectConverter.cs @@ -0,0 +1,55 @@ +using System; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace Signhost.APIClient.Rest.JsonConverters; + +/// +/// Converts JSON values of specific types (string, number, boolean) to/from a C# object. +/// Allows flexible deserialization of fields that can contain string, numeric, or boolean JSON values. +/// +public class JsonObjectConverter + : JsonConverter +{ + public override object? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + return reader.TokenType switch + { + JsonTokenType.String => reader.GetString(), + JsonTokenType.Number => reader.TryGetInt64(out var value) + ? value + : reader.GetDouble(), + JsonTokenType.True or JsonTokenType.False => reader.GetBoolean(), + JsonTokenType.Null => null, + _ => throw new JsonException($"Unexpected token {reader.TokenType} when parsing field value. Only string, number, boolean, or null are allowed."), + }; + } + + public override void Write(Utf8JsonWriter writer, object? value, JsonSerializerOptions options) + { + if (value is null) { + writer.WriteNullValue(); + } + else if (value is string s) { + writer.WriteStringValue(s); + } + else if (value is bool b) { + writer.WriteBooleanValue(b); + } + else if (value is int i) { + writer.WriteNumberValue(i); + } + else if (value is long l) { + writer.WriteNumberValue(l); + } + else if (value is double d) { + writer.WriteNumberValue(d); + } + else if (value is decimal dec) { + writer.WriteNumberValue(dec); + } + else { + throw new JsonException($"Field value must be string, number, or boolean, but got {value.GetType().Name}"); + } + } +} From f631016a7c4b91e2a64f5d1aa7aa62784c93fc03 Mon Sep 17 00:00:00 2001 From: Anthony Timmers Date: Thu, 27 Nov 2025 21:36:37 +0100 Subject: [PATCH 18/28] Remove unused activity types --- .../Rest/DataObjects/ActivityType.cs | 90 ++++--------------- 1 file changed, 17 insertions(+), 73 deletions(-) diff --git a/src/SignhostAPIClient/Rest/DataObjects/ActivityType.cs b/src/SignhostAPIClient/Rest/DataObjects/ActivityType.cs index 223be52..89e3f7b 100644 --- a/src/SignhostAPIClient/Rest/DataObjects/ActivityType.cs +++ b/src/SignhostAPIClient/Rest/DataObjects/ActivityType.cs @@ -1,135 +1,79 @@ namespace Signhost.APIClient.Rest.DataObjects; /// -/// type. +/// type codes as defined in the Signhost API. /// -// TO-DO: Remove unused activity types in v5. public enum ActivityType { /// - /// The invitation mail was sent. + /// Invitation sent (code 101). /// InvitationSent = 101, /// - /// The invitation mail was received. - /// - InvitationReceived = 102, - - /// - /// The sign url was opened. + /// Sign URL was opened (code 103). /// Opened = 103, /// - /// An invitation reminder mail was sent. + /// Invitation reminder sent (code 104). /// InvitationReminderResent = 104, /// - /// The document was opened. - /// The contains the fileId of the opened - /// document. + /// Document was opened. + /// The property contains the file ID of + /// the opened document (code 105). /// DocumentOpened = 105, /// - /// Consumer Signing identity approved. - /// - IdentityApproved = 110, - - /// - /// Consumer Signing identity failed. - /// - IdentityFailed = 111, - - /// - /// Cancelled. - /// - Cancelled = 201, - - /// - /// The signer rejected the sign request. + /// The signer rejected the sign request (code 202). /// Rejected = 202, /// - /// The signer signed the documents. + /// The signer signed the documents (code 203). /// Signed = 203, /// - /// The signer delegated signing to a different signer. + /// The signer delegated signing to a different signer (code 204). /// SignerDelegated = 204, /// - /// Signed document sent. + /// Signed document sent (code 301). /// SignedDocumentSent = 301, /// - /// Signed document opened. + /// Signed document opened (code 302). /// SignedDocumentOpened = 302, /// - /// Signed document downloaded. + /// Signed document downloaded (code 303). /// SignedDocumentDownloaded = 303, /// - /// Receipt sent. + /// Receipt sent (code 401). /// ReceiptSent = 401, /// - /// Receipt opened. + /// Receipt opened (code 402). /// ReceiptOpened = 402, /// - /// Receipt downloaded. + /// Receipt downloaded (code 403). /// ReceiptDownloaded = 403, /// - /// Finished. - /// - Finished = 500, - - /// - /// Deleted. - /// - Deleted = 600, - - /// - /// Expired. - /// - Expired = 700, - - /// - /// Email bounce - hard. - /// - EmailBounceHard = 901, - - /// - /// Email bounce - soft. - /// - EmailBounceSoft = 902, - - /// - /// Email bounce - blocked. - /// - EmailBounceBlocked = 903, - - /// - /// Email bounce - undetermined. - /// - EmailBounceUndetermined = 904, - - /// - /// Operation failed. + /// Transaction failed due to this entity, e.g. email bounce (code 999). /// Failed = 999, } From ce8142a04deed42f1f3cc7cbf575375377e8b39b Mon Sep 17 00:00:00 2001 From: Anthony Timmers Date: Thu, 27 Nov 2025 21:41:13 +0100 Subject: [PATCH 19/28] Refactor use BadAuthorizationException --- src/SignhostAPIClient.Tests/SignhostApiClientTests.cs | 3 ++- .../Rest/ErrorHandling/BadAuthorizationException.cs | 1 - .../HttpResponseMessageErrorHandlingExtensions.cs | 11 ++++------- 3 files changed, 6 insertions(+), 9 deletions(-) diff --git a/src/SignhostAPIClient.Tests/SignhostApiClientTests.cs b/src/SignhostAPIClient.Tests/SignhostApiClientTests.cs index 972b5e7..79bdc51 100644 --- a/src/SignhostAPIClient.Tests/SignhostApiClientTests.cs +++ b/src/SignhostAPIClient.Tests/SignhostApiClientTests.cs @@ -4,6 +4,7 @@ using System.IO; using Xunit; using Signhost.APIClient.Rest.DataObjects; +using Signhost.APIClient.Rest.ErrorHandling; using FluentAssertions; using System.Collections.Generic; using RichardSzalay.MockHttp; @@ -107,7 +108,7 @@ public async Task when_GetTransaction_is_called_and_the_authorization_is_bad_the var signhostApiClient = new SignhostApiClient(settings, httpClient); Func getTransaction = () => signhostApiClient.GetTransactionAsync("transaction Id"); - await getTransaction.Should().ThrowAsync(); + await getTransaction.Should().ThrowAsync(); } mockHttp.VerifyNoOutstandingExpectation(); diff --git a/src/SignhostAPIClient/Rest/ErrorHandling/BadAuthorizationException.cs b/src/SignhostAPIClient/Rest/ErrorHandling/BadAuthorizationException.cs index d9d8db3..18260ac 100644 --- a/src/SignhostAPIClient/Rest/ErrorHandling/BadAuthorizationException.cs +++ b/src/SignhostAPIClient/Rest/ErrorHandling/BadAuthorizationException.cs @@ -2,7 +2,6 @@ namespace Signhost.APIClient.Rest.ErrorHandling; -// TO-DO: Use this instead of Unauthorized exception in v5 [Serializable] public class BadAuthorizationException : SignhostRestApiClientException diff --git a/src/SignhostAPIClient/Rest/ErrorHandling/HttpResponseMessageErrorHandlingExtensions.cs b/src/SignhostAPIClient/Rest/ErrorHandling/HttpResponseMessageErrorHandlingExtensions.cs index d43b951..491c9d4 100644 --- a/src/SignhostAPIClient/Rest/ErrorHandling/HttpResponseMessageErrorHandlingExtensions.cs +++ b/src/SignhostAPIClient/Rest/ErrorHandling/HttpResponseMessageErrorHandlingExtensions.cs @@ -24,7 +24,7 @@ public static class HttpResponseMessageErrorHandlingExtensions /// Returns if the call is succesful. /// List of which should /// not be handled as an error. - /// + /// /// When the api authentication failed. /// /// @@ -71,9 +71,8 @@ public static async Task EnsureSignhostSuccessStatusCodeAsy errorMessage = error?.Message ?? "Unknown Signhost error"; } - // TO-DO: Use switch pattern in v5 - Exception exception = response.StatusCode switch { - HttpStatusCode.Unauthorized => new UnauthorizedAccessException(errorMessage), + SignhostRestApiClientException exception = response.StatusCode switch { + HttpStatusCode.Unauthorized => new BadAuthorizationException(errorMessage), HttpStatusCode.BadRequest => new BadRequestException(errorMessage), HttpStatusCode.NotFound => new NotFoundException(errorMessage), @@ -87,9 +86,7 @@ public static async Task EnsureSignhostSuccessStatusCodeAsy _ => new SignhostRestApiClientException(errorMessage), }; - if (exception is SignhostRestApiClientException signhostException) { - signhostException.ResponseBody = responseBody; - } + exception.ResponseBody = responseBody; throw exception; } From 5b08ca494c93e931220b201899c1e4a3297b80c7 Mon Sep 17 00:00:00 2001 From: Anthony Timmers Date: Thu, 27 Nov 2025 21:51:02 +0100 Subject: [PATCH 20/28] Refactor test ResponseBody in exceptions --- .../SignhostApiClientTests.cs | 106 ++++++++++-------- 1 file changed, 62 insertions(+), 44 deletions(-) diff --git a/src/SignhostAPIClient.Tests/SignhostApiClientTests.cs b/src/SignhostAPIClient.Tests/SignhostApiClientTests.cs index 79bdc51..031280e 100644 --- a/src/SignhostAPIClient.Tests/SignhostApiClientTests.cs +++ b/src/SignhostAPIClient.Tests/SignhostApiClientTests.cs @@ -95,20 +95,22 @@ public async Task when_a_GetTransaction_is_called_then_we_should_have_called_the public async Task when_GetTransaction_is_called_and_the_authorization_is_bad_then_we_should_get_a_BadAuthorizationException() { var mockHttp = new MockHttpMessageHandler(); - mockHttp - .Expect(HttpMethod.Get, "http://localhost/api/transaction/transaction Id") - .Respond(HttpStatusCode.Unauthorized, new StringContent(""" + const string expectedResponseBody = """ { "message": "unauthorized" } - """)); + """; + mockHttp + .Expect(HttpMethod.Get, "http://localhost/api/transaction/transaction Id") + .Respond(HttpStatusCode.Unauthorized, new StringContent(expectedResponseBody)); using (var httpClient = mockHttp.ToHttpClient()) { var signhostApiClient = new SignhostApiClient(settings, httpClient); Func getTransaction = () => signhostApiClient.GetTransactionAsync("transaction Id"); - await getTransaction.Should().ThrowAsync(); + var exception = await getTransaction.Should().ThrowAsync(); + exception.Which.ResponseBody.Should().Be(expectedResponseBody); } mockHttp.VerifyNoOutstandingExpectation(); @@ -118,20 +120,22 @@ public async Task when_GetTransaction_is_called_and_the_authorization_is_bad_the public async Task when_GetTransaction_is_called_and_request_is_bad_then_we_should_get_a_BadRequestException() { var mockHttp = new MockHttpMessageHandler(); - mockHttp - .Expect(HttpMethod.Get, "http://localhost/api/transaction/transaction Id") - .Respond(HttpStatusCode.BadRequest, new StringContent(""" + const string expectedResponseBody = """ { "message": "Bad Request" } - """)); + """; + mockHttp + .Expect(HttpMethod.Get, "http://localhost/api/transaction/transaction Id") + .Respond(HttpStatusCode.BadRequest, new StringContent(expectedResponseBody)); using (var httpClient = mockHttp.ToHttpClient()) { var signhostApiClient = new SignhostApiClient(settings, httpClient); Func getTransaction = () => signhostApiClient.GetTransactionAsync("transaction Id"); - await getTransaction.Should().ThrowAsync(); + var exception = await getTransaction.Should().ThrowAsync(); + exception.Which.ResponseBody.Should().Be(expectedResponseBody); } mockHttp.VerifyNoOutstandingExpectation(); @@ -141,20 +145,22 @@ public async Task when_GetTransaction_is_called_and_request_is_bad_then_we_shoul public async Task when_GetTransaction_is_called_and_credits_have_run_out_then_we_should_get_a_OutOfCreditsException() { var mockHttp = new MockHttpMessageHandler(); - mockHttp - .Expect(HttpMethod.Get, "http://localhost/api/transaction/transaction Id") - .Respond(HttpStatusCode.PaymentRequired, new StringContent(""" + const string expectedResponseBody = """ { "type": "https://api.signhost.com/problem/subscription/out-of-credits" } - """)); + """; + mockHttp + .Expect(HttpMethod.Get, "http://localhost/api/transaction/transaction Id") + .Respond(HttpStatusCode.PaymentRequired, new StringContent(expectedResponseBody)); using (var httpClient = mockHttp.ToHttpClient()) { var signhostApiClient = new SignhostApiClient(settings, httpClient); Func getTransaction = () => signhostApiClient.GetTransactionAsync("transaction Id"); - await getTransaction.Should().ThrowAsync(); + var exception = await getTransaction.Should().ThrowAsync(); + exception.Which.ResponseBody.Should().Be(expectedResponseBody); } mockHttp.VerifyNoOutstandingExpectation(); @@ -164,13 +170,14 @@ public async Task when_GetTransaction_is_called_and_credits_have_run_out_then_we public async Task when_GetTransaction_is_called_and_not_found_then_we_should_get_a_NotFoundException() { var mockHttp = new MockHttpMessageHandler(); - mockHttp - .Expect(HttpMethod.Get, "http://localhost/api/transaction/transaction Id") - .Respond(HttpStatusCode.NotFound, new StringContent(""" + const string expectedResponseBody = """ { "message": "Not Found" } - """)); + """; + mockHttp + .Expect(HttpMethod.Get, "http://localhost/api/transaction/transaction Id") + .Respond(HttpStatusCode.NotFound, new StringContent(expectedResponseBody)); using (var httpClient = mockHttp.ToHttpClient()) { @@ -178,7 +185,9 @@ public async Task when_GetTransaction_is_called_and_not_found_then_we_should_get Func getTransaction = () => signhostApiClient.GetTransactionAsync("transaction Id"); - await getTransaction.Should().ThrowAsync().WithMessage("Not Found"); + var exception = await getTransaction.Should().ThrowAsync(); + exception.Which.Message.Should().Be("Not Found"); + exception.Which.ResponseBody.Should().Be(expectedResponseBody); } mockHttp.VerifyNoOutstandingExpectation(); @@ -188,21 +197,23 @@ public async Task when_GetTransaction_is_called_and_not_found_then_we_should_get public async Task when_GetTransaction_is_called_and_unkownerror_like_418_occures_then_we_should_get_a_SignhostException() { var mockHttp = new MockHttpMessageHandler(); - mockHttp - .Expect(HttpMethod.Get, "http://localhost/api/transaction/transaction Id") - .Respond((HttpStatusCode)418, new StringContent(""" + const string expectedResponseBody = """ { "message": "418 I'm a teapot" } - """)); + """; + mockHttp + .Expect(HttpMethod.Get, "http://localhost/api/transaction/transaction Id") + .Respond((HttpStatusCode)418, new StringContent(expectedResponseBody)); using (var httpClient = mockHttp.ToHttpClient()) { var signhostApiClient = new SignhostApiClient(settings, httpClient); Func getTransaction = () => signhostApiClient.GetTransactionAsync("transaction Id"); - await getTransaction.Should().ThrowAsync() - .WithMessage("*418*"); + var exception = await getTransaction.Should().ThrowAsync(); + exception.Which.Message.Should().ContainAll("418"); + exception.Which.ResponseBody.Should().Be(expectedResponseBody); } mockHttp.VerifyNoOutstandingExpectation(); @@ -212,20 +223,22 @@ await getTransaction.Should().ThrowAsync getTransaction = () => signhostApiClient.GetTransactionAsync("transaction Id"); - await getTransaction.Should().ThrowAsync(); + var exception = await getTransaction.Should().ThrowAsync(); + exception.Which.ResponseBody.Should().Be(expectedResponseBody); } mockHttp.VerifyNoOutstandingExpectation(); @@ -243,7 +256,8 @@ public async Task When_GetTransaction_is_called_on_gone_transaction_we_shoud_get var signhostApiClient = new SignhostApiClient(settings, httpClient); Func getTransaction = () => signhostApiClient.GetTransactionAsync("transaction Id"); - await getTransaction.Should().ThrowAsync>(); + var exception = await getTransaction.Should().ThrowAsync>(); + exception.Which.ResponseBody.Should().Be(JsonResources.GetTransaction); } mockHttp.VerifyNoOutstandingExpectation(); @@ -318,14 +332,15 @@ public async Task when_a_CreateTransaction_is_called_we_can_add_custom_http_head public async Task when_CreateTransaction_is_called_with_invalid_email_then_we_should_get_a_BadRequestException() { var mockHttp = new MockHttpMessageHandler(); - mockHttp - .Expect(HttpMethod.Post, "http://localhost/api/transaction") - .WithHeaders("Content-Type", "application/json") - .Respond(HttpStatusCode.BadRequest, new StringContent(""" + const string expectedResponseBody = """ { "message": "Bad Request" } - """)); + """; + mockHttp + .Expect(HttpMethod.Post, "http://localhost/api/transaction") + .WithHeaders("Content-Type", "application/json") + .Respond(HttpStatusCode.BadRequest, new StringContent(expectedResponseBody)); using (var httpClient = mockHttp.ToHttpClient()) { var signhostApiClient = new SignhostApiClient(settings, httpClient); @@ -337,7 +352,8 @@ public async Task when_CreateTransaction_is_called_with_invalid_email_then_we_sh testTransaction.Signers.Add(testSigner); Func getTransaction = () => signhostApiClient.CreateTransactionAsync(testTransaction); - await getTransaction.Should().ThrowAsync(); + var exception = await getTransaction.Should().ThrowAsync(); + exception.Which.ResponseBody.Should().Be(expectedResponseBody); } mockHttp.VerifyNoOutstandingExpectation(); @@ -347,20 +363,22 @@ public async Task when_CreateTransaction_is_called_with_invalid_email_then_we_sh public async Task when_a_function_is_called_with_a_wrong_endpoint_we_should_get_a_SignhostRestApiClientException() { var mockHttp = new MockHttpMessageHandler(); - mockHttp - .Expect(HttpMethod.Get, "http://localhost/api/transaction/transaction Id") - .Respond(HttpStatusCode.BadGateway, new StringContent(""" + const string expectedResponseBody = """ { "message": "Bad Gateway" } - """)); + """; + mockHttp + .Expect(HttpMethod.Get, "http://localhost/api/transaction/transaction Id") + .Respond(HttpStatusCode.BadGateway, new StringContent(expectedResponseBody)); using (var httpClient = mockHttp.ToHttpClient()) { var signhostApiClient = new SignhostApiClient(settings, httpClient); Func getTransaction = () => signhostApiClient.GetTransactionAsync("transaction Id"); - await getTransaction.Should().ThrowAsync() - .WithMessage("Bad Gateway"); + var exception = await getTransaction.Should().ThrowAsync(); + exception.Which.Message.Should().Be("Bad Gateway"); + exception.Which.ResponseBody.Should().Be(expectedResponseBody); } mockHttp.VerifyNoOutstandingExpectation(); From 76a66f458bfd0959d3daf6d114c6bf519d659f92 Mon Sep 17 00:00:00 2001 From: Anthony Timmers Date: Thu, 27 Nov 2025 22:02:23 +0100 Subject: [PATCH 21/28] Remove obsolete methods --- .../SignhostApiClientTests.cs | 12 +- .../Rest/ISignHostApiClient.cs | 114 +--------------- .../Rest/SignHostApiClient.cs | 122 +----------------- 3 files changed, 14 insertions(+), 234 deletions(-) diff --git a/src/SignhostAPIClient.Tests/SignhostApiClientTests.cs b/src/SignhostAPIClient.Tests/SignhostApiClientTests.cs index 031280e..fe22dbd 100644 --- a/src/SignhostAPIClient.Tests/SignhostApiClientTests.cs +++ b/src/SignhostAPIClient.Tests/SignhostApiClientTests.cs @@ -437,7 +437,10 @@ public async Task when_AddOrReplaceFileToTransaction_is_called_then_we_should_ha // Create a 0 sized file using (Stream file = System.IO.File.Create("unittestdocument.pdf")) { - await signhostApiClient.AddOrReplaceFileToTransaction(file, "transaction Id", "file Id"); + await signhostApiClient.AddOrReplaceFileToTransactionAsync( + file, + "transaction Id", + "file Id"); } } @@ -456,7 +459,10 @@ public async Task when_AddOrReplaceFileToTransaction_is_called_default_digest_is using (var httpClient = mockHttp.ToHttpClient()) { var signhostApiClient = new SignhostApiClient(settings, httpClient); - await signhostApiClient.AddOrReplaceFileToTransaction(new MemoryStream(), "transaction Id", "file Id"); + await signhostApiClient.AddOrReplaceFileToTransactionAsync( + new MemoryStream(), + "transaction Id", + "file Id"); } mockHttp.VerifyNoOutstandingExpectation(); @@ -657,7 +663,7 @@ MockedRequest AddHeaders(MockedRequest request) var result = await signhostApiClient.CreateTransactionAsync(new Transaction()); await signhostApiClient.AddOrReplaceFileMetaToTransactionAsync(new FileMeta(), result.Id, "somefileid"); using (Stream file = System.IO.File.Create("unittestdocument.pdf")) { - await signhostApiClient.AddOrReplaceFileToTransaction(file, result.Id, "somefileid"); + await signhostApiClient.AddOrReplaceFileToTransactionAsync(file, result.Id, "somefileid"); } await signhostApiClient.StartTransactionAsync(result.Id); } diff --git a/src/SignhostAPIClient/Rest/ISignHostApiClient.cs b/src/SignhostAPIClient/Rest/ISignHostApiClient.cs index ee2fa04..fb0949f 100644 --- a/src/SignhostAPIClient/Rest/ISignHostApiClient.cs +++ b/src/SignhostAPIClient/Rest/ISignHostApiClient.cs @@ -10,13 +10,6 @@ namespace Signhost.APIClient.Rest; /// public interface ISignhostApiClient { - /// - /// Creates a new transaction. - /// - /// A transaction model. - /// A transaction object. - Task CreateTransactionAsync(Transaction transaction); - /// /// Creates a new transaction. /// @@ -27,21 +20,6 @@ Task CreateTransactionAsync( Transaction transaction, CancellationToken cancellationToken = default); - /// - /// Adds meta data for a file to an existing transaction by providing a - /// file location and a transaction id. - /// - /// Meta data for the file. - /// A valid transaction Id of an existing transaction. - /// An Id for the file. Should be the same as the fileId in the . - /// A task. - /// Make sure to call this method before - /// . - Task AddOrReplaceFileMetaToTransactionAsync( - FileMeta fileMeta, - string transactionId, - string fileId); - /// /// Adds meta data for a file to an existing transaction by providing a /// file location and a transaction id. @@ -59,22 +37,6 @@ Task AddOrReplaceFileMetaToTransactionAsync( string fileId, CancellationToken cancellationToken = default); - /// - /// Add a file to a existing transaction by providing a file location - /// and a transaction id. - /// - /// A Stream containing the file to upload. - /// A valid transaction Id of an existing transaction. - /// A Id for the file. Using the file name is recommended. - /// If a file with the same fileId allready exists the file wil be replaced. - /// . - /// A Task. - Task AddOrReplaceFileToTransactionAsync( - Stream fileStream, - string transactionId, - string fileId, - FileUploadOptions uploadOptions); - /// /// Add a file to a existing transaction by providing a file location /// and a transaction id. @@ -90,25 +52,9 @@ Task AddOrReplaceFileToTransactionAsync( Stream fileStream, string transactionId, string fileId, - FileUploadOptions uploadOptions, + FileUploadOptions? uploadOptions = default, CancellationToken cancellationToken = default); - /// - /// Add a file to a existing transaction by providing a file location - /// and a transaction id. - /// - /// A string representation of the file path. - /// A valid transaction Id of an existing transaction. - /// A Id for the file. Using the file name is recommended. - /// If a file with the same fileId allready exists the file wil be replaced. - /// Optional . - /// A Task. - Task AddOrReplaceFileToTransactionAsync( - string filePath, - string transactionId, - string fileId, - FileUploadOptions uploadOptions); - /// /// Add a file to a existing transaction by providing a file location /// and a transaction id. @@ -124,16 +70,9 @@ Task AddOrReplaceFileToTransactionAsync( string filePath, string transactionId, string fileId, - FileUploadOptions uploadOptions, + FileUploadOptions? uploadOptions = default, CancellationToken cancellationToken = default); - /// - /// start a existing transaction by providing transaction id. - /// - /// A valid transaction Id of an existing transaction. - /// A Task. - Task StartTransactionAsync(string transactionId); - /// /// start a existing transaction by providing transaction id. /// @@ -144,13 +83,6 @@ Task StartTransactionAsync( string transactionId, CancellationToken cancellationToken = default); - /// - /// Gets an exisiting transaction by providing a transaction id. - /// - /// A valid transaction id for an existing transaction. - /// A object. - Task GetTransactionAsync(string transactionId); - /// /// Gets an exisiting transaction by providing a transaction id. /// @@ -161,13 +93,6 @@ Task GetTransactionAsync( string transactionId, CancellationToken cancellationToken = default); - /// - /// Gets a existing transaction by providing a transaction id. - /// - /// A valid transaction Id of an existing transaction. - /// A object. - Task> GetTransactionResponseAsync(string transactionId); - /// /// Gets a existing transaction by providing a transaction id. /// @@ -178,26 +103,6 @@ Task> GetTransactionResponseAsync( string transactionId, CancellationToken cancellationToken = default); - /// - /// Deletes a existing transaction by providing a transaction id. - /// - /// A valid transaction Id of an existing transaction. - /// A cancellation token. - /// A Task. - Task DeleteTransactionAsync( - string transactionId, - CancellationToken cancellationToken = default); - - /// - /// Deletes a existing transaction by providing a transaction id. - /// - /// A valid transaction Id of an existing transaction. - /// Optional . - /// A Task. - Task DeleteTransactionAsync( - string transactionId, - DeleteTransactionOptions options); - /// /// Deletes a existing transaction by providing a transaction id. /// @@ -210,14 +115,6 @@ Task DeleteTransactionAsync( DeleteTransactionOptions? options = default, CancellationToken cancellationToken = default); - /// - /// Gets the signed document of a finished transaction by providing transaction id. - /// - /// A valid transaction Id of an existing transaction. - /// A valid file Id of a signed document. - /// Returns a stream containing the signed document data. - Task GetDocumentAsync(string transactionId, string fileId); - /// /// Gets the signed document of a finished transaction by providing transaction id. /// @@ -230,13 +127,6 @@ Task GetDocumentAsync( string fileId, CancellationToken cancellationToken = default); - /// - /// Gets the receipt of a finished transaction by providing transaction id. - /// - /// A valid transaction Id of an finnished transaction. - /// Returns a stream containing the receipt data. - Task GetReceiptAsync(string transactionId); - /// /// Gets the receipt of a finished transaction by providing transaction id. /// diff --git a/src/SignhostAPIClient/Rest/SignHostApiClient.cs b/src/SignhostAPIClient/Rest/SignHostApiClient.cs index d06b718..a25fba9 100644 --- a/src/SignhostAPIClient/Rest/SignHostApiClient.cs +++ b/src/SignhostAPIClient/Rest/SignHostApiClient.cs @@ -71,12 +71,6 @@ private string ApplicationHeader private string AuthorizationHeader => $"APIKey {settings.UserToken}"; - /// - public async Task CreateTransactionAsync( - Transaction transaction) - => await CreateTransactionAsync(transaction, default) - .ConfigureAwait(false); - /// public async Task CreateTransactionAsync( Transaction transaction, @@ -100,12 +94,6 @@ public async Task CreateTransactionAsync( "Failed to deserialize the transaction."); } - /// - public async Task> GetTransactionResponseAsync( - string transactionId) - => await GetTransactionResponseAsync(transactionId, default) - .ConfigureAwait(false); - /// public async Task> GetTransactionResponseAsync( string transactionId, @@ -131,11 +119,6 @@ public async Task> GetTransactionResponseAsync( return new ApiResponse(result, transaction); } - /// - public async Task GetTransactionAsync(string transactionId) - => await GetTransactionAsync(transactionId, default) - .ConfigureAwait(false); - /// public async Task GetTransactionAsync( string transactionId, @@ -156,25 +139,7 @@ await response.EnsureAvailableStatusCodeAsync(cancellationToken) /// public async Task DeleteTransactionAsync( string transactionId, - CancellationToken cancellationToken = default) - => await DeleteTransactionAsync( - transactionId, - default, - cancellationToken).ConfigureAwait(false); - - /// - public async Task DeleteTransactionAsync( - string transactionId, - DeleteTransactionOptions options) - => await DeleteTransactionAsync( - transactionId, - options, - default).ConfigureAwait(false); - - /// - public async Task DeleteTransactionAsync( - string transactionId, - DeleteTransactionOptions? options, + DeleteTransactionOptions? options = default, CancellationToken cancellationToken = default) { if (transactionId is null) { @@ -199,17 +164,6 @@ await client .ConfigureAwait(false); } - /// - public async Task AddOrReplaceFileMetaToTransactionAsync( - FileMeta fileMeta, - string transactionId, - string fileId) - => await AddOrReplaceFileMetaToTransactionAsync( - fileMeta, - transactionId, - fileId, - default).ConfigureAwait(false); - /// public async Task AddOrReplaceFileMetaToTransactionAsync( FileMeta fileMeta, @@ -246,25 +200,12 @@ await client .ConfigureAwait(false); } - /// - public async Task AddOrReplaceFileToTransactionAsync( - Stream fileStream, - string transactionId, - string fileId, - FileUploadOptions? uploadOptions) - => await AddOrReplaceFileToTransactionAsync( - fileStream, - transactionId, - fileId, - uploadOptions, - default).ConfigureAwait(false); - /// public async Task AddOrReplaceFileToTransactionAsync( Stream fileStream, string transactionId, string fileId, - FileUploadOptions? uploadOptions, + FileUploadOptions? uploadOptions = default, CancellationToken cancellationToken = default) { if (fileStream is null) { @@ -304,38 +245,12 @@ await client .ConfigureAwait(false); } - /// - public Task AddOrReplaceFileToTransaction( - Stream fileStream, - string transactionId, - string fileId) - { - return AddOrReplaceFileToTransactionAsync( - fileStream, - transactionId, - fileId, - null); - } - - /// - public async Task AddOrReplaceFileToTransactionAsync( - string filePath, - string transactionId, - string fileId, - FileUploadOptions? uploadOptions) - => await AddOrReplaceFileToTransactionAsync( - filePath, - transactionId, - fileId, - uploadOptions, - default).ConfigureAwait(false); - /// public async Task AddOrReplaceFileToTransactionAsync( string filePath, string transactionId, string fileId, - FileUploadOptions? uploadOptions, + FileUploadOptions? uploadOptions = default, CancellationToken cancellationToken = default) { if (filePath is null) { @@ -358,25 +273,6 @@ await AddOrReplaceFileToTransactionAsync( } } - /// - public Task AddOrReplaceFileToTransaction( - string filePath, - string transactionId, - string fileId) - { - return AddOrReplaceFileToTransactionAsync( - filePath, - transactionId, - fileId, - null); - } - - /// - public async Task StartTransactionAsync( - string transactionId) - => await StartTransactionAsync(transactionId, default) - .ConfigureAwait(false); - /// public async Task StartTransactionAsync( string transactionId, @@ -399,11 +295,6 @@ await client .ConfigureAwait(false); } - /// - public async Task GetReceiptAsync(string transactionId) - => await GetReceiptAsync(transactionId, default) - .ConfigureAwait(false); - /// public async Task GetReceiptAsync( string transactionId, @@ -425,13 +316,6 @@ public async Task GetReceiptAsync( return result; } - /// - public async Task GetDocumentAsync( - string transactionId, - string fileId) - => await GetDocumentAsync(transactionId, fileId, default) - .ConfigureAwait(false); - /// public async Task GetDocumentAsync( string transactionId, From b4eb6a2d91cd5d1274b37b4d9129cac88feb12f6 Mon Sep 17 00:00:00 2001 From: Anthony Timmers Date: Thu, 27 Nov 2025 22:11:44 +0100 Subject: [PATCH 22/28] Refactor to request objects for transaction creation --- README.md | 6 +- .../TransactionTests.cs | 6 +- .../SignhostApiClientTests.cs | 37 +++-- .../Rest/DataObjects/CreateReceiverRequest.cs | 47 ++++++ .../Rest/DataObjects/CreateSignerRequest.cs | 145 ++++++++++++++++++ .../DataObjects/CreateTransactionRequest.cs | 67 ++++++++ .../Rest/ISignHostApiClient.cs | 4 +- .../Rest/SignHostApiClient.cs | 8 +- 8 files changed, 289 insertions(+), 31 deletions(-) create mode 100644 src/SignhostAPIClient/Rest/DataObjects/CreateReceiverRequest.cs create mode 100644 src/SignhostAPIClient/Rest/DataObjects/CreateSignerRequest.cs create mode 100644 src/SignhostAPIClient/Rest/DataObjects/CreateTransactionRequest.cs diff --git a/README.md b/README.md index 16fa878..4cfd717 100644 --- a/README.md +++ b/README.md @@ -21,9 +21,9 @@ var settings = new SignhostApiClientSettings( var client = new SignhostApiClient(settings); -var transaction = await client.CreateTransactionAsync(new Transaction { - Signers = new List { - new Signer { +var transaction = await client.CreateTransactionAsync(new CreateTransactionRequest { + Signers = new List { + new CreateSignerRequest { Email = "john.doe@example.com", SignRequestMessage = "Could you please sign this document?", SendSignRequest = true, diff --git a/src/SignhostAPIClient.IntegrationTests/TransactionTests.cs b/src/SignhostAPIClient.IntegrationTests/TransactionTests.cs index 3bcfb80..bb15303 100644 --- a/src/SignhostAPIClient.IntegrationTests/TransactionTests.cs +++ b/src/SignhostAPIClient.IntegrationTests/TransactionTests.cs @@ -43,7 +43,7 @@ public async Task Given_complex_transaction_When_created_and_started_Then_all_pr var receiverName = "Jane Receiver"; var receiverReference = "RECEIVER-001"; - var transaction = new Transaction { + var transaction = new CreateTransactionRequest { Seal = false, Reference = testReference, PostbackUrl = testPostbackUrl, @@ -55,7 +55,7 @@ public async Task Given_complex_transaction_When_created_and_started_Then_all_pr TestContext = "integration-test", }, Signers = [ - new Signer { + new CreateSignerRequest { Id = "signer1", Email = signerEmail, Reference = signerReference, @@ -88,7 +88,7 @@ public async Task Given_complex_transaction_When_created_and_started_Then_all_pr } ], Receivers = [ - new Receiver { + new CreateReceiverRequest { Name = receiverName, Email = receiverEmail, Language = "en-US", diff --git a/src/SignhostAPIClient.Tests/SignhostApiClientTests.cs b/src/SignhostAPIClient.Tests/SignhostApiClientTests.cs index fe22dbd..9340462 100644 --- a/src/SignhostAPIClient.Tests/SignhostApiClientTests.cs +++ b/src/SignhostAPIClient.Tests/SignhostApiClientTests.cs @@ -292,10 +292,11 @@ public async Task when_a_CreateTransaction_is_called_then_we_should_have_called_ using (var httpClient = mockHttp.ToHttpClient()) { var signhostApiClient = new SignhostApiClient(settings, httpClient); - Signer testSigner = new Signer(); - testSigner.Email = "firstname.lastname@gmail.com"; + var testSigner = new CreateSignerRequest { + Email = "firstname.lastname@gmail.com" + }; - Transaction testTransaction = new Transaction(); + var testTransaction = new CreateTransactionRequest(); testTransaction.Signers.Add(testSigner); var result = await signhostApiClient.CreateTransactionAsync(testTransaction); @@ -320,7 +321,7 @@ public async Task when_a_CreateTransaction_is_called_we_can_add_custom_http_head var signhostApiClient = new SignhostApiClient(settings, httpClient); - Transaction testTransaction = new Transaction(); + var testTransaction = new CreateTransactionRequest(); var result = await signhostApiClient.CreateTransactionAsync(testTransaction); } @@ -345,10 +346,11 @@ public async Task when_CreateTransaction_is_called_with_invalid_email_then_we_sh using (var httpClient = mockHttp.ToHttpClient()) { var signhostApiClient = new SignhostApiClient(settings, httpClient); - Signer testSigner = new Signer(); - testSigner.Email = "firstname.lastnamegmail.com"; + var testSigner = new CreateSignerRequest { + Email = "firstname.lastnamegmail.com" + }; - Transaction testTransaction = new Transaction(); + var testTransaction = new CreateTransactionRequest(); testTransaction.Signers.Add(testSigner); Func getTransaction = () => signhostApiClient.CreateTransactionAsync(testTransaction); @@ -585,20 +587,17 @@ public async Task When_a_transaction_json_is_returned_it_is_deserialized_correct var signhostApiClient = new SignhostApiClient(settings, httpClient); - var result = await signhostApiClient.CreateTransactionAsync(new Transaction - { - Signers = new List{ - new Signer - { - Verifications = new List - { - new PhoneNumberVerification - { + var result = await signhostApiClient.CreateTransactionAsync(new CreateTransactionRequest { + Signers = [ + new CreateSignerRequest { + Email = "test@example.com", + Verifications = [ + new PhoneNumberVerification { Number = "31615087075" } - } + ] } - } + ] }); result.Id.Should().Be("50262c3f-9744-45bf-a4c6-8a3whatever"); @@ -660,7 +659,7 @@ MockedRequest AddHeaders(MockedRequest request) clientSettings.AddHeader = add => add("X-Custom", "test"); var signhostApiClient = new SignhostApiClient(clientSettings, httpClient); - var result = await signhostApiClient.CreateTransactionAsync(new Transaction()); + var result = await signhostApiClient.CreateTransactionAsync(new CreateTransactionRequest()); await signhostApiClient.AddOrReplaceFileMetaToTransactionAsync(new FileMeta(), result.Id, "somefileid"); using (Stream file = System.IO.File.Create("unittestdocument.pdf")) { await signhostApiClient.AddOrReplaceFileToTransactionAsync(file, result.Id, "somefileid"); diff --git a/src/SignhostAPIClient/Rest/DataObjects/CreateReceiverRequest.cs b/src/SignhostAPIClient/Rest/DataObjects/CreateReceiverRequest.cs new file mode 100644 index 0000000..59a82f0 --- /dev/null +++ b/src/SignhostAPIClient/Rest/DataObjects/CreateReceiverRequest.cs @@ -0,0 +1,47 @@ +namespace Signhost.APIClient.Rest.DataObjects; + +/// +/// Request object for creating a receiver in a transaction. +/// Receiver configuration for getting copies of signed documents. +/// +public class CreateReceiverRequest +{ + /// + /// Gets or sets the receiver's name. + /// + public string? Name { get; set; } + + /// + /// Gets or sets the receiver's email address (required). + /// + public string Email { get; set; } = default!; + + /// + /// Gets or sets the language for receiver communications. + /// Supported values: de-DE, en-US, es-ES, fr-FR, it-IT, pl-PL, nl-NL. + /// Default is nl-NL. + /// + public string? Language { get; set; } + + /// + /// Gets or sets the custom subject for notification email. + /// Maximum of 64 characters allowed. Omitting this parameter will enable the default subject. + /// + public string? Subject { get; set; } + + /// + /// Gets or sets the custom message for notification email (required). + /// Newlines can be created by including a \n in the message. HTML is not allowed. + /// + public string Message { get; set; } = default!; + + /// + /// Gets or sets the custom reference for this receiver. + /// + public string? Reference { get; set; } + + /// + /// Gets or sets the custom receiver data (JSON object only). + /// + public dynamic? Context { get; set; } +} diff --git a/src/SignhostAPIClient/Rest/DataObjects/CreateSignerRequest.cs b/src/SignhostAPIClient/Rest/DataObjects/CreateSignerRequest.cs new file mode 100644 index 0000000..20c2f1a --- /dev/null +++ b/src/SignhostAPIClient/Rest/DataObjects/CreateSignerRequest.cs @@ -0,0 +1,145 @@ +using System; +using System.Collections.Generic; + +namespace Signhost.APIClient.Rest.DataObjects; + +/// +/// Request object for creating a new signer in a transaction. Defines the signer's identity, +/// authentication/verification requirements, notification preferences, and signing behavior. +/// +/// +/// Key Requirements: +/// +/// Email address is mandatory +/// Either Authentications or Verifications must be provided (or both) +/// Signers with AllowDelegation enabled cannot have Authentications +/// SendSignRequest determines if sign request emails are sent automatically +/// +/// +public class CreateSignerRequest +{ + /// + /// Gets or sets the signer identifier (will be generated if not provided). + /// + public string? Id { get; set; } + + /// + /// Gets or sets when the signer's access expires. + /// + public DateTimeOffset? Expires { get; set; } + + /// + /// Gets or sets the signer's email address (required). + /// + public string Email { get; set; } = default!; + + /// + /// Gets or sets the list of authentication methods that the signer has to authenticate with. + /// The order in which the authentications are provided determine in which order the signer will have to perform the specified method. + /// Authentications must be performed before the document(s) can be viewed. + /// + public IList? Authentications { get; set; } + + /// + /// Gets or sets the list of verification methods that the signer has to verify with. + /// The order in which the verifications are provided determine in which order the signer will have to perform the specified method. + /// Verifications must be performed before the document(s) can be signed. + /// + /// + /// Critical Requirement: You must use one of the following verification methods as the last verification in the list: + /// + /// Consent + /// PhoneNumber + /// Scribble + /// CSC Qualified* + /// + /// Important Notes: + /// + /// CSC Qualified must always be the final verification if used + /// The other three methods (Consent, PhoneNumber, Scribble) can be succeeded by other verification methods + /// + /// + public IList? Verifications { get; set; } + + /// + /// Gets or sets a value indicating whether to send sign request to this signer's email address. + /// Default is true. + /// + public bool SendSignRequest { get; set; } = true; + + /// + /// Gets or sets a value indicating whether to send a confirmation email to the signer after signing. + /// Default value is the value of . + /// + public bool? SendSignConfirmation { get; set; } + + /// + /// Gets or sets the subject of the sign request email in plain text. + /// Maximum of 64 characters allowed. If omitted, the default subject will be used. + /// + public string? SignRequestSubject { get; set; } + + /// + /// Gets or sets the message of the sign request email in plain text. HTML is not allowed. + /// Newlines can be created by including a \n. + /// Required if is true. + /// + public string? SignRequestMessage { get; set; } + + /// + /// Gets or sets the number of days between automatic reminder emails sent to this signer. + /// Set to -1 to disable reminders entirely for this signer. + /// Set to 0 to use your organization's default reminder interval. + /// Set to a positive number (e.g., 3, 7) to send reminders every N days. + /// Default is 7. + /// + /// + /// Reminders are only sent if is true and the signer hasn't completed signing yet. + /// + public int? DaysToRemind { get; set; } + + /// + /// Gets or sets the language for signer interface and emails. + /// Supported values: de-DE, en-US, es-ES, fr-FR, it-IT, pl-PL, nl-NL. + /// Default is nl-NL. + /// + public string? Language { get; set; } + + /// + /// Gets or sets the custom reference for this signer. + /// + public string? Reference { get; set; } + + /// + /// Gets or sets the custom introduction text shown to the signer during the signing process. + /// This will be shown on the first screen to the signer and supports limited markdown markup. + /// + /// + /// The following markup is supported: + /// + /// # Headings + /// *Emphasis* / _Emphasis_ + /// **Strong** / __Strong__ + /// 1. Ordered and - Unordered lists + /// + /// + public string? IntroText { get; set; } + + /// + /// Gets or sets the URL to redirect signer after signing. + /// Default is https://signhost.com. + /// + public string? ReturnUrl { get; set; } + + /// + /// Gets or sets a value indicating whether this signer can delegate signing to another person. + /// Cannot be used together with . + /// Default is false. + /// + public bool AllowDelegation { get; set; } + + /// + /// Gets or sets the custom signer data (dynamic JSON object). + /// + public dynamic? Context { get; set; } +} diff --git a/src/SignhostAPIClient/Rest/DataObjects/CreateTransactionRequest.cs b/src/SignhostAPIClient/Rest/DataObjects/CreateTransactionRequest.cs new file mode 100644 index 0000000..7a2e028 --- /dev/null +++ b/src/SignhostAPIClient/Rest/DataObjects/CreateTransactionRequest.cs @@ -0,0 +1,67 @@ +using System.Collections.Generic; + +namespace Signhost.APIClient.Rest.DataObjects; + +/// +/// Request object for creating a new transaction. +/// Transaction creation data including signers, receivers, files, and configuration. +/// +public class CreateTransactionRequest +{ + /// + /// Gets or sets a value indicating whether to seal the transaction (no signers required). + /// When true, the transaction is automatically completed without requiring signatures. + /// + public bool Seal { get; set; } + + /// + /// Gets or sets the custom reference identifier for the transaction. + /// + public string? Reference { get; set; } + + /// + /// Gets or sets the URL to receive status notifications about the transaction. + /// + public string? PostbackUrl { get; set; } + + /// + /// Gets or sets the number of days until the transaction expires. + /// If 0, uses organization default. Maximum value depends on organization settings. + /// + public int DaysToExpire { get; set; } + + /// + /// Gets or sets a value indicating whether to send email notifications to signers and receivers. + /// + public bool SendEmailNotifications { get; set; } + + /// + /// Gets or sets the mode for sign request delivery. + /// 0: No sign requests, 1: Send immediately, 2: Send when ready (default when signers have SendSignRequest enabled). + /// + public int SignRequestMode { get; set; } + + /// + /// Gets or sets the language code for transaction interface and emails. + /// Supported values: de-DE, en-US, es-ES, fr-FR, it-IT, pl-PL, nl-NL. + /// + public string? Language { get; set; } + + /// + /// Gets or sets the custom JSON object for additional transaction data. + /// Only JSON objects are allowed (no arrays or primitives). + /// + public dynamic? Context { get; set; } + + /// + /// Gets or sets the list of signers for the transaction. + /// + public IList Signers { get; set; } = + new List(); + + /// + /// Gets or sets the list of receivers who get copies of completed documents. + /// + public IList Receivers { get; set; } = + new List(); +} diff --git a/src/SignhostAPIClient/Rest/ISignHostApiClient.cs b/src/SignhostAPIClient/Rest/ISignHostApiClient.cs index fb0949f..ceb27d0 100644 --- a/src/SignhostAPIClient/Rest/ISignHostApiClient.cs +++ b/src/SignhostAPIClient/Rest/ISignHostApiClient.cs @@ -13,11 +13,11 @@ public interface ISignhostApiClient /// /// Creates a new transaction. /// - /// A transaction model. + /// A transaction creation request. /// A cancellation token. /// A transaction object. Task CreateTransactionAsync( - Transaction transaction, + CreateTransactionRequest request, CancellationToken cancellationToken = default); /// diff --git a/src/SignhostAPIClient/Rest/SignHostApiClient.cs b/src/SignhostAPIClient/Rest/SignHostApiClient.cs index a25fba9..e617709 100644 --- a/src/SignhostAPIClient/Rest/SignHostApiClient.cs +++ b/src/SignhostAPIClient/Rest/SignHostApiClient.cs @@ -73,17 +73,17 @@ private string AuthorizationHeader /// public async Task CreateTransactionAsync( - Transaction transaction, + CreateTransactionRequest request, CancellationToken cancellationToken = default) { - if (transaction is null) { - throw new ArgumentNullException(nameof(transaction)); + if (request is null) { + throw new ArgumentNullException(nameof(request)); } var result = await client .PostAsync( "transaction", - JsonContent.From(transaction), + JsonContent.From(request), cancellationToken) .EnsureSignhostSuccessStatusCodeAsync() .ConfigureAwait(false); From 9e5c6257ee7c31de631030595c9bb8ab5ad50353 Mon Sep 17 00:00:00 2001 From: Anthony Timmers Date: Thu, 27 Nov 2025 22:12:27 +0100 Subject: [PATCH 23/28] Fix simplify null checks --- .../Rest/SignHostApiClient.cs | 56 +++++-------------- 1 file changed, 14 insertions(+), 42 deletions(-) diff --git a/src/SignhostAPIClient/Rest/SignHostApiClient.cs b/src/SignhostAPIClient/Rest/SignHostApiClient.cs index e617709..2caa708 100644 --- a/src/SignhostAPIClient/Rest/SignHostApiClient.cs +++ b/src/SignhostAPIClient/Rest/SignHostApiClient.cs @@ -76,9 +76,7 @@ public async Task CreateTransactionAsync( CreateTransactionRequest request, CancellationToken cancellationToken = default) { - if (request is null) { - throw new ArgumentNullException(nameof(request)); - } + ArgumentNullException.ThrowIfNull(request); var result = await client .PostAsync( @@ -99,9 +97,7 @@ public async Task> GetTransactionResponseAsync( string transactionId, CancellationToken cancellationToken = default) { - if (transactionId is null) { - throw new ArgumentNullException(nameof(transactionId)); - } + ArgumentNullException.ThrowIfNull(transactionId); if (string.IsNullOrWhiteSpace(transactionId)) { throw new ArgumentException("Cannot be empty or contain only whitespaces.", nameof(transactionId)); @@ -142,9 +138,7 @@ public async Task DeleteTransactionAsync( DeleteTransactionOptions? options = default, CancellationToken cancellationToken = default) { - if (transactionId is null) { - throw new ArgumentNullException(nameof(transactionId)); - } + ArgumentNullException.ThrowIfNull(transactionId); if (string.IsNullOrWhiteSpace(transactionId)) { throw new ArgumentException("Cannot be empty or contain only whitespaces.", nameof(transactionId)); @@ -171,21 +165,15 @@ public async Task AddOrReplaceFileMetaToTransactionAsync( string fileId, CancellationToken cancellationToken = default) { - if (fileMeta is null) { - throw new ArgumentNullException("fileMeta"); - } + ArgumentNullException.ThrowIfNull(fileMeta); - if (transactionId is null) { - throw new ArgumentNullException(nameof(transactionId)); - } + ArgumentNullException.ThrowIfNull(transactionId); if (string.IsNullOrWhiteSpace(transactionId)) { throw new ArgumentException("Cannot be empty or contain only whitespaces.", nameof(transactionId)); } - if (fileId is null) { - throw new ArgumentNullException(nameof(fileId)); - } + ArgumentNullException.ThrowIfNull(fileId); if (string.IsNullOrWhiteSpace(fileId)) { throw new ArgumentException("Cannot be empty or contain only whitespaces.", nameof(fileId)); @@ -208,21 +196,15 @@ public async Task AddOrReplaceFileToTransactionAsync( FileUploadOptions? uploadOptions = default, CancellationToken cancellationToken = default) { - if (fileStream is null) { - throw new ArgumentNullException(nameof(fileStream)); - } + ArgumentNullException.ThrowIfNull(fileStream); - if (transactionId is null) { - throw new ArgumentNullException(nameof(transactionId)); - } + ArgumentNullException.ThrowIfNull(transactionId); if (string.IsNullOrWhiteSpace(transactionId)) { throw new ArgumentException("Cannot be empty or contain only whitespaces.", nameof(transactionId)); } - if (fileId is null) { - throw new ArgumentNullException(nameof(fileId)); - } + ArgumentNullException.ThrowIfNull(fileId); if (string.IsNullOrWhiteSpace(fileId)) { throw new ArgumentException("Cannot be empty or contain only whitespaces.", nameof(fileId)); @@ -253,9 +235,7 @@ public async Task AddOrReplaceFileToTransactionAsync( FileUploadOptions? uploadOptions = default, CancellationToken cancellationToken = default) { - if (filePath is null) { - throw new ArgumentNullException(nameof(filePath)); - } + ArgumentNullException.ThrowIfNull(filePath); using (Stream fileStream = System.IO.File.Open( filePath, @@ -278,9 +258,7 @@ public async Task StartTransactionAsync( string transactionId, CancellationToken cancellationToken = default) { - if (transactionId is null) { - throw new ArgumentNullException(nameof(transactionId)); - } + ArgumentNullException.ThrowIfNull(transactionId); if (string.IsNullOrWhiteSpace(transactionId)) { throw new ArgumentException("Cannot be empty or contain only whitespaces.", nameof(transactionId)); @@ -300,9 +278,7 @@ public async Task GetReceiptAsync( string transactionId, CancellationToken cancellationToken = default) { - if (transactionId is null) { - throw new ArgumentNullException(nameof(transactionId)); - } + ArgumentNullException.ThrowIfNull(transactionId); if (string.IsNullOrWhiteSpace(transactionId)) { throw new ArgumentException("Cannot be empty or contain only whitespaces.", nameof(transactionId)); @@ -322,17 +298,13 @@ public async Task GetDocumentAsync( string fileId, CancellationToken cancellationToken = default) { - if (transactionId is null) { - throw new ArgumentNullException(nameof(transactionId)); - } + ArgumentNullException.ThrowIfNull(transactionId); if (string.IsNullOrWhiteSpace(transactionId)) { throw new ArgumentException("Cannot be empty or contain only whitespaces.", nameof(transactionId)); } - if (fileId is null) { - throw new ArgumentNullException(nameof(fileId)); - } + ArgumentNullException.ThrowIfNull(fileId); if (string.IsNullOrWhiteSpace(fileId)) { throw new ArgumentException("Cannot be empty or contain only whitespaces.", nameof(fileId)); From ed88e7983fea5ce8b16fbe94d5105e01552d0db2 Mon Sep 17 00:00:00 2001 From: Anthony Timmers Date: Thu, 27 Nov 2025 22:28:38 +0100 Subject: [PATCH 24/28] Refactor various style improvements --- .editorconfig | 46 +- docs/components-vs-dataobjects.md | 45 + .../TransactionTests.cs | 45 +- .../FieldValueTests.cs | 86 +- .../FileFieldTypeTests.cs | 14 +- .../JSON/JsonResources.cs | 15 +- .../LevelEnumConverterTests.cs | 35 +- src/SignhostAPIClient.Tests/PostbackTests.cs | 2 +- .../SignhostApiClientTests.cs | 617 ++++---- .../SignhostApiReceiverTests.cs | 54 +- src/SignhostAPIClient/Rest/ApiResponse.cs | 5 +- .../Rest/DataObjects/Activity.cs | 2 +- .../Rest/DataObjects/ActivityType.cs | 2 +- .../DataObjects/CreateTransactionRequest.cs | 6 +- .../DataObjects/DeleteTransactionOptions.cs | 2 +- .../Rest/DataObjects/FileEntry.cs | 2 +- .../Rest/DataObjects/FileLink.cs | 2 +- .../Rest/DataObjects/FileMetaData/Field.cs | 2 +- .../Rest/DataObjects/FileMetaData/FileMeta.cs | 2 +- .../FileMetaData/FileSignerMeta.cs | 2 +- .../Rest/DataObjects/FileMetaData/Location.cs | 2 +- .../Rest/DataObjects/Signer.cs | 3 +- .../Rest/DataObjects/Transaction.cs | 9 +- .../Rest/DataObjects/TransactionStatus.cs | 2 +- .../Verifications/ConsentVerification.cs | 2 +- .../Verifications/DigidVerification.cs | 2 +- .../Verifications/IVerification.cs | 2 +- .../Verifications/IdealVerification.cs | 2 +- .../Verifications/IdinVerification.cs | 2 +- .../Verifications/PhoneNumberVerification.cs | 2 +- .../Verifications/ScribbleVerification.cs | 2 +- .../Verifications/SurfnetVerification.cs | 2 +- .../BadAuthorizationException.cs | 2 +- .../Rest/ErrorHandling/NotFoundException.cs | 2 +- .../SignhostRestApiClientException.cs | 2 +- .../Rest/FileDigestOptions.cs | 2 +- .../Rest/FileUploadOptions.cs | 5 +- .../Rest/HttpContentJsonExtensions.cs | 6 +- .../Rest/ISignhostApiClientSettings.cs | 2 +- src/SignhostAPIClient/Rest/JsonContent.cs | 2 +- .../JsonConverters/JsonObjectConverter.cs | 5 +- .../Rest/JsonConverters/LevelEnumConverter.cs | 2 +- .../Rest/SignHostApiClient.cs | 190 +-- .../Rest/SignHostApiClientSettings.cs | 2 +- .../Rest/SignhostApiReceiver.cs | 24 +- .../Rest/SignhostApiReceiverSettings.cs | 2 +- .../Rest/SignhostJsonSerializerOptions.cs | 14 +- .../StreamContentDigestOptionsExtensions.cs | 5 +- .../Rest/ThrowIfNullExtensions.cs | 26 + src/SignhostAPIClient/components.yml | 1361 +++++++++++++++++ 50 files changed, 2043 insertions(+), 629 deletions(-) create mode 100644 docs/components-vs-dataobjects.md create mode 100644 src/SignhostAPIClient/Rest/ThrowIfNullExtensions.cs create mode 100644 src/SignhostAPIClient/components.yml diff --git a/.editorconfig b/.editorconfig index 122cc17..758aaea 100644 --- a/.editorconfig +++ b/.editorconfig @@ -3,10 +3,50 @@ root = true [*] charset = utf-8 indent_style = tab +insert_final_newline = true -[*.{cs,js}] +[*.{cs,cshtml,js}] trim_trailing_whitespace = true insert_final_newline = true -[*.csproj] -indent_style = space \ No newline at end of file +dotnet_sort_system_directives_first = true : warning +dotnet_style_predefined_type_for_locals_parameters_members = true : warning +dotnet_style_object_initializer = true : warning +dotnet_style_collection_initializer = true : warning +dotnet_style_explicit_tuple_names = true : error +dotnet_style_coalesce_expression = true : warning +dotnet_style_null_propagation = true : warning + +# Prefer "var" everywhere +csharp_style_var_for_built_in_types = false : suggestion +csharp_style_var_when_type_is_apparent = true : suggestion +csharp_style_var_elsewhere = false : none + +# Prefer method-like constructs to have a block body +csharp_style_expression_bodied_methods = false : none +csharp_style_expression_bodied_constructors = false : none +csharp_style_expression_bodied_operators = false : none + +# Prefer property-like constructs to have an expression-body +csharp_style_expression_bodied_properties = true : none +csharp_style_expression_bodied_indexers = true : none +csharp_style_expression_bodied_accessors = true : none + +# Suggest more modern language features when available +csharp_style_pattern_matching_over_is_with_cast_check = true : suggestion +csharp_style_pattern_matching_over_as_with_null_check = true : suggestion +csharp_style_inlined_variable_declaration = true : suggestion +csharp_style_throw_expression = true : suggestion +csharp_style_conditional_delegate_call = true : suggestion + +# Newline settings +csharp_new_line_before_open_brace = types, methods, properties, indexers, events, event_accessors, anonymous_types, object_collections, array_initializers, local_functions +csharp_new_line_before_else = true +csharp_new_line_before_catch = true +csharp_new_line_before_finally = true +csharp_new_line_before_members_in_object_initializers = true +csharp_new_line_before_members_in_anonymous_types = true + +[*.{props,targets,config,nuspec,csproj}] +indent_style = tab +indent_size = 2 diff --git a/docs/components-vs-dataobjects.md b/docs/components-vs-dataobjects.md new file mode 100644 index 0000000..c7c062a --- /dev/null +++ b/docs/components-vs-dataobjects.md @@ -0,0 +1,45 @@ +# components.yml vs DataObjects discrepancies + +_All discrepancies assume `components.yml` is the source of truth. File paths are relative to the repository root._ + +## Top-level transaction models +- `CreateTransactionRequest` exists in `components.yml` but there is no matching POCO in `src/SignhostAPIClient/Rest/DataObjects`. The runtime uses `Transaction`/`Signer`/`Receiver` directly, so schema rules such as `required: Seal`, the `SignRequestMode` enum (0/1/2), or receiver message requirements cannot be enforced in code. +- `Transaction` (`src/SignhostAPIClient/Rest/DataObjects/Transaction.cs`) lacks schema properties `ModifiedDateTime`, `CancelationReason` (note the single "l" in the schema) and exposes `CancelledDateTime`/`CancellationReason` (double "l") instead, so serialized JSON names drift from the OpenAPI contract. +- `Transaction.SignRequestMode` is a plain `int` and currently allows `0` even though the schema restricts the response values to `{1, 2}`; conversely the create schema allows `{0,1,2}` but there is no validation layer in the POCO. +- `Transaction.Context`/`Signer.Context`/`Receiver.Context` are typed as `dynamic`, which allows arrays, primitives, or nulls while the schema explicitly restricts them to JSON objects (`additionalProperties: true`). + +## Signer and receiver models +- There is no separate `CreateSignerRequest` model. `Signer` (`src/SignhostAPIClient/Rest/DataObjects/Signer.cs`) is reused for both request and response payloads, so request-only constraints (e.g., `SendSignRequest` default `true`, prohibiting `AllowDelegation` in combination with `Authentications`) and response-only fields cannot be represented per the schema. +- The `Signer` POCO exposes response-only properties such as `SignUrl`, `DelegateSignUrl`, `ScribbleName`, and `ScribbleNameFixed` that are not part of either `CreateSignerRequest` or `Signer` schemas. At the same time it is missing schema fields `SignedDateTime`, `RejectDateTime`, `CreatedDateTime`, `SignerDelegationDateTime`, `ModifiedDateTime`, `ShowUrl`, and `ReceiptUrl`. +- `Signer.SendSignConfirmation` is nullable (`bool?`) whereas both schemas treat it as a concrete boolean (defaulting to `SendSignRequest`). +- `Signer.DaysToRemind` is nullable with no minimum validation; the schema requires an integer `>= -1` and declares the default `7`. +- There is no `CreateReceiverRequest` class. `Receiver` (`src/SignhostAPIClient/Rest/DataObjects/Receiver.cs`) omits schema fields `Id`, `CreatedDateTime`, and `ModifiedDateTime`, and does not enforce the `Message` requirement that the create schema mandates. + +## Activity models +- `Activity` (`src/SignhostAPIClient/Rest/DataObjects/Activity.cs`) is missing the `Activity` string property that the schema exposes for human-readable descriptions. +- `Activity.Code` uses the `ActivityType` enum (`src/SignhostAPIClient/Rest/DataObjects/ActivityType.cs`) which defines many additional codes (e.g., `InvitationReceived=102`, `IdentityApproved=110`, `Finished=500`, `EmailBounceHard=901`, etc.) that do not appear in `components.yml`. Conversely, the schema constrains `Code` to a smaller set, so the enum values and schema enumerations are out of sync. + +## File metadata & attachment models +- `FileEntry` references `FileLink` objects (`src/SignhostAPIClient/Rest/DataObjects/FileEntry.cs` and `FileLink.cs`) while the schema names the nested type `Link`. Although the properties match, the type name drift means the generated contract does not reflect the C# surface area. +- `FileMeta` (`src/SignhostAPIClient/Rest/DataObjects/FileMeta.cs`) is the implementation of `FileMetadata`, but `Field.Value` (`src/SignhostAPIClient/Rest/DataObjects/Field.cs`) is hard-coded as a `string`. The schema allows numbers, booleans, or objects in addition to strings, so typed values cannot be represented correctly. +- `Field.Type` is an unconstrained `string`, but the schema constrains it to the `FileFieldType` enum (`Seal`, `Signature`, `Check`, `Radio`, `SingleLine`, `Number`, `Date`). + +## Authentication and verification types +- The client only has a single `IVerification` interface (`src/SignhostAPIClient/Rest/DataObjects/IVerification.cs`) that backs both `Authentications` and `Verifications`. `components.yml` differentiates between `SignerAuthentication` (currently only PhoneNumber + DigiD) and `SignerVerification` (15 discriminators with ordering rules). The POCO layer cannot express this split, so contracts that rely on the discriminator sets cannot be modeled. +- Missing discriminators: there is no implementation for `SignerEmailVerification` or `SignerEHerkenningVerification`, so those schema options cannot be produced or consumed. +- Extra discriminators: `ItsmeSignVerification.cs`, `KennisnetVerification.cs`, `SigningCertificateVerification.cs`, and `UnknownVerification.cs` exist in code but are absent from the schema, so they would produce undocumented payloads. +- `PhoneNumberVerification` lacks the `SecureDownload` flag that `SignerPhoneNumberIdentification` requires. +- `DigidVerification` omits the schema properties `Betrouwbaarheidsniveau` and `SecureDownload`. +- `IdealVerification` exposes writable `AccountHolderName`/`AccountHolderCity` properties that are not part of `SignerIDealVerification`. +- `SurfnetVerification` has no `Uid` or `Attributes` even though the schema marks both as read-only fields. +- `IdinVerification` marks every schema field (`AccountHolderName`, `AccountHolderAddress*`, `AccountHolderDateOfBirth`, `Attributes`) as writable and even requires `AccountHolderDateOfBirth` (`DateTime` non-nullable), whereas the schema states they are read-only and optional. +- `IPAddressVerification` aligns with `SignerIPAddressVerification`. +- `EHerkenning` verification is completely missing, so the `SignerEHerkenningVerification` schema entry cannot be serialized. +- `EidasLoginVerification` models `Level` as a custom enum (`Level.cs`) and exposes all properties as writable, conflicting with the schema that documents string values (`de-DE` style) and `readOnly: true` fields. +- `ItsmeIdentificationVerification` omits the `Attributes` dictionary (`readOnly`) defined in the schema. +- `CscVerification` treats `Provider`, `Issuer`, `Subject`, `Thumbprint`, and `AdditionalUserData` as writable, while the schema marks them `readOnly`. +- `OidcVerification` matches `SignerOidcIdentification`. +- `OnfidoVerification` requires GUID `WorkflowId` and `WorkflowRunId` plus writable `Version`/`Attributes`. The schema expects `WorkflowId` as a client-supplied UUID string, but `WorkflowRunId`, `Version`, and `Attributes` are read-only service outputs. + +## Transaction cancellation options +- The schema names the object `TransactionDeleteOptions`, but the POCO is `DeleteTransactionOptions` (`src/SignhostAPIClient/Rest/DataObjects/DeleteTransactionOptions.cs`). While the properties align, the type name mismatch leaks to any generated clients. diff --git a/src/SignhostAPIClient.IntegrationTests/TransactionTests.cs b/src/SignhostAPIClient.IntegrationTests/TransactionTests.cs index bb15303..edc3ebe 100644 --- a/src/SignhostAPIClient.IntegrationTests/TransactionTests.cs +++ b/src/SignhostAPIClient.IntegrationTests/TransactionTests.cs @@ -18,32 +18,31 @@ public TransactionTests() config = TestConfiguration.Instance; if (!config.IsConfigured) { - throw new InvalidOperationException( - "Integration tests are not configured"); + throw new InvalidOperationException("Integration tests are not configured"); } - var settings = new SignhostApiClientSettings(config.AppKey, config.UserToken) { - Endpoint = config.ApiBaseUrl + SignhostApiClientSettings settings = new(config.AppKey, config.UserToken) { + Endpoint = config.ApiBaseUrl, }; - client = new SignhostApiClient(settings); + client = new(settings); } [Fact] public async Task Given_complex_transaction_When_created_and_started_Then_all_properties_are_correctly_persisted() { // Arrange - var testReference = $"IntegrationTest-{DateTime.UtcNow:yyyyMMddHHmmss}"; - var testPostbackUrl = "https://example.com/postback"; - var signerEmail = "john.doe@example.com"; - var signerReference = "SIGNER-001"; - var signerIntroText = "Please review and sign this document carefully."; + string testReference = $"IntegrationTest-{DateTime.UtcNow:yyyyMMddHHmmss}"; + string testPostbackUrl = "https://example.com/postback"; + string signerEmail = "john.doe@example.com"; + string signerReference = "SIGNER-001"; + string signerIntroText = "Please review and sign this document carefully."; var signerExpires = DateTimeOffset.UtcNow.AddDays(15); - var receiverEmail = "receiver@example.com"; - var receiverName = "Jane Receiver"; - var receiverReference = "RECEIVER-001"; + string receiverEmail = "receiver@example.com"; + string receiverName = "Jane Receiver"; + string receiverReference = "RECEIVER-001"; - var transaction = new CreateTransactionRequest { + CreateTransactionRequest transaction = new() { Seal = false, Reference = testReference, PostbackUrl = testPostbackUrl, @@ -55,7 +54,7 @@ public async Task Given_complex_transaction_When_created_and_started_Then_all_pr TestContext = "integration-test", }, Signers = [ - new CreateSignerRequest { + new() { Id = "signer1", Email = signerEmail, Reference = signerReference, @@ -88,7 +87,7 @@ public async Task Given_complex_transaction_When_created_and_started_Then_all_pr } ], Receivers = [ - new CreateReceiverRequest { + new() { Name = receiverName, Email = receiverEmail, Language = "en-US", @@ -102,7 +101,7 @@ public async Task Given_complex_transaction_When_created_and_started_Then_all_pr ] }; - var pdfPath = Path.Combine("TestFiles", "small-example-pdf-file.pdf"); + string pdfPath = Path.Combine("TestFiles", "small-example-pdf-file.pdf"); if (!File.Exists(pdfPath)) { throw new FileNotFoundException($"Test PDF file not found at: {pdfPath}"); } @@ -121,7 +120,8 @@ public async Task Given_complex_transaction_When_created_and_started_Then_all_pr createdTransaction.SendEmailNotifications.Should().BeFalse(); createdTransaction.SignRequestMode.Should().Be(2); createdTransaction.Language.Should().Be("en-US"); - createdTransaction.CreatedDateTime.Should().BeCloseTo(DateTimeOffset.UtcNow, TimeSpan.FromMinutes(1)); + createdTransaction.CreatedDateTime.Should() + .BeCloseTo(DateTimeOffset.UtcNow, TimeSpan.FromMinutes(1)); createdTransaction.CanceledDateTime.Should().BeNull(); createdTransaction.CancellationReason.Should().BeNull(); @@ -138,7 +138,8 @@ public async Task Given_complex_transaction_When_created_and_started_Then_all_pr createdSigner.Reference.Should().Be(signerReference); createdSigner.IntroText.Should().Be(signerIntroText); createdSigner.Expires.Should().HaveValue(); - createdSigner.Expires.Should().BeCloseTo(signerExpires, TimeSpan.FromMinutes(1)); + createdSigner.Expires.Should() + .BeCloseTo(signerExpires, TimeSpan.FromMinutes(1)); createdSigner.SendSignRequest.Should().BeFalse(); createdSigner.SendSignConfirmation.Should().BeFalse(); createdSigner.DaysToRemind.Should().Be(7); @@ -162,14 +163,16 @@ public async Task Given_complex_transaction_When_created_and_started_Then_all_pr // Assert - Signer Verifications createdSigner.Verifications.Should().HaveCount(1); - var verification = createdSigner.Verifications[0].Should().BeOfType().Subject; + var verification = createdSigner.Verifications[0] + .Should().BeOfType().Subject; verification.ScribbleName.Should().Be("John Doe"); verification.RequireHandsignature.Should().BeTrue(); verification.ScribbleNameFixed.Should().BeTrue(); // Assert - Signer Authentications createdSigner.Authentications.Should().HaveCount(1); - var authentication = createdSigner.Authentications[0].Should().BeOfType().Subject; + var authentication = createdSigner.Authentications[0] + .Should().BeOfType().Subject; authentication.Number.Should().Be("+31612345678"); authentication.SecureDownload.Should().BeTrue(); diff --git a/src/SignhostAPIClient.Tests/FieldValueTests.cs b/src/SignhostAPIClient.Tests/FieldValueTests.cs index 4690a69..f704038 100644 --- a/src/SignhostAPIClient.Tests/FieldValueTests.cs +++ b/src/SignhostAPIClient.Tests/FieldValueTests.cs @@ -11,14 +11,14 @@ public class FieldValueTests public void Given_a_field_with_string_value_When_serialized_Then_value_is_json_string() { // Arrange - var field = new Field { + Field field = new() { Type = FileFieldType.SingleLine, Value = "John Smith", - Location = new Location { PageNumber = 1 } + Location = new() { PageNumber = 1 }, }; // Act - var json = JsonSerializer.Serialize(field); + string json = JsonSerializer.Serialize(field); // Assert json.Should().Contain("\"Value\":\"John Smith\""); @@ -28,14 +28,14 @@ public void Given_a_field_with_string_value_When_serialized_Then_value_is_json_s public void Given_a_field_with_numeric_integer_value_When_serialized_Then_value_is_json_number() { // Arrange - var field = new Field { + Field field = new() { Type = FileFieldType.Number, Value = 42, - Location = new Location { PageNumber = 1 } + Location = new() { PageNumber = 1 }, }; // Act - var json = JsonSerializer.Serialize(field); + string json = JsonSerializer.Serialize(field); // Assert json.Should().Contain("\"Value\":42"); @@ -46,14 +46,14 @@ public void Given_a_field_with_numeric_integer_value_When_serialized_Then_value_ public void Given_a_field_with_numeric_double_value_When_serialized_Then_value_is_json_number() { // Arrange - var field = new Field { + Field field = new() { Type = FileFieldType.Number, Value = 3.14, - Location = new Location { PageNumber = 1 } + Location = new() { PageNumber = 1 }, }; // Act - var json = JsonSerializer.Serialize(field); + string json = JsonSerializer.Serialize(field); // Assert json.Should().Contain("\"Value\":3.14"); @@ -64,14 +64,14 @@ public void Given_a_field_with_numeric_double_value_When_serialized_Then_value_i public void Given_a_field_with_boolean_true_value_When_serialized_Then_value_is_json_boolean() { // Arrange - var field = new Field { + Field field = new() { Type = FileFieldType.Check, Value = true, - Location = new Location { PageNumber = 1 } + Location = new() { PageNumber = 1 }, }; // Act - var json = JsonSerializer.Serialize(field); + string json = JsonSerializer.Serialize(field); // Assert json.Should().Contain("\"Value\":true"); @@ -82,14 +82,14 @@ public void Given_a_field_with_boolean_true_value_When_serialized_Then_value_is_ public void Given_a_field_with_boolean_false_value_When_serialized_Then_value_is_json_boolean() { // Arrange - var field = new Field { + Field field = new() { Type = FileFieldType.Check, Value = false, - Location = new Location { PageNumber = 1 } + Location = new() { PageNumber = 1 }, }; // Act - var json = JsonSerializer.Serialize(field); + string json = JsonSerializer.Serialize(field); // Assert json.Should().Contain("\"Value\":false"); @@ -100,24 +100,41 @@ public void Given_a_field_with_boolean_false_value_When_serialized_Then_value_is public void Given_a_field_with_null_value_When_serialized_Then_value_is_json_null() { // Arrange - var field = new Field { + Field field = new() { Type = FileFieldType.Signature, Value = null, - Location = new Location { PageNumber = 1 } + Location = new() { PageNumber = 1 }, }; // Act - var json = JsonSerializer.Serialize(field); + string json = JsonSerializer.Serialize(field); // Assert json.Should().Contain("\"Value\":null"); } + [Fact] + public void Given_a_field_with_invalid_object_value_When_serialized_Then_throws_json_exception() + { + // Arrange + Field field = new() { + Type = FileFieldType.SingleLine, + Value = new { Name = "Test" }, + Location = new() { PageNumber = 1 }, + }; + + // Act + var act = () => JsonSerializer.Serialize(field); + + // Assert + act.Should().Throw(); + } + [Fact] public void Given_json_with_string_value_When_deserialized_Then_field_value_is_string() { // Arrange - var json = @"{ + string json = @"{ ""Type"": ""SingleLine"", ""Value"": ""Test Name"", ""Location"": { ""PageNumber"": 1 } @@ -135,7 +152,7 @@ public void Given_json_with_string_value_When_deserialized_Then_field_value_is_s public void Given_json_with_number_integer_value_When_deserialized_Then_field_value_is_numeric() { // Arrange - var json = @"{ + string json = @"{ ""Type"": ""Number"", ""Value"": 123, ""Location"": { ""PageNumber"": 1 } @@ -153,7 +170,7 @@ public void Given_json_with_number_integer_value_When_deserialized_Then_field_va public void Given_json_with_number_decimal_value_When_deserialized_Then_field_value_is_double() { // Arrange - var json = @"{ + string json = @"{ ""Type"": ""Number"", ""Value"": 45.67, ""Location"": { ""PageNumber"": 1 } @@ -171,7 +188,7 @@ public void Given_json_with_number_decimal_value_When_deserialized_Then_field_va public void Given_json_with_boolean_true_value_When_deserialized_Then_field_value_is_boolean_true() { // Arrange - var json = @"{ + string json = @"{ ""Type"": ""Check"", ""Value"": true, ""Location"": { ""PageNumber"": 1 } @@ -189,7 +206,7 @@ public void Given_json_with_boolean_true_value_When_deserialized_Then_field_valu public void Given_json_with_boolean_false_value_When_deserialized_Then_field_value_is_boolean_false() { // Arrange - var json = @"{ + string json = @"{ ""Type"": ""Check"", ""Value"": false, ""Location"": { ""PageNumber"": 1 } @@ -207,7 +224,7 @@ public void Given_json_with_boolean_false_value_When_deserialized_Then_field_val public void Given_json_with_null_value_When_deserialized_Then_field_value_is_null() { // Arrange - var json = @"{ + string json = @"{ ""Type"": ""Signature"", ""Value"": null, ""Location"": { ""PageNumber"": 1 } @@ -221,28 +238,11 @@ public void Given_json_with_null_value_When_deserialized_Then_field_value_is_nul field!.Value.Should().BeNull(); } - [Fact] - public void Given_a_field_with_invalid_object_value_When_serialized_Then_throws_json_exception() - { - // Arrange - var field = new Field { - Type = FileFieldType.SingleLine, - Value = new { Name = "Test" }, - Location = new Location { PageNumber = 1 } - }; - - // Act - var act = () => JsonSerializer.Serialize(field); - - // Assert - act.Should().Throw(); - } - [Fact] public void Given_json_with_object_value_When_deserialized_Then_throws_json_exception() { // Arrange - var json = @"{ + string json = @"{ ""Type"": ""SingleLine"", ""Value"": { ""nested"": ""object"" }, ""Location"": { ""PageNumber"": 1 } @@ -259,7 +259,7 @@ public void Given_json_with_object_value_When_deserialized_Then_throws_json_exce public void Given_json_with_array_value_When_deserialized_Then_throws_json_exception() { // Arrange - var json = @"{ + string json = @"{ ""Type"": ""SingleLine"", ""Value"": [1, 2, 3], ""Location"": { ""PageNumber"": 1 } diff --git a/src/SignhostAPIClient.Tests/FileFieldTypeTests.cs b/src/SignhostAPIClient.Tests/FileFieldTypeTests.cs index f3afd47..7693465 100644 --- a/src/SignhostAPIClient.Tests/FileFieldTypeTests.cs +++ b/src/SignhostAPIClient.Tests/FileFieldTypeTests.cs @@ -15,18 +15,20 @@ public class FileFieldTypeTests [InlineData(FileFieldType.SingleLine, "SingleLine")] [InlineData(FileFieldType.Number, "Number")] [InlineData(FileFieldType.Date, "Date")] - public void Given_a_field_with_specific_type_When_serialized_to_json_Then_type_is_string_not_numeric(FileFieldType fieldType, string expectedString) + public void Given_a_field_with_specific_type_When_serialized_to_json_Then_type_is_string_not_numeric( + FileFieldType fieldType, + string expectedString + ) { // Arrange - var field = new Field - { + Field field = new() { Type = fieldType, Value = "test", - Location = new Location { PageNumber = 1 } + Location = new() { PageNumber = 1 }, }; // Act - var json = JsonSerializer.Serialize(field); + string json = JsonSerializer.Serialize(field); // Assert json.Should().Contain($"\"Type\":\"{expectedString}\""); @@ -37,7 +39,7 @@ public void Given_a_field_with_specific_type_When_serialized_to_json_Then_type_i public void Given_json_with_string_field_type_When_deserialized_Then_field_type_enum_is_correctly_parsed() { // Arrange - var json = @"{ + string json = @"{ ""Type"": ""Signature"", ""Value"": ""test"", ""Location"": { ""PageNumber"": 1 } diff --git a/src/SignhostAPIClient.Tests/JSON/JsonResources.cs b/src/SignhostAPIClient.Tests/JSON/JsonResources.cs index 9bbfbe6..eb74280 100644 --- a/src/SignhostAPIClient.Tests/JSON/JsonResources.cs +++ b/src/SignhostAPIClient.Tests/JSON/JsonResources.cs @@ -10,18 +10,13 @@ public static class JsonResources public static string AddOrReplaceFileMetaToTransaction { get; } = GetJson("AddOrReplaceFileMetaToTransaction"); - public static string AddTransaction { get; } = - GetJson("AddTransaction"); - public static string DeleteTransaction { get; } = - GetJson("DeleteTransaction"); - public static string GetTransaction { get; } = - GetJson("GetTransaction"); + public static string AddTransaction { get; } = GetJson("AddTransaction"); + public static string DeleteTransaction { get; } = GetJson("DeleteTransaction"); + public static string GetTransaction { get; } = GetJson("GetTransaction"); public static string MinimalTransactionResponse { get; } = GetJson("MinimalTransactionResponse"); - public static string MockPostbackInvalid { get; } = - GetJson("MockPostbackInvalid"); - public static string MockPostbackValid { get; } = - GetJson("MockPostbackValid"); + public static string MockPostbackInvalid { get; } = GetJson("MockPostbackInvalid"); + public static string MockPostbackValid { get; } = GetJson("MockPostbackValid"); private static string GetJson(string fileName) { diff --git a/src/SignhostAPIClient.Tests/LevelEnumConverterTests.cs b/src/SignhostAPIClient.Tests/LevelEnumConverterTests.cs index 92ca645..175ab3f 100644 --- a/src/SignhostAPIClient.Tests/LevelEnumConverterTests.cs +++ b/src/SignhostAPIClient.Tests/LevelEnumConverterTests.cs @@ -1,9 +1,9 @@ -using FluentAssertions; -using System.Text.Json; -using Signhost.APIClient.Rest.DataObjects; using System; using System.Collections; using System.Collections.Generic; +using System.Text.Json; +using FluentAssertions; +using Signhost.APIClient.Rest.DataObjects; using Xunit; namespace Signhost.APIClient.Rest.Tests; @@ -11,39 +11,48 @@ namespace Signhost.APIClient.Rest.Tests; public class LevelEnumConverterTests { [Fact] - public void when_Level_is_null_should_deserialize_to_null() + public void When_Level_is_null_should_deserialize_to_null() { // Arrange const string json = "{\"Type\":\"eIDAS Login\",\"Level\":null}"; // Act - var eidasLogin = JsonSerializer.Deserialize(json, SignhostJsonSerializerOptions.Default); + var eidasLogin = JsonSerializer.Deserialize( + json, + SignhostJsonSerializerOptions.Default + ); // Assert eidasLogin.Level.Should().Be(null); } [Fact] - public void when_Level_is_not_supplied_should_deserialize_to_null() + public void When_Level_is_not_supplied_should_deserialize_to_null() { // Arrange const string json = "{\"Type\":\"eIDAS Login\"}"; // Act - var eidasLogin = JsonSerializer.Deserialize(json, SignhostJsonSerializerOptions.Default); + var eidasLogin = JsonSerializer.Deserialize( + json, + SignhostJsonSerializerOptions.Default + ); // Assert eidasLogin.Level.Should().Be(null); } [Fact] - public void when_Level_is_unknown_should_deserialize_to_Unknown_Level() + public void When_Level_is_unknown_should_deserialize_to_Unknown_Level() { // Arrange const string json = "{\"Type\":\"eIDAS Login\",\"Level\":\"foobar\"}"; // Act - var eidasLogin = JsonSerializer.Deserialize(json, SignhostJsonSerializerOptions.Default); + var eidasLogin = JsonSerializer.Deserialize( + json, + SignhostJsonSerializerOptions.Default + ); // Assert eidasLogin.Level.Should().Be(Level.Unknown); @@ -51,13 +60,15 @@ public void when_Level_is_unknown_should_deserialize_to_Unknown_Level() [Theory] [ClassData(typeof(LevelTestData))] - public void when_Level_is_valid_should_deserialize_to_correct_value(Level level) + public void When_Level_is_valid_should_deserialize_to_correct_value(Level level) { // Arrange string json = $"{{\"Type\":\"eIDAS Login\",\"Level\":\"{level}\"}}"; // Act - var eidasLogin = JsonSerializer.Deserialize(json, SignhostJsonSerializerOptions.Default); + var eidasLogin = JsonSerializer.Deserialize( + json, + SignhostJsonSerializerOptions.Default); // Assert eidasLogin.Level.Should().Be(level); @@ -68,7 +79,7 @@ private class LevelTestData { public IEnumerator GetEnumerator() { - foreach (var value in Enum.GetValues(typeof(Level))) { + foreach (object value in Enum.GetValues()) { yield return new[] { value }; } } diff --git a/src/SignhostAPIClient.Tests/PostbackTests.cs b/src/SignhostAPIClient.Tests/PostbackTests.cs index c469638..1e08e4d 100644 --- a/src/SignhostAPIClient.Tests/PostbackTests.cs +++ b/src/SignhostAPIClient.Tests/PostbackTests.cs @@ -107,7 +107,7 @@ public void PostbackTransaction_should_get_serialized_correctly() links.Should().HaveCount(1); var link = links.Single(); - link.Rel .Should().Be("file"); + link.Rel.Should().Be("file"); link.Type.Should().Be("application/pdf"); link.Link.Should().Be("https://api.signhost.com/api/transaction/b10ae331-af78-4e79-a39e-5b64693b6b68/file/file1"); } diff --git a/src/SignhostAPIClient.Tests/SignhostApiClientTests.cs b/src/SignhostAPIClient.Tests/SignhostApiClientTests.cs index 9340462..bd3d1dc 100644 --- a/src/SignhostAPIClient.Tests/SignhostApiClientTests.cs +++ b/src/SignhostAPIClient.Tests/SignhostApiClientTests.cs @@ -1,245 +1,230 @@ using System; -using System.Threading.Tasks; -using System.Net.Http; +using System.Collections.Generic; using System.IO; -using Xunit; -using Signhost.APIClient.Rest.DataObjects; -using Signhost.APIClient.Rest.ErrorHandling; +using System.Net; +using System.Net.Http; +using System.Threading.Tasks; using FluentAssertions; -using System.Collections.Generic; using RichardSzalay.MockHttp; -using System.Net; +using Signhost.APIClient.Rest.DataObjects; +using Signhost.APIClient.Rest.ErrorHandling; using SignhostAPIClient.Tests.JSON; +using Xunit; namespace Signhost.APIClient.Rest.Tests; public class SignhostApiClientTests { private readonly SignhostApiClientSettings settings = new("AppKey", "Usertoken") { - Endpoint = "http://localhost/api/" + Endpoint = "http://localhost/api/", }; private readonly SignhostApiClientSettings oauthSettings = new("AppKey") { - Endpoint = "http://localhost/api/" + Endpoint = "http://localhost/api/", }; [Fact] - public async Task when_AddOrReplaceFileMetaToTransaction_is_called_then_the_request_body_should_contain_the_serialized_file_meta() + public async Task When_AddOrReplaceFileMetaToTransaction_is_called_Then_the_request_body_should_contain_the_serialized_file_meta() { - var mockHttp = new MockHttpMessageHandler(); - - mockHttp.Expect(HttpMethod.Put, "http://localhost/api/transaction/transactionId/file/fileId") + MockHttpMessageHandler mockHttp = new(); + mockHttp + .Expect(HttpMethod.Put, "http://localhost/api/transaction/transactionId/file/fileId") .WithContent(JsonResources.AddOrReplaceFileMetaToTransaction) .Respond(HttpStatusCode.OK); - using (var httpClient = mockHttp.ToHttpClient()) { - var signhostApiClient = new SignhostApiClient(settings, httpClient); - - var fileSignerMeta = new FileSignerMeta - { - FormSets = new string[] { "SampleFormSet" } - }; - - var field = new Field - { - Type = FileFieldType.Check, - Value = "I agree", - Location = new Location - { - Search = "test" - } - }; - - FileMeta fileMeta = new FileMeta - { - Signers = new Dictionary - { - { "someSignerId", fileSignerMeta } + using var httpClient = mockHttp.ToHttpClient(); + SignhostApiClient signhostApiClient = new(settings, httpClient); + + var fileSignerMeta = new FileSignerMeta { + FormSets = ["SampleFormSet"] + }; + + var field = new Field { + Type = FileFieldType.Check, + Value = "I agree", + Location = new Location { Search = "test" }, + }; + + FileMeta fileMeta = new FileMeta { + Signers = new Dictionary { + ["someSignerId"] = fileSignerMeta, + }, + FormSets = new Dictionary> { + ["SampleFormSet"] = new Dictionary { + ["SampleCheck"] = field, }, - FormSets = new Dictionary> - { - { "SampleFormSet", new Dictionary - { - { "SampleCheck", field } - } - } - } - }; + }, + }; - await signhostApiClient.AddOrReplaceFileMetaToTransactionAsync(fileMeta, "transactionId", "fileId"); - } + await signhostApiClient.AddOrReplaceFileMetaToTransactionAsync( + fileMeta, + "transactionId", + "fileId" + ); mockHttp.VerifyNoOutstandingExpectation(); } [Fact] - public async Task when_a_GetTransaction_is_called_then_we_should_have_called_the_transaction_get_once() + public async Task When_a_GetTransaction_is_called_Then_we_should_have_called_the_transaction_get_once() { - var mockHttp = new MockHttpMessageHandler(); + MockHttpMessageHandler mockHttp = new(); mockHttp - .Expect(HttpMethod.Get, "http://localhost/api/transaction/transaction Id") + .Expect(HttpMethod.Get, "http://localhost/api/transaction/transactionId") .Respond(HttpStatusCode.OK, new StringContent(JsonResources.GetTransaction)); - using (var httpClient = mockHttp.ToHttpClient()) { - - var signhostApiClient = new SignhostApiClient(settings, httpClient); + using var httpClient = mockHttp.ToHttpClient(); + SignhostApiClient signhostApiClient = new(settings, httpClient); - var result = await signhostApiClient.GetTransactionAsync("transaction Id"); - result.Id.Should().Be("c487be92-0255-40c7-bd7d-20805a65e7d9"); - } + var result = await signhostApiClient.GetTransactionAsync("transactionId"); + result.Id.Should().Be("c487be92-0255-40c7-bd7d-20805a65e7d9"); mockHttp.VerifyNoOutstandingExpectation(); } [Fact] - public async Task when_GetTransaction_is_called_and_the_authorization_is_bad_then_we_should_get_a_BadAuthorizationException() + public async Task When_GetTransaction_is_called_and_the_authorization_is_bad_Then_we_should_get_a_BadAuthorizationException() { - var mockHttp = new MockHttpMessageHandler(); + MockHttpMessageHandler mockHttp = new(); const string expectedResponseBody = """ { "message": "unauthorized" } """; mockHttp - .Expect(HttpMethod.Get, "http://localhost/api/transaction/transaction Id") + .Expect(HttpMethod.Get, "http://localhost/api/transaction/transactionId") .Respond(HttpStatusCode.Unauthorized, new StringContent(expectedResponseBody)); - using (var httpClient = mockHttp.ToHttpClient()) { + using var httpClient = mockHttp.ToHttpClient(); - var signhostApiClient = new SignhostApiClient(settings, httpClient); + SignhostApiClient signhostApiClient = new(settings, httpClient); - Func getTransaction = () => signhostApiClient.GetTransactionAsync("transaction Id"); - var exception = await getTransaction.Should().ThrowAsync(); - exception.Which.ResponseBody.Should().Be(expectedResponseBody); - } + Func getTransaction = () => signhostApiClient.GetTransactionAsync("transactionId"); + var exception = await getTransaction.Should().ThrowAsync(); + exception.Which.ResponseBody.Should().Be(expectedResponseBody); mockHttp.VerifyNoOutstandingExpectation(); } [Fact] - public async Task when_GetTransaction_is_called_and_request_is_bad_then_we_should_get_a_BadRequestException() + public async Task When_GetTransaction_is_called_and_request_is_bad_Then_we_should_get_a_BadRequestException() { - var mockHttp = new MockHttpMessageHandler(); + MockHttpMessageHandler mockHttp = new(); const string expectedResponseBody = """ { "message": "Bad Request" } """; mockHttp - .Expect(HttpMethod.Get, "http://localhost/api/transaction/transaction Id") + .Expect(HttpMethod.Get, "http://localhost/api/transaction/transactionId") .Respond(HttpStatusCode.BadRequest, new StringContent(expectedResponseBody)); - using (var httpClient = mockHttp.ToHttpClient()) { + using var httpClient = mockHttp.ToHttpClient(); - var signhostApiClient = new SignhostApiClient(settings, httpClient); + SignhostApiClient signhostApiClient = new(settings, httpClient); - Func getTransaction = () => signhostApiClient.GetTransactionAsync("transaction Id"); - var exception = await getTransaction.Should().ThrowAsync(); - exception.Which.ResponseBody.Should().Be(expectedResponseBody); - } + Func getTransaction = () => signhostApiClient.GetTransactionAsync("transactionId"); + var exception = await getTransaction.Should().ThrowAsync(); + exception.Which.ResponseBody.Should().Be(expectedResponseBody); mockHttp.VerifyNoOutstandingExpectation(); } [Fact] - public async Task when_GetTransaction_is_called_and_credits_have_run_out_then_we_should_get_a_OutOfCreditsException() + public async Task When_GetTransaction_is_called_and_credits_have_run_out_Then_we_should_get_a_OutOfCreditsException() { - var mockHttp = new MockHttpMessageHandler(); + MockHttpMessageHandler mockHttp = new(); const string expectedResponseBody = """ { "type": "https://api.signhost.com/problem/subscription/out-of-credits" } """; mockHttp - .Expect(HttpMethod.Get, "http://localhost/api/transaction/transaction Id") + .Expect(HttpMethod.Get, "http://localhost/api/transaction/transactionId") .Respond(HttpStatusCode.PaymentRequired, new StringContent(expectedResponseBody)); - using (var httpClient = mockHttp.ToHttpClient()) { + using var httpClient = mockHttp.ToHttpClient(); - var signhostApiClient = new SignhostApiClient(settings, httpClient); + SignhostApiClient signhostApiClient = new(settings, httpClient); - Func getTransaction = () => signhostApiClient.GetTransactionAsync("transaction Id"); - var exception = await getTransaction.Should().ThrowAsync(); - exception.Which.ResponseBody.Should().Be(expectedResponseBody); - } + Func getTransaction = () => signhostApiClient.GetTransactionAsync("transactionId"); + var exception = await getTransaction.Should().ThrowAsync(); + exception.Which.ResponseBody.Should().Be(expectedResponseBody); mockHttp.VerifyNoOutstandingExpectation(); } [Fact] - public async Task when_GetTransaction_is_called_and_not_found_then_we_should_get_a_NotFoundException() + public async Task When_GetTransaction_is_called_and_not_found_Then_we_should_get_a_NotFoundException() { - var mockHttp = new MockHttpMessageHandler(); + MockHttpMessageHandler mockHttp = new(); const string expectedResponseBody = """ { "message": "Not Found" } """; mockHttp - .Expect(HttpMethod.Get, "http://localhost/api/transaction/transaction Id") + .Expect(HttpMethod.Get, "http://localhost/api/transaction/transactionId") .Respond(HttpStatusCode.NotFound, new StringContent(expectedResponseBody)); - using (var httpClient = mockHttp.ToHttpClient()) { + using var httpClient = mockHttp.ToHttpClient(); - var signhostApiClient = new SignhostApiClient(settings, httpClient); + SignhostApiClient signhostApiClient = new(settings, httpClient); - Func getTransaction = () => signhostApiClient.GetTransactionAsync("transaction Id"); + Func getTransaction = () => signhostApiClient.GetTransactionAsync("transactionId"); - var exception = await getTransaction.Should().ThrowAsync(); - exception.Which.Message.Should().Be("Not Found"); - exception.Which.ResponseBody.Should().Be(expectedResponseBody); - } + var exception = await getTransaction.Should().ThrowAsync(); + exception.Which.Message.Should().Be("Not Found"); + exception.Which.ResponseBody.Should().Be(expectedResponseBody); mockHttp.VerifyNoOutstandingExpectation(); } [Fact] - public async Task when_GetTransaction_is_called_and_unkownerror_like_418_occures_then_we_should_get_a_SignhostException() + public async Task When_GetTransaction_is_called_and_unkownerror_like_418_occures_Then_we_should_get_a_SignhostException() { - var mockHttp = new MockHttpMessageHandler(); + MockHttpMessageHandler mockHttp = new(); const string expectedResponseBody = """ { "message": "418 I'm a teapot" } """; mockHttp - .Expect(HttpMethod.Get, "http://localhost/api/transaction/transaction Id") + .Expect(HttpMethod.Get, "http://localhost/api/transaction/transactionId") .Respond((HttpStatusCode)418, new StringContent(expectedResponseBody)); - using (var httpClient = mockHttp.ToHttpClient()) { + using var httpClient = mockHttp.ToHttpClient(); - var signhostApiClient = new SignhostApiClient(settings, httpClient); + SignhostApiClient signhostApiClient = new(settings, httpClient); - Func getTransaction = () => signhostApiClient.GetTransactionAsync("transaction Id"); - var exception = await getTransaction.Should().ThrowAsync(); - exception.Which.Message.Should().ContainAll("418"); - exception.Which.ResponseBody.Should().Be(expectedResponseBody); - } + Func getTransaction = () => signhostApiClient.GetTransactionAsync("transactionId"); + var exception = await getTransaction.Should().ThrowAsync(); + exception.Which.Message.Should().ContainAll("418"); + exception.Which.ResponseBody.Should().Be(expectedResponseBody); mockHttp.VerifyNoOutstandingExpectation(); } [Fact] - public async Task when_GetTransaction_is_called_and_there_is_an_InternalServerError_then_we_should_get_a_InternalServerErrorException() + public async Task When_GetTransaction_is_called_and_there_is_an_InternalServerError_Then_we_should_get_a_InternalServerErrorException() { - var mockHttp = new MockHttpMessageHandler(); + MockHttpMessageHandler mockHttp = new(); const string expectedResponseBody = """ { "message": "Internal Server Error" } """; mockHttp - .Expect(HttpMethod.Get, "http://localhost/api/transaction/transaction Id") + .Expect(HttpMethod.Get, "http://localhost/api/transaction/transactionId") .Respond(HttpStatusCode.InternalServerError, new StringContent(expectedResponseBody)); - using (var httpClient = mockHttp.ToHttpClient()) { + using var httpClient = mockHttp.ToHttpClient(); - var signhostApiClient = new SignhostApiClient(settings, httpClient); + SignhostApiClient signhostApiClient = new(settings, httpClient); - Func getTransaction = () => signhostApiClient.GetTransactionAsync("transaction Id"); - var exception = await getTransaction.Should().ThrowAsync(); - exception.Which.ResponseBody.Should().Be(expectedResponseBody); - } + Func getTransaction = () => signhostApiClient.GetTransactionAsync("transactionId"); + var exception = await getTransaction.Should().ThrowAsync(); + exception.Which.ResponseBody.Should().Be(expectedResponseBody); mockHttp.VerifyNoOutstandingExpectation(); } @@ -247,18 +232,17 @@ public async Task when_GetTransaction_is_called_and_there_is_an_InternalServerEr [Fact] public async Task When_GetTransaction_is_called_on_gone_transaction_we_shoud_get_a_GoneException() { - var mockHttp = new MockHttpMessageHandler(); + MockHttpMessageHandler mockHttp = new(); mockHttp - .Expect(HttpMethod.Get, "http://localhost/api/transaction/transaction Id") + .Expect(HttpMethod.Get, "http://localhost/api/transaction/transactionId") .Respond(HttpStatusCode.Gone, new StringContent(JsonResources.GetTransaction)); - using (var httpClient = mockHttp.ToHttpClient()) { - var signhostApiClient = new SignhostApiClient(settings, httpClient); + using var httpClient = mockHttp.ToHttpClient(); + SignhostApiClient signhostApiClient = new(settings, httpClient); - Func getTransaction = () => signhostApiClient.GetTransactionAsync("transaction Id"); - var exception = await getTransaction.Should().ThrowAsync>(); - exception.Which.ResponseBody.Should().Be(JsonResources.GetTransaction); - } + Func getTransaction = () => signhostApiClient.GetTransactionAsync("transactionId"); + var exception = await getTransaction.Should().ThrowAsync>(); + exception.Which.ResponseBody.Should().Be(JsonResources.GetTransaction); mockHttp.VerifyNoOutstandingExpectation(); } @@ -266,73 +250,70 @@ public async Task When_GetTransaction_is_called_on_gone_transaction_we_shoud_get [Fact] public async Task When_GetTransaction_is_called_and_gone_is_expected_we_should_get_a_transaction() { - var mockHttp = new MockHttpMessageHandler(); + MockHttpMessageHandler mockHttp = new(); mockHttp - .Expect(HttpMethod.Get, "http://localhost/api/transaction/transaction Id") + .Expect(HttpMethod.Get, "http://localhost/api/transaction/transactionId") .Respond(HttpStatusCode.Gone, new StringContent(JsonResources.GetTransaction)); - using (var httpClient = mockHttp.ToHttpClient()) { - var signhostApiClient = new SignhostApiClient(settings, httpClient); + using var httpClient = mockHttp.ToHttpClient(); + SignhostApiClient signhostApiClient = new(settings, httpClient); - Func getTransaction = () => signhostApiClient.GetTransactionResponseAsync("transaction Id"); - await getTransaction.Should().NotThrowAsync(); - } + Func getTransaction = () => signhostApiClient.GetTransactionResponseAsync("transactionId"); + await getTransaction.Should().NotThrowAsync(); mockHttp.VerifyNoOutstandingExpectation(); } [Fact] - public async Task when_a_CreateTransaction_is_called_then_we_should_have_called_the_transaction_Post_once() + public async Task When_a_CreateTransaction_is_called_Then_we_should_have_called_the_transaction_Post_once() { - var mockHttp = new MockHttpMessageHandler(); + MockHttpMessageHandler mockHttp = new(); mockHttp .Expect(HttpMethod.Post, "http://localhost/api/transaction") .Respond(HttpStatusCode.OK, new StringContent(JsonResources.AddTransaction)); - using (var httpClient = mockHttp.ToHttpClient()) { - var signhostApiClient = new SignhostApiClient(settings, httpClient); + using var httpClient = mockHttp.ToHttpClient(); + SignhostApiClient signhostApiClient = new(settings, httpClient); - var testSigner = new CreateSignerRequest { - Email = "firstname.lastname@gmail.com" - }; + CreateSignerRequest testSigner = new() { + Email = "firstname.lastname@gmail.com", + }; - var testTransaction = new CreateTransactionRequest(); - testTransaction.Signers.Add(testSigner); + CreateTransactionRequest testTransaction = new(); + testTransaction.Signers.Add(testSigner); - var result = await signhostApiClient.CreateTransactionAsync(testTransaction); - result.Id.Should().Be("c487be92-0255-40c7-bd7d-20805a65e7d9"); - } + var result = await signhostApiClient.CreateTransactionAsync(testTransaction); + result.Id.Should().Be("c487be92-0255-40c7-bd7d-20805a65e7d9"); mockHttp.VerifyNoOutstandingExpectation(); } [Fact] - public async Task when_a_CreateTransaction_is_called_we_can_add_custom_http_headers() + public async Task When_a_CreateTransaction_is_called_we_can_add_custom_http_headers() { - var mockHttp = new MockHttpMessageHandler(); + MockHttpMessageHandler mockHttp = new(); mockHttp .Expect(HttpMethod.Post, "http://localhost/api/transaction") .WithHeaders("X-Forwarded-For", "localhost") .With(matcher => matcher.Headers.UserAgent.ToString().Contains("SignhostClientLibrary")) .Respond(HttpStatusCode.OK, new StringContent(JsonResources.AddTransaction)); - using (var httpClient = mockHttp.ToHttpClient()) { - settings.AddHeader = (AddHeaders a) => a("X-Forwarded-For", "localhost"); + using var httpClient = mockHttp.ToHttpClient(); + settings.AddHeader = ah => ah("X-Forwarded-For", "localhost"); - var signhostApiClient = new SignhostApiClient(settings, httpClient); + SignhostApiClient signhostApiClient = new(settings, httpClient); - var testTransaction = new CreateTransactionRequest(); + CreateTransactionRequest testTransaction = new(); - var result = await signhostApiClient.CreateTransactionAsync(testTransaction); - } + var result = await signhostApiClient.CreateTransactionAsync(testTransaction); mockHttp.VerifyNoOutstandingExpectation(); } [Fact] - public async Task when_CreateTransaction_is_called_with_invalid_email_then_we_should_get_a_BadRequestException() + public async Task When_CreateTransaction_is_called_with_invalid_email_Then_we_should_get_a_BadRequestException() { - var mockHttp = new MockHttpMessageHandler(); + MockHttpMessageHandler mockHttp = new(); const string expectedResponseBody = """ { "message": "Bad Request" @@ -343,235 +324,225 @@ public async Task when_CreateTransaction_is_called_with_invalid_email_then_we_sh .WithHeaders("Content-Type", "application/json") .Respond(HttpStatusCode.BadRequest, new StringContent(expectedResponseBody)); - using (var httpClient = mockHttp.ToHttpClient()) { - var signhostApiClient = new SignhostApiClient(settings, httpClient); + using var httpClient = mockHttp.ToHttpClient(); + SignhostApiClient signhostApiClient = new(settings, httpClient); - var testSigner = new CreateSignerRequest { - Email = "firstname.lastnamegmail.com" - }; + CreateSignerRequest testSigner = new() { + Email = "firstname.lastnamegmail.com", + }; - var testTransaction = new CreateTransactionRequest(); - testTransaction.Signers.Add(testSigner); + CreateTransactionRequest testTransaction = new(); + testTransaction.Signers.Add(testSigner); - Func getTransaction = () => signhostApiClient.CreateTransactionAsync(testTransaction); - var exception = await getTransaction.Should().ThrowAsync(); - exception.Which.ResponseBody.Should().Be(expectedResponseBody); - } + Func getTransaction = () => signhostApiClient.CreateTransactionAsync(testTransaction); + var exception = await getTransaction.Should().ThrowAsync(); + exception.Which.ResponseBody.Should().Be(expectedResponseBody); mockHttp.VerifyNoOutstandingExpectation(); } [Fact] - public async Task when_a_function_is_called_with_a_wrong_endpoint_we_should_get_a_SignhostRestApiClientException() + public async Task When_a_function_is_called_with_a_wrong_endpoint_we_should_get_a_SignhostRestApiClientException() { - var mockHttp = new MockHttpMessageHandler(); + MockHttpMessageHandler mockHttp = new(); const string expectedResponseBody = """ { "message": "Bad Gateway" } """; mockHttp - .Expect(HttpMethod.Get, "http://localhost/api/transaction/transaction Id") + .Expect(HttpMethod.Get, "http://localhost/api/transaction/transactionId") .Respond(HttpStatusCode.BadGateway, new StringContent(expectedResponseBody)); - using (var httpClient = mockHttp.ToHttpClient()) { - var signhostApiClient = new SignhostApiClient(settings, httpClient); + using var httpClient = mockHttp.ToHttpClient(); + SignhostApiClient signhostApiClient = new(settings, httpClient); - Func getTransaction = () => signhostApiClient.GetTransactionAsync("transaction Id"); - var exception = await getTransaction.Should().ThrowAsync(); - exception.Which.Message.Should().Be("Bad Gateway"); - exception.Which.ResponseBody.Should().Be(expectedResponseBody); - } + Func getTransaction = () => signhostApiClient.GetTransactionAsync("transactionId"); + var exception = await getTransaction.Should().ThrowAsync(); + exception.Which.Message.Should().Be("Bad Gateway"); + exception.Which.ResponseBody.Should().Be(expectedResponseBody); mockHttp.VerifyNoOutstandingExpectation(); } [Fact] - public async Task when_a_DeleteTransaction_is_called_then_we_should_have_called_the_transaction_delete_once() + public async Task When_a_DeleteTransaction_is_called_Then_we_should_have_called_the_transaction_delete_once() { - var mockHttp = new MockHttpMessageHandler(); + MockHttpMessageHandler mockHttp = new(); mockHttp - .Expect(HttpMethod.Delete, "http://localhost/api/transaction/transaction Id") + .Expect(HttpMethod.Delete, "http://localhost/api/transaction/transactionId") .Respond(HttpStatusCode.OK, new StringContent(JsonResources.DeleteTransaction)); - using (var httpClient = mockHttp.ToHttpClient()) { - var signhostApiClient = new SignhostApiClient(settings, httpClient); + using var httpClient = mockHttp.ToHttpClient(); + SignhostApiClient signhostApiClient = new(settings, httpClient); - await signhostApiClient.DeleteTransactionAsync("transaction Id"); - } + await signhostApiClient.DeleteTransactionAsync("transactionId"); mockHttp.VerifyNoOutstandingExpectation(); } [Fact] - public async Task when_a_DeleteTransaction_with_notification_is_called_then_we_should_have_called_the_transaction_delete_once() + public async Task When_a_DeleteTransaction_with_notification_is_called_Then_we_should_have_called_the_transaction_delete_once() { - var mockHttp = new MockHttpMessageHandler(); + MockHttpMessageHandler mockHttp = new(); mockHttp - .Expect(HttpMethod.Delete, "http://localhost/api/transaction/transaction Id") + .Expect(HttpMethod.Delete, "http://localhost/api/transaction/transactionId") .WithHeaders("Content-Type", "application/json") //.With(matcher => matcher.Content.ToString().Contains("'SendNotifications': true")) .Respond(HttpStatusCode.OK, new StringContent(JsonResources.DeleteTransaction)); - using (var httpClient = mockHttp.ToHttpClient()) { - var signhostApiClient = new SignhostApiClient(settings, httpClient); + using var httpClient = mockHttp.ToHttpClient(); + SignhostApiClient signhostApiClient = new(settings, httpClient); - await signhostApiClient.DeleteTransactionAsync( - "transaction Id", - new DeleteTransactionOptions { SendNotifications = true }); - } + await signhostApiClient.DeleteTransactionAsync( + "transactionId", + new DeleteTransactionOptions { SendNotifications = true }); mockHttp.VerifyNoOutstandingExpectation(); } [Fact] - public async Task when_AddOrReplaceFileToTransaction_is_called_then_we_should_have_called_the_file_put_once() + public async Task When_AddOrReplaceFileToTransaction_is_called_Then_we_should_have_called_the_file_put_once() { - var mockHttp = new MockHttpMessageHandler(); + MockHttpMessageHandler mockHttp = new(); mockHttp - .Expect(HttpMethod.Put, "http://localhost/api/transaction/transaction Id/file/file Id") + .Expect(HttpMethod.Put, "http://localhost/api/transaction/transactionId/file/fileId") .WithHeaders("Content-Type", "application/pdf") .WithHeaders("Digest", "SHA-256=47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU=") .Respond(HttpStatusCode.OK); - using (var httpClient = mockHttp.ToHttpClient()) { - var signhostApiClient = new SignhostApiClient(settings, httpClient); + using var httpClient = mockHttp.ToHttpClient(); + SignhostApiClient signhostApiClient = new(settings, httpClient); - // Create a 0 sized file - using (Stream file = System.IO.File.Create("unittestdocument.pdf")) { - await signhostApiClient.AddOrReplaceFileToTransactionAsync( - file, - "transaction Id", - "file Id"); - } - } + // Create a 0 sized file + using Stream file = File.Create("unittestdocument.pdf"); + await signhostApiClient.AddOrReplaceFileToTransactionAsync( + file, + "transactionId", + "fileId"); mockHttp.VerifyNoOutstandingExpectation(); } [Fact] - public async Task when_AddOrReplaceFileToTransaction_is_called_default_digest_is_sha256() + public async Task When_AddOrReplaceFileToTransaction_is_called_default_digest_is_sha256() { - var mockHttp = new MockHttpMessageHandler(); + MockHttpMessageHandler mockHttp = new(); mockHttp - .Expect(HttpMethod.Put, "http://localhost/api/transaction/transaction Id/file/file Id") + .Expect(HttpMethod.Put, "http://localhost/api/transaction/transactionId/file/fileId") .WithHeaders("Digest", "SHA-256=47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU=") .Respond(HttpStatusCode.OK); - using (var httpClient = mockHttp.ToHttpClient()) { - var signhostApiClient = new SignhostApiClient(settings, httpClient); + using var httpClient = mockHttp.ToHttpClient(); + SignhostApiClient signhostApiClient = new(settings, httpClient); - await signhostApiClient.AddOrReplaceFileToTransactionAsync( - new MemoryStream(), - "transaction Id", - "file Id"); - } + await signhostApiClient.AddOrReplaceFileToTransactionAsync( + new MemoryStream(), + "transactionId", + "fileId"); mockHttp.VerifyNoOutstandingExpectation(); } [Fact] - public async Task when_AddOrReplaceFileToTransaction_with_sha512_is_called_default_digest_is_sha512() + public async Task When_AddOrReplaceFileToTransaction_with_sha512_is_called_default_digest_is_sha512() { - var mockHttp = new MockHttpMessageHandler(); + MockHttpMessageHandler mockHttp = new(); mockHttp - .Expect(HttpMethod.Put, "http://localhost/api/transaction/transaction Id/file/file Id") - .WithHeaders("Digest", "SHA-512=z4PhNX7vuL3xVChQ1m2AB9Yg5AULVxXcg/SpIdNs6c5H0NE8XYXysP+DGNKHfuwvY7kxvUdBeoGlODJ6+SfaPg==") + .Expect(HttpMethod.Put, "http://localhost/api/transaction/transactionId/file/fileId") + .WithHeaders( + "Digest", + "SHA-512=z4PhNX7vuL3xVChQ1m2AB9Yg5AULVxXcg/SpIdNs6c5H0NE8XYXysP+DGNKHfuwvY7kxvUdBeoGlODJ6+SfaPg==" + ) .Respond(HttpStatusCode.OK); - using (var httpClient = mockHttp.ToHttpClient()) { - var signhostApiClient = new SignhostApiClient(settings, httpClient); - - await signhostApiClient.AddOrReplaceFileToTransactionAsync( - new MemoryStream(), - "transaction Id", - "file Id", - new FileUploadOptions{ - DigestOptions = new FileDigestOptions - { - DigestHashAlgorithm = DigestHashAlgorithm.SHA512 - } - }); - } + using var httpClient = mockHttp.ToHttpClient(); + SignhostApiClient signhostApiClient = new(settings, httpClient); + + await signhostApiClient.AddOrReplaceFileToTransactionAsync( + new MemoryStream(), + "transactionId", + "fileId", + new FileUploadOptions { + DigestOptions = new FileDigestOptions { + DigestHashAlgorithm = DigestHashAlgorithm.SHA512, + }, + } + ); mockHttp.VerifyNoOutstandingExpectation(); } [Fact] - public async Task when_AddOrReplaceFileToTransaction_with_digest_value_is_used_as_is() + public async Task When_AddOrReplaceFileToTransaction_with_digest_value_is_used_as_is() { - var mockHttp = new MockHttpMessageHandler(); + MockHttpMessageHandler mockHttp = new(); mockHttp - .Expect(HttpMethod.Put, "http://localhost/api/transaction/transaction Id/file/file Id") + .Expect(HttpMethod.Put, "http://localhost/api/transaction/transactionId/file/fileId") .WithHeaders("Digest", "SHA-256=AAEC") .Respond(HttpStatusCode.OK); - using (var httpClient = mockHttp.ToHttpClient()) { - var signhostApiClient = new SignhostApiClient(settings, httpClient); - - await signhostApiClient.AddOrReplaceFileToTransactionAsync( - new MemoryStream(), - "transaction Id", - "file Id", - new FileUploadOptions - { - DigestOptions = new FileDigestOptions - { - DigestHashAlgorithm = DigestHashAlgorithm.SHA256, - DigestHashValue = new byte[] { 0x00, 0x01, 0x02 } - } - }); - } + using var httpClient = mockHttp.ToHttpClient(); + SignhostApiClient signhostApiClient = new(settings, httpClient); + + await signhostApiClient.AddOrReplaceFileToTransactionAsync( + new MemoryStream(), + "transactionId", + "fileId", + new FileUploadOptions { + DigestOptions = new FileDigestOptions { + DigestHashAlgorithm = DigestHashAlgorithm.SHA256, + DigestHashValue = [0x00, 0x01, 0x02], + }, + } + ); mockHttp.VerifyNoOutstandingExpectation(); } [Fact] - public async Task when_StartTransaction_is_called_then_we_should_have_called_the_transaction_put_once() + public async Task When_StartTransaction_is_called_Then_we_should_have_called_the_transaction_put_once() { - var mockHttp = new MockHttpMessageHandler(); - mockHttp.Expect("http://localhost/api/transaction/transaction Id/start") + MockHttpMessageHandler mockHttp = new(); + mockHttp.Expect("http://localhost/api/transaction/transactionId/start") .Respond(HttpStatusCode.NoContent); - using (var httpClient = mockHttp.ToHttpClient()) { + using var httpClient = mockHttp.ToHttpClient(); - var signhostApiClient = new SignhostApiClient(settings, httpClient); + SignhostApiClient signhostApiClient = new(settings, httpClient); - await signhostApiClient.StartTransactionAsync("transaction Id"); - } + await signhostApiClient.StartTransactionAsync("transactionId"); mockHttp.VerifyNoOutstandingExpectation(); } [Fact] - public async Task when_GetReceipt_is_called_then_we_should_have_called_the_filereceipt_get_once() + public async Task When_GetReceipt_is_called_Then_we_should_have_called_the_filereceipt_get_once() { - var mockHttp = new MockHttpMessageHandler(); - mockHttp.Expect("http://localhost/api/file/receipt/transaction ID") + MockHttpMessageHandler mockHttp = new(); + mockHttp.Expect("http://localhost/api/file/receipt/transactionId") .Respond(HttpStatusCode.OK); - using (var httpClient = mockHttp.ToHttpClient()) { - var signhostApiClient = new SignhostApiClient(settings, httpClient); + using var httpClient = mockHttp.ToHttpClient(); + SignhostApiClient signhostApiClient = new(settings, httpClient); - var receipt = await signhostApiClient.GetReceiptAsync("transaction ID"); - } + var receipt = await signhostApiClient.GetReceiptAsync("transactionId"); mockHttp.VerifyNoOutstandingExpectation(); } [Fact] - public async Task when_GetDocument_is_called_then_we_should_have_called_the_file_get_once() + public async Task When_GetDocument_is_called_Then_we_should_have_called_the_file_get_once() { - var mockHttp = new MockHttpMessageHandler(); - mockHttp.Expect(HttpMethod.Get, "http://localhost/api/transaction/*/file/file Id") + MockHttpMessageHandler mockHttp = new(); + mockHttp.Expect(HttpMethod.Get, "http://localhost/api/transaction/*/file/fileId") .Respond(HttpStatusCode.OK, new StringContent(string.Empty)); - using (var httpClient = mockHttp.ToHttpClient()) { + using var httpClient = mockHttp.ToHttpClient(); - var signhostApiClient = new SignhostApiClient(settings, httpClient); + SignhostApiClient signhostApiClient = new(settings, httpClient); - var document = await signhostApiClient.GetDocumentAsync("transaction Id", "file Id"); - } + var document = await signhostApiClient.GetDocumentAsync("transactionId", "fileId"); mockHttp.VerifyNoOutstandingExpectation(); } @@ -579,17 +550,20 @@ public async Task when_GetDocument_is_called_then_we_should_have_called_the_file [Fact] public async Task When_a_transaction_json_is_returned_it_is_deserialized_correctly() { - var mockHttp = new MockHttpMessageHandler(); - mockHttp.Expect(HttpMethod.Post, "http://localhost/api/transaction") - .Respond(HttpStatusCode.OK, new StringContent(JsonResources.TransactionSingleSignerJson)); - - using (var httpClient = mockHttp.ToHttpClient()) { + MockHttpMessageHandler mockHttp = new(); + mockHttp + .Expect(HttpMethod.Post, "http://localhost/api/transaction") + .Respond( + HttpStatusCode.OK, + new StringContent(JsonResources.TransactionSingleSignerJson) + ); - var signhostApiClient = new SignhostApiClient(settings, httpClient); + using var httpClient = mockHttp.ToHttpClient(); + SignhostApiClient signhostApiClient = new(settings, httpClient); - var result = await signhostApiClient.CreateTransactionAsync(new CreateTransactionRequest { - Signers = [ - new CreateSignerRequest { + var result = await signhostApiClient.CreateTransactionAsync(new() { + Signers = [ + new() { Email = "test@example.com", Verifications = [ new PhoneNumberVerification { @@ -597,32 +571,30 @@ public async Task When_a_transaction_json_is_returned_it_is_deserialized_correct } ] } - ] - }); - - result.Id.Should().Be("50262c3f-9744-45bf-a4c6-8a3whatever"); - result.CanceledDateTime.Should().HaveYear(2017); - result.Status.Should().Be(TransactionStatus.WaitingForDocument); - result.Signers.Should().HaveCount(1); - result.Receivers.Should().HaveCount(0); - result.Reference.Should().Be("Contract #123"); - result.SignRequestMode.Should().Be(2); - result.DaysToExpire.Should().Be(14); - result.Signers[0].Id.Should().Be("Signer1"); - result.Signers[0].Email.Should().Be("test1@example.com"); - result.Signers[0].Verifications.Should().HaveCount(1); - result.Signers[0].Verifications[0].Should().BeOfType() - .And.Subject.Should().BeEquivalentTo(new PhoneNumberVerification { - Number = "+31615123456" - }); - result.Signers[0].Activities.Should().HaveCount(3); - result.Signers[0].Activities[0].Should().BeEquivalentTo(new Activity - { - Id = "Activity1", - Code = ActivityType.Opened, - CreatedDateTime = DateTimeOffset.Parse("2017-05-31T22:15:17.6409005+02:00") - }); - } + ] + }); + + result.Id.Should().Be("50262c3f-9744-45bf-a4c6-8a3whatever"); + result.CanceledDateTime.Should().HaveYear(2017); + result.Status.Should().Be(TransactionStatus.WaitingForDocument); + result.Signers.Should().HaveCount(1); + result.Receivers.Should().HaveCount(0); + result.Reference.Should().Be("Contract #123"); + result.SignRequestMode.Should().Be(2); + result.DaysToExpire.Should().Be(14); + result.Signers[0].Id.Should().Be("Signer1"); + result.Signers[0].Email.Should().Be("test1@example.com"); + result.Signers[0].Verifications.Should().HaveCount(1); + result.Signers[0].Verifications[0].Should().BeOfType() + .And.Subject.Should().BeEquivalentTo(new PhoneNumberVerification { + Number = "+31615123456" + }); + result.Signers[0].Activities.Should().HaveCount(3); + result.Signers[0].Activities[0].Should().BeEquivalentTo(new Activity { + Id = "Activity1", + Code = ActivityType.Opened, + CreatedDateTime = DateTimeOffset.Parse("2017-05-31T22:15:17.6409005+02:00") + }); mockHttp.VerifyNoOutstandingExpectation(); } @@ -644,7 +616,7 @@ MockedRequest AddHeaders(MockedRequest request) .WithHeaders("X-Custom", "test"); } - var mockHttp = new MockHttpMessageHandler(); + MockHttpMessageHandler mockHttp = new(); AddHeaders(mockHttp.Expect(HttpMethod.Post, "http://localhost/api/transaction")) .Respond(new StringContent(JsonResources.TransactionSingleSignerJson)); AddHeaders(mockHttp.Expect(HttpMethod.Put, "http://localhost/api/transaction/*/file/somefileid")) @@ -654,18 +626,19 @@ MockedRequest AddHeaders(MockedRequest request) AddHeaders(mockHttp.Expect(HttpMethod.Put, "http://localhost/api/transaction/*/start")) .Respond(HttpStatusCode.NoContent); - using (var httpClient = mockHttp.ToHttpClient()) { - var clientSettings = isOauth ? oauthSettings : settings; - clientSettings.AddHeader = add => add("X-Custom", "test"); - var signhostApiClient = new SignhostApiClient(clientSettings, httpClient); + using var httpClient = mockHttp.ToHttpClient(); + var clientSettings = isOauth ? oauthSettings : settings; + clientSettings.AddHeader = add => add("X-Custom", "test"); + SignhostApiClient signhostApiClient = new(clientSettings, httpClient); - var result = await signhostApiClient.CreateTransactionAsync(new CreateTransactionRequest()); - await signhostApiClient.AddOrReplaceFileMetaToTransactionAsync(new FileMeta(), result.Id, "somefileid"); - using (Stream file = System.IO.File.Create("unittestdocument.pdf")) { - await signhostApiClient.AddOrReplaceFileToTransactionAsync(file, result.Id, "somefileid"); - } - await signhostApiClient.StartTransactionAsync(result.Id); - } + var result = await signhostApiClient + .CreateTransactionAsync(new CreateTransactionRequest()); + await signhostApiClient + .AddOrReplaceFileMetaToTransactionAsync(new FileMeta(), result.Id, "somefileid"); + + using Stream file = File.Create("unittestdocument.pdf"); + await signhostApiClient.AddOrReplaceFileToTransactionAsync(file, result.Id, "somefileid"); + await signhostApiClient.StartTransactionAsync(result.Id); mockHttp.VerifyNoOutstandingExpectation(); mockHttp.VerifyNoOutstandingRequest(); @@ -674,20 +647,18 @@ MockedRequest AddHeaders(MockedRequest request) [Fact] public async Task When_a_minimal_response_is_retrieved_list_and_dictionaries_are_not_null() { - var mockHttp = new MockHttpMessageHandler(); + MockHttpMessageHandler mockHttp = new(); mockHttp .Expect(HttpMethod.Get, "http://localhost/api/transaction/c487be92-0255-40c7-bd7d-20805a65e7d9") .Respond(new StringContent(JsonResources.MinimalTransactionResponse)); - using (var httpClient = mockHttp.ToHttpClient()) - { - var signhostApiClient = new SignhostApiClient(settings, httpClient); + using var httpClient = mockHttp.ToHttpClient(); + SignhostApiClient signhostApiClient = new(settings, httpClient); - var result = await signhostApiClient.GetTransactionAsync("c487be92-0255-40c7-bd7d-20805a65e7d9"); + var result = await signhostApiClient.GetTransactionAsync("c487be92-0255-40c7-bd7d-20805a65e7d9"); - result.Signers.Should().BeEmpty(); - result.Receivers.Should().BeEmpty(); - result.Files.Should().BeEmpty(); - } + result.Signers.Should().BeEmpty(); + result.Receivers.Should().BeEmpty(); + result.Files.Should().BeEmpty(); } } diff --git a/src/SignhostAPIClient.Tests/SignhostApiReceiverTests.cs b/src/SignhostAPIClient.Tests/SignhostApiReceiverTests.cs index 4bab87e..81b6bcb 100644 --- a/src/SignhostAPIClient.Tests/SignhostApiReceiverTests.cs +++ b/src/SignhostAPIClient.Tests/SignhostApiReceiverTests.cs @@ -8,69 +8,81 @@ namespace Signhost.APIClient.Rest.Tests; public class SignhostApiReceiverTests { - private SignhostApiReceiverSettings receiverSettings = new SignhostApiReceiverSettings("SharedSecret"); + private readonly SignhostApiReceiverSettings receiverSettings = + new("SharedSecret"); [Fact] - public void when_IsPostbackChecksumValid_is_called_with_valid_postback_in_body_then_true_is_returned() + public void When_IsPostbackChecksumValid_is_called_with_valid_postback_in_body_Then_true_is_returned() { // Arrange - IDictionary headers = new Dictionary { { "Content-Type", new[] { "application/json" } } }; + var headers = new Dictionary { + ["Content-Type"] = ["application/json"] + }; + string body = JsonResources.MockPostbackValid; // Act - SignhostApiReceiver signhostApiReceiver = new SignhostApiReceiver(receiverSettings); - bool result = signhostApiReceiver.IsPostbackChecksumValid(headers, body, out Transaction transaction); + SignhostApiReceiver signhostApiReceiver = new(receiverSettings); + bool result = signhostApiReceiver + .IsPostbackChecksumValid(headers, body, out Transaction _); // Assert result.Should().BeTrue(); } [Fact] - public void when_IsPostbackChecksumValid_is_called_with_invalid_postback_in_body_then_false_is_returned() + public void When_IsPostbackChecksumValid_is_called_with_invalid_postback_in_body_Then_false_is_returned() { // Arrange - IDictionary headers = new Dictionary { { "Content-Type", new[] { "application/json" } } }; + var headers = new Dictionary { + ["Content-Type"] = ["application/json"] + }; + string body = JsonResources.MockPostbackInvalid; // Act - SignhostApiReceiver signhostApiReceiver = new SignhostApiReceiver(receiverSettings); - bool result = signhostApiReceiver.IsPostbackChecksumValid(headers, body, out Transaction transaction); + SignhostApiReceiver signhostApiReceiver = new(receiverSettings); + bool result = signhostApiReceiver + .IsPostbackChecksumValid(headers, body, out Transaction _); // Assert result.Should().BeFalse(); } [Fact] - public void when_IsPostbackChecksumValid_is_called_with_valid_postback_in_header_then_true_is_returned() + public void When_IsPostbackChecksumValid_is_called_with_valid_postback_in_header_Then_true_is_returned() { // Arrange - IDictionary headers = new Dictionary { - { "Content-Type", new[] { "application/json" }}, - {"Checksum", new[] {"cdc09eee2ed6df2846dcc193aedfef59f2834f8d"}} + var headers = new Dictionary { + ["Content-Type"] = ["application/json"], + ["Checksum"] = ["cdc09eee2ed6df2846dcc193aedfef59f2834f8d"] }; + string body = JsonResources.MockPostbackValid; // Act - SignhostApiReceiver signhostApiReceiver = new SignhostApiReceiver(receiverSettings); - bool result = signhostApiReceiver.IsPostbackChecksumValid(headers, body, out Transaction transaction); + SignhostApiReceiver signhostApiReceiver = new(receiverSettings); + bool result = signhostApiReceiver + .IsPostbackChecksumValid(headers, body, out Transaction _); // Assert result.Should().BeTrue(); } [Fact] - public void when_IsPostbackChecksumValid_is_called_with_invalid_postback_in_header_then_false_is_returned() + public void When_IsPostbackChecksumValid_is_called_with_invalid_postback_in_header_Then_false_is_returned() { // Arrange - IDictionary headers = new Dictionary { - { "Content-Type", new[] { "application/json" }}, - {"Checksum", new[] {"70dda90616f744797972c0d2f787f86643a60c83"}} + var headers = new Dictionary { + ["Content-Type"] = ["application/json"], + ["Checksum"] = ["70dda90616f744797972c0d2f787f86643a60c83"] }; string body = JsonResources.MockPostbackValid; // Act - SignhostApiReceiver signhostApiReceiver = new SignhostApiReceiver(receiverSettings); - bool result = signhostApiReceiver.IsPostbackChecksumValid(headers, body, out Transaction transaction); + SignhostApiReceiver signhostApiReceiver = new(receiverSettings); + bool result = signhostApiReceiver + .IsPostbackChecksumValid(headers, body, out Transaction _); // Assert result.Should().BeFalse(); diff --git a/src/SignhostAPIClient/Rest/ApiResponse.cs b/src/SignhostAPIClient/Rest/ApiResponse.cs index ac54a92..06e6b0b 100644 --- a/src/SignhostAPIClient/Rest/ApiResponse.cs +++ b/src/SignhostAPIClient/Rest/ApiResponse.cs @@ -12,7 +12,7 @@ public class ApiResponse public ApiResponse(HttpResponseMessage httpResponse, TValue? value) { this.httpResponse = httpResponse; - this.Value = value; + Value = value; } public TValue? Value { get; private set; } @@ -25,8 +25,7 @@ public async Task EnsureAvailableStatusCodeAsync( if (HttpStatusCode == HttpStatusCode.Gone) { throw new ErrorHandling.GoneException( httpResponse.ReasonPhrase ?? "No reason phrase provided", - Value) - { + Value) { ResponseBody = await httpResponse.Content #if NETFRAMEWORK || NETSTANDARD2_0 .ReadAsStringAsync() diff --git a/src/SignhostAPIClient/Rest/DataObjects/Activity.cs b/src/SignhostAPIClient/Rest/DataObjects/Activity.cs index c550a2b..86f4453 100644 --- a/src/SignhostAPIClient/Rest/DataObjects/Activity.cs +++ b/src/SignhostAPIClient/Rest/DataObjects/Activity.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Text.Json.Serialization; namespace Signhost.APIClient.Rest.DataObjects; diff --git a/src/SignhostAPIClient/Rest/DataObjects/ActivityType.cs b/src/SignhostAPIClient/Rest/DataObjects/ActivityType.cs index 89e3f7b..2ef5d2a 100644 --- a/src/SignhostAPIClient/Rest/DataObjects/ActivityType.cs +++ b/src/SignhostAPIClient/Rest/DataObjects/ActivityType.cs @@ -1,4 +1,4 @@ -namespace Signhost.APIClient.Rest.DataObjects; +namespace Signhost.APIClient.Rest.DataObjects; /// /// type codes as defined in the Signhost API. diff --git a/src/SignhostAPIClient/Rest/DataObjects/CreateTransactionRequest.cs b/src/SignhostAPIClient/Rest/DataObjects/CreateTransactionRequest.cs index 7a2e028..6d0d57e 100644 --- a/src/SignhostAPIClient/Rest/DataObjects/CreateTransactionRequest.cs +++ b/src/SignhostAPIClient/Rest/DataObjects/CreateTransactionRequest.cs @@ -56,12 +56,10 @@ public class CreateTransactionRequest /// /// Gets or sets the list of signers for the transaction. /// - public IList Signers { get; set; } = - new List(); + public IList Signers { get; set; } = new List(); /// /// Gets or sets the list of receivers who get copies of completed documents. /// - public IList Receivers { get; set; } = - new List(); + public IList Receivers { get; set; } = new List(); } diff --git a/src/SignhostAPIClient/Rest/DataObjects/DeleteTransactionOptions.cs b/src/SignhostAPIClient/Rest/DataObjects/DeleteTransactionOptions.cs index 5defee9..f2ec106 100644 --- a/src/SignhostAPIClient/Rest/DataObjects/DeleteTransactionOptions.cs +++ b/src/SignhostAPIClient/Rest/DataObjects/DeleteTransactionOptions.cs @@ -1,4 +1,4 @@ -namespace Signhost.APIClient.Rest.DataObjects; +namespace Signhost.APIClient.Rest.DataObjects; public class DeleteTransactionOptions { diff --git a/src/SignhostAPIClient/Rest/DataObjects/FileEntry.cs b/src/SignhostAPIClient/Rest/DataObjects/FileEntry.cs index 208c070..333ad67 100644 --- a/src/SignhostAPIClient/Rest/DataObjects/FileEntry.cs +++ b/src/SignhostAPIClient/Rest/DataObjects/FileEntry.cs @@ -1,4 +1,4 @@ -using System.Collections.Generic; +using System.Collections.Generic; namespace Signhost.APIClient.Rest.DataObjects; diff --git a/src/SignhostAPIClient/Rest/DataObjects/FileLink.cs b/src/SignhostAPIClient/Rest/DataObjects/FileLink.cs index 6fe1ad1..8dced5f 100644 --- a/src/SignhostAPIClient/Rest/DataObjects/FileLink.cs +++ b/src/SignhostAPIClient/Rest/DataObjects/FileLink.cs @@ -1,4 +1,4 @@ -namespace Signhost.APIClient.Rest.DataObjects; +namespace Signhost.APIClient.Rest.DataObjects; public class FileLink { diff --git a/src/SignhostAPIClient/Rest/DataObjects/FileMetaData/Field.cs b/src/SignhostAPIClient/Rest/DataObjects/FileMetaData/Field.cs index f8ed5bd..62ed0ac 100644 --- a/src/SignhostAPIClient/Rest/DataObjects/FileMetaData/Field.cs +++ b/src/SignhostAPIClient/Rest/DataObjects/FileMetaData/Field.cs @@ -1,4 +1,4 @@ -using System.Text.Json.Serialization; +using System.Text.Json.Serialization; using Signhost.APIClient.Rest.JsonConverters; namespace Signhost.APIClient.Rest.DataObjects; diff --git a/src/SignhostAPIClient/Rest/DataObjects/FileMetaData/FileMeta.cs b/src/SignhostAPIClient/Rest/DataObjects/FileMetaData/FileMeta.cs index 93ab26c..4212292 100644 --- a/src/SignhostAPIClient/Rest/DataObjects/FileMetaData/FileMeta.cs +++ b/src/SignhostAPIClient/Rest/DataObjects/FileMetaData/FileMeta.cs @@ -1,4 +1,4 @@ -using System.Collections.Generic; +using System.Collections.Generic; using System.Text.Json.Serialization; namespace Signhost.APIClient.Rest.DataObjects; diff --git a/src/SignhostAPIClient/Rest/DataObjects/FileMetaData/FileSignerMeta.cs b/src/SignhostAPIClient/Rest/DataObjects/FileMetaData/FileSignerMeta.cs index 6ef1c7f..89b8a6c 100644 --- a/src/SignhostAPIClient/Rest/DataObjects/FileMetaData/FileSignerMeta.cs +++ b/src/SignhostAPIClient/Rest/DataObjects/FileMetaData/FileSignerMeta.cs @@ -1,4 +1,4 @@ -namespace Signhost.APIClient.Rest.DataObjects; +namespace Signhost.APIClient.Rest.DataObjects; public class FileSignerMeta { diff --git a/src/SignhostAPIClient/Rest/DataObjects/FileMetaData/Location.cs b/src/SignhostAPIClient/Rest/DataObjects/FileMetaData/Location.cs index 4492636..997bc21 100644 --- a/src/SignhostAPIClient/Rest/DataObjects/FileMetaData/Location.cs +++ b/src/SignhostAPIClient/Rest/DataObjects/FileMetaData/Location.cs @@ -1,4 +1,4 @@ -namespace Signhost.APIClient.Rest.DataObjects; +namespace Signhost.APIClient.Rest.DataObjects; public class Location { diff --git a/src/SignhostAPIClient/Rest/DataObjects/Signer.cs b/src/SignhostAPIClient/Rest/DataObjects/Signer.cs index 9b32182..f445f73 100644 --- a/src/SignhostAPIClient/Rest/DataObjects/Signer.cs +++ b/src/SignhostAPIClient/Rest/DataObjects/Signer.cs @@ -61,8 +61,7 @@ public class Signer public string ReceiptUrl { get; set; } = default!; - public IList Activities { get; set; } = - new List(); + public IList Activities { get; set; } = new List(); public dynamic? Context { get; set; } } diff --git a/src/SignhostAPIClient/Rest/DataObjects/Transaction.cs b/src/SignhostAPIClient/Rest/DataObjects/Transaction.cs index e395370..089958d 100644 --- a/src/SignhostAPIClient/Rest/DataObjects/Transaction.cs +++ b/src/SignhostAPIClient/Rest/DataObjects/Transaction.cs @@ -23,18 +23,15 @@ public class Transaction /// public string? CancellationReason { get; set; } - public IDictionary Files { get; set; } = - new Dictionary(); + public IDictionary Files { get; set; } = new Dictionary(); public TransactionStatus Status { get; set; } public bool Seal { get; set; } - public IList Signers { get; set; } - = new List(); + public IList Signers { get; set; } = new List(); - public IList Receivers { get; set; } - = new List(); + public IList Receivers { get; set; } = new List(); public string? Reference { get; set; } diff --git a/src/SignhostAPIClient/Rest/DataObjects/TransactionStatus.cs b/src/SignhostAPIClient/Rest/DataObjects/TransactionStatus.cs index 7485bf1..a4b62d7 100644 --- a/src/SignhostAPIClient/Rest/DataObjects/TransactionStatus.cs +++ b/src/SignhostAPIClient/Rest/DataObjects/TransactionStatus.cs @@ -1,4 +1,4 @@ -namespace Signhost.APIClient.Rest.DataObjects; +namespace Signhost.APIClient.Rest.DataObjects; public enum TransactionStatus { diff --git a/src/SignhostAPIClient/Rest/DataObjects/Verifications/ConsentVerification.cs b/src/SignhostAPIClient/Rest/DataObjects/Verifications/ConsentVerification.cs index 6a045ce..b43c7ce 100644 --- a/src/SignhostAPIClient/Rest/DataObjects/Verifications/ConsentVerification.cs +++ b/src/SignhostAPIClient/Rest/DataObjects/Verifications/ConsentVerification.cs @@ -1,4 +1,4 @@ -namespace Signhost.APIClient.Rest.DataObjects; +namespace Signhost.APIClient.Rest.DataObjects; /// /// Adds a consent verification screen diff --git a/src/SignhostAPIClient/Rest/DataObjects/Verifications/DigidVerification.cs b/src/SignhostAPIClient/Rest/DataObjects/Verifications/DigidVerification.cs index d0bb405..2fb6dd2 100644 --- a/src/SignhostAPIClient/Rest/DataObjects/Verifications/DigidVerification.cs +++ b/src/SignhostAPIClient/Rest/DataObjects/Verifications/DigidVerification.cs @@ -1,4 +1,4 @@ -namespace Signhost.APIClient.Rest.DataObjects; +namespace Signhost.APIClient.Rest.DataObjects; public class DigidVerification : IVerification diff --git a/src/SignhostAPIClient/Rest/DataObjects/Verifications/IVerification.cs b/src/SignhostAPIClient/Rest/DataObjects/Verifications/IVerification.cs index de6a297..9c58647 100644 --- a/src/SignhostAPIClient/Rest/DataObjects/Verifications/IVerification.cs +++ b/src/SignhostAPIClient/Rest/DataObjects/Verifications/IVerification.cs @@ -1,4 +1,4 @@ -using System.Text.Json.Serialization; +using System.Text.Json.Serialization; namespace Signhost.APIClient.Rest.DataObjects; diff --git a/src/SignhostAPIClient/Rest/DataObjects/Verifications/IdealVerification.cs b/src/SignhostAPIClient/Rest/DataObjects/Verifications/IdealVerification.cs index 53600a4..d13a96c 100644 --- a/src/SignhostAPIClient/Rest/DataObjects/Verifications/IdealVerification.cs +++ b/src/SignhostAPIClient/Rest/DataObjects/Verifications/IdealVerification.cs @@ -1,4 +1,4 @@ -namespace Signhost.APIClient.Rest.DataObjects; +namespace Signhost.APIClient.Rest.DataObjects; public class IdealVerification : IVerification diff --git a/src/SignhostAPIClient/Rest/DataObjects/Verifications/IdinVerification.cs b/src/SignhostAPIClient/Rest/DataObjects/Verifications/IdinVerification.cs index f753810..c0f360b 100644 --- a/src/SignhostAPIClient/Rest/DataObjects/Verifications/IdinVerification.cs +++ b/src/SignhostAPIClient/Rest/DataObjects/Verifications/IdinVerification.cs @@ -1,4 +1,4 @@ -using System.Collections.Generic; +using System.Collections.Generic; namespace Signhost.APIClient.Rest.DataObjects; diff --git a/src/SignhostAPIClient/Rest/DataObjects/Verifications/PhoneNumberVerification.cs b/src/SignhostAPIClient/Rest/DataObjects/Verifications/PhoneNumberVerification.cs index b1c6867..84e15c7 100644 --- a/src/SignhostAPIClient/Rest/DataObjects/Verifications/PhoneNumberVerification.cs +++ b/src/SignhostAPIClient/Rest/DataObjects/Verifications/PhoneNumberVerification.cs @@ -1,4 +1,4 @@ -namespace Signhost.APIClient.Rest.DataObjects; +namespace Signhost.APIClient.Rest.DataObjects; public class PhoneNumberVerification : IVerification diff --git a/src/SignhostAPIClient/Rest/DataObjects/Verifications/ScribbleVerification.cs b/src/SignhostAPIClient/Rest/DataObjects/Verifications/ScribbleVerification.cs index 4cb1ee2..8ba484d 100644 --- a/src/SignhostAPIClient/Rest/DataObjects/Verifications/ScribbleVerification.cs +++ b/src/SignhostAPIClient/Rest/DataObjects/Verifications/ScribbleVerification.cs @@ -1,4 +1,4 @@ -namespace Signhost.APIClient.Rest.DataObjects; +namespace Signhost.APIClient.Rest.DataObjects; public class ScribbleVerification : IVerification diff --git a/src/SignhostAPIClient/Rest/DataObjects/Verifications/SurfnetVerification.cs b/src/SignhostAPIClient/Rest/DataObjects/Verifications/SurfnetVerification.cs index a34adc4..45db66a 100644 --- a/src/SignhostAPIClient/Rest/DataObjects/Verifications/SurfnetVerification.cs +++ b/src/SignhostAPIClient/Rest/DataObjects/Verifications/SurfnetVerification.cs @@ -1,4 +1,4 @@ -using System.Collections.Generic; +using System.Collections.Generic; namespace Signhost.APIClient.Rest.DataObjects; diff --git a/src/SignhostAPIClient/Rest/ErrorHandling/BadAuthorizationException.cs b/src/SignhostAPIClient/Rest/ErrorHandling/BadAuthorizationException.cs index 18260ac..5beab9e 100644 --- a/src/SignhostAPIClient/Rest/ErrorHandling/BadAuthorizationException.cs +++ b/src/SignhostAPIClient/Rest/ErrorHandling/BadAuthorizationException.cs @@ -7,7 +7,7 @@ public class BadAuthorizationException : SignhostRestApiClientException { public BadAuthorizationException() - : base("API call returned a 401 error code. Please check your request headers.") + : base("API call returned a 401 error code. Please check your request headers.") { HelpLink = "https://api.signhost.com/Help"; } diff --git a/src/SignhostAPIClient/Rest/ErrorHandling/NotFoundException.cs b/src/SignhostAPIClient/Rest/ErrorHandling/NotFoundException.cs index 0b61c9e..138cc56 100644 --- a/src/SignhostAPIClient/Rest/ErrorHandling/NotFoundException.cs +++ b/src/SignhostAPIClient/Rest/ErrorHandling/NotFoundException.cs @@ -7,7 +7,7 @@ public class NotFoundException : SignhostRestApiClientException { public NotFoundException() - : base() + : base() { } diff --git a/src/SignhostAPIClient/Rest/ErrorHandling/SignhostRestApiClientException.cs b/src/SignhostAPIClient/Rest/ErrorHandling/SignhostRestApiClientException.cs index d7e1296..3b4ef90 100644 --- a/src/SignhostAPIClient/Rest/ErrorHandling/SignhostRestApiClientException.cs +++ b/src/SignhostAPIClient/Rest/ErrorHandling/SignhostRestApiClientException.cs @@ -7,7 +7,7 @@ public class SignhostRestApiClientException : Exception { public SignhostRestApiClientException() - : base() + : base() { } diff --git a/src/SignhostAPIClient/Rest/FileDigestOptions.cs b/src/SignhostAPIClient/Rest/FileDigestOptions.cs index bb5d770..9493d52 100644 --- a/src/SignhostAPIClient/Rest/FileDigestOptions.cs +++ b/src/SignhostAPIClient/Rest/FileDigestOptions.cs @@ -1,4 +1,4 @@ -namespace Signhost.APIClient.Rest; +namespace Signhost.APIClient.Rest; /// /// File digest options for file uploads. diff --git a/src/SignhostAPIClient/Rest/FileUploadOptions.cs b/src/SignhostAPIClient/Rest/FileUploadOptions.cs index 4336b6d..09ff94c 100644 --- a/src/SignhostAPIClient/Rest/FileUploadOptions.cs +++ b/src/SignhostAPIClient/Rest/FileUploadOptions.cs @@ -1,4 +1,4 @@ -namespace Signhost.APIClient.Rest; +namespace Signhost.APIClient.Rest; /// /// Options to be used during a file upload @@ -8,6 +8,5 @@ public class FileUploadOptions /// /// Gets or sets the . /// - public FileDigestOptions DigestOptions { get; set; } - = new FileDigestOptions(); + public FileDigestOptions DigestOptions { get; set; } = new FileDigestOptions(); } diff --git a/src/SignhostAPIClient/Rest/HttpContentJsonExtensions.cs b/src/SignhostAPIClient/Rest/HttpContentJsonExtensions.cs index f875998..9491187 100644 --- a/src/SignhostAPIClient/Rest/HttpContentJsonExtensions.cs +++ b/src/SignhostAPIClient/Rest/HttpContentJsonExtensions.cs @@ -16,15 +16,13 @@ internal static class HttpContentJsonExtensions /// to read. /// A deserialized value of /// or default(T) if no content is available. - internal static async Task FromJsonAsync( - this HttpContent httpContent) + internal static async Task FromJsonAsync(this HttpContent httpContent) { if (httpContent is null) { return default; } - var json = await httpContent.ReadAsStringAsync() - .ConfigureAwait(false); + string json = await httpContent.ReadAsStringAsync().ConfigureAwait(false); return JsonSerializer.Deserialize(json, SignhostJsonSerializerOptions.Default); } } diff --git a/src/SignhostAPIClient/Rest/ISignhostApiClientSettings.cs b/src/SignhostAPIClient/Rest/ISignhostApiClientSettings.cs index f017de5..5b42396 100644 --- a/src/SignhostAPIClient/Rest/ISignhostApiClientSettings.cs +++ b/src/SignhostAPIClient/Rest/ISignhostApiClientSettings.cs @@ -1,4 +1,4 @@ -using System; +using System; namespace Signhost.APIClient.Rest; diff --git a/src/SignhostAPIClient/Rest/JsonContent.cs b/src/SignhostAPIClient/Rest/JsonContent.cs index cb31cea..bb29567 100644 --- a/src/SignhostAPIClient/Rest/JsonContent.cs +++ b/src/SignhostAPIClient/Rest/JsonContent.cs @@ -1,4 +1,4 @@ -using System.Net.Http; +using System.Net.Http; using System.Net.Http.Headers; using System.Text.Json; diff --git a/src/SignhostAPIClient/Rest/JsonConverters/JsonObjectConverter.cs b/src/SignhostAPIClient/Rest/JsonConverters/JsonObjectConverter.cs index ceaa0e4..a604e69 100644 --- a/src/SignhostAPIClient/Rest/JsonConverters/JsonObjectConverter.cs +++ b/src/SignhostAPIClient/Rest/JsonConverters/JsonObjectConverter.cs @@ -16,7 +16,7 @@ public class JsonObjectConverter return reader.TokenType switch { JsonTokenType.String => reader.GetString(), - JsonTokenType.Number => reader.TryGetInt64(out var value) + JsonTokenType.Number => reader.TryGetInt64(out long value) ? value : reader.GetDouble(), JsonTokenType.True or JsonTokenType.False => reader.GetBoolean(), @@ -49,7 +49,8 @@ public override void Write(Utf8JsonWriter writer, object? value, JsonSerializerO writer.WriteNumberValue(dec); } else { - throw new JsonException($"Field value must be string, number, or boolean, but got {value.GetType().Name}"); + throw new JsonException( + $"Field value must be string, number, or boolean, but got {value.GetType().Name}"); } } } diff --git a/src/SignhostAPIClient/Rest/JsonConverters/LevelEnumConverter.cs b/src/SignhostAPIClient/Rest/JsonConverters/LevelEnumConverter.cs index 06b9c0b..043ceb0 100644 --- a/src/SignhostAPIClient/Rest/JsonConverters/LevelEnumConverter.cs +++ b/src/SignhostAPIClient/Rest/JsonConverters/LevelEnumConverter.cs @@ -22,7 +22,7 @@ internal class LevelEnumConverter } if (reader.TokenType == JsonTokenType.String) { - var value = reader.GetString() ?? string.Empty; + string value = reader.GetString() ?? string.Empty; if (Enum.TryParse(value, out var level)) { return level; } diff --git a/src/SignhostAPIClient/Rest/SignHostApiClient.cs b/src/SignhostAPIClient/Rest/SignHostApiClient.cs index 2caa708..6a7b6c8 100644 --- a/src/SignhostAPIClient/Rest/SignHostApiClient.cs +++ b/src/SignhostAPIClient/Rest/SignHostApiClient.cs @@ -48,48 +48,40 @@ public SignhostApiClient( HttpClient httpClient) { this.settings = settings; - this.client = httpClient; - this.client.BaseAddress = new Uri( + client = httpClient; + client.BaseAddress = new Uri( settings.Endpoint + (settings.Endpoint.EndsWith("/") ? string.Empty : "/")); - this.client.DefaultRequestHeaders.UserAgent.Add( - new System.Net.Http.Headers.ProductInfoHeaderValue( - "SignhostClientLibrary", - Version)); - this.client.DefaultRequestHeaders.Add("Application", ApplicationHeader); + client.DefaultRequestHeaders.UserAgent.Add( + new ProductInfoHeaderValue("SignhostClientLibrary", Version)); + client.DefaultRequestHeaders.Add("Application", ApplicationHeader); if (!string.IsNullOrWhiteSpace(settings.UserToken)) { - this.client.DefaultRequestHeaders.Add("Authorization", AuthorizationHeader); + client.DefaultRequestHeaders.Add("Authorization", AuthorizationHeader); } - this.client.DefaultRequestHeaders.Accept.Add(MediaTypeWithQualityHeaderValue.Parse($"application/vnd.signhost.{ApiVersion}+json")); - settings.AddHeader?.Invoke(this.client.DefaultRequestHeaders.Add); + client.DefaultRequestHeaders.Accept.Add( + MediaTypeWithQualityHeaderValue.Parse($"application/vnd.signhost.{ApiVersion}+json")); + settings.AddHeader?.Invoke(client.DefaultRequestHeaders.Add); } - private string ApplicationHeader - => $"APPKey {settings.APPKey}"; + private string ApplicationHeader => $"APPKey {settings.APPKey}"; - private string AuthorizationHeader - => $"APIKey {settings.UserToken}"; + private string AuthorizationHeader => $"APIKey {settings.UserToken}"; /// public async Task CreateTransactionAsync( CreateTransactionRequest request, CancellationToken cancellationToken = default) { - ArgumentNullException.ThrowIfNull(request); + request.ThrowIfNullOrEmpty(nameof(request)); var result = await client - .PostAsync( - "transaction", - JsonContent.From(request), - cancellationToken) + .PostAsync("transaction", JsonContent.From(request), cancellationToken) .EnsureSignhostSuccessStatusCodeAsync() .ConfigureAwait(false); - return await result.Content.FromJsonAsync() - .ConfigureAwait(false) - ?? throw new InvalidOperationException( - "Failed to deserialize the transaction."); + return await result.Content.FromJsonAsync().ConfigureAwait(false) + ?? throw new InvalidOperationException("Failed to deserialize the transaction."); } /// @@ -97,20 +89,13 @@ public async Task> GetTransactionResponseAsync( string transactionId, CancellationToken cancellationToken = default) { - ArgumentNullException.ThrowIfNull(transactionId); - - if (string.IsNullOrWhiteSpace(transactionId)) { - throw new ArgumentException("Cannot be empty or contain only whitespaces.", nameof(transactionId)); - } + transactionId.ThrowIfNullOrEmpty(nameof(transactionId)); var result = await client - .GetAsync( - "transaction".JoinPaths(transactionId), - cancellationToken) + .GetAsync("transaction".JoinPaths(transactionId), cancellationToken) .EnsureSignhostSuccessStatusCodeAsync(HttpStatusCode.Gone) .ConfigureAwait(false); - var transaction = await result.Content.FromJsonAsync() - .ConfigureAwait(false); + var transaction = await result.Content.FromJsonAsync().ConfigureAwait(false); return new ApiResponse(result, transaction); } @@ -120,16 +105,13 @@ public async Task GetTransactionAsync( string transactionId, CancellationToken cancellationToken = default) { - var response = await GetTransactionResponseAsync( - transactionId, - cancellationToken) + var response = await GetTransactionResponseAsync(transactionId, cancellationToken) .ConfigureAwait(false); - await response.EnsureAvailableStatusCodeAsync(cancellationToken) - .ConfigureAwait(false); + await response.EnsureAvailableStatusCodeAsync(cancellationToken).ConfigureAwait(false); - return response.Value ?? throw new InvalidOperationException( - "Failed to deserialize the transaction."); + return response.Value + ?? throw new InvalidOperationException("Failed to deserialize the transaction."); } /// @@ -138,22 +120,17 @@ public async Task DeleteTransactionAsync( DeleteTransactionOptions? options = default, CancellationToken cancellationToken = default) { - ArgumentNullException.ThrowIfNull(transactionId); + transactionId.ThrowIfNullOrEmpty(nameof(transactionId)); + options ??= new DeleteTransactionOptions(); - if (string.IsNullOrWhiteSpace(transactionId)) { - throw new ArgumentException("Cannot be empty or contain only whitespaces.", nameof(transactionId)); - } + var request = new HttpRequestMessage( + HttpMethod.Delete, + "transaction".JoinPaths(transactionId)) { + Content = JsonContent.From(options), + }; - if (options is null) { - options = new DeleteTransactionOptions(); - } - - var request = new HttpRequestMessage(HttpMethod.Delete, "transaction".JoinPaths(transactionId)); - request.Content = JsonContent.From(options); await client - .SendAsync( - request, - cancellationToken) + .SendAsync(request, cancellationToken) .EnsureSignhostSuccessStatusCodeAsync() .ConfigureAwait(false); } @@ -165,19 +142,9 @@ public async Task AddOrReplaceFileMetaToTransactionAsync( string fileId, CancellationToken cancellationToken = default) { - ArgumentNullException.ThrowIfNull(fileMeta); - - ArgumentNullException.ThrowIfNull(transactionId); - - if (string.IsNullOrWhiteSpace(transactionId)) { - throw new ArgumentException("Cannot be empty or contain only whitespaces.", nameof(transactionId)); - } - - ArgumentNullException.ThrowIfNull(fileId); - - if (string.IsNullOrWhiteSpace(fileId)) { - throw new ArgumentException("Cannot be empty or contain only whitespaces.", nameof(fileId)); - } + fileMeta.ThrowIfNullOrEmpty(nameof(fileMeta)); + transactionId.ThrowIfNullOrEmpty(nameof(transactionId)); + fileId.ThrowIfNullOrEmpty(nameof(fileId)); await client .PutAsync( @@ -196,26 +163,15 @@ public async Task AddOrReplaceFileToTransactionAsync( FileUploadOptions? uploadOptions = default, CancellationToken cancellationToken = default) { - ArgumentNullException.ThrowIfNull(fileStream); + fileStream.ThrowIfNullOrEmpty(nameof(fileStream)); + transactionId.ThrowIfNullOrEmpty(nameof(transactionId)); + fileId.ThrowIfNullOrEmpty(nameof(fileId)); - ArgumentNullException.ThrowIfNull(transactionId); + uploadOptions ??= new FileUploadOptions(); - if (string.IsNullOrWhiteSpace(transactionId)) { - throw new ArgumentException("Cannot be empty or contain only whitespaces.", nameof(transactionId)); - } - - ArgumentNullException.ThrowIfNull(fileId); - - if (string.IsNullOrWhiteSpace(fileId)) { - throw new ArgumentException("Cannot be empty or contain only whitespaces.", nameof(fileId)); - } - - if (uploadOptions is null) { - uploadOptions = new FileUploadOptions(); - } - - var content = new StreamContent(fileStream) - .WithDigest(fileStream, uploadOptions.DigestOptions); + var content = new StreamContent(fileStream).WithDigest( + fileStream, + uploadOptions.DigestOptions); content.Headers.ContentType = new MediaTypeHeaderValue("application/pdf"); await client @@ -235,22 +191,21 @@ public async Task AddOrReplaceFileToTransactionAsync( FileUploadOptions? uploadOptions = default, CancellationToken cancellationToken = default) { - ArgumentNullException.ThrowIfNull(filePath); - - using (Stream fileStream = System.IO.File.Open( - filePath, - FileMode.Open, - FileAccess.Read, - FileShare.Delete | FileShare.Read)) - { - await AddOrReplaceFileToTransactionAsync( - fileStream, - transactionId, - fileId, - uploadOptions, - cancellationToken) - .ConfigureAwait(false); - } + filePath.ThrowIfNullOrEmpty(nameof(filePath)); + + using Stream fileStream = File.Open( + filePath, + FileMode.Open, + FileAccess.Read, + FileShare.Delete | FileShare.Read); + + await AddOrReplaceFileToTransactionAsync( + fileStream, + transactionId, + fileId, + uploadOptions, + cancellationToken) + .ConfigureAwait(false); } /// @@ -258,17 +213,16 @@ public async Task StartTransactionAsync( string transactionId, CancellationToken cancellationToken = default) { - ArgumentNullException.ThrowIfNull(transactionId); + transactionId.ThrowIfNullOrEmpty(nameof(transactionId)); if (string.IsNullOrWhiteSpace(transactionId)) { - throw new ArgumentException("Cannot be empty or contain only whitespaces.", nameof(transactionId)); + throw new ArgumentException( + "Cannot be empty or contain only whitespaces.", + nameof(transactionId)); } await client - .PutAsync( - "transaction".JoinPaths(transactionId, "start"), - null, - cancellationToken) + .PutAsync("transaction".JoinPaths(transactionId, "start"), null, cancellationToken) .EnsureSignhostSuccessStatusCodeAsync() .ConfigureAwait(false); } @@ -278,15 +232,16 @@ public async Task GetReceiptAsync( string transactionId, CancellationToken cancellationToken = default) { - ArgumentNullException.ThrowIfNull(transactionId); + transactionId.ThrowIfNullOrEmpty(nameof(transactionId)); if (string.IsNullOrWhiteSpace(transactionId)) { - throw new ArgumentException("Cannot be empty or contain only whitespaces.", nameof(transactionId)); + throw new ArgumentException( + "Cannot be empty or contain only whitespaces.", + nameof(transactionId)); } var result = await client - .GetStreamAsync( - "file".JoinPaths("receipt", transactionId)) + .GetStreamAsync("file".JoinPaths("receipt", transactionId)) .ConfigureAwait(false); return result; @@ -298,21 +253,24 @@ public async Task GetDocumentAsync( string fileId, CancellationToken cancellationToken = default) { - ArgumentNullException.ThrowIfNull(transactionId); + transactionId.ThrowIfNullOrEmpty(nameof(transactionId)); if (string.IsNullOrWhiteSpace(transactionId)) { - throw new ArgumentException("Cannot be empty or contain only whitespaces.", nameof(transactionId)); + throw new ArgumentException( + "Cannot be empty or contain only whitespaces.", + nameof(transactionId)); } - ArgumentNullException.ThrowIfNull(fileId); + fileId.ThrowIfNullOrEmpty(nameof(fileId)); if (string.IsNullOrWhiteSpace(fileId)) { - throw new ArgumentException("Cannot be empty or contain only whitespaces.", nameof(fileId)); + throw new ArgumentException( + "Cannot be empty or contain only whitespaces.", + nameof(fileId)); } var result = await client - .GetStreamAsync( - "transaction".JoinPaths(transactionId, "file", fileId)) + .GetStreamAsync("transaction".JoinPaths(transactionId, "file", fileId)) .ConfigureAwait(false); return result; diff --git a/src/SignhostAPIClient/Rest/SignHostApiClientSettings.cs b/src/SignhostAPIClient/Rest/SignHostApiClientSettings.cs index 15cd1df..7e71898 100644 --- a/src/SignhostAPIClient/Rest/SignHostApiClientSettings.cs +++ b/src/SignhostAPIClient/Rest/SignHostApiClientSettings.cs @@ -1,4 +1,4 @@ -using System; +using System; namespace Signhost.APIClient.Rest; diff --git a/src/SignhostAPIClient/Rest/SignhostApiReceiver.cs b/src/SignhostAPIClient/Rest/SignhostApiReceiver.cs index aae8071..361fe7c 100644 --- a/src/SignhostAPIClient/Rest/SignhostApiReceiver.cs +++ b/src/SignhostAPIClient/Rest/SignhostApiReceiver.cs @@ -28,7 +28,7 @@ public class SignhostApiReceiver /// public SignhostApiReceiver(SignhostApiReceiverSettings receiverSettings) { - this.settings = receiverSettings; + settings = receiverSettings; } /// @@ -50,7 +50,8 @@ public bool IsPostbackChecksumValid( if (parametersAreValid) { calculatedChecksum = CalculateChecksumFromPostback(postback); postbackTransaction = postback; - } else { + } + else { return false; } @@ -59,18 +60,19 @@ public bool IsPostbackChecksumValid( private string CalculateChecksumFromPostback(PostbackTransaction postback) { - using (var sha1 = SHA1.Create()) { - var checksumBytes = sha1.ComputeHash(Encoding.UTF8.GetBytes( - $"{postback.Id}||{(int)postback.Status}|{settings.SharedSecret}")); - return BitConverter.ToString(checksumBytes) - .Replace("-", string.Empty) - .ToLower(); - } + using var sha1 = SHA1.Create(); + byte[] checksumBytes = sha1.ComputeHash(Encoding.UTF8.GetBytes( + $"{postback.Id}||{(int)postback.Status}|{settings.SharedSecret}")); + return BitConverter.ToString(checksumBytes) + .Replace("-", string.Empty) + .ToLower(); } private PostbackTransaction? DeserializeToPostbackTransaction(string body) { - return JsonSerializer.Deserialize(body, SignhostJsonSerializerOptions.Default); + return JsonSerializer.Deserialize( + body, + SignhostJsonSerializerOptions.Default); } private string GetChecksumFromHeadersOrPostback( @@ -78,7 +80,7 @@ private string GetChecksumFromHeadersOrPostback( PostbackTransaction postback) { if ( - headers.TryGetValue("Checksum", out var postbackChecksumArray) && + headers.TryGetValue("Checksum", out string[]? postbackChecksumArray) && postbackChecksumArray is not null ) { return postbackChecksumArray.First(); diff --git a/src/SignhostAPIClient/Rest/SignhostApiReceiverSettings.cs b/src/SignhostAPIClient/Rest/SignhostApiReceiverSettings.cs index c1b2bbc..4f7e014 100644 --- a/src/SignhostAPIClient/Rest/SignhostApiReceiverSettings.cs +++ b/src/SignhostAPIClient/Rest/SignhostApiReceiverSettings.cs @@ -1,4 +1,4 @@ -namespace Signhost.APIClient.Rest; +namespace Signhost.APIClient.Rest; /// /// Registers the necessary settings for the class. diff --git a/src/SignhostAPIClient/Rest/SignhostJsonSerializerOptions.cs b/src/SignhostAPIClient/Rest/SignhostJsonSerializerOptions.cs index 22c3454..7c7040c 100644 --- a/src/SignhostAPIClient/Rest/SignhostJsonSerializerOptions.cs +++ b/src/SignhostAPIClient/Rest/SignhostJsonSerializerOptions.cs @@ -11,12 +11,10 @@ public static class SignhostJsonSerializerOptions /// /// Gets the default JSON serializer options. /// - public static JsonSerializerOptions Default { get; } = new JsonSerializerOptions - { - PropertyNameCaseInsensitive = true, - DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull, - Converters = { - new JsonStringEnumConverter(), - }, - }; + public static JsonSerializerOptions Default { get; } = + new JsonSerializerOptions { + PropertyNameCaseInsensitive = true, + DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull, + Converters = { new JsonStringEnumConverter() }, + }; } diff --git a/src/SignhostAPIClient/Rest/StreamContentDigestOptionsExtensions.cs b/src/SignhostAPIClient/Rest/StreamContentDigestOptionsExtensions.cs index 8867a49..ccee6c1 100644 --- a/src/SignhostAPIClient/Rest/StreamContentDigestOptionsExtensions.cs +++ b/src/SignhostAPIClient/Rest/StreamContentDigestOptionsExtensions.cs @@ -68,9 +68,8 @@ private static void SetHashValue( long position = fileStream.Position; - using (var algo = HashAlgorithmCreate(options)) { - options.DigestHashValue = algo.ComputeHash(fileStream); - } + using var algo = HashAlgorithmCreate(options); + options.DigestHashValue = algo.ComputeHash(fileStream); fileStream.Position = position; } diff --git a/src/SignhostAPIClient/Rest/ThrowIfNullExtensions.cs b/src/SignhostAPIClient/Rest/ThrowIfNullExtensions.cs new file mode 100644 index 0000000..ea627ad --- /dev/null +++ b/src/SignhostAPIClient/Rest/ThrowIfNullExtensions.cs @@ -0,0 +1,26 @@ +using System; + +namespace Signhost.APIClient.Rest; + +/// +/// Polyfill extensions for ArgumentNullException.ThrowIfNull that works across all target frameworks. +/// +internal static class ThrowIfNullExtensions +{ + /// + /// Throws an if the specified argument is null. + /// + /// The value to check for null. + /// The name of the parameter. + /// is null. + internal static void ThrowIfNullOrEmpty(this object? value, string? paramName = null) + { + if (value is null) { + throw new ArgumentNullException(paramName); + } + + if (value is string str && string.IsNullOrWhiteSpace(str)) { + throw new ArgumentException("Argument cannot be empty or whitespace.", paramName); + } + } +} diff --git a/src/SignhostAPIClient/components.yml b/src/SignhostAPIClient/components.yml new file mode 100644 index 0000000..72d01bd --- /dev/null +++ b/src/SignhostAPIClient/components.yml @@ -0,0 +1,1361 @@ +components: + schemas: + CreateTransactionRequest: + type: object + description: | + Transaction creation data including signers, receivers, files, and configuration. + This is the request body for creating a new transaction. + example: + Seal: false + Reference: "CONTRACT-2024-001" + PostbackUrl: "https://example.com/webhook/signhost" + DaysToExpire: 30 + SendEmailNotifications: true + SignRequestMode: 2 + Language: "en-US" + Context: + department: "HR" + contract_type: "employment" + Signers: + - Email: "john.doe@example.com" + SendSignRequest: true + SignRequestMessage: "Please review and sign the attached employment contract." + Language: "en-US" + Receivers: + - Email: "hr@example.com" + Message: "The employment contract has been signed and is attached." + Language: "en-US" + properties: + Seal: + type: boolean + description: | + Whether to seal the transaction (no signers required). When true, + the transaction is automatically completed without requiring signatures. + example: false + default: false + Reference: + type: string + description: Custom reference identifier for the transaction + example: "CONTRACT-2024-001" + PostbackUrl: + type: string + format: uri + description: URL to receive status notifications about the transaction + example: "https://example.com/webhook/signhost" + DaysToExpire: + type: integer + description: | + Number of days until the transaction expires. If 0, uses organization default. + Maximum value depends on organization settings. + example: 30 + default: 30 + minimum: 0 + maximum: 90 + SendEmailNotifications: + type: boolean + description: Whether to send email notifications to signers and receivers + example: true + default: true + SignRequestMode: + type: integer + description: | + Mode for sign request delivery: + - 0: No sign requests + - 1: Send immediately + - 2: Send when ready (default when signers have SendSignRequest enabled) + enum: [0, 1, 2] + default: 2 + example: 2 + Language: + type: string + description: Language code for transaction interface and emails + default: "nl-NL" + example: "en-US" + enum: + - de-DE + - en-US + - es-ES + - fr-FR + - it-IT + - pl-PL + - nl-NL + Context: + type: object + description: | + Custom JSON object for additional transaction data. + Only JSON objects are allowed (no arrays or primitives). + additionalProperties: true + example: + department: "HR" + contract_type: "employment" + Signers: + type: array + description: List of signers for the transaction + items: + $ref: "#/components/schemas/CreateSignerRequest" + Receivers: + type: array + description: List of receivers who get copies of completed documents + items: + $ref: "#/components/schemas/CreateReceiverRequest" + required: + - Seal + Transaction: + type: object + description: | + Complete transaction data returned when retrieving or creating a transaction. + Includes all configuration, current status, and file information. + properties: + Id: + type: string + format: uuid + description: Unique transaction identifier + example: "550e8400-e29b-41d4-a716-446655440000" + Seal: + type: boolean + description: Whether the transaction is sealed (no signers required) + example: false + Reference: + type: string + description: Custom reference identifier + example: "CONTRACT-2024-001" + PostbackUrl: + type: string + format: uri + description: Webhook URL for status notifications + example: "https://example.com/webhook/signhost" + DaysToExpire: + type: integer + description: Days until the transaction expires. + format: int32 + example: 30 + SendEmailNotifications: + type: boolean + description: Send e-mail notifications to the sender. + example: true + SignRequestMode: + type: integer + description: | + Sign request delivery mode + - 1: Send immediately + - 2: Send sequentially when ready (default when signers have SendSignRequest enabled) + **Note:** Ignored if `SendSignRequest` is set to false. + format: int32 + enum: + - 1 + - 2 + example: 1 + Language: + type: string + description: | + Language code for the Sender email notifications, and transaction receipt. + example: "en-US" + enum: + - de-DE + - en-US + - es-ES + - fr-FR + - it-IT + - pl-PL + - nl-NL + Status: + type: integer + description: | + Current transaction status: + - 5: Waiting For Document - Transaction created, documents being processed/prepared + - 10: Waiting For Signer - Documents ready, waiting for signers to sign + - 20: In Progress - A signer is currently in the process of signing + - 30: Signed - All signers have successfully signed the transaction + - 40: Rejected - One or more signers rejected the transaction + - 50: Expired - Transaction expired before all signers could complete + - 60: Cancelled - Transaction was cancelled by the creator + - 70: Failed - Transaction failed due to system errors or validation issues + format: int32 + example: 30 + enum: + - 5 + - 10 + - 20 + - 30 + - 40 + - 50 + - 60 + - 70 + Context: + type: object + description: | + Custom JSON object for additional transaction metadata that was provided during creation. + additionalProperties: true + CreatedDateTime: + type: string + format: date-time + description: When the transaction was created + example: "2025-07-23T18:09:22.1276241+02:00" + ModifiedDateTime: + type: string + format: date-time + description: When the transaction was last modified + example: "2025-07-23T18:09:28.3503174+02:00" + CanceledDateTime: + type: string + format: date-time + description: When the transaction was cancelled, if applicable + example: "2025-07-23T18:09:28.3503174+02:00" + Files: + type: object + description: | + Files attached to the transaction as a key-value mapping. + Key is the file ID, value is the file information. + additionalProperties: + $ref: "#/components/schemas/FileEntry" + Signers: + type: array + description: | + List of signers attached to this transaction, including their configuration, + current status, verification methods, and signing progress. Each signer represents + an individual who needs to sign the document or has already completed the signing process. + items: + $ref: "#/components/schemas/Signer" + Receivers: + type: array + description: | + List of receivers who will receive copies of the completed signed documents. + Receivers are notified via email once the transaction is successfully completed + and all required signatures have been obtained. + items: + $ref: "#/components/schemas/Receiver" + CancelationReason: + type: string + description: > + The reason for the cancellation of the transaction, if applicable. + This is provided when the transaction is cancelled via the DELETE endpoint. + example: "Transaction cancelled by the user due to a change in requirements." + CreateSignerRequest: + type: object + description: | + Request object for creating a new signer in a transaction. Defines the signer's identity, + authentication/verification requirements, notification preferences, and signing behavior. + + **Key Requirements:** + - Email address is mandatory + - Either Authentications or Verifications must be provided (or both) + - Signers with AllowDelegation enabled cannot have Authentications + - SendSignRequest determines if sign request emails are sent automatically + properties: + Id: + type: string + description: Signer identifier (can be provided or generated) + example: "signer1" + Expires: + type: string + format: date-time + description: When the signer's access expires + example: "2024-02-15T10:30:00Z" + Email: + type: string + format: email + description: Signer's email address + example: "john.doe@example.com" + Authentications: + type: array + description: > + List of authentications that the signer has to authenticate + with. + + The order in which the authentications are provided determine + in which order the signer will have to perform the specified + method. + + + Authentications must be performed before the document(s) can + be viewed. + + + When the authentication SecureDownload is configured, the + download url for a signer is authenticated with the same + method as the signing url. + + + The download url is returned in the response, and (optionally) + emailed to the signer. + + + You **must** explicitly specify the API-version when using + this feature. + + This is done with the header: 'Accept: application/vnd.signhost.v1+json'. + items: + $ref: "#/components/schemas/SignerAuthentication" + Verifications: + type: array + description: > + List of verifications that the signer has to verify with. + + + The order in which the verifications are provided determine in + which order the signer will have to perform the specified + method. + + + Verifications must be performed before the document(s) can be + signed. + + + **Critical Requirement:** You **must** use one of the following verification methods as the **last** verification in the list: + + - Consent + + - PhoneNumber + + - Scribble + + - CSC Qualified* + + + **Important Notes:** + + - CSC Qualified **must always be the final verification** if used + + - The other three methods (Consent, PhoneNumber, Scribble) can be succeeded by other verification methods + + + Common mistake: Providing only authentication methods (e.g., iDIN) without including one of these required final verification methods. + items: + $ref: "#/components/schemas/SignerVerification" + SendSignRequest: + type: boolean + description: Whether to send sign request to this signer's email address + default: true + example: true + SendSignConfirmation: + type: boolean + description: > + Whether to send a confirmation email to the signer after signing. + + Default value is the value of `SendSignRequest` + example: true + SignRequestSubject: + type: string + description: | + The subject of the sign request email in plain text. + Maximum of 64 characters allowed. + + If omitted, the default subject will be used. + example: "Please sign the employment contract" + SignRequestMessage: + type: string + description: > + The message of the sign request email in plain text. HTML is + not allowed. + + Newlines can be created by including a `\n`. + + Required if `SendSignRequest` is true. + example: "Please review and sign the attached employment contract." + DaysToRemind: + type: integer + description: | + Number of days between automatic reminder emails sent to this signer. + + - Set to `-1` to disable reminders entirely for this signer + - Set to `0` to use your organization's default reminder interval + - Set to a positive number (e.g., `3`, `7`) to send reminders every N days + + **Note:** Reminders are only sent if `SendSignRequest` is `true` and the signer hasn't completed signing yet. + format: int32 + minimum: -1 + example: 3 + default: 7 + Language: + type: string + description: Language for signer interface and emails + example: "en-US" + default: "nl-NL" + enum: + - de-DE + - en-US + - es-ES + - fr-FR + - it-IT + - pl-PL + - nl-NL + Reference: + type: string + description: Custom reference for this signer + example: "EMPLOYEE-001" + IntroText: + type: string + description: > + Custom introduction text shown to the signer during the signing proces. + + This will be shown on the first screen to the signer and supports limited markdown markup. + + The following markup is supported: + + - `\# Headings` + + - `\*Emphasis\*` / `\_Emphasis\_` + + - `\*\*Strong\*\*` / `\_\_Strong\_\_` + + - `1. Ordered` and `- Unordered` lists + example: "Please review and sign this employment contract carefully." + ReturnUrl: + type: string + format: uri + description: URL to redirect signer after signing + example: "https://example.com/signed" + default: "https://signhost.com" + AllowDelegation: + type: boolean + description: | + Whether this signer can delegate signing to another person. + Cannot be used together with `Authentications`. + default: false + example: false + Context: + type: object + description: Custom signer data (dynamic JSON object) + additionalProperties: true + required: + - Email + Signer: + type: object + description: Signer information with current status (based on SignerGetDto which extends SignerBaseDto) + properties: + # Properties from SignerBaseDto + Id: + type: string + description: Unique signer identifier + example: "signer1" + Expires: + type: string + format: date-time + description: When the signer's access expires + example: "2024-02-15T10:30:00Z" + Email: + type: string + format: email + description: Signer's email address + example: "john.doe@example.com" + Authentications: + type: array + description: Authentication methods for this signer + items: + $ref: "#/components/schemas/SignerAuthentication" + Verifications: + type: array + description: Verification methods for this signer + items: + $ref: "#/components/schemas/SignerVerification" + SendSignRequest: + type: boolean + description: Whether to send sign request to this signer + default: true + example: true + SendSignConfirmation: + type: boolean + description: Whether to send a confirmation email after signing + example: true + SignRequestSubject: + type: string + description: Custom subject for sign request email + example: "Please sign the employment contract" + SignRequestMessage: + type: string + description: Custom message for sign request email + example: "Please review and sign the attached employment contract." + DaysToRemind: + type: integer + description: | + Number of days between automatic reminder emails sent to this signer. + + - Set to `-1` to disable reminders entirely for this signer + - Set to `0` to use your organization's default reminder interval + - Set to a positive number (e.g., `3`, `7`) to send reminders every N days + + **Note:** Reminders are only sent if `SendSignRequest` is `true` and the signer hasn't completed signing yet. + format: int32 + minimum: -1 + example: 3 + default: 7 + Language: + type: string + description: Language for signer interface and emails + example: "en-US" + enum: + - de-DE + - en-US + - es-ES + - fr-FR + - it-IT + - pl-PL + - nl-NL + Reference: + type: string + description: Custom reference for this signer + example: "EMPLOYEE-001" + IntroText: + type: string + description: Custom introduction text shown to the signer + example: "Please review and sign this employment contract carefully." + ReturnUrl: + type: string + format: uri + description: URL to redirect signer after signing + example: "https://example.com/signed" + AllowDelegation: + type: boolean + description: Whether this signer can delegate signing to another person + example: false + Context: + type: object + description: Custom signer data (dynamic JSON object) + additionalProperties: true + # Additional properties from SignerGetDto + Activities: + type: array + description: Activities/events for this signer + items: + $ref: "#/components/schemas/Activity" + RejectReason: + type: string + description: Reason provided if the signer rejected the transaction + example: "Contract terms not acceptable" + DelegateReason: + type: string + description: Reason provided for delegation + example: "Unable to sign personally" + DelegateSignerEmail: + type: string + format: email + description: Email of the delegate signer + example: "delegate@example.com" + DelegateSignerName: + type: string + description: Name of the delegate signer + example: "Jane Smith" + DelegateSignUrl: + type: string + format: uri + description: URL for delegate to sign + example: "https://view.signhost.com/sign/delegate123" + SignedDateTime: + type: string + format: date-time + description: When the signer completed signing + example: "2024-01-15T16:45:00Z" + RejectDateTime: + type: string + format: date-time + description: When the signer rejected the transaction + example: "2024-01-15T16:30:00Z" + CreatedDateTime: + type: string + format: date-time + description: When the signer was added + example: "2024-01-15T10:30:00Z" + SignerDelegationDateTime: + type: string + format: date-time + description: When the signer delegated to another person + example: "2024-01-15T12:00:00Z" + ModifiedDateTime: + type: string + format: date-time + description: When the signer was last modified + example: "2024-01-15T14:20:00Z" + ShowUrl: + type: string + format: uri + description: > + A unique URL per signer that provides the secure download flow + for the signer. + + Available if any of the authentications `SecureDownload` field is set to true. + ReceiptUrl: + type: string + format: uri + description: > + A unique URL per signer that provides the receipt download flow + for the signer. + + Available if any of the authentications `SecureDownload` field is set to true. + CreateReceiverRequest: + type: object + description: Receiver configuration for getting copies of signed documents + properties: + Name: + type: string + description: Receiver's name + example: "HR Department" + Email: + type: string + format: email + description: Receiver's email address + example: "hr@example.com" + Language: + type: string + description: Language for receiver communications + example: "en-US" + default: "nl-NL" + enum: + - de-DE + - en-US + - es-ES + - fr-FR + - it-IT + - pl-PL + - nl-NL + Subject: + type: string + description: > + Custom subject for notification email. Maximum of 64 characters allowed. Omitting this parameter will enable the default subject. + example: "Signed contract received" + Message: + type: string + description: > + Custom message for notification email. Newlines can + be created by including a `\n` in the message. HTML is not allowed. + example: "The employment contract has been signed and is attached." + Reference: + type: string + description: Custom reference for this receiver + example: "HR-COPY" + Context: + type: object + description: Custom receiver data (JSON object only) + additionalProperties: true + required: + - Email + - Message + Receiver: + type: object + description: Receiver information (based on ReceiverGetDto which extends ReceiverBaseDto) + properties: + Name: + type: string + description: Receiver's name + example: "HR Department" + Email: + type: string + format: email + description: Receiver's email address + example: "hr@example.com" + Language: + type: string + description: Language for receiver communications + example: "en-US" + Subject: + type: string + description: Custom subject for notification email + example: "Signed contract received" + Message: + type: string + description: Custom message for notification email + example: "The employment contract has been signed and is attached." + Reference: + type: string + description: Custom reference for this receiver + example: "HR-COPY" + Context: + type: object + description: Custom receiver data (dynamic JSON object) + additionalProperties: true + Id: + type: string + format: uuid + description: Unique receiver identifier + example: "880e8400-e29b-41d4-a716-446655440003" + Activities: + type: array + description: Activities/events for this receiver + items: + $ref: "#/components/schemas/Activity" + CreatedDateTime: + type: string + format: date-time + description: When the receiver was added + example: "2024-01-15T10:30:00Z" + ModifiedDateTime: + type: string + format: date-time + description: When the receiver was last modified + example: "2024-01-15T14:20:00Z" + SignerAuthentication: + type: object + description: | + Authentication methods used to verify signer identity before document access. + These methods must be completed before the signer can view documents. + + **Important:** The "Type" property is case-sensitive and must be capitalized. + + discriminator: + propertyName: Type + oneOf: + - $ref: "#/components/schemas/SignerPhoneNumberIdentification" + - $ref: "#/components/schemas/SignerDigidIdentification" + SignerVerification: + type: object + description: | + Verification methods used to confirm signer identity before document signing. + These methods must be completed before the signer can sign documents. + + **Important:** The "Type" property is case-sensitive and must be capitalized. + + **Critical Requirement:** You **must** use one of the following verification methods as the **last** verification in your verifications list: + + - Consent + - PhoneNumber + - Scribble + - CSC Qualified* + + **Rules for CSC Qualified:** + + - This method **must always be the absolute final verification** - nothing can come after it + + **Rules for Consent, PhoneNumber, and Scribble:** + + - These three can be used as the final verification method + - They are commonly used when you want additional verification steps before the final consent/signature + + **Common Mistakes:** + + - Using only authentication methods (e.g., iDIN) without a final verification method + - Placing verifications after CSC Qualified + + **Valid Examples:** + + - `[Consent]` ✓ + - `[iDIN, Consent]` ✓ + - `[iDIN, PhoneNumber]` ✓ + - `[iDIN, CSC Qualified]` ✓ + - `[CSC Qualified, Consent]` ✗ (Nothing can come after CSC Qualified) + - `[iDIN]` ✗ (Missing required final verification) + + discriminator: + propertyName: Type + oneOf: + - $ref: "#/components/schemas/SignerScribbleVerification" + - $ref: "#/components/schemas/SignerEmailVerification" + - $ref: "#/components/schemas/SignerPhoneNumberIdentification" + - $ref: "#/components/schemas/SignerDigidIdentification" + - $ref: "#/components/schemas/SignerIDealVerification" + - $ref: "#/components/schemas/SignerSurfnetVerification" + - $ref: "#/components/schemas/SignerIDINVerification" + - $ref: "#/components/schemas/SignerIPAddressVerification" + - $ref: "#/components/schemas/SignerEHerkenningVerification" + - $ref: "#/components/schemas/SignerEidasLoginVerification" + - $ref: "#/components/schemas/SignerItsmeIdentificationVerification" + - $ref: "#/components/schemas/SignerConsentVerification" + - $ref: "#/components/schemas/SignerCscVerification" + - $ref: "#/components/schemas/SignerOidcIdentification" + - $ref: "#/components/schemas/SignerOnfidoIdentification" + SignerScribbleVerification: + type: object + description: Handwritten signature verification + properties: + Type: + type: string + enum: [Scribble] + RequireHandsignature: + type: boolean + description: Whether a hand-drawn signature is required + example: true + ScribbleName: + type: string + description: Name to appear in signature + example: "John Doe" + ScribbleNameFixed: + type: boolean + description: Whether the name cannot be changed + example: false + SignerEmailVerification: + type: object + description: Email address verification + properties: + Type: + type: string + enum: [Email] + Email: + type: string + format: email + description: Email address to verify + example: "john.doe@example.com" + SignerPhoneNumberIdentification: + type: object + description: SMS phone number verification + properties: + Type: + type: string + enum: [PhoneNumber] + Number: + type: string + description: Phone number for SMS verification + example: "+31612345678" + SecureDownload: + type: boolean + description: Whether this verification enables secure file downloads + example: false + SignerDigidIdentification: + type: object + description: Dutch DigiD verification + properties: + Type: + type: string + enum: [DigiD] + Bsn: + type: string + description: Dutch social security number + example: "123456789" + Betrouwbaarheidsniveau: + type: string + description: DigiD trust level (reliability level) + example: "10" + readOnly: true + SecureDownload: + type: boolean + description: Whether this verification enables secure file downloads + example: false + SignerIDealVerification: + type: object + description: iDEAL bank verification + properties: + Type: + type: string + enum: [iDeal] + Iban: + type: string + description: IBAN for verification + example: "NL91ABNA0417164300" + SignerSurfnetVerification: + type: object + description: SURFnet academic verification + properties: + Type: + type: string + enum: [SURFnet] + Uid: + type: string + description: SURFnet user identifier + example: "john.doe@university.edu" + readOnly: true + Attributes: + type: object + description: Additional SURFnet attributes + readOnly: true + additionalProperties: + type: string + SignerIDINVerification: + type: object + description: iDIN bank identification verification + properties: + Type: + type: string + enum: [iDIN] + AccountHolderName: + type: string + description: Account holder's name + example: "J. Doe" + readOnly: true + AccountHolderAddress1: + type: string + description: Account holder's primary address + example: "Main Street 123" + readOnly: true + AccountHolderAddress2: + type: string + description: Account holder's secondary address + example: "Apt 4B" + readOnly: true + AccountHolderDateOfBirth: + type: string + description: Account holder's date of birth + example: "1985-03-15" + readOnly: true + Attributes: + type: object + description: Raw iDIN attributes (availability not guaranteed) + readOnly: true + additionalProperties: + type: string + SignerIPAddressVerification: + type: object + description: IP address verification + properties: + Type: + type: string + enum: [IPAddress] + IPAddress: + type: string + description: IP address for verification + example: "192.168.1.1" + SignerEHerkenningVerification: + type: object + description: eHerkenning business identity verification + properties: + Type: + type: string + enum: [eHerkenning] + Uid: + type: string + description: eHerkenning user identifier + example: "12345678" + readOnly: true + EntityConcernIdKvkNr: + type: string + description: KvK (Chamber of Commerce) number + example: "12345678" + SignerEidasLoginVerification: + type: object + description: eIDAS electronic identification verification + properties: + Type: + type: string + enum: [eIDAS Login] + Uid: + type: string + description: eIDAS user identifier + example: "eidas-uid-123" + readOnly: true + Level: + type: string + description: eIDAS assurance level + example: "substantial" + readOnly: true + FirstName: + type: string + description: First name from eIDAS + example: "John" + readOnly: true + LastName: + type: string + description: Last name from eIDAS + example: "Doe" + readOnly: true + DateOfBirth: + type: string + description: Date of birth from eIDAS + example: "1985-03-15" + readOnly: true + Attributes: + type: object + description: Raw eIDAS attributes (availability not guaranteed) + readOnly: true + additionalProperties: + type: string + SignerItsmeIdentificationVerification: + type: object + description: itsme identification verification + properties: + Type: + type: string + enum: [itsme Identification] + PhoneNumber: + type: string + description: Phone number for itsme verification + example: "+32470123456" + Attributes: + type: object + description: Raw itsme attributes (availability not guaranteed) + additionalProperties: true + readOnly: true + SignerConsentVerification: + type: object + description: Consent-based verification + properties: + Type: + type: string + enum: [Consent] + SignerCscVerification: + type: object + description: Cloud Signature Consortium (CSC) verification + properties: + Type: + type: string + enum: [CSC Qualified] + Provider: + type: string + description: Provider identifier + example: "csc-provider" + readOnly: true + Issuer: + type: string + description: Certificate issuer + example: "CSC CA" + readOnly: true + Subject: + type: string + description: Certificate subject + example: "CN=John Doe" + readOnly: true + Thumbprint: + type: string + description: Certificate thumbprint + example: "1234567890abcdef" + readOnly: true + AdditionalUserData: + type: object + description: Additional user data + readOnly: true + additionalProperties: + type: string + SignerOidcIdentification: + type: object + description: OpenID Connect identification + properties: + Type: + type: string + enum: [OpenID Providers] + ProviderName: + type: string + description: OIDC provider name + example: "google" + SignerOnfidoIdentification: + type: object + description: Onfido identity verification + properties: + Type: + type: string + enum: [Onfido] + WorkflowId: + type: string + format: uuid + description: Onfido workflow identifier + example: "550e8400-e29b-41d4-a716-446655440000" + WorkflowRunId: + type: string + format: uuid + description: Onfido workflow run identifier + example: "660e8400-e29b-41d4-a716-446655440001" + readOnly: true + Version: + type: integer + description: Onfido API version + example: 1 + readOnly: true + Attributes: + type: object + description: Raw Onfido attributes (availability not guaranteed) + additionalProperties: true + readOnly: true + FileEntry: + type: object + description: | + File information and metadata associated with a transaction. Contains details about uploaded documents + including display properties, download links, and current processing status. Files are referenced by + their unique identifier within the transaction and can include PDFs, receipts, and other document types. + properties: + Links: + type: array + description: Related links for the file (download, etc.) + items: + $ref: "#/components/schemas/Link" + DisplayName: + type: string + description: Display name for the file + example: "Employment Contract.pdf" + Link: + type: object + description: URI link with relationship and type information + properties: + Rel: + type: string + description: Relationship type of the link + Type: + type: string + description: MIME type or content type of the linked resource. Include this in the `Accept` header when requesting the file. + example: "application/pdf" + Link: + type: string + format: uri + description: The actual URI/URL of the linked resource + example: "https://api.signhost.com/api/file/download/abc123" + Activity: + type: object + description: Activity/event information for a signer (based on ActivityGetDto) + properties: + Id: + type: string + format: uuid + description: Unique activity identifier + example: "550e8400-e29b-41d4-a716-446655440001" + Code: + type: integer + example: 101 + description: > + Activity code indicating the type of activity. Possible values include: + + * 101 - Invitation sent + + * 103 - Opened + + * 104 - Reminder sent + + * 105 - Document opened, Info property contains the file id of the opened document. + + * 202 - Rejected + + * 203 - Signed + + * 204 - Signer delegated + + * 301 - Signed document sent + + * 302 - Signed document opened + + * 303 - Signed document downloaded + + * 401 - Receipt sent + + * 402 - Receipt opened + + * 403 - Receipt downloaded + enum: + - 101 + - 103 + - 104 + - 105 + - 202 + - 203 + - 204 + - 301 + - 302 + - 303 + - 401 + - 402 + - 403 + Activity: + type: string + description: Activity description + example: "DocumentSigned" + Info: + type: string + description: Additional activity information + example: "Document signed successfully" + CreatedDateTime: + type: string + format: date-time + description: When the activity occurred + example: "2024-01-15T16:45:00Z" + FileMetadata: + type: object + description: | + Comprehensive metadata information for a file in a transaction. This metadata defines how the file should be displayed, + processed, and what form fields and signing areas it contains. The metadata can be uploaded before or after the actual PDF file. + properties: + DisplayName: + type: [string, "null"] + description: | + Human-readable display name for the file that will be shown to users in the signing interface. + If not provided, the fileId will be used as the display name. + example: "Employment Contract - John Doe" + DisplayOrder: + type: [integer, "null"] + description: | + Numeric value determining the order in which files are displayed and processed when multiple files + are present in a transaction. Lower numbers appear first. If not specified, a timestamp-based order is used. + example: 1 + Description: + type: [string, "null"] + description: | + Detailed description of the file's purpose and contents. This helps users understand what they are signing + and may be displayed in the signing interface or audit logs. + example: "Main employment contract document containing terms of employment, salary details, and company policies" + SetParaph: + type: [boolean, "null"] + description: | + Indicates whether a paraph (initial) should be set on each page of the document in addition to the main signature. + When true, signers will be required to initial every page. Defaults to false if not specified. + example: false + Signers: + type: object + description: | + Dictionary mapping signer identifiers to their specific configuration for this file. Each signer can have + different form sets and signing requirements for the same document. The key is the signer identifier. + additionalProperties: + $ref: "#/components/schemas/FileSignerData" + example: + "signer1": + FormSets: ["employee_signature_set", "emergency_contact_info"] + "signer2": + FormSets: ["manager_approval_set"] + FormSets: + type: object + description: | + Nested dictionary structure defining form fields and signing areas within the document. The outer key represents + the form set name, and the inner key represents individual field identifiers within that set. Form sets allow + grouping related fields together for organization and signer assignment. + + The following characters are allowed as a key / field name: `a-z A-Z 0-9 _`. + + Map of pdf field definitions. + + additionalProperties: + type: object + description: "Collection of fields within a specific form set" + additionalProperties: + $ref: "#/components/schemas/FileField" + example: + "employee_signature_set": + "signature_field": + Type: "Signature" + Location: + PageNumber: 2 + Top: 500 + Left: 100 + Width: 200 + Height: 50 + "date_field": + Type: "Date" + Location: + PageNumber: 2 + Top: 600 + Left: 100 + Width: 150 + Height: 30 + FileSignerData: + type: object + description: | + Configuration data specific to a signer for a particular file. This determines which form sets + the signer needs to complete when signing the document. + properties: + FormSets: + type: array + description: | + Array of form set identifiers that this signer is responsible for completing. Each form set + contains related fields that the signer must fill out or sign. Multiple signers can be assigned + to the same form set, or each signer can have unique form sets. + items: + type: string + example: ["employee_details", "signature_block", "emergency_contacts"] + FileField: + type: object + description: | + Represents an individual form field or signing area within a document. Fields define where and how + users interact with the document during the signing process. + required: + - Type + - Location + properties: + Type: + $ref: "#/components/schemas/FileFieldType" + Location: + $ref: "#/components/schemas/FileFieldLocation" + Value: + description: | + The value content for the field. The type and format depend on the field type: + - Signature/Seal fields: Usually null or empty until signed + - Text fields (SingleLine): String values + - Number fields: Numeric values + - Date fields: Date string in appropriate format + - Check/Radio fields: Boolean values or selection identifiers + oneOf: + - type: string + description: "Text content for text-based fields" + - type: number + description: "Numeric content for number fields" + - type: boolean + description: "Boolean value for checkbox fields" + - type: object + description: "Complex objects for specialized field types" + example: "John Smith" + FileFieldType: + type: string + description: | + Specifies the type of interaction or data entry required for this field. Different types have + different behaviors and validation rules in the signing interface. + enum: + - "Seal" + - "Signature" + - "Check" + - "Radio" + - "SingleLine" + - "Number" + - "Date" + example: "Signature" + x-enum-descriptions: + Seal: "Official seal or stamp area, typically for organizational authentication" + Signature: "Digital signature field where users draw or type their signature" + Check: "Checkbox for boolean yes/no selections" + Radio: "Radio button for single selection from multiple options" + SingleLine: "Single-line text input field for names, addresses, etc." + Number: "Numeric input field with validation for numerical data" + Date: "Date picker or date input field with date format validation" + FileFieldLocation: + type: object + description: | + Defines the precise positioning and sizing of a field within the PDF document. Fields can be positioned + using absolute coordinates or by searching for specific text anchors within the document. + properties: + PageNumber: + type: [integer, "null"] + description: | + The page number where this field should be placed (1-based indexing). If not specified, + the field may be positioned based on search criteria across all pages. + example: 1 + Search: + type: [string, "null"] + description: | + Text string to search for in the document to anchor the field position. When specified, + the field will be positioned relative to this text rather than using absolute coordinates. + example: "Signature:" + Occurence: + type: [integer, "null"] + description: | + When using search text positioning, specifies which occurrence of the search text to use + if the text appears multiple times on the page or document (1-based indexing). + example: 1 + Top: + type: [integer, "null"] + description: | + Vertical position from the top of the page in pixels or points. Used for absolute positioning + or as an offset when combined with search positioning. + example: 100 + Right: + type: [integer, "null"] + description: | + Horizontal position from the right edge of the page in pixels or points. Typically used + for right-aligned field positioning. + example: 300 + Bottom: + type: [integer, "null"] + description: | + Vertical position from the bottom of the page in pixels or points. Can be used instead of + or in combination with the top property for precise vertical positioning. + example: 150 + Left: + type: [integer, "null"] + description: | + Horizontal position from the left edge of the page in pixels or points. The most common + way to specify horizontal field positioning. + example: 50 + Width: + type: [integer, "null"] + description: | + Width of the field in pixels or points. Determines how much horizontal space the field + occupies and affects text wrapping for text fields. + + For signature and seal fields we suggest a width of 140. + example: 200 + Height: + type: [integer, "null"] + description: | + Height of the field in pixels or points. Determines how much vertical space the field + occupies and affects text size and line spacing for text fields. + + For signature and seal fields we suggest a height of 70. + example: 50 + TransactionDeleteOptions: + type: object + description: Options for cancelling a transaction + properties: + Reason: + type: string + description: Reason for cancelling the transaction + example: "Contract terms changed" + SendNotifications: + type: boolean + description: Whether to notify signers about the cancellation. Note that this only applies when SendSignRequest has been set to true during the transaction creation. + default: false + example: true \ No newline at end of file From e02e24f7358ef36eb9875c6b020d703d09e43c1c Mon Sep 17 00:00:00 2001 From: Anthony Timmers Date: Fri, 28 Nov 2025 11:05:53 +0100 Subject: [PATCH 25/28] Add integration test for complex file metadata --- .../TransactionTests.cs | 187 ++++++++++++++++++ 1 file changed, 187 insertions(+) diff --git a/src/SignhostAPIClient.IntegrationTests/TransactionTests.cs b/src/SignhostAPIClient.IntegrationTests/TransactionTests.cs index edc3ebe..92593e7 100644 --- a/src/SignhostAPIClient.IntegrationTests/TransactionTests.cs +++ b/src/SignhostAPIClient.IntegrationTests/TransactionTests.cs @@ -1,5 +1,7 @@ using System; +using System.Collections.Generic; using System.IO; +using System.Text.Json; using System.Threading.Tasks; using FluentAssertions; using Signhost.APIClient.Rest.DataObjects; @@ -255,6 +257,191 @@ await client.AddOrReplaceFileToTransactionAsync( finalReceiver.Reference.Should().Be(receiverReference); } + [Fact] + public async Task Given_complex_file_metadata_When_added_to_transaction_Then_all_properties_are_accepted() + { + // Arrange - Create a simple transaction + string testReference = $"MetadataTest-{DateTime.UtcNow:yyyyMMddHHmmss}"; + CreateTransactionRequest transaction = new() { + Reference = testReference, + SendEmailNotifications = false, + Signers = [ + new() { + Id = "signer1", + Email = "test@example.com", + SendSignRequest = false, + }, + new() { + Id = "signer2", + Email = "test2@example.com", + SendSignRequest = false, + } + ] + }; + + var createdTransaction = await client.CreateTransactionAsync(transaction); + createdTransaction.Should().NotBeNull(); + + // Arrange - Create a very complex FileMeta + FileMeta fileMeta = new() { + DisplayOrder = 1, + DisplayName = "Complex Test Document", + Description = "This is a test document with complex metadata", + SetParaph = true, + Signers = new Dictionary { + ["signer1"] = new() { + FormSets = ["FormSet1", "FormSet2"], + }, + ["signer2"] = new() { + FormSets = ["FormSet2", "FormSet3"], + }, + }, + FormSets = new Dictionary> { + ["FormSet1"] = new Dictionary { + // Test all FileFieldType enum values + ["SealField"] = new Field { + Type = FileFieldType.Seal, + Value = null, + Location = new Location { + Search = "seal_placeholder", + Occurence = 1, + PageNumber = 1, + }, + }, + ["SignatureField"] = new Field { + Type = FileFieldType.Signature, + Value = null, + Location = new Location { + Top = 100, + Left = 50, + Width = 200, + Height = 60, + PageNumber = 1, + }, + }, + ["CheckField"] = new Field { + Type = FileFieldType.Check, + Value = "I agree to the terms", + Location = new Location { + Search = "checkbox_location", + Occurence = 1, + }, + }, + }, + ["FormSet2"] = new Dictionary { + // Test different value types: string, number, boolean, null + ["RadioFieldString"] = new Field { + Type = FileFieldType.Radio, + Value = "Option A", + Location = new Location { + Top = 200, + Left = 100, + Right = 300, + Bottom = 220, + PageNumber = 2, + }, + }, + ["SingleLineFieldString"] = new Field { + Type = FileFieldType.SingleLine, + Value = "John Doe", + Location = new Location { + Search = "name_field", + Width = 150, + Height = 20, + } + }, + ["NumberFieldInteger"] = new Field { + Type = FileFieldType.Number, + Value = 42, + Location = new Location { + Top = 300, + Left = 50, + PageNumber = 2, + }, + }, + ["NumberFieldDecimal"] = new Field { + Type = FileFieldType.Number, + Value = 123.45, + Location = new Location { + Top = 320, + Left = 50, + Width = 100, + Height = 20, + PageNumber = 2, + }, + }, + ["DateField"] = new Field { + Type = FileFieldType.Date, + Value = "2025-11-28", + Location = new Location { + Search = "date_placeholder", + Occurence = 2, + PageNumber = 3, + }, + }, + }, + ["FormSet3"] = new Dictionary { + // Test boolean values and all Location properties + ["CheckFieldTrue"] = new Field { + Type = FileFieldType.Check, + Value = true, + Location = new Location { + Top = 400, + Right = 200, + Bottom = 420, + Left = 50, + PageNumber = 3, + }, + }, + ["CheckFieldFalse"] = new Field { + Type = FileFieldType.Check, + Value = false, + Location = new Location { + Top = 450, + Right = 200, + Bottom = 470, + Left = 50, + Width = 150, + Height = 20, + PageNumber = 3, + }, + }, + ["SingleLineFieldNull"] = new Field { + Type = FileFieldType.SingleLine, + Value = null, + Location = new Location { + Search = "optional_field", + Occurence = 1, + Top = 500, + Left = 50, + Width = 200, + Height = 25, + PageNumber = 4, + }, + }, + ["RadioFieldNumber"] = new Field { + Type = FileFieldType.Radio, + Value = 1, + Location = new Location { + PageNumber = 4, + Top = 550, + Left = 50, + }, + }, + }, + }, + }; + + // Act + Func act = () => client.AddOrReplaceFileMetaToTransactionAsync( + fileMeta, + createdTransaction.Id, + "test-document.pdf"); + + // Assert + await act.Should().NotThrowAsync(); + } + public void Dispose() { client?.Dispose(); From 4c5d8a77b8fb12085ad160b95ca9216e3b836b8e Mon Sep 17 00:00:00 2001 From: Anthony Timmers Date: Fri, 26 Sep 2025 17:46:11 +0200 Subject: [PATCH 26/28] Refactor ensure successful status code --- .../Rest/SignHostApiClient.cs | 30 +++++++++++++++---- 1 file changed, 24 insertions(+), 6 deletions(-) diff --git a/src/SignhostAPIClient/Rest/SignHostApiClient.cs b/src/SignhostAPIClient/Rest/SignHostApiClient.cs index 6a7b6c8..0fd8531 100644 --- a/src/SignhostAPIClient/Rest/SignHostApiClient.cs +++ b/src/SignhostAPIClient/Rest/SignHostApiClient.cs @@ -240,11 +240,20 @@ public async Task GetReceiptAsync( nameof(transactionId)); } - var result = await client - .GetStreamAsync("file".JoinPaths("receipt", transactionId)) + var response = await client + .GetAsync( + "file".JoinPaths("receipt", transactionId), + cancellationToken) + .EnsureSignhostSuccessStatusCodeAsync() .ConfigureAwait(false); - return result; + return await response.Content +#if NET8_0_OR_GREATER + .ReadAsStreamAsync(cancellationToken) +#else + .ReadAsStreamAsync() +#endif + .ConfigureAwait(false); } /// @@ -269,11 +278,20 @@ public async Task GetDocumentAsync( nameof(fileId)); } - var result = await client - .GetStreamAsync("transaction".JoinPaths(transactionId, "file", fileId)) + var response = await client + .GetAsync( + "transaction".JoinPaths(transactionId, "file", fileId), + cancellationToken) + .EnsureSignhostSuccessStatusCodeAsync() .ConfigureAwait(false); - return result; + return await response.Content +#if NET8_0_OR_GREATER + .ReadAsStreamAsync(cancellationToken) +#else + .ReadAsStreamAsync() +#endif + .ConfigureAwait(false); } /// From b0b94bdc20b1f523d9deb8f53aac2d36d7a583f8 Mon Sep 17 00:00:00 2001 From: Anthony Timmers Date: Tue, 27 Jan 2026 10:43:02 +0100 Subject: [PATCH 27/28] fixup! Make remove documentation warnings --- src/signhost.ruleset | 1 + 1 file changed, 1 insertion(+) diff --git a/src/signhost.ruleset b/src/signhost.ruleset index ec914e6..d225934 100644 --- a/src/signhost.ruleset +++ b/src/signhost.ruleset @@ -13,6 +13,7 @@ + From 325afbfaa83ff6430a21539128a7803c57bdcdac Mon Sep 17 00:00:00 2001 From: Anthony Timmers Date: Tue, 27 Jan 2026 10:45:35 +0100 Subject: [PATCH 28/28] fixup! Refactor various style improvements --- README.md | 2 +- docs/components-vs-dataobjects.md | 45 - .../TransactionTests.cs | 2 +- .../JSON/JsonResources.cs | 21 +- .../AddOrReplaceFileMetaToTransaction.json | 0 .../JSON/{ => Responses}/AddTransaction.json | 0 .../{ => Responses}/DeleteTransaction.json | 0 .../JSON/{ => Responses}/GetTransaction.json | 0 .../MinimalTransactionResponse.json | 0 .../{ => Responses}/MockPostbackInvalid.json | 0 .../{ => Responses}/MockPostbackValid.json | 0 .../TransactionSingleSignerJson.json | 0 .../SignhostAPIClient.Tests.csproj | 2 +- src/SignhostAPIClient/Rest/ApiResponse.cs | 2 + .../Rest/SignHostApiClient.cs | 3 + .../Rest/SignHostApiClientSettings.cs | 5 + .../Rest/SignhostApiReceiver.cs | 31 +- .../Rest/SignhostApiReceiverSettings.cs | 2 + .../StreamContentDigestOptionsExtensions.cs | 8 +- .../Rest/UriPathExtensions.cs | 7 - src/SignhostAPIClient/components.yml | 1361 ----------------- 21 files changed, 49 insertions(+), 1442 deletions(-) delete mode 100644 docs/components-vs-dataobjects.md rename src/SignhostAPIClient.Tests/JSON/{ => Requests}/AddOrReplaceFileMetaToTransaction.json (100%) rename src/SignhostAPIClient.Tests/JSON/{ => Responses}/AddTransaction.json (100%) rename src/SignhostAPIClient.Tests/JSON/{ => Responses}/DeleteTransaction.json (100%) rename src/SignhostAPIClient.Tests/JSON/{ => Responses}/GetTransaction.json (100%) rename src/SignhostAPIClient.Tests/JSON/{ => Responses}/MinimalTransactionResponse.json (100%) rename src/SignhostAPIClient.Tests/JSON/{ => Responses}/MockPostbackInvalid.json (100%) rename src/SignhostAPIClient.Tests/JSON/{ => Responses}/MockPostbackValid.json (100%) rename src/SignhostAPIClient.Tests/JSON/{ => Responses}/TransactionSingleSignerJson.json (100%) delete mode 100644 src/SignhostAPIClient/components.yml diff --git a/README.md b/README.md index 4cfd717..b655563 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ The following code is an example of how to create and start a sign transaction w ```c# var settings = new SignhostApiClientSettings( "AppName appkey", - "apikey or usertoken")); + "apikey or usertoken"); var client = new SignhostApiClient(settings); diff --git a/docs/components-vs-dataobjects.md b/docs/components-vs-dataobjects.md deleted file mode 100644 index c7c062a..0000000 --- a/docs/components-vs-dataobjects.md +++ /dev/null @@ -1,45 +0,0 @@ -# components.yml vs DataObjects discrepancies - -_All discrepancies assume `components.yml` is the source of truth. File paths are relative to the repository root._ - -## Top-level transaction models -- `CreateTransactionRequest` exists in `components.yml` but there is no matching POCO in `src/SignhostAPIClient/Rest/DataObjects`. The runtime uses `Transaction`/`Signer`/`Receiver` directly, so schema rules such as `required: Seal`, the `SignRequestMode` enum (0/1/2), or receiver message requirements cannot be enforced in code. -- `Transaction` (`src/SignhostAPIClient/Rest/DataObjects/Transaction.cs`) lacks schema properties `ModifiedDateTime`, `CancelationReason` (note the single "l" in the schema) and exposes `CancelledDateTime`/`CancellationReason` (double "l") instead, so serialized JSON names drift from the OpenAPI contract. -- `Transaction.SignRequestMode` is a plain `int` and currently allows `0` even though the schema restricts the response values to `{1, 2}`; conversely the create schema allows `{0,1,2}` but there is no validation layer in the POCO. -- `Transaction.Context`/`Signer.Context`/`Receiver.Context` are typed as `dynamic`, which allows arrays, primitives, or nulls while the schema explicitly restricts them to JSON objects (`additionalProperties: true`). - -## Signer and receiver models -- There is no separate `CreateSignerRequest` model. `Signer` (`src/SignhostAPIClient/Rest/DataObjects/Signer.cs`) is reused for both request and response payloads, so request-only constraints (e.g., `SendSignRequest` default `true`, prohibiting `AllowDelegation` in combination with `Authentications`) and response-only fields cannot be represented per the schema. -- The `Signer` POCO exposes response-only properties such as `SignUrl`, `DelegateSignUrl`, `ScribbleName`, and `ScribbleNameFixed` that are not part of either `CreateSignerRequest` or `Signer` schemas. At the same time it is missing schema fields `SignedDateTime`, `RejectDateTime`, `CreatedDateTime`, `SignerDelegationDateTime`, `ModifiedDateTime`, `ShowUrl`, and `ReceiptUrl`. -- `Signer.SendSignConfirmation` is nullable (`bool?`) whereas both schemas treat it as a concrete boolean (defaulting to `SendSignRequest`). -- `Signer.DaysToRemind` is nullable with no minimum validation; the schema requires an integer `>= -1` and declares the default `7`. -- There is no `CreateReceiverRequest` class. `Receiver` (`src/SignhostAPIClient/Rest/DataObjects/Receiver.cs`) omits schema fields `Id`, `CreatedDateTime`, and `ModifiedDateTime`, and does not enforce the `Message` requirement that the create schema mandates. - -## Activity models -- `Activity` (`src/SignhostAPIClient/Rest/DataObjects/Activity.cs`) is missing the `Activity` string property that the schema exposes for human-readable descriptions. -- `Activity.Code` uses the `ActivityType` enum (`src/SignhostAPIClient/Rest/DataObjects/ActivityType.cs`) which defines many additional codes (e.g., `InvitationReceived=102`, `IdentityApproved=110`, `Finished=500`, `EmailBounceHard=901`, etc.) that do not appear in `components.yml`. Conversely, the schema constrains `Code` to a smaller set, so the enum values and schema enumerations are out of sync. - -## File metadata & attachment models -- `FileEntry` references `FileLink` objects (`src/SignhostAPIClient/Rest/DataObjects/FileEntry.cs` and `FileLink.cs`) while the schema names the nested type `Link`. Although the properties match, the type name drift means the generated contract does not reflect the C# surface area. -- `FileMeta` (`src/SignhostAPIClient/Rest/DataObjects/FileMeta.cs`) is the implementation of `FileMetadata`, but `Field.Value` (`src/SignhostAPIClient/Rest/DataObjects/Field.cs`) is hard-coded as a `string`. The schema allows numbers, booleans, or objects in addition to strings, so typed values cannot be represented correctly. -- `Field.Type` is an unconstrained `string`, but the schema constrains it to the `FileFieldType` enum (`Seal`, `Signature`, `Check`, `Radio`, `SingleLine`, `Number`, `Date`). - -## Authentication and verification types -- The client only has a single `IVerification` interface (`src/SignhostAPIClient/Rest/DataObjects/IVerification.cs`) that backs both `Authentications` and `Verifications`. `components.yml` differentiates between `SignerAuthentication` (currently only PhoneNumber + DigiD) and `SignerVerification` (15 discriminators with ordering rules). The POCO layer cannot express this split, so contracts that rely on the discriminator sets cannot be modeled. -- Missing discriminators: there is no implementation for `SignerEmailVerification` or `SignerEHerkenningVerification`, so those schema options cannot be produced or consumed. -- Extra discriminators: `ItsmeSignVerification.cs`, `KennisnetVerification.cs`, `SigningCertificateVerification.cs`, and `UnknownVerification.cs` exist in code but are absent from the schema, so they would produce undocumented payloads. -- `PhoneNumberVerification` lacks the `SecureDownload` flag that `SignerPhoneNumberIdentification` requires. -- `DigidVerification` omits the schema properties `Betrouwbaarheidsniveau` and `SecureDownload`. -- `IdealVerification` exposes writable `AccountHolderName`/`AccountHolderCity` properties that are not part of `SignerIDealVerification`. -- `SurfnetVerification` has no `Uid` or `Attributes` even though the schema marks both as read-only fields. -- `IdinVerification` marks every schema field (`AccountHolderName`, `AccountHolderAddress*`, `AccountHolderDateOfBirth`, `Attributes`) as writable and even requires `AccountHolderDateOfBirth` (`DateTime` non-nullable), whereas the schema states they are read-only and optional. -- `IPAddressVerification` aligns with `SignerIPAddressVerification`. -- `EHerkenning` verification is completely missing, so the `SignerEHerkenningVerification` schema entry cannot be serialized. -- `EidasLoginVerification` models `Level` as a custom enum (`Level.cs`) and exposes all properties as writable, conflicting with the schema that documents string values (`de-DE` style) and `readOnly: true` fields. -- `ItsmeIdentificationVerification` omits the `Attributes` dictionary (`readOnly`) defined in the schema. -- `CscVerification` treats `Provider`, `Issuer`, `Subject`, `Thumbprint`, and `AdditionalUserData` as writable, while the schema marks them `readOnly`. -- `OidcVerification` matches `SignerOidcIdentification`. -- `OnfidoVerification` requires GUID `WorkflowId` and `WorkflowRunId` plus writable `Version`/`Attributes`. The schema expects `WorkflowId` as a client-supplied UUID string, but `WorkflowRunId`, `Version`, and `Attributes` are read-only service outputs. - -## Transaction cancellation options -- The schema names the object `TransactionDeleteOptions`, but the POCO is `DeleteTransactionOptions` (`src/SignhostAPIClient/Rest/DataObjects/DeleteTransactionOptions.cs`). While the properties align, the type name mismatch leaks to any generated clients. diff --git a/src/SignhostAPIClient.IntegrationTests/TransactionTests.cs b/src/SignhostAPIClient.IntegrationTests/TransactionTests.cs index 92593e7..1d39224 100644 --- a/src/SignhostAPIClient.IntegrationTests/TransactionTests.cs +++ b/src/SignhostAPIClient.IntegrationTests/TransactionTests.cs @@ -20,7 +20,7 @@ public TransactionTests() config = TestConfiguration.Instance; if (!config.IsConfigured) { - throw new InvalidOperationException("Integration tests are not configured"); + throw new InvalidOperationException("Integration tests are not configured."); } SignhostApiClientSettings settings = new(config.AppKey, config.UserToken) { diff --git a/src/SignhostAPIClient.Tests/JSON/JsonResources.cs b/src/SignhostAPIClient.Tests/JSON/JsonResources.cs index eb74280..f0d4f24 100644 --- a/src/SignhostAPIClient.Tests/JSON/JsonResources.cs +++ b/src/SignhostAPIClient.Tests/JSON/JsonResources.cs @@ -5,18 +5,17 @@ namespace SignhostAPIClient.Tests.JSON; public static class JsonResources { - public static string TransactionSingleSignerJson { get; } = - GetJson("TransactionSingleSignerJson"); + // Request JSONs + public static string AddOrReplaceFileMetaToTransaction { get; } = GetJson("Requests.AddOrReplaceFileMetaToTransaction"); - public static string AddOrReplaceFileMetaToTransaction { get; } = - GetJson("AddOrReplaceFileMetaToTransaction"); - public static string AddTransaction { get; } = GetJson("AddTransaction"); - public static string DeleteTransaction { get; } = GetJson("DeleteTransaction"); - public static string GetTransaction { get; } = GetJson("GetTransaction"); - public static string MinimalTransactionResponse { get; } = - GetJson("MinimalTransactionResponse"); - public static string MockPostbackInvalid { get; } = GetJson("MockPostbackInvalid"); - public static string MockPostbackValid { get; } = GetJson("MockPostbackValid"); + // Response JSONs + public static string TransactionSingleSignerJson { get; } = GetJson("Responses.TransactionSingleSignerJson"); + public static string AddTransaction { get; } = GetJson("Responses.AddTransaction"); + public static string DeleteTransaction { get; } = GetJson("Responses.DeleteTransaction"); + public static string GetTransaction { get; } = GetJson("Responses.GetTransaction"); + public static string MinimalTransactionResponse { get; } = GetJson("Responses.MinimalTransactionResponse"); + public static string MockPostbackInvalid { get; } = GetJson("Responses.MockPostbackInvalid"); + public static string MockPostbackValid { get; } = GetJson("Responses.MockPostbackValid"); private static string GetJson(string fileName) { diff --git a/src/SignhostAPIClient.Tests/JSON/AddOrReplaceFileMetaToTransaction.json b/src/SignhostAPIClient.Tests/JSON/Requests/AddOrReplaceFileMetaToTransaction.json similarity index 100% rename from src/SignhostAPIClient.Tests/JSON/AddOrReplaceFileMetaToTransaction.json rename to src/SignhostAPIClient.Tests/JSON/Requests/AddOrReplaceFileMetaToTransaction.json diff --git a/src/SignhostAPIClient.Tests/JSON/AddTransaction.json b/src/SignhostAPIClient.Tests/JSON/Responses/AddTransaction.json similarity index 100% rename from src/SignhostAPIClient.Tests/JSON/AddTransaction.json rename to src/SignhostAPIClient.Tests/JSON/Responses/AddTransaction.json diff --git a/src/SignhostAPIClient.Tests/JSON/DeleteTransaction.json b/src/SignhostAPIClient.Tests/JSON/Responses/DeleteTransaction.json similarity index 100% rename from src/SignhostAPIClient.Tests/JSON/DeleteTransaction.json rename to src/SignhostAPIClient.Tests/JSON/Responses/DeleteTransaction.json diff --git a/src/SignhostAPIClient.Tests/JSON/GetTransaction.json b/src/SignhostAPIClient.Tests/JSON/Responses/GetTransaction.json similarity index 100% rename from src/SignhostAPIClient.Tests/JSON/GetTransaction.json rename to src/SignhostAPIClient.Tests/JSON/Responses/GetTransaction.json diff --git a/src/SignhostAPIClient.Tests/JSON/MinimalTransactionResponse.json b/src/SignhostAPIClient.Tests/JSON/Responses/MinimalTransactionResponse.json similarity index 100% rename from src/SignhostAPIClient.Tests/JSON/MinimalTransactionResponse.json rename to src/SignhostAPIClient.Tests/JSON/Responses/MinimalTransactionResponse.json diff --git a/src/SignhostAPIClient.Tests/JSON/MockPostbackInvalid.json b/src/SignhostAPIClient.Tests/JSON/Responses/MockPostbackInvalid.json similarity index 100% rename from src/SignhostAPIClient.Tests/JSON/MockPostbackInvalid.json rename to src/SignhostAPIClient.Tests/JSON/Responses/MockPostbackInvalid.json diff --git a/src/SignhostAPIClient.Tests/JSON/MockPostbackValid.json b/src/SignhostAPIClient.Tests/JSON/Responses/MockPostbackValid.json similarity index 100% rename from src/SignhostAPIClient.Tests/JSON/MockPostbackValid.json rename to src/SignhostAPIClient.Tests/JSON/Responses/MockPostbackValid.json diff --git a/src/SignhostAPIClient.Tests/JSON/TransactionSingleSignerJson.json b/src/SignhostAPIClient.Tests/JSON/Responses/TransactionSingleSignerJson.json similarity index 100% rename from src/SignhostAPIClient.Tests/JSON/TransactionSingleSignerJson.json rename to src/SignhostAPIClient.Tests/JSON/Responses/TransactionSingleSignerJson.json diff --git a/src/SignhostAPIClient.Tests/SignhostAPIClient.Tests.csproj b/src/SignhostAPIClient.Tests/SignhostAPIClient.Tests.csproj index d7aa909..d4bf700 100644 --- a/src/SignhostAPIClient.Tests/SignhostAPIClient.Tests.csproj +++ b/src/SignhostAPIClient.Tests/SignhostAPIClient.Tests.csproj @@ -22,7 +22,7 @@ - + diff --git a/src/SignhostAPIClient/Rest/ApiResponse.cs b/src/SignhostAPIClient/Rest/ApiResponse.cs index 06e6b0b..0752cb3 100644 --- a/src/SignhostAPIClient/Rest/ApiResponse.cs +++ b/src/SignhostAPIClient/Rest/ApiResponse.cs @@ -11,6 +11,8 @@ public class ApiResponse public ApiResponse(HttpResponseMessage httpResponse, TValue? value) { + httpResponse.ThrowIfNullOrEmpty(nameof(httpResponse)); + this.httpResponse = httpResponse; Value = value; } diff --git a/src/SignhostAPIClient/Rest/SignHostApiClient.cs b/src/SignhostAPIClient/Rest/SignHostApiClient.cs index 0fd8531..eba1279 100644 --- a/src/SignhostAPIClient/Rest/SignHostApiClient.cs +++ b/src/SignhostAPIClient/Rest/SignHostApiClient.cs @@ -47,6 +47,9 @@ public SignhostApiClient( ISignhostApiClientSettings settings, HttpClient httpClient) { + settings.ThrowIfNullOrEmpty(nameof(settings)); + httpClient.ThrowIfNullOrEmpty(nameof(httpClient)); + this.settings = settings; client = httpClient; client.BaseAddress = new Uri( diff --git a/src/SignhostAPIClient/Rest/SignHostApiClientSettings.cs b/src/SignhostAPIClient/Rest/SignHostApiClientSettings.cs index 7e71898..9512f2b 100644 --- a/src/SignhostAPIClient/Rest/SignHostApiClientSettings.cs +++ b/src/SignhostAPIClient/Rest/SignHostApiClientSettings.cs @@ -9,12 +9,17 @@ public class SignhostApiClientSettings public SignhostApiClientSettings(string appkey, string userToken) { + appkey.ThrowIfNullOrEmpty(nameof(appkey)); + userToken.ThrowIfNullOrEmpty(nameof(userToken)); + APPKey = appkey; UserToken = userToken; } public SignhostApiClientSettings(string appkey) { + appkey.ThrowIfNullOrEmpty(nameof(appkey)); + APPKey = appkey; } diff --git a/src/SignhostAPIClient/Rest/SignhostApiReceiver.cs b/src/SignhostAPIClient/Rest/SignhostApiReceiver.cs index 361fe7c..4a935e4 100644 --- a/src/SignhostAPIClient/Rest/SignhostApiReceiver.cs +++ b/src/SignhostAPIClient/Rest/SignhostApiReceiver.cs @@ -28,6 +28,8 @@ public class SignhostApiReceiver /// public SignhostApiReceiver(SignhostApiReceiverSettings receiverSettings) { + receiverSettings.ThrowIfNullOrEmpty(nameof(receiverSettings)); + settings = receiverSettings; } @@ -37,6 +39,9 @@ public bool IsPostbackChecksumValid( string body, [NotNullWhen(true)] out Transaction? postbackTransaction) { + headers.ThrowIfNullOrEmpty(nameof(headers)); + body.ThrowIfNullOrEmpty(nameof(body)); + postbackTransaction = null; var postback = DeserializeToPostbackTransaction(body); if (postback is null) { @@ -58,24 +63,14 @@ public bool IsPostbackChecksumValid( return Equals(calculatedChecksum, postbackChecksum); } - private string CalculateChecksumFromPostback(PostbackTransaction postback) - { - using var sha1 = SHA1.Create(); - byte[] checksumBytes = sha1.ComputeHash(Encoding.UTF8.GetBytes( - $"{postback.Id}||{(int)postback.Status}|{settings.SharedSecret}")); - return BitConverter.ToString(checksumBytes) - .Replace("-", string.Empty) - .ToLower(); - } - - private PostbackTransaction? DeserializeToPostbackTransaction(string body) + private static PostbackTransaction? DeserializeToPostbackTransaction(string body) { return JsonSerializer.Deserialize( body, SignhostJsonSerializerOptions.Default); } - private string GetChecksumFromHeadersOrPostback( + private static string GetChecksumFromHeadersOrPostback( IDictionary headers, PostbackTransaction postback) { @@ -90,8 +85,18 @@ postbackChecksumArray is not null } } - private bool HasValidChecksumProperties(string postbackChecksum, PostbackTransaction postback) + private static bool HasValidChecksumProperties(string postbackChecksum, PostbackTransaction postback) { return !string.IsNullOrWhiteSpace(postbackChecksum) && !string.IsNullOrWhiteSpace(postback.Id); } + + private string CalculateChecksumFromPostback(PostbackTransaction postback) + { + using var sha1 = SHA1.Create(); + byte[] checksumBytes = sha1.ComputeHash(Encoding.UTF8.GetBytes( + $"{postback.Id}||{(int)postback.Status}|{settings.SharedSecret}")); + return BitConverter.ToString(checksumBytes) + .Replace("-", string.Empty) + .ToLower(); + } } diff --git a/src/SignhostAPIClient/Rest/SignhostApiReceiverSettings.cs b/src/SignhostAPIClient/Rest/SignhostApiReceiverSettings.cs index 4f7e014..a8f8f97 100644 --- a/src/SignhostAPIClient/Rest/SignhostApiReceiverSettings.cs +++ b/src/SignhostAPIClient/Rest/SignhostApiReceiverSettings.cs @@ -7,6 +7,8 @@ public class SignhostApiReceiverSettings { public SignhostApiReceiverSettings(string sharedsecret) { + sharedsecret.ThrowIfNullOrEmpty(nameof(sharedsecret)); + SharedSecret = sharedsecret; } diff --git a/src/SignhostAPIClient/Rest/StreamContentDigestOptionsExtensions.cs b/src/SignhostAPIClient/Rest/StreamContentDigestOptionsExtensions.cs index ccee6c1..09d2e9d 100644 --- a/src/SignhostAPIClient/Rest/StreamContentDigestOptionsExtensions.cs +++ b/src/SignhostAPIClient/Rest/StreamContentDigestOptionsExtensions.cs @@ -23,6 +23,10 @@ public static StreamContent WithDigest( Stream fileStream, FileDigestOptions options) { + content.ThrowIfNullOrEmpty(nameof(content)); + fileStream.ThrowIfNullOrEmpty(nameof(fileStream)); + options.ThrowIfNullOrEmpty(nameof(options)); + if ( !options.UseFileDigesting || options.DigestHashAlgorithm == DigestHashAlgorithm.None @@ -50,7 +54,7 @@ private static string GetDigestHashAlgorithmName(FileDigestOptions options) DigestHashAlgorithm.SHA512 => "SHA-512", _ => throw new InvalidOperationException( - $"No hash algorithm name for '{options.DigestHashAlgorithm}'"), + $"No hash algorithm name for '{options.DigestHashAlgorithm}'."), }; } @@ -86,7 +90,7 @@ private static HashAlgorithm HashAlgorithmCreate( DigestHashAlgorithm.SHA512 => SHA512.Create(), #endif _ => throw new InvalidOperationException( - $"No hash algorithm for '{options.DigestHashAlgorithm}'"), + $"No hash algorithm for '{options.DigestHashAlgorithm}'."), }; } } diff --git a/src/SignhostAPIClient/Rest/UriPathExtensions.cs b/src/SignhostAPIClient/Rest/UriPathExtensions.cs index 9f13c52..9b5639b 100644 --- a/src/SignhostAPIClient/Rest/UriPathExtensions.cs +++ b/src/SignhostAPIClient/Rest/UriPathExtensions.cs @@ -5,13 +5,6 @@ namespace Signhost.APIClient.Rest; internal static class UriPathExtensions { - internal static UriBuilder AppendPathSegment(this string url) - { - var builder = new UriBuilder(url); - - return builder; - } - internal static Uri JoinPaths( this string url, params string[] segments) diff --git a/src/SignhostAPIClient/components.yml b/src/SignhostAPIClient/components.yml deleted file mode 100644 index 72d01bd..0000000 --- a/src/SignhostAPIClient/components.yml +++ /dev/null @@ -1,1361 +0,0 @@ -components: - schemas: - CreateTransactionRequest: - type: object - description: | - Transaction creation data including signers, receivers, files, and configuration. - This is the request body for creating a new transaction. - example: - Seal: false - Reference: "CONTRACT-2024-001" - PostbackUrl: "https://example.com/webhook/signhost" - DaysToExpire: 30 - SendEmailNotifications: true - SignRequestMode: 2 - Language: "en-US" - Context: - department: "HR" - contract_type: "employment" - Signers: - - Email: "john.doe@example.com" - SendSignRequest: true - SignRequestMessage: "Please review and sign the attached employment contract." - Language: "en-US" - Receivers: - - Email: "hr@example.com" - Message: "The employment contract has been signed and is attached." - Language: "en-US" - properties: - Seal: - type: boolean - description: | - Whether to seal the transaction (no signers required). When true, - the transaction is automatically completed without requiring signatures. - example: false - default: false - Reference: - type: string - description: Custom reference identifier for the transaction - example: "CONTRACT-2024-001" - PostbackUrl: - type: string - format: uri - description: URL to receive status notifications about the transaction - example: "https://example.com/webhook/signhost" - DaysToExpire: - type: integer - description: | - Number of days until the transaction expires. If 0, uses organization default. - Maximum value depends on organization settings. - example: 30 - default: 30 - minimum: 0 - maximum: 90 - SendEmailNotifications: - type: boolean - description: Whether to send email notifications to signers and receivers - example: true - default: true - SignRequestMode: - type: integer - description: | - Mode for sign request delivery: - - 0: No sign requests - - 1: Send immediately - - 2: Send when ready (default when signers have SendSignRequest enabled) - enum: [0, 1, 2] - default: 2 - example: 2 - Language: - type: string - description: Language code for transaction interface and emails - default: "nl-NL" - example: "en-US" - enum: - - de-DE - - en-US - - es-ES - - fr-FR - - it-IT - - pl-PL - - nl-NL - Context: - type: object - description: | - Custom JSON object for additional transaction data. - Only JSON objects are allowed (no arrays or primitives). - additionalProperties: true - example: - department: "HR" - contract_type: "employment" - Signers: - type: array - description: List of signers for the transaction - items: - $ref: "#/components/schemas/CreateSignerRequest" - Receivers: - type: array - description: List of receivers who get copies of completed documents - items: - $ref: "#/components/schemas/CreateReceiverRequest" - required: - - Seal - Transaction: - type: object - description: | - Complete transaction data returned when retrieving or creating a transaction. - Includes all configuration, current status, and file information. - properties: - Id: - type: string - format: uuid - description: Unique transaction identifier - example: "550e8400-e29b-41d4-a716-446655440000" - Seal: - type: boolean - description: Whether the transaction is sealed (no signers required) - example: false - Reference: - type: string - description: Custom reference identifier - example: "CONTRACT-2024-001" - PostbackUrl: - type: string - format: uri - description: Webhook URL for status notifications - example: "https://example.com/webhook/signhost" - DaysToExpire: - type: integer - description: Days until the transaction expires. - format: int32 - example: 30 - SendEmailNotifications: - type: boolean - description: Send e-mail notifications to the sender. - example: true - SignRequestMode: - type: integer - description: | - Sign request delivery mode - - 1: Send immediately - - 2: Send sequentially when ready (default when signers have SendSignRequest enabled) - **Note:** Ignored if `SendSignRequest` is set to false. - format: int32 - enum: - - 1 - - 2 - example: 1 - Language: - type: string - description: | - Language code for the Sender email notifications, and transaction receipt. - example: "en-US" - enum: - - de-DE - - en-US - - es-ES - - fr-FR - - it-IT - - pl-PL - - nl-NL - Status: - type: integer - description: | - Current transaction status: - - 5: Waiting For Document - Transaction created, documents being processed/prepared - - 10: Waiting For Signer - Documents ready, waiting for signers to sign - - 20: In Progress - A signer is currently in the process of signing - - 30: Signed - All signers have successfully signed the transaction - - 40: Rejected - One or more signers rejected the transaction - - 50: Expired - Transaction expired before all signers could complete - - 60: Cancelled - Transaction was cancelled by the creator - - 70: Failed - Transaction failed due to system errors or validation issues - format: int32 - example: 30 - enum: - - 5 - - 10 - - 20 - - 30 - - 40 - - 50 - - 60 - - 70 - Context: - type: object - description: | - Custom JSON object for additional transaction metadata that was provided during creation. - additionalProperties: true - CreatedDateTime: - type: string - format: date-time - description: When the transaction was created - example: "2025-07-23T18:09:22.1276241+02:00" - ModifiedDateTime: - type: string - format: date-time - description: When the transaction was last modified - example: "2025-07-23T18:09:28.3503174+02:00" - CanceledDateTime: - type: string - format: date-time - description: When the transaction was cancelled, if applicable - example: "2025-07-23T18:09:28.3503174+02:00" - Files: - type: object - description: | - Files attached to the transaction as a key-value mapping. - Key is the file ID, value is the file information. - additionalProperties: - $ref: "#/components/schemas/FileEntry" - Signers: - type: array - description: | - List of signers attached to this transaction, including their configuration, - current status, verification methods, and signing progress. Each signer represents - an individual who needs to sign the document or has already completed the signing process. - items: - $ref: "#/components/schemas/Signer" - Receivers: - type: array - description: | - List of receivers who will receive copies of the completed signed documents. - Receivers are notified via email once the transaction is successfully completed - and all required signatures have been obtained. - items: - $ref: "#/components/schemas/Receiver" - CancelationReason: - type: string - description: > - The reason for the cancellation of the transaction, if applicable. - This is provided when the transaction is cancelled via the DELETE endpoint. - example: "Transaction cancelled by the user due to a change in requirements." - CreateSignerRequest: - type: object - description: | - Request object for creating a new signer in a transaction. Defines the signer's identity, - authentication/verification requirements, notification preferences, and signing behavior. - - **Key Requirements:** - - Email address is mandatory - - Either Authentications or Verifications must be provided (or both) - - Signers with AllowDelegation enabled cannot have Authentications - - SendSignRequest determines if sign request emails are sent automatically - properties: - Id: - type: string - description: Signer identifier (can be provided or generated) - example: "signer1" - Expires: - type: string - format: date-time - description: When the signer's access expires - example: "2024-02-15T10:30:00Z" - Email: - type: string - format: email - description: Signer's email address - example: "john.doe@example.com" - Authentications: - type: array - description: > - List of authentications that the signer has to authenticate - with. - - The order in which the authentications are provided determine - in which order the signer will have to perform the specified - method. - - - Authentications must be performed before the document(s) can - be viewed. - - - When the authentication SecureDownload is configured, the - download url for a signer is authenticated with the same - method as the signing url. - - - The download url is returned in the response, and (optionally) - emailed to the signer. - - - You **must** explicitly specify the API-version when using - this feature. - - This is done with the header: 'Accept: application/vnd.signhost.v1+json'. - items: - $ref: "#/components/schemas/SignerAuthentication" - Verifications: - type: array - description: > - List of verifications that the signer has to verify with. - - - The order in which the verifications are provided determine in - which order the signer will have to perform the specified - method. - - - Verifications must be performed before the document(s) can be - signed. - - - **Critical Requirement:** You **must** use one of the following verification methods as the **last** verification in the list: - - - Consent - - - PhoneNumber - - - Scribble - - - CSC Qualified* - - - **Important Notes:** - - - CSC Qualified **must always be the final verification** if used - - - The other three methods (Consent, PhoneNumber, Scribble) can be succeeded by other verification methods - - - Common mistake: Providing only authentication methods (e.g., iDIN) without including one of these required final verification methods. - items: - $ref: "#/components/schemas/SignerVerification" - SendSignRequest: - type: boolean - description: Whether to send sign request to this signer's email address - default: true - example: true - SendSignConfirmation: - type: boolean - description: > - Whether to send a confirmation email to the signer after signing. - - Default value is the value of `SendSignRequest` - example: true - SignRequestSubject: - type: string - description: | - The subject of the sign request email in plain text. - Maximum of 64 characters allowed. - - If omitted, the default subject will be used. - example: "Please sign the employment contract" - SignRequestMessage: - type: string - description: > - The message of the sign request email in plain text. HTML is - not allowed. - - Newlines can be created by including a `\n`. - - Required if `SendSignRequest` is true. - example: "Please review and sign the attached employment contract." - DaysToRemind: - type: integer - description: | - Number of days between automatic reminder emails sent to this signer. - - - Set to `-1` to disable reminders entirely for this signer - - Set to `0` to use your organization's default reminder interval - - Set to a positive number (e.g., `3`, `7`) to send reminders every N days - - **Note:** Reminders are only sent if `SendSignRequest` is `true` and the signer hasn't completed signing yet. - format: int32 - minimum: -1 - example: 3 - default: 7 - Language: - type: string - description: Language for signer interface and emails - example: "en-US" - default: "nl-NL" - enum: - - de-DE - - en-US - - es-ES - - fr-FR - - it-IT - - pl-PL - - nl-NL - Reference: - type: string - description: Custom reference for this signer - example: "EMPLOYEE-001" - IntroText: - type: string - description: > - Custom introduction text shown to the signer during the signing proces. - - This will be shown on the first screen to the signer and supports limited markdown markup. - - The following markup is supported: - - - `\# Headings` - - - `\*Emphasis\*` / `\_Emphasis\_` - - - `\*\*Strong\*\*` / `\_\_Strong\_\_` - - - `1. Ordered` and `- Unordered` lists - example: "Please review and sign this employment contract carefully." - ReturnUrl: - type: string - format: uri - description: URL to redirect signer after signing - example: "https://example.com/signed" - default: "https://signhost.com" - AllowDelegation: - type: boolean - description: | - Whether this signer can delegate signing to another person. - Cannot be used together with `Authentications`. - default: false - example: false - Context: - type: object - description: Custom signer data (dynamic JSON object) - additionalProperties: true - required: - - Email - Signer: - type: object - description: Signer information with current status (based on SignerGetDto which extends SignerBaseDto) - properties: - # Properties from SignerBaseDto - Id: - type: string - description: Unique signer identifier - example: "signer1" - Expires: - type: string - format: date-time - description: When the signer's access expires - example: "2024-02-15T10:30:00Z" - Email: - type: string - format: email - description: Signer's email address - example: "john.doe@example.com" - Authentications: - type: array - description: Authentication methods for this signer - items: - $ref: "#/components/schemas/SignerAuthentication" - Verifications: - type: array - description: Verification methods for this signer - items: - $ref: "#/components/schemas/SignerVerification" - SendSignRequest: - type: boolean - description: Whether to send sign request to this signer - default: true - example: true - SendSignConfirmation: - type: boolean - description: Whether to send a confirmation email after signing - example: true - SignRequestSubject: - type: string - description: Custom subject for sign request email - example: "Please sign the employment contract" - SignRequestMessage: - type: string - description: Custom message for sign request email - example: "Please review and sign the attached employment contract." - DaysToRemind: - type: integer - description: | - Number of days between automatic reminder emails sent to this signer. - - - Set to `-1` to disable reminders entirely for this signer - - Set to `0` to use your organization's default reminder interval - - Set to a positive number (e.g., `3`, `7`) to send reminders every N days - - **Note:** Reminders are only sent if `SendSignRequest` is `true` and the signer hasn't completed signing yet. - format: int32 - minimum: -1 - example: 3 - default: 7 - Language: - type: string - description: Language for signer interface and emails - example: "en-US" - enum: - - de-DE - - en-US - - es-ES - - fr-FR - - it-IT - - pl-PL - - nl-NL - Reference: - type: string - description: Custom reference for this signer - example: "EMPLOYEE-001" - IntroText: - type: string - description: Custom introduction text shown to the signer - example: "Please review and sign this employment contract carefully." - ReturnUrl: - type: string - format: uri - description: URL to redirect signer after signing - example: "https://example.com/signed" - AllowDelegation: - type: boolean - description: Whether this signer can delegate signing to another person - example: false - Context: - type: object - description: Custom signer data (dynamic JSON object) - additionalProperties: true - # Additional properties from SignerGetDto - Activities: - type: array - description: Activities/events for this signer - items: - $ref: "#/components/schemas/Activity" - RejectReason: - type: string - description: Reason provided if the signer rejected the transaction - example: "Contract terms not acceptable" - DelegateReason: - type: string - description: Reason provided for delegation - example: "Unable to sign personally" - DelegateSignerEmail: - type: string - format: email - description: Email of the delegate signer - example: "delegate@example.com" - DelegateSignerName: - type: string - description: Name of the delegate signer - example: "Jane Smith" - DelegateSignUrl: - type: string - format: uri - description: URL for delegate to sign - example: "https://view.signhost.com/sign/delegate123" - SignedDateTime: - type: string - format: date-time - description: When the signer completed signing - example: "2024-01-15T16:45:00Z" - RejectDateTime: - type: string - format: date-time - description: When the signer rejected the transaction - example: "2024-01-15T16:30:00Z" - CreatedDateTime: - type: string - format: date-time - description: When the signer was added - example: "2024-01-15T10:30:00Z" - SignerDelegationDateTime: - type: string - format: date-time - description: When the signer delegated to another person - example: "2024-01-15T12:00:00Z" - ModifiedDateTime: - type: string - format: date-time - description: When the signer was last modified - example: "2024-01-15T14:20:00Z" - ShowUrl: - type: string - format: uri - description: > - A unique URL per signer that provides the secure download flow - for the signer. - - Available if any of the authentications `SecureDownload` field is set to true. - ReceiptUrl: - type: string - format: uri - description: > - A unique URL per signer that provides the receipt download flow - for the signer. - - Available if any of the authentications `SecureDownload` field is set to true. - CreateReceiverRequest: - type: object - description: Receiver configuration for getting copies of signed documents - properties: - Name: - type: string - description: Receiver's name - example: "HR Department" - Email: - type: string - format: email - description: Receiver's email address - example: "hr@example.com" - Language: - type: string - description: Language for receiver communications - example: "en-US" - default: "nl-NL" - enum: - - de-DE - - en-US - - es-ES - - fr-FR - - it-IT - - pl-PL - - nl-NL - Subject: - type: string - description: > - Custom subject for notification email. Maximum of 64 characters allowed. Omitting this parameter will enable the default subject. - example: "Signed contract received" - Message: - type: string - description: > - Custom message for notification email. Newlines can - be created by including a `\n` in the message. HTML is not allowed. - example: "The employment contract has been signed and is attached." - Reference: - type: string - description: Custom reference for this receiver - example: "HR-COPY" - Context: - type: object - description: Custom receiver data (JSON object only) - additionalProperties: true - required: - - Email - - Message - Receiver: - type: object - description: Receiver information (based on ReceiverGetDto which extends ReceiverBaseDto) - properties: - Name: - type: string - description: Receiver's name - example: "HR Department" - Email: - type: string - format: email - description: Receiver's email address - example: "hr@example.com" - Language: - type: string - description: Language for receiver communications - example: "en-US" - Subject: - type: string - description: Custom subject for notification email - example: "Signed contract received" - Message: - type: string - description: Custom message for notification email - example: "The employment contract has been signed and is attached." - Reference: - type: string - description: Custom reference for this receiver - example: "HR-COPY" - Context: - type: object - description: Custom receiver data (dynamic JSON object) - additionalProperties: true - Id: - type: string - format: uuid - description: Unique receiver identifier - example: "880e8400-e29b-41d4-a716-446655440003" - Activities: - type: array - description: Activities/events for this receiver - items: - $ref: "#/components/schemas/Activity" - CreatedDateTime: - type: string - format: date-time - description: When the receiver was added - example: "2024-01-15T10:30:00Z" - ModifiedDateTime: - type: string - format: date-time - description: When the receiver was last modified - example: "2024-01-15T14:20:00Z" - SignerAuthentication: - type: object - description: | - Authentication methods used to verify signer identity before document access. - These methods must be completed before the signer can view documents. - - **Important:** The "Type" property is case-sensitive and must be capitalized. - - discriminator: - propertyName: Type - oneOf: - - $ref: "#/components/schemas/SignerPhoneNumberIdentification" - - $ref: "#/components/schemas/SignerDigidIdentification" - SignerVerification: - type: object - description: | - Verification methods used to confirm signer identity before document signing. - These methods must be completed before the signer can sign documents. - - **Important:** The "Type" property is case-sensitive and must be capitalized. - - **Critical Requirement:** You **must** use one of the following verification methods as the **last** verification in your verifications list: - - - Consent - - PhoneNumber - - Scribble - - CSC Qualified* - - **Rules for CSC Qualified:** - - - This method **must always be the absolute final verification** - nothing can come after it - - **Rules for Consent, PhoneNumber, and Scribble:** - - - These three can be used as the final verification method - - They are commonly used when you want additional verification steps before the final consent/signature - - **Common Mistakes:** - - - Using only authentication methods (e.g., iDIN) without a final verification method - - Placing verifications after CSC Qualified - - **Valid Examples:** - - - `[Consent]` ✓ - - `[iDIN, Consent]` ✓ - - `[iDIN, PhoneNumber]` ✓ - - `[iDIN, CSC Qualified]` ✓ - - `[CSC Qualified, Consent]` ✗ (Nothing can come after CSC Qualified) - - `[iDIN]` ✗ (Missing required final verification) - - discriminator: - propertyName: Type - oneOf: - - $ref: "#/components/schemas/SignerScribbleVerification" - - $ref: "#/components/schemas/SignerEmailVerification" - - $ref: "#/components/schemas/SignerPhoneNumberIdentification" - - $ref: "#/components/schemas/SignerDigidIdentification" - - $ref: "#/components/schemas/SignerIDealVerification" - - $ref: "#/components/schemas/SignerSurfnetVerification" - - $ref: "#/components/schemas/SignerIDINVerification" - - $ref: "#/components/schemas/SignerIPAddressVerification" - - $ref: "#/components/schemas/SignerEHerkenningVerification" - - $ref: "#/components/schemas/SignerEidasLoginVerification" - - $ref: "#/components/schemas/SignerItsmeIdentificationVerification" - - $ref: "#/components/schemas/SignerConsentVerification" - - $ref: "#/components/schemas/SignerCscVerification" - - $ref: "#/components/schemas/SignerOidcIdentification" - - $ref: "#/components/schemas/SignerOnfidoIdentification" - SignerScribbleVerification: - type: object - description: Handwritten signature verification - properties: - Type: - type: string - enum: [Scribble] - RequireHandsignature: - type: boolean - description: Whether a hand-drawn signature is required - example: true - ScribbleName: - type: string - description: Name to appear in signature - example: "John Doe" - ScribbleNameFixed: - type: boolean - description: Whether the name cannot be changed - example: false - SignerEmailVerification: - type: object - description: Email address verification - properties: - Type: - type: string - enum: [Email] - Email: - type: string - format: email - description: Email address to verify - example: "john.doe@example.com" - SignerPhoneNumberIdentification: - type: object - description: SMS phone number verification - properties: - Type: - type: string - enum: [PhoneNumber] - Number: - type: string - description: Phone number for SMS verification - example: "+31612345678" - SecureDownload: - type: boolean - description: Whether this verification enables secure file downloads - example: false - SignerDigidIdentification: - type: object - description: Dutch DigiD verification - properties: - Type: - type: string - enum: [DigiD] - Bsn: - type: string - description: Dutch social security number - example: "123456789" - Betrouwbaarheidsniveau: - type: string - description: DigiD trust level (reliability level) - example: "10" - readOnly: true - SecureDownload: - type: boolean - description: Whether this verification enables secure file downloads - example: false - SignerIDealVerification: - type: object - description: iDEAL bank verification - properties: - Type: - type: string - enum: [iDeal] - Iban: - type: string - description: IBAN for verification - example: "NL91ABNA0417164300" - SignerSurfnetVerification: - type: object - description: SURFnet academic verification - properties: - Type: - type: string - enum: [SURFnet] - Uid: - type: string - description: SURFnet user identifier - example: "john.doe@university.edu" - readOnly: true - Attributes: - type: object - description: Additional SURFnet attributes - readOnly: true - additionalProperties: - type: string - SignerIDINVerification: - type: object - description: iDIN bank identification verification - properties: - Type: - type: string - enum: [iDIN] - AccountHolderName: - type: string - description: Account holder's name - example: "J. Doe" - readOnly: true - AccountHolderAddress1: - type: string - description: Account holder's primary address - example: "Main Street 123" - readOnly: true - AccountHolderAddress2: - type: string - description: Account holder's secondary address - example: "Apt 4B" - readOnly: true - AccountHolderDateOfBirth: - type: string - description: Account holder's date of birth - example: "1985-03-15" - readOnly: true - Attributes: - type: object - description: Raw iDIN attributes (availability not guaranteed) - readOnly: true - additionalProperties: - type: string - SignerIPAddressVerification: - type: object - description: IP address verification - properties: - Type: - type: string - enum: [IPAddress] - IPAddress: - type: string - description: IP address for verification - example: "192.168.1.1" - SignerEHerkenningVerification: - type: object - description: eHerkenning business identity verification - properties: - Type: - type: string - enum: [eHerkenning] - Uid: - type: string - description: eHerkenning user identifier - example: "12345678" - readOnly: true - EntityConcernIdKvkNr: - type: string - description: KvK (Chamber of Commerce) number - example: "12345678" - SignerEidasLoginVerification: - type: object - description: eIDAS electronic identification verification - properties: - Type: - type: string - enum: [eIDAS Login] - Uid: - type: string - description: eIDAS user identifier - example: "eidas-uid-123" - readOnly: true - Level: - type: string - description: eIDAS assurance level - example: "substantial" - readOnly: true - FirstName: - type: string - description: First name from eIDAS - example: "John" - readOnly: true - LastName: - type: string - description: Last name from eIDAS - example: "Doe" - readOnly: true - DateOfBirth: - type: string - description: Date of birth from eIDAS - example: "1985-03-15" - readOnly: true - Attributes: - type: object - description: Raw eIDAS attributes (availability not guaranteed) - readOnly: true - additionalProperties: - type: string - SignerItsmeIdentificationVerification: - type: object - description: itsme identification verification - properties: - Type: - type: string - enum: [itsme Identification] - PhoneNumber: - type: string - description: Phone number for itsme verification - example: "+32470123456" - Attributes: - type: object - description: Raw itsme attributes (availability not guaranteed) - additionalProperties: true - readOnly: true - SignerConsentVerification: - type: object - description: Consent-based verification - properties: - Type: - type: string - enum: [Consent] - SignerCscVerification: - type: object - description: Cloud Signature Consortium (CSC) verification - properties: - Type: - type: string - enum: [CSC Qualified] - Provider: - type: string - description: Provider identifier - example: "csc-provider" - readOnly: true - Issuer: - type: string - description: Certificate issuer - example: "CSC CA" - readOnly: true - Subject: - type: string - description: Certificate subject - example: "CN=John Doe" - readOnly: true - Thumbprint: - type: string - description: Certificate thumbprint - example: "1234567890abcdef" - readOnly: true - AdditionalUserData: - type: object - description: Additional user data - readOnly: true - additionalProperties: - type: string - SignerOidcIdentification: - type: object - description: OpenID Connect identification - properties: - Type: - type: string - enum: [OpenID Providers] - ProviderName: - type: string - description: OIDC provider name - example: "google" - SignerOnfidoIdentification: - type: object - description: Onfido identity verification - properties: - Type: - type: string - enum: [Onfido] - WorkflowId: - type: string - format: uuid - description: Onfido workflow identifier - example: "550e8400-e29b-41d4-a716-446655440000" - WorkflowRunId: - type: string - format: uuid - description: Onfido workflow run identifier - example: "660e8400-e29b-41d4-a716-446655440001" - readOnly: true - Version: - type: integer - description: Onfido API version - example: 1 - readOnly: true - Attributes: - type: object - description: Raw Onfido attributes (availability not guaranteed) - additionalProperties: true - readOnly: true - FileEntry: - type: object - description: | - File information and metadata associated with a transaction. Contains details about uploaded documents - including display properties, download links, and current processing status. Files are referenced by - their unique identifier within the transaction and can include PDFs, receipts, and other document types. - properties: - Links: - type: array - description: Related links for the file (download, etc.) - items: - $ref: "#/components/schemas/Link" - DisplayName: - type: string - description: Display name for the file - example: "Employment Contract.pdf" - Link: - type: object - description: URI link with relationship and type information - properties: - Rel: - type: string - description: Relationship type of the link - Type: - type: string - description: MIME type or content type of the linked resource. Include this in the `Accept` header when requesting the file. - example: "application/pdf" - Link: - type: string - format: uri - description: The actual URI/URL of the linked resource - example: "https://api.signhost.com/api/file/download/abc123" - Activity: - type: object - description: Activity/event information for a signer (based on ActivityGetDto) - properties: - Id: - type: string - format: uuid - description: Unique activity identifier - example: "550e8400-e29b-41d4-a716-446655440001" - Code: - type: integer - example: 101 - description: > - Activity code indicating the type of activity. Possible values include: - - * 101 - Invitation sent - - * 103 - Opened - - * 104 - Reminder sent - - * 105 - Document opened, Info property contains the file id of the opened document. - - * 202 - Rejected - - * 203 - Signed - - * 204 - Signer delegated - - * 301 - Signed document sent - - * 302 - Signed document opened - - * 303 - Signed document downloaded - - * 401 - Receipt sent - - * 402 - Receipt opened - - * 403 - Receipt downloaded - enum: - - 101 - - 103 - - 104 - - 105 - - 202 - - 203 - - 204 - - 301 - - 302 - - 303 - - 401 - - 402 - - 403 - Activity: - type: string - description: Activity description - example: "DocumentSigned" - Info: - type: string - description: Additional activity information - example: "Document signed successfully" - CreatedDateTime: - type: string - format: date-time - description: When the activity occurred - example: "2024-01-15T16:45:00Z" - FileMetadata: - type: object - description: | - Comprehensive metadata information for a file in a transaction. This metadata defines how the file should be displayed, - processed, and what form fields and signing areas it contains. The metadata can be uploaded before or after the actual PDF file. - properties: - DisplayName: - type: [string, "null"] - description: | - Human-readable display name for the file that will be shown to users in the signing interface. - If not provided, the fileId will be used as the display name. - example: "Employment Contract - John Doe" - DisplayOrder: - type: [integer, "null"] - description: | - Numeric value determining the order in which files are displayed and processed when multiple files - are present in a transaction. Lower numbers appear first. If not specified, a timestamp-based order is used. - example: 1 - Description: - type: [string, "null"] - description: | - Detailed description of the file's purpose and contents. This helps users understand what they are signing - and may be displayed in the signing interface or audit logs. - example: "Main employment contract document containing terms of employment, salary details, and company policies" - SetParaph: - type: [boolean, "null"] - description: | - Indicates whether a paraph (initial) should be set on each page of the document in addition to the main signature. - When true, signers will be required to initial every page. Defaults to false if not specified. - example: false - Signers: - type: object - description: | - Dictionary mapping signer identifiers to their specific configuration for this file. Each signer can have - different form sets and signing requirements for the same document. The key is the signer identifier. - additionalProperties: - $ref: "#/components/schemas/FileSignerData" - example: - "signer1": - FormSets: ["employee_signature_set", "emergency_contact_info"] - "signer2": - FormSets: ["manager_approval_set"] - FormSets: - type: object - description: | - Nested dictionary structure defining form fields and signing areas within the document. The outer key represents - the form set name, and the inner key represents individual field identifiers within that set. Form sets allow - grouping related fields together for organization and signer assignment. - - The following characters are allowed as a key / field name: `a-z A-Z 0-9 _`. - - Map of pdf field definitions. - - additionalProperties: - type: object - description: "Collection of fields within a specific form set" - additionalProperties: - $ref: "#/components/schemas/FileField" - example: - "employee_signature_set": - "signature_field": - Type: "Signature" - Location: - PageNumber: 2 - Top: 500 - Left: 100 - Width: 200 - Height: 50 - "date_field": - Type: "Date" - Location: - PageNumber: 2 - Top: 600 - Left: 100 - Width: 150 - Height: 30 - FileSignerData: - type: object - description: | - Configuration data specific to a signer for a particular file. This determines which form sets - the signer needs to complete when signing the document. - properties: - FormSets: - type: array - description: | - Array of form set identifiers that this signer is responsible for completing. Each form set - contains related fields that the signer must fill out or sign. Multiple signers can be assigned - to the same form set, or each signer can have unique form sets. - items: - type: string - example: ["employee_details", "signature_block", "emergency_contacts"] - FileField: - type: object - description: | - Represents an individual form field or signing area within a document. Fields define where and how - users interact with the document during the signing process. - required: - - Type - - Location - properties: - Type: - $ref: "#/components/schemas/FileFieldType" - Location: - $ref: "#/components/schemas/FileFieldLocation" - Value: - description: | - The value content for the field. The type and format depend on the field type: - - Signature/Seal fields: Usually null or empty until signed - - Text fields (SingleLine): String values - - Number fields: Numeric values - - Date fields: Date string in appropriate format - - Check/Radio fields: Boolean values or selection identifiers - oneOf: - - type: string - description: "Text content for text-based fields" - - type: number - description: "Numeric content for number fields" - - type: boolean - description: "Boolean value for checkbox fields" - - type: object - description: "Complex objects for specialized field types" - example: "John Smith" - FileFieldType: - type: string - description: | - Specifies the type of interaction or data entry required for this field. Different types have - different behaviors and validation rules in the signing interface. - enum: - - "Seal" - - "Signature" - - "Check" - - "Radio" - - "SingleLine" - - "Number" - - "Date" - example: "Signature" - x-enum-descriptions: - Seal: "Official seal or stamp area, typically for organizational authentication" - Signature: "Digital signature field where users draw or type their signature" - Check: "Checkbox for boolean yes/no selections" - Radio: "Radio button for single selection from multiple options" - SingleLine: "Single-line text input field for names, addresses, etc." - Number: "Numeric input field with validation for numerical data" - Date: "Date picker or date input field with date format validation" - FileFieldLocation: - type: object - description: | - Defines the precise positioning and sizing of a field within the PDF document. Fields can be positioned - using absolute coordinates or by searching for specific text anchors within the document. - properties: - PageNumber: - type: [integer, "null"] - description: | - The page number where this field should be placed (1-based indexing). If not specified, - the field may be positioned based on search criteria across all pages. - example: 1 - Search: - type: [string, "null"] - description: | - Text string to search for in the document to anchor the field position. When specified, - the field will be positioned relative to this text rather than using absolute coordinates. - example: "Signature:" - Occurence: - type: [integer, "null"] - description: | - When using search text positioning, specifies which occurrence of the search text to use - if the text appears multiple times on the page or document (1-based indexing). - example: 1 - Top: - type: [integer, "null"] - description: | - Vertical position from the top of the page in pixels or points. Used for absolute positioning - or as an offset when combined with search positioning. - example: 100 - Right: - type: [integer, "null"] - description: | - Horizontal position from the right edge of the page in pixels or points. Typically used - for right-aligned field positioning. - example: 300 - Bottom: - type: [integer, "null"] - description: | - Vertical position from the bottom of the page in pixels or points. Can be used instead of - or in combination with the top property for precise vertical positioning. - example: 150 - Left: - type: [integer, "null"] - description: | - Horizontal position from the left edge of the page in pixels or points. The most common - way to specify horizontal field positioning. - example: 50 - Width: - type: [integer, "null"] - description: | - Width of the field in pixels or points. Determines how much horizontal space the field - occupies and affects text wrapping for text fields. - - For signature and seal fields we suggest a width of 140. - example: 200 - Height: - type: [integer, "null"] - description: | - Height of the field in pixels or points. Determines how much vertical space the field - occupies and affects text size and line spacing for text fields. - - For signature and seal fields we suggest a height of 70. - example: 50 - TransactionDeleteOptions: - type: object - description: Options for cancelling a transaction - properties: - Reason: - type: string - description: Reason for cancelling the transaction - example: "Contract terms changed" - SendNotifications: - type: boolean - description: Whether to notify signers about the cancellation. Note that this only applies when SendSignRequest has been set to true during the transaction creation. - default: false - example: true \ No newline at end of file