diff --git a/Vonage.Test/SimSwap/Authenticate/AuthenticateRequest.cs b/Vonage.Test/SimSwap/Authenticate/AuthenticateRequest.cs index 0c9a1333..34e37d4d 100644 --- a/Vonage.Test/SimSwap/Authenticate/AuthenticateRequest.cs +++ b/Vonage.Test/SimSwap/Authenticate/AuthenticateRequest.cs @@ -16,13 +16,6 @@ public void Parse_ShouldReturnFailure_GivenNumberIsNullOrWhitespace(string value Vonage.SimSwap.Authenticate.AuthenticateRequest.Parse(value).Should() .BeFailure(ResultFailure.FromErrorMessage("Number cannot be null or whitespace.")); - [Fact] - public void Parse_ShouldReturnNumberWithPlusIndicator() => - Vonage.SimSwap.Authenticate.AuthenticateRequest.Parse("123456789") - .Map(number => number.PhoneNumber.NumberWithInternationalIndicator) - .Should() - .BeSuccess("+123456789"); - [Fact] public void Parse_ShouldReturnFailure_GivenNumberContainsNonDigits() => Vonage.SimSwap.Authenticate.AuthenticateRequest.Parse("1234567abc123").Should() @@ -41,24 +34,13 @@ public void Parse_ShouldReturnFailure_GivenNumberLengthIsLowerThan15() => [Theory] [InlineData("1234567", "1234567")] [InlineData("123456789012345", "123456789012345")] - public void Parse_ShouldReturnSuccess_GivenNumberIsValid(string value, string expected) => - Vonage.SimSwap.Authenticate.AuthenticateRequest.Parse(value).Map(number => number.PhoneNumber.Number).Should() - .BeSuccess(expected); - - [Theory] [InlineData("+1234567890", "1234567890")] [InlineData("+123456789012345", "123456789012345")] [InlineData("+++1234567890", "1234567890")] - public void Parse_ShouldReturnSuccess_GivenNumberStartWithPlus(string value, string expected) => + public void Parse_ShouldReturnSuccess_GivenNumberIsValid(string value, string expected) => Vonage.SimSwap.Authenticate.AuthenticateRequest.Parse(value).Map(number => number.PhoneNumber.Number).Should() .BeSuccess(expected); - [Fact] - public void ToString_ShouldReturnNumber() => - Vonage.SimSwap.Authenticate.AuthenticateRequest.Parse("123456789").Map(number => number.PhoneNumber.ToString()) - .Should() - .BeSuccess("123456789"); - [Fact] public void BuildAuthorizeRequest() { diff --git a/Vonage.Test/SimSwap/Authenticate/AuthenticateResponseTest.cs b/Vonage.Test/SimSwap/Authenticate/AuthenticateResponseTest.cs new file mode 100644 index 00000000..477f3696 --- /dev/null +++ b/Vonage.Test/SimSwap/Authenticate/AuthenticateResponseTest.cs @@ -0,0 +1,17 @@ +using System.Net.Http.Headers; +using FluentAssertions; +using Vonage.SimSwap.Authenticate; +using Xunit; + +namespace Vonage.Test.SimSwap.Authenticate; + +[Trait("Category", "Request")] +public class AuthenticateResponseTest +{ + [Fact] + public void BuildAuthenticationHeader_ShouldReturnBearerAuth() => + new AuthenticateResponse("123456789") + .BuildAuthenticationHeader() + .Should() + .Be(new AuthenticationHeaderValue("Bearer", "123456789")); +} \ No newline at end of file diff --git a/Vonage.Test/SimSwap/Check/Data/ShouldDeserializeAccessToken-response.json b/Vonage.Test/SimSwap/Check/Data/ShouldDeserializeAccessToken-response.json new file mode 100644 index 00000000..73ab58da --- /dev/null +++ b/Vonage.Test/SimSwap/Check/Data/ShouldDeserializeAccessToken-response.json @@ -0,0 +1,5 @@ +{ + "access_token": "ABCDEFG", + "token_type": "Bearer", + "expires_in": 3600 +} \ No newline at end of file diff --git a/Vonage.Test/SimSwap/Check/Data/ShouldDeserializeAuthorize-response.json b/Vonage.Test/SimSwap/Check/Data/ShouldDeserializeAuthorize-response.json new file mode 100644 index 00000000..7b424919 --- /dev/null +++ b/Vonage.Test/SimSwap/Check/Data/ShouldDeserializeAuthorize-response.json @@ -0,0 +1,5 @@ +{ + "auth_req_id": "123456789", + "expires_in": "120", + "interval": "2" +} \ No newline at end of file diff --git a/Vonage.Test/SimSwap/Check/Data/ShouldDeserializeCheck-response.json b/Vonage.Test/SimSwap/Check/Data/ShouldDeserializeCheck-response.json new file mode 100644 index 00000000..d7ea7844 --- /dev/null +++ b/Vonage.Test/SimSwap/Check/Data/ShouldDeserializeCheck-response.json @@ -0,0 +1,3 @@ +{ + "swapped": true +} \ No newline at end of file diff --git a/Vonage.Test/SimSwap/Check/Data/ShouldSerialize-request.json b/Vonage.Test/SimSwap/Check/Data/ShouldSerialize-request.json new file mode 100644 index 00000000..1709c701 --- /dev/null +++ b/Vonage.Test/SimSwap/Check/Data/ShouldSerialize-request.json @@ -0,0 +1,4 @@ +{ + "phoneNumber": "346661113334", + "maxAge": 240 +} \ No newline at end of file diff --git a/Vonage.Test/SimSwap/Check/Data/ShouldSerializeWithPeriod-request.json b/Vonage.Test/SimSwap/Check/Data/ShouldSerializeWithPeriod-request.json new file mode 100644 index 00000000..687bc593 --- /dev/null +++ b/Vonage.Test/SimSwap/Check/Data/ShouldSerializeWithPeriod-request.json @@ -0,0 +1,4 @@ +{ + "phoneNumber": "346661113334", + "maxAge": 15 +} \ No newline at end of file diff --git a/Vonage.Test/SimSwap/Check/E2ETest.cs b/Vonage.Test/SimSwap/Check/E2ETest.cs new file mode 100644 index 00000000..2538f22f --- /dev/null +++ b/Vonage.Test/SimSwap/Check/E2ETest.cs @@ -0,0 +1,68 @@ +using System.Net; +using System.Threading.Tasks; +using Vonage.SimSwap.Check; +using Vonage.Test.Common.Extensions; +using WireMock.ResponseBuilders; +using Xunit; + +namespace Vonage.Test.SimSwap.Check; + +[Trait("Category", "E2E")] +public class E2ETest : E2EBase +{ + public E2ETest() : base(typeof(E2ETest).Namespace) + { + } + + [Fact] + public async Task CheckAsync() + { + this.SetupAuthorization(); + this.SetupToken(); + this.SetupSimSwap(nameof(SerializationTest.ShouldSerialize)); + await this.Helper.VonageClient.SimSwapClient + .CheckAsync(CheckRequest.Build().WithPhoneNumber("346661113334").Create()) + .Should() + .BeSuccessAsync(true); + } + + [Fact] + public async Task CheckAsyncWithPeriod() + { + this.SetupAuthorization(); + this.SetupToken(); + this.SetupSimSwap(nameof(SerializationTest.ShouldSerializeWithPeriod)); + await this.Helper.VonageClient.SimSwapClient + .CheckAsync(CheckRequest.Build().WithPhoneNumber("346661113334").WithPeriod(15).Create()) + .Should() + .BeSuccessAsync(true); + } + + private void SetupSimSwap(string expectedOutput) => + this.Helper.Server.Given(WireMock.RequestBuilders.Request.Create() + .WithPath("/camara/sim-swap/v040/check") + .WithHeader("Authorization", "Bearer ABCDEFG") + .WithBody(this.Serialization.GetRequestJson(expectedOutput)) + .UsingPost()) + .RespondWith(Response.Create().WithStatusCode(HttpStatusCode.OK) + .WithBody(this.Serialization.GetResponseJson(nameof(SerializationTest.ShouldDeserializeCheck)))); + + private void SetupToken() => + this.Helper.Server.Given(WireMock.RequestBuilders.Request.Create() + .WithPath("/oauth2/token") + .WithHeader("Authorization", this.Helper.ExpectedAuthorizationHeaderValue) + .WithBody("auth_req_id=123456789&grant_type=urn:openid:params:grant-type:ciba") + .UsingPost()) + .RespondWith(Response.Create().WithStatusCode(HttpStatusCode.OK) + .WithBody(this.Serialization.GetResponseJson(nameof(SerializationTest.ShouldDeserializeAccessToken)))); + + private void SetupAuthorization() => + this.Helper.Server.Given(WireMock.RequestBuilders.Request.Create() + .WithPath("/oauth2/bc-authorize") + .WithHeader("Authorization", this.Helper.ExpectedAuthorizationHeaderValue) + .WithBody( + "login_hint=tel:%2B346661113334&scope=openid+dpv%3AFraudPreventionAndDetection%23check-sim-swap") + .UsingPost()) + .RespondWith(Response.Create().WithStatusCode(HttpStatusCode.OK) + .WithBody(this.Serialization.GetResponseJson(nameof(SerializationTest.ShouldDeserializeAuthorize)))); +} \ No newline at end of file diff --git a/Vonage.Test/SimSwap/Check/RequestBuilderTest.cs b/Vonage.Test/SimSwap/Check/RequestBuilderTest.cs new file mode 100644 index 00000000..7d5f717b --- /dev/null +++ b/Vonage.Test/SimSwap/Check/RequestBuilderTest.cs @@ -0,0 +1,102 @@ +using Vonage.Common.Failures; +using Vonage.SimSwap.Check; +using Vonage.Test.Common.Extensions; +using Xunit; + +namespace Vonage.Test.SimSwap.Check; + +[Trait("Category", "Request")] +public class RequestBuilderTest +{ + [Theory] + [InlineData("")] + [InlineData(" ")] + [InlineData(null)] + public void Build_ShouldReturnFailure_GivenNumberIsNullOrWhitespace(string value) => + CheckRequest.Build() + .WithPhoneNumber(value) + .Create() + .Should() + .BeFailure(ResultFailure.FromErrorMessage("Number cannot be null or whitespace.")); + + [Fact] + public void Build_ShouldReturnFailure_GivenNumberContainsNonDigits() => + CheckRequest.Build() + .WithPhoneNumber("1234567abc123") + .Create() + .Should() + .BeFailure(ResultFailure.FromErrorMessage("Number can only contain digits.")); + + [Fact] + public void Build_ShouldReturnFailure_GivenNumberLengthIsHigherThan7() => + CheckRequest.Build() + .WithPhoneNumber("123456") + .Create() + .Should() + .BeFailure(ResultFailure.FromErrorMessage("Number length cannot be lower than 7.")); + + [Fact] + public void Build_ShouldReturnFailure_GivenNumberLengthIsLowerThan15() => + CheckRequest.Build() + .WithPhoneNumber("1234567890123456") + .Create() + .Should() + .BeFailure(ResultFailure.FromErrorMessage("Number length cannot be higher than 15.")); + + [Theory] + [InlineData("1234567", "1234567")] + [InlineData("123456789012345", "123456789012345")] + [InlineData("+1234567890", "1234567890")] + [InlineData("+123456789012345", "123456789012345")] + [InlineData("+++1234567890", "1234567890")] + public void Build_ShouldSetPhoneNumber_GivenNumberIsValid(string value, string expected) => + CheckRequest.Build() + .WithPhoneNumber(value) + .Create() + .Map(number => number.PhoneNumber.Number) + .Should() + .BeSuccess(expected); + + [Theory] + [InlineData(1)] + [InlineData(2400)] + public void Build_ShouldSetPeriod(int value) => + CheckRequest.Build() + .WithPhoneNumber("1234567") + .WithPeriod(value) + .Create() + .Map(number => number.Period) + .Should() + .BeSuccess(value); + + [Fact] + public void Build_ShouldSetPeriodToDefault_GivenPeriodIsNotSet() => + CheckRequest.Build() + .WithPhoneNumber("1234567") + .Create() + .Map(number => number.Period) + .Should() + .BeSuccess(240); + + [Theory] + [InlineData(0)] + [InlineData(-1)] + public void Build_ShouldReturnFailure_GivenPeriodIsLowerThan15(int incorrectValue) => + CheckRequest.Build() + .WithPhoneNumber("1234567") + .WithPeriod(incorrectValue) + .Create() + .Should() + .BeParsingFailure("Period cannot be lower than 1."); + + [Theory] + [InlineData(2401)] + [InlineData(2402)] + public void Build_ShouldReturnFailure_GivenPeriodIsHigherThan2400(int incorrectValue) => + CheckRequest.Build() + .WithPhoneNumber("1234567") + .WithPeriod(incorrectValue) + .Create() + .Should() + .BeParsingFailure("Period cannot be higher than 2400."); +} \ No newline at end of file diff --git a/Vonage.Test/SimSwap/Check/RequestTest.cs b/Vonage.Test/SimSwap/Check/RequestTest.cs new file mode 100644 index 00000000..05cb9831 --- /dev/null +++ b/Vonage.Test/SimSwap/Check/RequestTest.cs @@ -0,0 +1,18 @@ +using Vonage.SimSwap.Check; +using Vonage.Test.Common.Extensions; +using Xunit; + +namespace Vonage.Test.SimSwap.Check; + +[Trait("Category", "Request")] +public class RequestTest +{ + [Fact] + public void GetEndpointPath_ShouldReturnApiEndpoint() => + CheckRequest.Build() + .WithPhoneNumber("123456789") + .Create() + .Map(request => request.GetEndpointPath()) + .Should() + .BeSuccess("camara/sim-swap/v040/check"); +} \ No newline at end of file diff --git a/Vonage.Test/SimSwap/Check/SerializationTest.cs b/Vonage.Test/SimSwap/Check/SerializationTest.cs new file mode 100644 index 00000000..7eb0f51d --- /dev/null +++ b/Vonage.Test/SimSwap/Check/SerializationTest.cs @@ -0,0 +1,59 @@ +using Vonage.Serialization; +using Vonage.SimSwap.Authenticate; +using Vonage.SimSwap.Check; +using Vonage.Test.Common; +using Vonage.Test.Common.Extensions; +using Xunit; + +namespace Vonage.Test.SimSwap.Check; + +[Trait("Category", "Serialization")] +public class SerializationTest +{ + private readonly SerializationTestHelper helper = new SerializationTestHelper( + typeof(SerializationTest).Namespace, + JsonSerializerBuilder.BuildWithSnakeCase()); + + [Fact] + public void ShouldDeserializeAuthorize() => this.helper.Serializer + .DeserializeObject(this.helper.GetResponseJson()) + .Should() + .BeSuccess(GetExpectedAuthorizeResponse()); + + [Fact] + public void ShouldDeserializeAccessToken() => this.helper.Serializer + .DeserializeObject(this.helper.GetResponseJson()) + .Should() + .BeSuccess(GetExpectedTokenResponse()); + + [Fact] + public void ShouldDeserializeCheck() => this.helper.Serializer + .DeserializeObject(this.helper.GetResponseJson()) + .Should() + .BeSuccess(GetExpectedResponse()); + + [Fact] + public void ShouldSerialize() => + CheckRequest.Build() + .WithPhoneNumber("346661113334") + .Create() + .GetStringContent() + .Should() + .BeSuccess(this.helper.GetRequestJson()); + + [Fact] + public void ShouldSerializeWithPeriod() => + CheckRequest.Build() + .WithPhoneNumber("346661113334") + .WithPeriod(15) + .Create() + .GetStringContent() + .Should() + .BeSuccess(this.helper.GetRequestJson()); + + private static AuthorizeResponse GetExpectedAuthorizeResponse() => new AuthorizeResponse("123456789", "120", "2"); + + private static GetTokenResponse GetExpectedTokenResponse() => new GetTokenResponse("ABCDEFG", "Bearer", 3600); + + private static CheckResponse GetExpectedResponse() => new CheckResponse(true); +} \ No newline at end of file diff --git a/Vonage.Test/Vonage.Test.csproj b/Vonage.Test/Vonage.Test.csproj index d9279e5b..758bb54a 100644 --- a/Vonage.Test/Vonage.Test.csproj +++ b/Vonage.Test/Vonage.Test.csproj @@ -47,7 +47,6 @@ - PreserveNewest @@ -1108,6 +1107,21 @@ Always + + Always + + + Always + + + Always + + + Always + + + Always + diff --git a/Vonage/Common/Client/VonageHttpClient.cs b/Vonage/Common/Client/VonageHttpClient.cs index 4508629f..314b9a77 100644 --- a/Vonage/Common/Client/VonageHttpClient.cs +++ b/Vonage/Common/Client/VonageHttpClient.cs @@ -16,6 +16,7 @@ public class VonageHttpClient private readonly HttpClient client; private readonly IJsonSerializer jsonSerializer; private readonly Result requestOptions; + private readonly string userAgent; /// /// Creates a custom Http Client for Vonage purposes. @@ -26,11 +27,16 @@ public VonageHttpClient(VonageHttpClientConfiguration configuration, IJsonSerial { this.client = configuration.HttpClient; this.jsonSerializer = serializer; + this.userAgent = configuration.UserAgent; this.requestOptions = configuration.AuthenticationHeader .Map(header => - new HttpClientOptions(header, UserAgentProvider.GetFormattedUserAgent(configuration.UserAgent))); + new HttpClientOptions(header, UserAgentProvider.GetFormattedUserAgent(this.userAgent))); } + internal VonageHttpClient WithDifferentHeader(Result header) => + new VonageHttpClient(new VonageHttpClientConfiguration(this.client, header, this.userAgent), + this.jsonSerializer); + /// /// Sends a HttpRequest. /// diff --git a/Vonage/SimSwap/Authenticate/AuthenticateResponse.cs b/Vonage/SimSwap/Authenticate/AuthenticateResponse.cs index 4e95dd51..78f8a8ef 100644 --- a/Vonage/SimSwap/Authenticate/AuthenticateResponse.cs +++ b/Vonage/SimSwap/Authenticate/AuthenticateResponse.cs @@ -1,7 +1,16 @@ -namespace Vonage.SimSwap.Authenticate; +using System.Net.Http.Headers; + +namespace Vonage.SimSwap.Authenticate; /// /// Represents an authentication response. /// /// The access token. -public record AuthenticateResponse(string AccessToken); \ No newline at end of file +public record AuthenticateResponse(string AccessToken) +{ + /// + /// + /// + public AuthenticationHeaderValue BuildAuthenticationHeader() => + new AuthenticationHeaderValue("Bearer", this.AccessToken); +} \ No newline at end of file diff --git a/Vonage/SimSwap/Check/CheckRequest.cs b/Vonage/SimSwap/Check/CheckRequest.cs new file mode 100644 index 00000000..c0efdc2c --- /dev/null +++ b/Vonage/SimSwap/Check/CheckRequest.cs @@ -0,0 +1,50 @@ +using System.Net.Http; +using System.Text; +using System.Text.Json.Serialization; +using Vonage.Common; +using Vonage.Common.Client; +using Vonage.Common.Serialization; +using Vonage.Serialization; + +namespace Vonage.SimSwap.Check; + +/// +/// Represents a request to check if a SIM swap has been performed. +/// +public readonly struct CheckRequest : IVonageRequest + +{ + /// + public HttpRequestMessage BuildRequestMessage() => VonageRequestBuilder + .Initialize(HttpMethod.Post, this.GetEndpointPath()) + .WithContent(this.GetRequestContent()) + .Build(); + + private StringContent GetRequestContent() => + new StringContent(JsonSerializerBuilder.BuildWithSnakeCase().SerializeObject(this), Encoding.UTF8, + "application/json"); + + /// + public string GetEndpointPath() => "camara/sim-swap/v040/check"; + + /// + /// Subscriber number in E.164 format (starting with country code). Optionally prefixed with '+'. + /// + [JsonConverter(typeof(PhoneNumberJsonConverter))] + [JsonPropertyOrder(0)] + [JsonPropertyName("phoneNumber")] + public PhoneNumber PhoneNumber { get; internal init; } + + /// + /// Period in hours to be checked for SIM swap. + /// + [JsonPropertyOrder(1)] + [JsonPropertyName("maxAge")] + public int Period { get; internal init; } + + /// + /// Initializes a builder. + /// + /// The builder. + public static IBuilderForPhoneNumber Build() => new CheckRequestBuilder(); +} \ No newline at end of file diff --git a/Vonage/SimSwap/Check/CheckRequestBuilder.cs b/Vonage/SimSwap/Check/CheckRequestBuilder.cs new file mode 100644 index 00000000..ecbe23fe --- /dev/null +++ b/Vonage/SimSwap/Check/CheckRequestBuilder.cs @@ -0,0 +1,72 @@ +using Vonage.Common; +using Vonage.Common.Client; +using Vonage.Common.Monads; +using Vonage.Common.Validation; + +namespace Vonage.SimSwap.Check; + +internal class CheckRequestBuilder : IBuilderForPhoneNumber, IBuilderForOptional +{ + private const int MaximumPeriod = 2400; + private const int MinimumPeriod = 1; + private const int DefaultPeriod = 240; + private string number; + private int period = DefaultPeriod; + + /// + public Result Create() => + PhoneNumber.Parse(this.number) + .Map(phoneNumber => new CheckRequest + { + PhoneNumber = phoneNumber, + Period = this.period, + }) + .Map(InputEvaluation.Evaluate) + .Bind(evaluation => evaluation.WithRules(VerifyAgeMinimumPeriod, VerifyMaximumPeriod)); + + /// + public IVonageRequestBuilder WithPeriod(int value) => new CheckRequestBuilder + { + number = this.number, + period = value, + }; + + /// + public IBuilderForOptional WithPhoneNumber(string value) => new CheckRequestBuilder + { + number = value, + period = this.period, + }; + + private static Result VerifyAgeMinimumPeriod(CheckRequest request) => + InputValidation.VerifyHigherOrEqualThan(request, request.Period, MinimumPeriod, nameof(request.Period)); + + private static Result VerifyMaximumPeriod(CheckRequest request) => + InputValidation.VerifyLowerOrEqualThan(request, request.Period, MaximumPeriod, nameof(request.Period)); +} + +/// +/// Represents a builder for PhoneNumber. +/// +public interface IBuilderForPhoneNumber +{ + /// + /// Sets the phone number on the builder. + /// + /// The phone number. + /// The builder. + IBuilderForOptional WithPhoneNumber(string value); +} + +/// +/// Represents a builder for optional values. +/// +public interface IBuilderForOptional : IVonageRequestBuilder +{ + /// + /// Sets the period on the builder. + /// + /// The period in hours to be checked for SIM swap. + /// The builder. + IVonageRequestBuilder WithPeriod(int value); +} \ No newline at end of file diff --git a/Vonage/SimSwap/Check/CheckResponse.cs b/Vonage/SimSwap/Check/CheckResponse.cs new file mode 100644 index 00000000..8f4f859c --- /dev/null +++ b/Vonage/SimSwap/Check/CheckResponse.cs @@ -0,0 +1,3 @@ +namespace Vonage.SimSwap.Check; + +internal record CheckResponse(bool Swapped); \ No newline at end of file diff --git a/Vonage/SimSwap/ISimSwapClient.cs b/Vonage/SimSwap/ISimSwapClient.cs index 19937470..a0323189 100644 --- a/Vonage/SimSwap/ISimSwapClient.cs +++ b/Vonage/SimSwap/ISimSwapClient.cs @@ -1,6 +1,7 @@ using System.Threading.Tasks; using Vonage.Common.Monads; using Vonage.SimSwap.Authenticate; +using Vonage.SimSwap.Check; namespace Vonage.SimSwap; @@ -15,4 +16,11 @@ public interface ISimSwapClient /// The request. /// Success or Failure. Task> AuthenticateAsync(Result request); + + /// + /// Check if SIM swap has been performed during a past period. + /// + /// The request. + /// Success or Failure. + Task> CheckAsync(Result request); } \ No newline at end of file diff --git a/Vonage/SimSwap/SimSwapClient.cs b/Vonage/SimSwap/SimSwapClient.cs index 7d7fc79e..a2329c36 100644 --- a/Vonage/SimSwap/SimSwapClient.cs +++ b/Vonage/SimSwap/SimSwapClient.cs @@ -1,8 +1,10 @@ -using System.Threading.Tasks; +using System.Net.Http.Headers; +using System.Threading.Tasks; using Vonage.Common.Client; using Vonage.Common.Monads; using Vonage.Serialization; using Vonage.SimSwap.Authenticate; +using Vonage.SimSwap.Check; namespace Vonage.SimSwap; @@ -21,6 +23,23 @@ public Task> AuthenticateAsync(Result + public async Task> CheckAsync(Result request) => + await request.BindAsync(this.AuthenticateCheckRequest) + .Map(BuildAuthenticationHeader) + .Map(this.BuildClientWithAuthenticationHeader) + .BindAsync(client => client.SendWithResponseAsync(request)) + .Map(response => response.Swapped); + + private VonageHttpClient BuildClientWithAuthenticationHeader(AuthenticationHeaderValue header) => + this.vonageClient.WithDifferentHeader(header); + + private static AuthenticationHeaderValue BuildAuthenticationHeader(AuthenticateResponse authentication) => + authentication.BuildAuthenticationHeader(); + + private Task> AuthenticateCheckRequest(CheckRequest r) => + this.AuthenticateAsync(AuthenticateRequest.Parse(r.PhoneNumber.NumberWithInternationalIndicator)); + private static AuthenticateResponse BuildAuthenticateResponse(GetTokenResponse response) => new AuthenticateResponse(response.AccessToken);