Skip to content

Commit

Permalink
feat: implement Verify feature for NumberVerification
Browse files Browse the repository at this point in the history
  • Loading branch information
Tr00d committed Jun 19, 2024
1 parent dfa1c39 commit 9c95a13
Show file tree
Hide file tree
Showing 14 changed files with 285 additions and 4 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"access_token": "ABCDEFG",
"token_type": "Bearer",
"expires_in": 3600
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"auth_req_id": "123456789",
"expires_in": 120,
"interval": 2
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"devicePhoneNumberVerified": true
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"phoneNumber": "346661113334"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"phoneNumber": "346661113334",
"maxAge": 15
}
56 changes: 56 additions & 0 deletions Vonage.Test/NumberVerification/Verify/E2ETest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
using System.Net;
using System.Threading.Tasks;
using Vonage.NumberVerification.Verify;
using Vonage.Test.Common.Extensions;
using WireMock.ResponseBuilders;
using Xunit;

namespace Vonage.Test.NumberVerification.Verify;

[Trait("Category", "E2E")]
public class E2ETest : SimSwap.E2EBase
{
public E2ETest() : base(typeof(E2ETest).Namespace)
{
}

[Fact]
public async Task CheckAsync()
{
this.SetupAuthorization();
this.SetupToken();
this.SetupCheck(nameof(SerializationTest.ShouldSerialize));
await this.Helper.VonageClient.NumberVerificationClient
.VerifyAsync(VerifyRequest.Parse("346661113334"))
.Should()
.BeSuccessAsync(true);
}

private void SetupCheck(string expectedOutput) =>
this.Helper.Server.Given(WireMock.RequestBuilders.Request.Create()
.WithPath("/camara/number-verification/v031/verify")
.WithHeader("Authorization", "Bearer ABCDEFG")
.WithBody(this.Serialization.GetRequestJson(expectedOutput))
.UsingPost())
.RespondWith(Response.Create().WithStatusCode(HttpStatusCode.OK)
.WithBody(this.Serialization.GetResponseJson(nameof(SerializationTest.ShouldDeserializeVerify))));

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/auth")
.WithHeader("Authorization", this.Helper.ExpectedAuthorizationHeaderValue)
.WithBody(
"login_hint=tel:%2B346661113334&scope=openid+dpv%3AFraudPreventionAndDetection%23number-verification-verify-read")
.UsingPost())
.RespondWith(Response.Create().WithStatusCode(HttpStatusCode.OK)
.WithBody(this.Serialization.GetResponseJson(nameof(SerializationTest.ShouldDeserializeAuthorize))));
}
56 changes: 56 additions & 0 deletions Vonage.Test/NumberVerification/Verify/RequestTest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
using Vonage.Common.Failures;
using Vonage.NumberVerification.Verify;
using Vonage.Test.Common.Extensions;
using Xunit;

namespace Vonage.Test.NumberVerification.Verify;

[Trait("Category", "Request")]
public class RequestTest
{
[Fact]
public void GetEndpointPath_ShouldReturnApiEndpoint() =>
VerifyRequest.Parse("123456789")
.Map(request => request.GetEndpointPath())
.Should()
.BeSuccess("camara/number-verification/v031/verify");

[Theory]
[InlineData("")]
[InlineData(" ")]
[InlineData(null)]
public void Build_ShouldReturnFailure_GivenNumberIsNullOrWhitespace(string value) =>
VerifyRequest.Parse(value)
.Should()
.BeFailure(ResultFailure.FromErrorMessage("Number cannot be null or whitespace."));

[Fact]
public void Build_ShouldReturnFailure_GivenNumberContainsNonDigits() =>
VerifyRequest.Parse("123456abc789")
.Should()
.BeFailure(ResultFailure.FromErrorMessage("Number can only contain digits."));

[Fact]
public void Build_ShouldReturnFailure_GivenNumberLengthIsHigherThan7() =>
VerifyRequest.Parse("123456")
.Should()
.BeFailure(ResultFailure.FromErrorMessage("Number length cannot be lower than 7."));

[Fact]
public void Build_ShouldReturnFailure_GivenNumberLengthIsLowerThan15() =>
VerifyRequest.Parse("1234567890123456")
.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) =>
VerifyRequest.Parse(value)
.Map(number => number.PhoneNumber.Number)
.Should()
.BeSuccess(expected);
}
47 changes: 47 additions & 0 deletions Vonage.Test/NumberVerification/Verify/SerializationTest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
using Vonage.NumberVerification.Authenticate;
using Vonage.NumberVerification.Verify;
using Vonage.Serialization;
using Vonage.Test.Common;
using Vonage.Test.Common.Extensions;
using Xunit;

namespace Vonage.Test.NumberVerification.Verify;

[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<AuthorizeResponse>(this.helper.GetResponseJson())
.Should()
.BeSuccess(GetExpectedAuthorizeResponse());

[Fact]
public void ShouldDeserializeAccessToken() => this.helper.Serializer
.DeserializeObject<GetTokenResponse>(this.helper.GetResponseJson())
.Should()
.BeSuccess(GetExpectedTokenResponse());

[Fact]
public void ShouldDeserializeVerify() => this.helper.Serializer
.DeserializeObject<VerifyResponse>(this.helper.GetResponseJson())
.Should()
.BeSuccess(GetExpectedResponse());

[Fact]
public void ShouldSerialize() =>
VerifyRequest.Parse("346661113334")
.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 VerifyResponse GetExpectedResponse() => new VerifyResponse(true);
}
15 changes: 15 additions & 0 deletions Vonage.Test/Vonage.Test.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -1218,6 +1218,21 @@
<None Update="NumberVerification\Authenticate\Data\ShouldDeserializeAuthorize-response.json">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="NumberVerification\Verify\Data\ShouldDeserializeAccessToken-response.json">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="NumberVerification\Verify\Data\ShouldDeserializeAuthorize-response.json">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="NumberVerification\Verify\Data\ShouldDeserializeVerify-response.json">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="NumberVerification\Verify\Data\ShouldSerialize-request.json">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="NumberVerification\Verify\Data\ShouldSerializeWithPeriod-request.json">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
</ItemGroup>
<Target Name="PreBuild" BeforeTargets="PreBuildEvent">
<Exec Command="node ../.scripts/init.js"/>
Expand Down
8 changes: 8 additions & 0 deletions Vonage/NumberVerification/INumberVerificationClient.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using System.Threading.Tasks;
using Vonage.Common.Monads;
using Vonage.NumberVerification.Authenticate;
using Vonage.NumberVerification.Verify;

namespace Vonage.NumberVerification;

Expand All @@ -15,4 +16,11 @@ public interface INumberVerificationClient
/// <param name="request">The request.</param>
/// <returns>Success or Failure.</returns>
Task<Result<AuthenticateResponse>> AuthenticateAsync(Result<AuthenticateRequest> request);

/// <summary>
/// Verifies if the specified phone number matches the one that the user is currently using.
/// </summary>
/// <param name="request">The request.</param>
/// <returns>Success or Failure.</returns>
Task<Result<bool>> VerifyAsync(Result<VerifyRequest> request);
}
23 changes: 22 additions & 1 deletion Vonage/NumberVerification/NumberVerificationClient.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
using System.Threading.Tasks;
using System.Net.Http.Headers;
using System.Threading.Tasks;
using Vonage.Common.Client;
using Vonage.Common.Monads;
using Vonage.NumberVerification.Authenticate;
using Vonage.NumberVerification.Verify;
using Vonage.Serialization;

namespace Vonage.NumberVerification;
Expand All @@ -21,6 +23,25 @@ internal class NumberVerificationClient : INumberVerificationClient
.BindAsync(this.SendGetTokenRequest)
.Map(BuildAuthenticateResponse);

/// <inheritdoc />
public async Task<Result<bool>> VerifyAsync(Result<VerifyRequest> request) =>
await request
.Map(BuildAuthenticationRequest)
.BindAsync(this.AuthenticateAsync)
.Map(BuildAuthenticationHeader)
.Map(this.BuildClientWithAuthenticationHeader)
.BindAsync(client => client.SendWithResponseAsync<VerifyRequest, VerifyResponse>(request))
.Map(response => response.Verified);

private VonageHttpClient BuildClientWithAuthenticationHeader(AuthenticationHeaderValue header) =>
this.vonageClient.WithDifferentHeader(header);

private static Result<AuthenticateRequest> BuildAuthenticationRequest(VerifyRequest request) =>
request.BuildAuthenticationRequest();

private static AuthenticationHeaderValue BuildAuthenticationHeader(AuthenticateResponse authentication) =>
authentication.BuildAuthenticationHeader();

private static AuthenticateResponse BuildAuthenticateResponse(GetTokenResponse response) =>
new AuthenticateResponse(response.AccessToken);

Expand Down
54 changes: 54 additions & 0 deletions Vonage/NumberVerification/Verify/VerifyRequest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
using System.Net.Http;
using System.Text;
using System.Text.Json.Serialization;
using Vonage.Common;
using Vonage.Common.Client;
using Vonage.Common.Monads;
using Vonage.Common.Serialization;
using Vonage.NumberVerification.Authenticate;
using Vonage.Serialization;

namespace Vonage.NumberVerification.Verify;

/// <summary>
/// Represents a request to verify if the specified phone number matches the one that the user is currently using.
/// </summary>
public readonly struct VerifyRequest : IVonageRequest
{
/// <inheritdoc />
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");

/// <inheritdoc />
public string GetEndpointPath() => "camara/number-verification/v031/verify";

/// <summary>
/// Subscriber number in E.164 format (starting with country code). Optionally prefixed with '+'.
/// </summary>
[JsonConverter(typeof(PhoneNumberJsonConverter))]
[JsonPropertyOrder(0)]
[JsonPropertyName("phoneNumber")]
public PhoneNumber PhoneNumber { get; internal init; }

private static string Scope => "dpv:FraudPreventionAndDetection#number-verification-verify-read";

internal Result<AuthenticateRequest> BuildAuthenticationRequest() =>
AuthenticateRequest.Parse(this.PhoneNumber.NumberWithInternationalIndicator, Scope);

/// <summary>
/// Parses the input into an VerifyRequest.
/// </summary>
/// <param name="number">The phone number.</param>
/// <returns>Success if the input matches all requirements. Failure otherwise.</returns>
public static Result<VerifyRequest> Parse(string number) =>
PhoneNumber.Parse(number).Map(phoneNumber => new VerifyRequest
{
PhoneNumber = phoneNumber,
});
}
7 changes: 7 additions & 0 deletions Vonage/NumberVerification/Verify/VerifyResponse.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
using System.Text.Json.Serialization;

namespace Vonage.NumberVerification.Verify;

internal record VerifyResponse(
[property: JsonPropertyName("devicePhoneNumberVerified")]
bool Verified);
3 changes: 0 additions & 3 deletions Vonage/Vonage.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -80,9 +80,6 @@
<PackagePath/>
</None>
</ItemGroup>
<ItemGroup>
<Folder Include="NumberVerification\Verify\"/>
</ItemGroup>
<Target Name="PreBuild" BeforeTargets="PreBuildEvent">
<Exec Command="node ../.scripts/init.js"/>
</Target>
Expand Down

0 comments on commit 9c95a13

Please sign in to comment.