convert twilio unit test from groovy to java (#13077)

Co-authored-by: Jean Bisutti <jean.bisutti@gmail.com>
Co-authored-by: Jay DeLuca <jaydeluca4@gmail.com>
This commit is contained in:
shalk(xiao kun) 2025-01-24 19:30:24 +08:00 committed by GitHub
parent e210c0ea02
commit 9e70f93d35
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 551 additions and 628 deletions

View File

@ -1,628 +0,0 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package test
import com.fasterxml.jackson.databind.ObjectMapper
import com.google.common.util.concurrent.ListenableFuture
import com.twilio.Twilio
import com.twilio.exception.ApiException
import com.twilio.http.NetworkHttpClient
import com.twilio.http.Response
import com.twilio.http.TwilioRestClient
import com.twilio.rest.api.v2010.account.Call
import com.twilio.rest.api.v2010.account.Message
import com.twilio.type.PhoneNumber
import io.opentelemetry.instrumentation.test.AgentInstrumentationSpecification
import io.opentelemetry.semconv.ServerAttributes
import io.opentelemetry.semconv.ErrorAttributes
import io.opentelemetry.semconv.HttpAttributes
import io.opentelemetry.semconv.NetworkAttributes
import io.opentelemetry.semconv.UrlAttributes
import org.apache.http.HttpEntity
import org.apache.http.StatusLine
import org.apache.http.client.methods.CloseableHttpResponse
import org.apache.http.impl.client.CloseableHttpClient
import org.apache.http.impl.client.HttpClientBuilder
import java.util.concurrent.ExecutionException
import java.util.concurrent.TimeUnit
import static io.opentelemetry.api.trace.SpanKind.CLIENT
import static io.opentelemetry.api.trace.StatusCode.ERROR
class TwilioClientTest extends AgentInstrumentationSpecification {
final static String ACCOUNT_SID = "abc"
final static String AUTH_TOKEN = "efg"
final static String MESSAGE_RESPONSE_BODY = """
{
"account_sid": "AC14984e09e497506cf0d5eb59b1f6ace7",
"api_version": "2010-04-01",
"body": "Hello, World!",
"date_created": "Thu, 30 Jul 2015 20:12:31 +0000",
"date_sent": "Thu, 30 Jul 2015 20:12:33 +0000",
"date_updated": "Thu, 30 Jul 2015 20:12:33 +0000",
"direction": "outbound-api",
"from": "+14155552345",
"messaging_service_sid": "MGXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
"num_media": "0",
"num_segments": "1",
"price": -0.00750,
"price_unit": "USD",
"sid": "MMXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
"status": "sent",
"subresource_uris": {
"media": "/2010-04-01/Accounts/AC14984e09e497506cf0d5eb59b1f6ace7/Messages/SMXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX/Media.json"
},
"to": "+14155552345",
"uri": "/2010-04-01/Accounts/AC14984e09e497506cf0d5eb59b1f6ace7/Messages/SMXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX.json"
}
"""
final static String CALL_RESPONSE_BODY = """
{
"account_sid": "ACXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
"annotation": null,
"answered_by": null,
"api_version": "2010-04-01",
"caller_name": null,
"date_created": "Tue, 31 Aug 2010 20:36:28 +0000",
"date_updated": "Tue, 31 Aug 2010 20:36:44 +0000",
"direction": "inbound",
"duration": "15",
"end_time": "Tue, 31 Aug 2010 20:36:44 +0000",
"forwarded_from": "+141586753093",
"from": "+15017122661",
"from_formatted": "(501) 712-2661",
"group_sid": null,
"parent_call_sid": null,
"phone_number_sid": "PNXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
"price": -0.03000,
"price_unit": "USD",
"sid": "CAXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
"start_time": "Tue, 31 Aug 2010 20:36:29 +0000",
"status": "completed",
"subresource_uris": {
"notifications": "/2010-04-01/Accounts/ACXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX/Calls/CAXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX/Notifications.json",
"recordings": "/2010-04-01/Accounts/ACXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX/Calls/CAXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX/Recordings.json",
"feedback": "/2010-04-01/Accounts/ACXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX/Calls/CAXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX/Feedback.json",
"feedback_summaries": "/2010-04-01/Accounts/ACXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX/Calls/FeedbackSummary.json"
},
"to": "+15558675310",
"to_formatted": "(555) 867-5310",
"uri": "/2010-04-01/Accounts/ACXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX/Calls/CAXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX.json"
}
"""
final static String ERROR_RESPONSE_BODY = """
{
"code": 123,
"message": "Testing Failure",
"code": 567,
"more_info": "Testing"
}
"""
TwilioRestClient twilioRestClient = Mock()
def setupSpec() {
Twilio.init(ACCOUNT_SID, AUTH_TOKEN)
}
def cleanup() {
Twilio.getExecutorService().shutdown()
Twilio.setExecutorService(null)
Twilio.setRestClient(null)
}
def "synchronous message"() {
setup:
twilioRestClient.getObjectMapper() >> new ObjectMapper()
1 * twilioRestClient.request(_) >> new Response(new ByteArrayInputStream(MESSAGE_RESPONSE_BODY.getBytes()), 200)
Message message = runWithSpan("test") {
Message.creator(
new PhoneNumber("+1 555 720 5913"), // To number
new PhoneNumber("+1 555 555 5215"), // From number
"Hello world!" // SMS body
).create(twilioRestClient)
}
expect:
message.body == "Hello, World!"
assertTraces(1) {
trace(0, 2) {
span(0) {
name "test"
hasNoParent()
attributes {
}
}
span(1) {
name "MessageCreator.create"
kind CLIENT
attributes {
"twilio.type" "com.twilio.rest.api.v2010.account.Message"
"twilio.account" "AC14984e09e497506cf0d5eb59b1f6ace7"
"twilio.sid" "MMXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
"twilio.status" "sent"
}
}
}
}
}
def "synchronous call"() {
setup:
twilioRestClient.getObjectMapper() >> new ObjectMapper()
1 * twilioRestClient.request(_) >> new Response(new ByteArrayInputStream(CALL_RESPONSE_BODY.getBytes()), 200)
Call call = runWithSpan("test") {
Call.creator(
new PhoneNumber("+15558881234"), // To number
new PhoneNumber("+15559994321"), // From number
// Read TwiML at this URL when a call connects (hold music)
new URI("http://twimlets.com/holdmusic?Bucket=com.twilio.music.ambient")
).create(twilioRestClient)
}
expect:
call.status == Call.Status.COMPLETED
assertTraces(1) {
trace(0, 2) {
span(0) {
name "test"
hasNoParent()
attributes {
}
}
span(1) {
name "CallCreator.create"
kind CLIENT
attributes {
"twilio.type" "com.twilio.rest.api.v2010.account.Call"
"twilio.account" "ACXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
"twilio.sid" "CAXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
"twilio.status" "completed"
}
}
}
}
}
def "http client"() {
setup:
CloseableHttpClient httpClient = Mock()
httpClient.execute(_) >> mockResponse(MESSAGE_RESPONSE_BODY, 200)
HttpClientBuilder clientBuilder = Mock()
clientBuilder.build() >> httpClient
NetworkHttpClient networkHttpClient = new NetworkHttpClient(clientBuilder)
TwilioRestClient realTwilioRestClient =
new TwilioRestClient.Builder("username", "password")
.accountSid(ACCOUNT_SID)
.httpClient(networkHttpClient)
.build()
Message message = runWithSpan("test") {
Message.creator(
new PhoneNumber("+1 555 720 5913"), // To number
new PhoneNumber("+1 555 555 5215"), // From number
"Hello world!" // SMS body
).create(realTwilioRestClient)
}
expect:
message.body == "Hello, World!"
assertTraces(1) {
trace(0, 3) {
span(0) {
name "test"
hasNoParent()
attributes {
}
}
span(1) {
name "MessageCreator.create"
kind CLIENT
childOf(span(0))
attributes {
"twilio.type" "com.twilio.rest.api.v2010.account.Message"
"twilio.account" "AC14984e09e497506cf0d5eb59b1f6ace7"
"twilio.sid" "MMXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
"twilio.status" "sent"
}
}
span(2) {
name "POST"
kind CLIENT
childOf span(1)
attributes {
"$NetworkAttributes.NETWORK_PROTOCOL_VERSION" "1.1"
"$ServerAttributes.SERVER_ADDRESS.key" "api.twilio.com"
"$HttpAttributes.HTTP_REQUEST_METHOD.key" "POST"
"$UrlAttributes.URL_FULL.key" "https://api.twilio.com/2010-04-01/Accounts/abc/Messages.json"
"$HttpAttributes.HTTP_RESPONSE_STATUS_CODE.key" 200
}
}
}
}
}
def "http client retry"() {
setup:
CloseableHttpClient httpClient = Mock()
httpClient.execute(_) >>> [
mockResponse(ERROR_RESPONSE_BODY, 500),
mockResponse(MESSAGE_RESPONSE_BODY, 200)
]
HttpClientBuilder clientBuilder = Mock()
clientBuilder.build() >> httpClient
NetworkHttpClient networkHttpClient = new NetworkHttpClient(clientBuilder)
TwilioRestClient realTwilioRestClient =
new TwilioRestClient.Builder("username", "password")
.accountSid(ACCOUNT_SID)
.httpClient(networkHttpClient)
.build()
Message message = runWithSpan("test") {
Message.creator(
new PhoneNumber("+1 555 720 5913"), // To number
new PhoneNumber("+1 555 555 5215"), // From number
"Hello world!" // SMS body
).create(realTwilioRestClient)
}
expect:
message.body == "Hello, World!"
assertTraces(1) {
trace(0, 4) {
span(0) {
name "test"
hasNoParent()
attributes {
}
}
span(1) {
name "MessageCreator.create"
kind CLIENT
childOf(span(0))
attributes {
"twilio.type" "com.twilio.rest.api.v2010.account.Message"
"twilio.account" "AC14984e09e497506cf0d5eb59b1f6ace7"
"twilio.sid" "MMXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
"twilio.status" "sent"
}
}
span(2) {
name "POST"
kind CLIENT
childOf span(1)
status ERROR
attributes {
"$NetworkAttributes.NETWORK_PROTOCOL_VERSION" "1.1"
"$ServerAttributes.SERVER_ADDRESS.key" "api.twilio.com"
"$HttpAttributes.HTTP_REQUEST_METHOD.key" "POST"
"$UrlAttributes.URL_FULL.key" "https://api.twilio.com/2010-04-01/Accounts/abc/Messages.json"
"$HttpAttributes.HTTP_RESPONSE_STATUS_CODE.key" 500
"$ErrorAttributes.ERROR_TYPE" "500"
}
}
span(3) {
name "POST"
kind CLIENT
childOf span(1)
attributes {
"$NetworkAttributes.NETWORK_PROTOCOL_VERSION" "1.1"
"$ServerAttributes.SERVER_ADDRESS.key" "api.twilio.com"
"$HttpAttributes.HTTP_REQUEST_METHOD.key" "POST"
"$UrlAttributes.URL_FULL.key" "https://api.twilio.com/2010-04-01/Accounts/abc/Messages.json"
"$HttpAttributes.HTTP_RESPONSE_STATUS_CODE.key" 200
}
}
}
}
}
def "http client retry async"() {
setup:
CloseableHttpClient httpClient = Mock()
httpClient.execute(_) >>> [
mockResponse(ERROR_RESPONSE_BODY, 500),
mockResponse(MESSAGE_RESPONSE_BODY, 200)
]
HttpClientBuilder clientBuilder = Mock()
clientBuilder.build() >> httpClient
NetworkHttpClient networkHttpClient = new NetworkHttpClient(clientBuilder)
TwilioRestClient realTwilioRestClient =
new TwilioRestClient.Builder("username", "password")
.accountSid(ACCOUNT_SID)
.httpClient(networkHttpClient)
.build()
Message message = runWithSpan("test") {
ListenableFuture<Message> future = Message.creator(
new PhoneNumber("+1 555 720 5913"), // To number
new PhoneNumber("+1 555 555 5215"), // From number
"Hello world!" // SMS body
).createAsync(realTwilioRestClient)
try {
return future.get(10, TimeUnit.SECONDS)
} finally {
// Give the future callback a chance to run
Thread.sleep(1000)
}
}
expect:
message.body == "Hello, World!"
assertTraces(1) {
trace(0, 4) {
span(0) {
name "test"
hasNoParent()
attributes {
}
}
span(1) {
name "MessageCreator.createAsync"
kind CLIENT
childOf(span(0))
attributes {
"twilio.type" "com.twilio.rest.api.v2010.account.Message"
"twilio.account" "AC14984e09e497506cf0d5eb59b1f6ace7"
"twilio.sid" "MMXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
"twilio.status" "sent"
}
}
span(2) {
name "POST"
kind CLIENT
childOf span(1)
status ERROR
attributes {
"$NetworkAttributes.NETWORK_PROTOCOL_VERSION" "1.1"
"$ServerAttributes.SERVER_ADDRESS.key" "api.twilio.com"
"$HttpAttributes.HTTP_REQUEST_METHOD.key" "POST"
"$UrlAttributes.URL_FULL.key" "https://api.twilio.com/2010-04-01/Accounts/abc/Messages.json"
"$HttpAttributes.HTTP_RESPONSE_STATUS_CODE.key" 500
"$ErrorAttributes.ERROR_TYPE" "500"
}
}
span(3) {
name "POST"
kind CLIENT
childOf span(1)
attributes {
"$NetworkAttributes.NETWORK_PROTOCOL_VERSION" "1.1"
"$ServerAttributes.SERVER_ADDRESS.key" "api.twilio.com"
"$HttpAttributes.HTTP_REQUEST_METHOD.key" "POST"
"$UrlAttributes.URL_FULL.key" "https://api.twilio.com/2010-04-01/Accounts/abc/Messages.json"
"$HttpAttributes.HTTP_RESPONSE_STATUS_CODE.key" 200
}
}
}
}
cleanup:
Twilio.getExecutorService().shutdown()
Twilio.setExecutorService(null)
Twilio.setRestClient(null)
}
def "Sync Failure"() {
setup:
twilioRestClient.getObjectMapper() >> new ObjectMapper()
1 * twilioRestClient.request(_) >> new Response(new ByteArrayInputStream(ERROR_RESPONSE_BODY.getBytes()), 500)
when:
runWithSpan("test") {
Message.creator(
new PhoneNumber("+1 555 720 5913"), // To number
new PhoneNumber("+1 555 555 5215"), // From number
"Hello world!" // SMS body
).create(twilioRestClient)
}
then:
thrown(ApiException)
expect:
assertTraces(1) {
trace(0, 2) {
span(0) {
name "test"
status ERROR
errorEvent(ApiException, "Testing Failure")
hasNoParent()
}
span(1) {
name "MessageCreator.create"
kind CLIENT
status ERROR
errorEvent(ApiException, "Testing Failure")
}
}
}
}
def "root span"() {
setup:
twilioRestClient.getObjectMapper() >> new ObjectMapper()
1 * twilioRestClient.request(_) >> new Response(new ByteArrayInputStream(MESSAGE_RESPONSE_BODY.getBytes()), 200)
Message message = Message.creator(
new PhoneNumber("+1 555 720 5913"), // To number
new PhoneNumber("+1 555 555 5215"), // From number
"Hello world!" // SMS body
).create(twilioRestClient)
expect:
message.body == "Hello, World!"
assertTraces(1) {
trace(0, 1) {
span(0) {
name "MessageCreator.create"
kind CLIENT
hasNoParent()
attributes {
"twilio.type" "com.twilio.rest.api.v2010.account.Message"
"twilio.account" "AC14984e09e497506cf0d5eb59b1f6ace7"
"twilio.sid" "MMXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
"twilio.status" "sent"
}
}
}
}
}
def "asynchronous call"(a) {
setup:
twilioRestClient.getObjectMapper() >> new ObjectMapper()
1 * twilioRestClient.request(_) >> new Response(new ByteArrayInputStream(MESSAGE_RESPONSE_BODY.getBytes()), 200)
when:
Message message = runWithSpan("test") {
ListenableFuture<Message> future = Message.creator(
new PhoneNumber("+1 555 720 5913"), // To number
new PhoneNumber("+1 555 555 5215"), // From number
"Hello world!" // SMS body
).createAsync(twilioRestClient)
try {
return future.get(10, TimeUnit.SECONDS)
} finally {
// Give the future callback a chance to run
Thread.sleep(1000)
}
}
then:
message != null
message.body == "Hello, World!"
assertTraces(1) {
trace(0, 2) {
span(0) {
name "test"
hasNoParent()
attributes {
}
}
span(1) {
name "MessageCreator.createAsync"
kind CLIENT
attributes {
"twilio.type" "com.twilio.rest.api.v2010.account.Message"
"twilio.account" "AC14984e09e497506cf0d5eb59b1f6ace7"
"twilio.sid" "MMXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
"twilio.status" "sent"
}
}
}
}
cleanup:
Twilio.getExecutorService().shutdown()
Twilio.setExecutorService(null)
Twilio.setRestClient(null)
where:
a | _
1 | _
2 | _
}
def "asynchronous error"() {
setup:
twilioRestClient.getObjectMapper() >> new ObjectMapper()
1 * twilioRestClient.request(_) >> new Response(new ByteArrayInputStream(ERROR_RESPONSE_BODY.getBytes()), 500)
when:
runWithSpan("test") {
ListenableFuture<Message> future = Message.creator(
new PhoneNumber("+1 555 720 5913"), // To number
new PhoneNumber("+1 555 555 5215"), // From number
"Hello world!" // SMS body
).createAsync(twilioRestClient)
try {
return future.get(10, TimeUnit.SECONDS)
} finally {
Thread.sleep(1000)
}
}
then:
thrown(ExecutionException)
expect:
assertTraces(1) {
trace(0, 2) {
span(0) {
name "test"
status ERROR
errorEvent(ApiException, "Testing Failure")
hasNoParent()
}
span(1) {
name "MessageCreator.createAsync"
kind CLIENT
status ERROR
errorEvent(ApiException, "Testing Failure")
}
}
}
}
private CloseableHttpResponse mockResponse(String body, int statusCode) {
HttpEntity httpEntity = Mock()
httpEntity.getContent() >> { new ByteArrayInputStream(body.getBytes()) }
httpEntity.isRepeatable() >> true
httpEntity.getContentLength() >> body.length()
StatusLine statusLine = Mock()
statusLine.getStatusCode() >> statusCode
CloseableHttpResponse httpResponse = Mock()
httpResponse.getEntity() >> httpEntity
httpResponse.getStatusLine() >> statusLine
return httpResponse
}
}

View File

@ -0,0 +1,551 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.javaagent.instrumentation.twilio;
import static io.opentelemetry.api.common.AttributeKey.stringKey;
import static io.opentelemetry.api.trace.SpanKind.CLIENT;
import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.equalTo;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.when;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.util.concurrent.ListenableFuture;
import com.twilio.Twilio;
import com.twilio.exception.ApiException;
import com.twilio.http.NetworkHttpClient;
import com.twilio.http.Response;
import com.twilio.http.TwilioRestClient;
import com.twilio.rest.api.v2010.account.Call;
import com.twilio.rest.api.v2010.account.Message;
import com.twilio.type.PhoneNumber;
import io.opentelemetry.api.common.Attributes;
import io.opentelemetry.instrumentation.testing.junit.AgentInstrumentationExtension;
import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension;
import io.opentelemetry.sdk.trace.data.StatusData;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import org.apache.http.HttpEntity;
import org.apache.http.StatusLine;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.jetbrains.annotations.NotNull;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.junit.jupiter.api.extension.RegisterExtension;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
@ExtendWith(MockitoExtension.class)
class TwilioClientTest {
@RegisterExtension
private static final InstrumentationExtension testing = AgentInstrumentationExtension.create();
private static final String ACCOUNT_SID = "abc";
private static final String AUTH_TOKEN = "efg";
private static final String MESSAGE_RESPONSE_BODY =
" {\n"
+ " \"account_sid\": \"AC14984e09e497506cf0d5eb59b1f6ace7\",\n"
+ " \"api_version\": \"2010-04-01\",\n"
+ " \"body\": \"Hello, World!\",\n"
+ " \"date_created\": \"Thu, 30 Jul 2015 20:12:31 +0000\",\n"
+ " \"date_sent\": \"Thu, 30 Jul 2015 20:12:33 +0000\",\n"
+ " \"date_updated\": \"Thu, 30 Jul 2015 20:12:33 +0000\",\n"
+ " \"direction\": \"outbound-api\",\n"
+ " \"from\": \"+14155552345\",\n"
+ " \"messaging_service_sid\": \"MGXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX\",\n"
+ " \"num_media\": \"0\",\n"
+ " \"num_segments\": \"1\",\n"
+ " \"price\": -0.00750,\n"
+ " \"price_unit\": \"USD\",\n"
+ " \"sid\": \"MMXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX\",\n"
+ " \"status\": \"sent\",\n"
+ " \"subresource_uris\": {\n"
+ " \"media\": \"/2010-04-01/Accounts/AC14984e09e497506cf0d5eb59b1f6ace7/Messages/SMXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX/Media.json\"\n"
+ " },\n"
+ " \"to\": \"+14155552345\",\n"
+ " \"uri\": \"/2010-04-01/Accounts/AC14984e09e497506cf0d5eb59b1f6ace7/Messages/SMXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX.json\"\n"
+ " }";
private static final String CALL_RESPONSE_BODY =
" {\n"
+ " \"account_sid\": \"ACXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX\",\n"
+ " \"annotation\": null,\n"
+ " \"answered_by\": null,\n"
+ " \"api_version\": \"2010-04-01\",\n"
+ " \"caller_name\": null,\n"
+ " \"date_created\": \"Tue, 31 Aug 2010 20:36:28 +0000\",\n"
+ " \"date_updated\": \"Tue, 31 Aug 2010 20:36:44 +0000\",\n"
+ " \"direction\": \"inbound\",\n"
+ " \"duration\": \"15\",\n"
+ " \"end_time\": \"Tue, 31 Aug 2010 20:36:44 +0000\",\n"
+ " \"forwarded_from\": \"+141586753093\",\n"
+ " \"from\": \"+15017122661\",\n"
+ " \"from_formatted\": \"(501) 712-2661\",\n"
+ " \"group_sid\": null,\n"
+ " \"parent_call_sid\": null,\n"
+ " \"phone_number_sid\": \"PNXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX\",\n"
+ " \"price\": -0.03000,\n"
+ " \"price_unit\": \"USD\",\n"
+ " \"sid\": \"CAXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX\",\n"
+ " \"start_time\": \"Tue, 31 Aug 2010 20:36:29 +0000\",\n"
+ " \"status\": \"completed\",\n"
+ " \"subresource_uris\": {\n"
+ " \"notifications\": \"/2010-04-01/Accounts/ACXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX/Calls/CAXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX/Notifications.json\",\n"
+ " \"recordings\": \"/2010-04-01/Accounts/ACXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX/Calls/CAXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX/Recordings.json\",\n"
+ " \"feedback\": \"/2010-04-01/Accounts/ACXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX/Calls/CAXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX/Feedback.json\",\n"
+ " \"feedback_summaries\": \"/2010-04-01/Accounts/ACXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX/Calls/FeedbackSummary.json\"\n"
+ " },\n"
+ " \"to\": \"+15558675310\",\n"
+ " \"to_formatted\": \"(555) 867-5310\",\n"
+ " \"uri\": \"/2010-04-01/Accounts/ACXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX/Calls/CAXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX.json\"\n"
+ " }";
private static final String ERROR_RESPONSE_BODY =
"{\n"
+ " \"code\": 123,\n"
+ " \"message\": \"Testing Failure\",\n"
+ " \"code\": 567,\n"
+ " \"more_info\": \"Testing\"\n"
+ " }";
@Mock private TwilioRestClient twilioRestClient;
@Mock private CloseableHttpClient httpClient;
@BeforeAll
static void setUp() {
Twilio.init(ACCOUNT_SID, AUTH_TOKEN);
}
@AfterAll
static void tearDown() {
Twilio.getExecutorService().shutdown();
Twilio.setExecutorService(null);
Twilio.setRestClient(null);
}
@Test
void synchronousMessage() {
when(twilioRestClient.getObjectMapper()).thenReturn(new ObjectMapper());
when(twilioRestClient.request(any()))
.thenReturn(
new Response(
new ByteArrayInputStream(MESSAGE_RESPONSE_BODY.getBytes(StandardCharsets.UTF_8)),
200));
Message message =
testing.runWithSpan(
"test",
() ->
Message.creator(
new PhoneNumber("+1 555 720 5913"),
new PhoneNumber("+1 555 555 5215"),
"Hello world!")
.create(twilioRestClient));
assertThat(message.getBody()).isEqualTo("Hello, World!");
testing.waitAndAssertTraces(
trace ->
trace.hasSpansSatisfyingExactly(
span -> span.hasName("test").hasNoParent().hasAttributes(Attributes.empty()),
span ->
span.hasName("MessageCreator.create")
.hasKind(CLIENT)
.hasParent(trace.getSpan(0))
.hasAttributesSatisfyingExactly(
equalTo(
stringKey("twilio.type"),
"com.twilio.rest.api.v2010.account.Message"),
equalTo(
stringKey("twilio.account"), "AC14984e09e497506cf0d5eb59b1f6ace7"),
equalTo(stringKey("twilio.sid"), "MMXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"),
equalTo(stringKey("twilio.status"), "sent"))));
}
@Test
void synchronousCall() throws URISyntaxException {
when(twilioRestClient.getObjectMapper()).thenReturn(new ObjectMapper());
when(twilioRestClient.request(any()))
.thenReturn(
new Response(
new ByteArrayInputStream(CALL_RESPONSE_BODY.getBytes(StandardCharsets.UTF_8)),
200));
Call call =
testing.runWithSpan(
"test",
() ->
Call.creator(
new PhoneNumber("+15558881234"),
new PhoneNumber("+15559994321"),
new URI("http://twimlets.com/holdmusic?Bucket=com.twilio.music.ambient"))
.create(twilioRestClient));
assertThat(call.getStatus()).isEqualTo(Call.Status.COMPLETED);
testing.waitAndAssertTraces(
trace ->
trace.hasSpansSatisfyingExactly(
span -> span.hasName("test").hasNoParent().hasAttributes(Attributes.empty()),
span ->
span.hasName("CallCreator.create")
.hasKind(CLIENT)
.hasParent(trace.getSpan(0))
.hasAttributesSatisfyingExactly(
equalTo(
stringKey("twilio.type"), "com.twilio.rest.api.v2010.account.Call"),
equalTo(
stringKey("twilio.account"), "ACXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"),
equalTo(stringKey("twilio.sid"), "CAXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"),
equalTo(stringKey("twilio.status"), "completed"))));
}
@Test
void httpClient() throws IOException {
CloseableHttpResponse response = mockResponse(MESSAGE_RESPONSE_BODY, 200);
when(httpClient.execute(any())).thenReturn(response);
HttpClientBuilder clientBuilder = getHttpClientBuilder(httpClient);
NetworkHttpClient networkHttpClient = new NetworkHttpClient(clientBuilder);
TwilioRestClient realTwilioRestClient =
new TwilioRestClient.Builder("username", "password")
.accountSid(ACCOUNT_SID)
.httpClient(networkHttpClient)
.build();
Message message =
testing.runWithSpan(
"test",
() ->
Message.creator(
new PhoneNumber("+1 555 720 5913"),
new PhoneNumber("+1 555 555 5215"),
"Hello world!")
.create(realTwilioRestClient));
assertThat(message.getBody()).isEqualTo("Hello, World!");
testing.waitAndAssertTraces(
trace ->
trace.hasSpansSatisfyingExactly(
span -> span.hasName("test").hasNoParent().hasAttributes(Attributes.empty()),
span ->
span.hasName("MessageCreator.create")
.hasKind(CLIENT)
.hasParent(trace.getSpan(0))
.hasAttributesSatisfyingExactly(
equalTo(
stringKey("twilio.type"),
"com.twilio.rest.api.v2010.account.Message"),
equalTo(
stringKey("twilio.account"), "AC14984e09e497506cf0d5eb59b1f6ace7"),
equalTo(stringKey("twilio.sid"), "MMXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"),
equalTo(stringKey("twilio.status"), "sent"))));
}
@SuppressWarnings("CannotMockMethod")
private static @NotNull HttpClientBuilder getHttpClientBuilder(CloseableHttpClient httpClient) {
HttpClientBuilder clientBuilder = spy(HttpClientBuilder.create());
when(clientBuilder.build()).thenReturn(httpClient);
return clientBuilder;
}
@Test
void httpClientRetry() throws IOException {
CloseableHttpResponse response1 = mockResponse(ERROR_RESPONSE_BODY, 500);
CloseableHttpResponse response2 = mockResponse(MESSAGE_RESPONSE_BODY, 200);
when(httpClient.execute(any())).thenReturn(response1, response2);
HttpClientBuilder clientBuilder = getHttpClientBuilder(httpClient);
NetworkHttpClient networkHttpClient = new NetworkHttpClient(clientBuilder);
TwilioRestClient realTwilioRestClient =
new TwilioRestClient.Builder("username", "password")
.accountSid(ACCOUNT_SID)
.httpClient(networkHttpClient)
.build();
Message message =
testing.runWithSpan(
"test",
() ->
Message.creator(
new PhoneNumber("+1 555 720 5913"),
new PhoneNumber("+1 555 555 5215"),
"Hello world!")
.create(realTwilioRestClient));
assertThat(message.getBody()).isEqualTo("Hello, World!");
testing.waitAndAssertTraces(
trace ->
trace.hasSpansSatisfyingExactly(
span -> span.hasName("test").hasNoParent().hasAttributes(Attributes.empty()),
span ->
span.hasName("MessageCreator.create")
.hasParent(trace.getSpan(0))
.hasKind(CLIENT)
.hasAttributesSatisfyingExactly(
equalTo(
stringKey("twilio.type"),
"com.twilio.rest.api.v2010.account.Message"),
equalTo(
stringKey("twilio.account"), "AC14984e09e497506cf0d5eb59b1f6ace7"),
equalTo(stringKey("twilio.sid"), "MMXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"),
equalTo(stringKey("twilio.status"), "sent"))));
}
@Test
void httpClientRetryAsync() throws Exception {
CloseableHttpResponse response1 = mockResponse(ERROR_RESPONSE_BODY, 500);
CloseableHttpResponse response2 = mockResponse(MESSAGE_RESPONSE_BODY, 200);
when(httpClient.execute(any())).thenReturn(response1, response2);
HttpClientBuilder clientBuilder = getHttpClientBuilder(httpClient);
NetworkHttpClient networkHttpClient = new NetworkHttpClient(clientBuilder);
TwilioRestClient realTwilioRestClient =
new TwilioRestClient.Builder("username", "password")
.accountSid(ACCOUNT_SID)
.httpClient(networkHttpClient)
.build();
Message message =
testing.runWithSpan(
"test",
() -> {
ListenableFuture<Message> future =
Message.creator(
new PhoneNumber("+1 555 720 5913"),
new PhoneNumber("+1 555 555 5215"),
"Hello world!")
.createAsync(realTwilioRestClient);
try {
return future.get(10, TimeUnit.SECONDS);
} finally {
Thread.sleep(1000);
}
});
assertThat(message.getBody()).isEqualTo("Hello, World!");
testing.waitAndAssertTraces(
trace ->
trace.hasSpansSatisfyingExactly(
span -> span.hasName("test").hasNoParent().hasAttributes(Attributes.empty()),
span ->
span.hasName("MessageCreator.createAsync")
.hasKind(CLIENT)
.hasParent(trace.getSpan(0))
.hasAttributesSatisfyingExactly(
equalTo(
stringKey("twilio.type"),
"com.twilio.rest.api.v2010.account.Message"),
equalTo(
stringKey("twilio.account"), "AC14984e09e497506cf0d5eb59b1f6ace7"),
equalTo(stringKey("twilio.sid"), "MMXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"),
equalTo(stringKey("twilio.status"), "sent"))));
}
@Test
void syncFailure() {
when(twilioRestClient.getObjectMapper()).thenReturn(new ObjectMapper());
when(twilioRestClient.request(any()))
.thenReturn(
new Response(
new ByteArrayInputStream(ERROR_RESPONSE_BODY.getBytes(StandardCharsets.UTF_8)),
500));
assertThatThrownBy(
() ->
testing.runWithSpan(
"test",
() ->
Message.creator(
new PhoneNumber("+1 555 720 5913"),
new PhoneNumber("+1 555 555 5215"),
"Hello world!")
.create(twilioRestClient)))
.isInstanceOf(ApiException.class);
testing.waitAndAssertTraces(
trace ->
trace.hasSpansSatisfyingExactly(
span ->
span.hasName("test")
.hasNoParent()
.hasStatus(StatusData.error())
.hasException(new ApiException("Testing Failure")),
span ->
span.hasName("MessageCreator.create")
.hasKind(CLIENT)
.hasParent(trace.getSpan(0))
.hasStatus(StatusData.error())
.hasException(new ApiException("Testing Failure"))));
}
@Test
void rootSpan() {
when(twilioRestClient.getObjectMapper()).thenReturn(new ObjectMapper());
when(twilioRestClient.request(any()))
.thenReturn(
new Response(
new ByteArrayInputStream(MESSAGE_RESPONSE_BODY.getBytes(StandardCharsets.UTF_8)),
200));
Message message =
Message.creator(
new PhoneNumber("+1 555 720 5913"),
new PhoneNumber("+1 555 555 5215"),
"Hello world!")
.create(twilioRestClient);
assertThat(message.getBody()).isEqualTo("Hello, World!");
testing.waitAndAssertTraces(
trace ->
trace.hasSpansSatisfyingExactly(
span ->
span.hasName("MessageCreator.create")
.hasKind(CLIENT)
.hasNoParent()
.hasAttributesSatisfyingExactly(
equalTo(
stringKey("twilio.type"),
"com.twilio.rest.api.v2010.account.Message"),
equalTo(
stringKey("twilio.account"), "AC14984e09e497506cf0d5eb59b1f6ace7"),
equalTo(stringKey("twilio.sid"), "MMXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"),
equalTo(stringKey("twilio.status"), "sent"))));
}
@Test
void asynchronousCall() throws Exception {
when(twilioRestClient.getObjectMapper()).thenReturn(new ObjectMapper());
when(twilioRestClient.request(any()))
.thenReturn(
new Response(
new ByteArrayInputStream(MESSAGE_RESPONSE_BODY.getBytes(StandardCharsets.UTF_8)),
200));
Message message =
testing.runWithSpan(
"test",
() -> {
ListenableFuture<Message> future =
Message.creator(
new PhoneNumber("+1 555 720 5913"),
new PhoneNumber("+1 555 555 5215"),
"Hello world!")
.createAsync(twilioRestClient);
try {
return future.get(10, TimeUnit.SECONDS);
} finally {
Thread.sleep(1000);
}
});
assertThat(message).isNotNull();
assertThat(message.getBody()).isEqualTo("Hello, World!");
testing.waitAndAssertTraces(
trace ->
trace.hasSpansSatisfyingExactly(
span -> span.hasName("test").hasNoParent().hasAttributes(Attributes.empty()),
span ->
span.hasName("MessageCreator.createAsync")
.hasKind(CLIENT)
.hasParent(trace.getSpan(0))
.hasAttributesSatisfyingExactly(
equalTo(
stringKey("twilio.type"),
"com.twilio.rest.api.v2010.account.Message"),
equalTo(
stringKey("twilio.account"), "AC14984e09e497506cf0d5eb59b1f6ace7"),
equalTo(stringKey("twilio.sid"), "MMXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"),
equalTo(stringKey("twilio.status"), "sent"))));
}
@Test
void asynchronousError() {
when(twilioRestClient.getObjectMapper()).thenReturn(new ObjectMapper());
when(twilioRestClient.request(any()))
.thenReturn(
new Response(
new ByteArrayInputStream(ERROR_RESPONSE_BODY.getBytes(StandardCharsets.UTF_8)),
500));
assertThatThrownBy(
() ->
testing.runWithSpan(
"test",
() -> {
ListenableFuture<Message> future =
Message.creator(
new PhoneNumber("+1 555 720 5913"),
new PhoneNumber("+1 555 555 5215"),
"Hello world!")
.createAsync(twilioRestClient);
try {
return future.get(10, TimeUnit.SECONDS);
} finally {
Thread.sleep(1000);
}
}))
.isInstanceOf(ExecutionException.class);
testing.waitAndAssertTraces(
trace ->
trace.hasSpansSatisfyingExactly(
span ->
span.hasName("test")
.hasNoParent()
.hasStatus(StatusData.error())
.hasException(new ApiException("Testing Failure")),
span ->
span.hasName("MessageCreator.createAsync")
.hasKind(CLIENT)
.hasParent(trace.getSpan(0))
.hasStatus(StatusData.error())
.hasException(new ApiException("Testing Failure"))));
}
private static CloseableHttpResponse mockResponse(String body, int statusCode)
throws IOException {
HttpEntity httpEntity = mock(HttpEntity.class);
when(httpEntity.getContent())
.thenReturn(new ByteArrayInputStream(body.getBytes(StandardCharsets.UTF_8)));
when(httpEntity.isRepeatable()).thenReturn(true);
StatusLine statusLine = mock(StatusLine.class);
when(statusLine.getStatusCode()).thenReturn(statusCode);
CloseableHttpResponse httpResponse = mock(CloseableHttpResponse.class);
when(httpResponse.getEntity()).thenReturn(httpEntity);
when(httpResponse.getStatusLine()).thenReturn(statusLine);
return httpResponse;
}
}