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:
parent
e210c0ea02
commit
9e70f93d35
|
@ -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
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue