Skip to content

Commit

Permalink
feat: Add uncached Number Verification method
Browse files Browse the repository at this point in the history
  • Loading branch information
SMadani committed Jun 19, 2024
1 parent 0f8ce4b commit 16ca2b8
Show file tree
Hide file tree
Showing 4 changed files with 89 additions and 16 deletions.
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](http://keepachangelog.com/)
and this project adheres to [Semantic Versioning](http://semver.org/).

# [8.9.0] - 2024-06-19
# [8.9.0] - 2024-06-20
- Added `User-to-User` header in Voice Connect SIP endpoint
- Added missing custom `headers` field in `com.vonage.client.voice.SipEndpoint`
- Added CAMARA Number Verification API
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@
import java.net.URI;
import java.util.Objects;
import java.util.UUID;
import java.util.function.Supplier;

/**
* A client for communicating with the Vonage Number Verification API. The standard way to obtain an instance
Expand All @@ -52,7 +51,7 @@ public NumberVerificationClient(HttpWrapper wrapper) {
verifyNumber = DynamicEndpoint.<VerifyNumberRequest, VerifyNumberResponse> builder(VerifyNumberResponse.class)
.authMethod(NetworkAuthMethod.class).requestMethod(HttpMethod.POST)
.responseExceptionType(CamaraResponseException.class).pathGetter((de, req) -> {
setNetworkAuth(new TokenRequest(req.redirectUrl, req.code));
setNetworkAuth(new TokenRequest(req.redirectUrl, req.getCode()));
return getCamaraBaseUri() + "number-verification/v031/verify";
})
.wrapper(wrapper).build();
Expand Down Expand Up @@ -102,7 +101,38 @@ public boolean verifyNumber(String code) {
if (cachedRequest == null) {
throw new IllegalStateException("You must first call initiateVerification using this client.");
}
cachedRequest.code = Objects.requireNonNull(code, "Code is required.");
return verifyNumber.execute(cachedRequest).getDevicePhoneNumberVerified();
return verifyNumber(cachedRequest.withCode(code));
}

/**
* Stateless implementation of {@link #verifyNumber(String)}, which creates a new request
* without having to call {@link #initiateVerification(String, URI, String)} first. This is
* useful in cases where concurrent verifications are required, or the URL is obtained another way.
*
* @param phoneNumber MSISDN of the target device to verify.
* @param redirectUri Redirect URL, as set in your Vonage application for Network APIs.
* @param code The code obtained from the inbound callback's query parameters.
*
* @return {@code true} if the device that followed the link was using the SIM card associated
* with the phone number provided in {@linkplain #initiateVerification(String, URI, String)},
* {@code false} otherwise (e.g. it was unknown, the link was not followed, the device that followed
* the link didn't use the SIM card with that phone number when doing so).
*
* @throws com.vonage.client.auth.camara.NetworkAuthResponseException If there was an error
* exchanging the code for an access token when using the Vonage Network Auth API.
*
* @throws CamaraResponseException If there was an error in communicating with the Number Verification API.
*/
public boolean verifyNumber(String phoneNumber, URI redirectUri, String code) {
return verifyNumber(new VerifyNumberRequest(phoneNumber, redirectUri).withCode(code));
}

private boolean verifyNumber(VerifyNumberRequest request) {
try {
return verifyNumber.execute(request).getDevicePhoneNumberVerified();
}
finally {
cachedRequest = null;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,24 @@
class VerifyNumberRequest extends JsonableBaseObject {
private final String phoneNumber;
@JsonIgnore final URI redirectUrl;
@JsonIgnore String code;
@JsonIgnore private String code;

VerifyNumberRequest(String phoneNumber, URI redirectUrl) {
this.phoneNumber = '+' + new E164(phoneNumber).toString();
this.redirectUrl = Objects.requireNonNull(redirectUrl, "Redirect URL is required.");
}

@JsonIgnore
VerifyNumberRequest withCode(String code) {
this.code = Objects.requireNonNull(code, "Code is required.");
return this;
}

@JsonIgnore
String getCode() {
return code;
}

/**
* Gets the MSISDN for this request.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,14 @@

public class NumberVerificationClientTest extends AbstractClientTest<NumberVerificationClient> {
final URI redirectUrl = URI.create("https://domain.example.org/redirect");
final String msisdn = "346 661113334", code = "987123";
final String msisdn = "346 661113334", code = "987123",
trueResponse = "{\"devicePhoneNumberVerified\": true}";

public NumberVerificationClientTest() {
client = new NumberVerificationClient(wrapper);
}

void setUpVerifyNumber() throws Exception {
void setUpCachedVerifyNumber() throws Exception {
stubResponse(302);
URI uri = client.initiateVerification(msisdn, redirectUrl, null);
assertEquals(uri, new FrontendAuthRequest(
Expand Down Expand Up @@ -71,35 +72,68 @@ void assert403CamaraResponseException(Executable invocation) throws Exception {
}

@Test
public void testVerifyNumber() throws Exception {
final String trueResponse = "{\"devicePhoneNumberVerified\": true}";

public void testVerifyNumberCached() throws Exception {
stubFrontendNetworkResponse(trueResponse);
assertThrows(IllegalStateException.class, () -> client.verifyNumber(code));

setUpVerifyNumber();

setUpCachedVerifyNumber();
stubFrontendNetworkResponse(trueResponse);
assertTrue(client.verifyNumber(code));

stubFrontendNetworkResponse(trueResponse);
assertThrows(IllegalStateException.class, () -> client.verifyNumber(code));

setUpCachedVerifyNumber();
stubFrontendNetworkResponse("{\"devicePhoneNumberVerified\":false}");
assertFalse(client.verifyNumber(code));

setUpCachedVerifyNumber();
stubFrontendNetworkResponse("{}");
assertFalse(client.verifyNumber(code));

setUpCachedVerifyNumber();
stubFrontendNetworkResponse("{\"devicePhoneNumberVerified\":\"true\"}");
assertTrue(client.verifyNumber(code));

setUpCachedVerifyNumber();
stubFrontendNetworkResponse("{\"devicePhoneNumberVerified\": \"false\"}");
assertFalse(client.verifyNumber(code));

setUpCachedVerifyNumber();
stubFrontendNetworkResponse(trueResponse);
assertThrows(NullPointerException.class, () -> client.verifyNumber(null));

setUpCachedVerifyNumber();
assert403CamaraResponseException(() -> client.verifyNumber(code));
}

@Test
public void testVerifyNumberRaw() throws Exception {
stubFrontendNetworkResponse(trueResponse);
assertTrue(client.verifyNumber(msisdn, redirectUrl, code));

stubFrontendNetworkResponse(trueResponse);
assertThrows(NullPointerException.class, () -> client.verifyNumber(msisdn, redirectUrl, null));

stubFrontendNetworkResponse(trueResponse);
assertThrows(NullPointerException.class, () -> client.verifyNumber(msisdn, null, code));

stubFrontendNetworkResponse(trueResponse);
assertThrows(NullPointerException.class, () -> client.verifyNumber(null, redirectUrl, code));

assert403CamaraResponseException(() -> client.verifyNumber(msisdn, redirectUrl, code));
}

@Test
public void testVerifyNumberCacheClearedAfterRawCall() throws Exception {
setUpCachedVerifyNumber();
stubFrontendNetworkResponse(trueResponse);
assertTrue(client.verifyNumber(msisdn, redirectUrl, code));

stubFrontendNetworkResponse(trueResponse);
assertThrows(IllegalStateException.class, () -> client.verifyNumber(code));
}

@Test
public void testVerifyNumberEndpoint() throws Exception {
new DynamicEndpointTestSpec<VerifyNumberRequest, VerifyNumberResponse>() {
Expand Down Expand Up @@ -131,9 +165,7 @@ protected HttpMethod expectedHttpMethod() {

@Override
protected VerifyNumberRequest sampleRequest() {
var request = new VerifyNumberRequest(msisdn, redirectUrl);
request.code = code;
return request;
return new VerifyNumberRequest(msisdn, redirectUrl).withCode(code);
}

@Override
Expand Down

0 comments on commit 16ca2b8

Please sign in to comment.