Skip to content

Commit

Permalink
feat: Conversion API
Browse files Browse the repository at this point in the history
  • Loading branch information
SMadani committed Jul 5, 2024
1 parent 7068d33 commit 816fd11
Show file tree
Hide file tree
Showing 8 changed files with 105 additions and 50 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/).

### Added
- SMS API
- Conversion API
- Redact API

### Removed
- `parseInboundMessage`
Expand Down
20 changes: 20 additions & 0 deletions src/main/kotlin/com/vonage/client/kt/Conversion.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package com.vonage.client.kt

import com.vonage.client.conversion.*
import java.time.Instant
import java.util.*

class Conversion(private val conversionClient: ConversionClient) {

private fun convert(type: ConversionRequest.Type, messageId: String, delivered: Boolean,
timestamp: Instant? = null) =
conversionClient.submitConversion(type, messageId, delivered,
if (timestamp == null) null else Date.from(timestamp)
)

fun convertSms(messageId: String, delivered: Boolean, timestamp: Instant = Instant.now()) =
convert(ConversionRequest.Type.SMS, messageId, delivered, timestamp)

fun convertVoice(callId: String, delivered: Boolean, timestamp: Instant = Instant.now()) =
convert(ConversionRequest.Type.VOICE, callId, delivered, timestamp)
}
2 changes: 2 additions & 0 deletions src/main/kotlin/com/vonage/client/kt/Vonage.kt
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ class Vonage(init: VonageClient.Builder.() -> Unit) {
val verify = Verify(vonageClient.verify2Client)
val voice = Voice(vonageClient.voiceClient)
val sms = Sms(vonageClient.smsClient)
val conversion = Conversion(vonageClient.conversionClient)
val redact = Redact(vonageClient.redactClient)
}

fun VonageClient.Builder.authFromEnv(): VonageClient.Builder {
Expand Down
81 changes: 43 additions & 38 deletions src/test/kotlin/com/vonage/client/kt/AbstractTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package com.vonage.client.kt
import com.fasterxml.jackson.databind.ObjectMapper
import com.github.tomakehurst.wiremock.WireMockServer
import com.github.tomakehurst.wiremock.client.WireMock
import com.github.tomakehurst.wiremock.client.WireMock.*
import com.github.tomakehurst.wiremock.common.ConsoleNotifier
import com.github.tomakehurst.wiremock.core.WireMockConfiguration.options
import com.marcinziolo.kotlin.wiremock.*
Expand All @@ -14,6 +15,7 @@ import org.junit.jupiter.api.assertThrows
import java.net.URI
import java.net.URLEncoder
import java.nio.charset.StandardCharsets
import java.time.Instant
import java.util.*
import kotlin.test.assertEquals

Expand All @@ -24,16 +26,27 @@ abstract class AbstractTest {
private val signatureSecret = "abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQR"
private val apiKeySecretEncoded = "YTFiMmMzZDQ6MTIzNDU2Nzg5MGFiY2RlZg=="
private val privateKeyPath = "src/test/resources/com/vonage/client/kt/application_key"
protected val apiSecretName = "api_secret"
protected val apiKeyName = "api_key"
protected val signatureSecretName = "sig"
protected val testUuidStr = "aaaaaaaa-bbbb-4ccc-8ddd-0123456789ab"
protected val testUuid = UUID.fromString(testUuidStr)
protected val toNumber = "447712345689"
protected val altNumber = "447700900001"
protected val text = "Hello, World!"
protected val textHexEncoded = "48656c6c6f2c20576f726c6421"
protected val smsMessageId = "0C000000217B7F02"
protected val callIdStr = "63f61863-4a51-4f6b-86e1-46edebcf9356"
protected val networkCode = "65512"
protected val startTime = "2020-09-17T12:34:56Z"
protected val endTime = "2021-09-17T12:35:28Z"
protected val timestamp = "2016-11-14T07:45:14Z"
protected val startTimeStr = "2020-09-17T12:34:56Z"
protected val startTime = Instant.parse(startTimeStr)
protected val endTimeStr = "2021-09-17T12:35:28Z"
protected val endTime = Instant.parse(endTimeStr)
protected val timestampStr = "2016-11-14T07:45:14Z"
protected val timestampDateStr = "2016-11-14 07:45:14"
protected val timestamp = Instant.parse(timestampStr)
protected val timestamp2Str = "2020-01-29T14:08:30.201Z"
protected val timestamp2 = Instant.parse(timestamp2Str)

private val port = 8081
val wiremock: WireMockServer = WireMockServer(
Expand Down Expand Up @@ -73,13 +86,34 @@ abstract class AbstractTest {
JWT, API_KEY_SECRET_HEADER, API_KEY_SECRET_QUERY_PARAMS, API_KEY_SIGNATURE_SECRET
}

private fun HttpMethod.toWireMockMethod(): Method = when (this) {
HttpMethod.GET -> WireMock::get
HttpMethod.POST -> WireMock::post
HttpMethod.PUT -> WireMock::put
HttpMethod.PATCH -> WireMock::patch
HttpMethod.DELETE -> WireMock::delete
else -> throw IllegalArgumentException("Unhandled HTTP method: $this")
}

protected fun Map<String, Any>.toFormEncodedString(): String {
val utf8 = StandardCharsets.UTF_8.toString()
return entries.joinToString("&") { (key, value) ->
"${URLEncoder.encode(key, utf8)}=${URLEncoder.encode(value.toString(), utf8)}"
}
}

protected fun mockPostQueryParams(expectedUrl: String, expectedRequestParams: Map<String, Any>,
status: Int = 200, expectedResponseParams: Map<String, Any>? = null) {
val stub = post(urlPathEqualTo(expectedUrl))
expectedRequestParams.forEach {(k, v) -> stub.withFormParam(k, equalTo(v.toString()))}
val response = aResponse().withStatus(status)
if (expectedResponseParams != null) {
response.withBody(expectedResponseParams.toFormEncodedString())
}
stub.willReturn(response)
wiremock.stubFor(stub)
}

protected fun mockRequest(
httpMethod: HttpMethod,
expectedUrl: String,
Expand All @@ -96,13 +130,9 @@ abstract class AbstractTest {
if (accept != null) {
headers contains "Accept" equalTo accept.mime
}
val formEncodedParams = if (
contentType == ContentType.FORM_URLENCODED && httpMethod != HttpMethod.GET
) mutableMapOf<String, Any>() else null

if (authType != null) {
val authHeaderName = "Authorization"
val apiKeyName = "api_key"
when (authType) {
AuthType.JWT -> headers contains authHeaderName like
"Bearer eyJ0eXBlIjoiSldUIiwiYWxnIjoiUlMyNTYifQ(\\..+){2}"
Expand All @@ -111,50 +141,25 @@ abstract class AbstractTest {
headers contains authHeaderName equalTo "Basic $apiKeySecretEncoded"

AuthType.API_KEY_SECRET_QUERY_PARAMS -> {
val apiSecretName = "api_secret"
if (formEncodedParams != null) {
formEncodedParams[apiKeyName] = apiKey
formEncodedParams[apiSecretName] = apiSecret
}
else {
queryParams contains apiKeyName equalTo apiKey
queryParams contains apiSecretName equalTo apiSecret
}
queryParams contains apiKeyName equalTo apiKey
queryParams contains apiSecretName equalTo apiSecret
}

AuthType.API_KEY_SIGNATURE_SECRET -> {
val signatureName = "sig"
if (formEncodedParams != null) {
formEncodedParams[apiKeyName] = apiKey
formEncodedParams[signatureName] = signatureSecret
}
else {
queryParams contains apiKeyName equalTo apiKey
queryParams contains signatureName equalTo signatureSecret
}
queryParams contains apiKeyName equalTo apiKey
queryParams contains signatureSecretName equalTo signatureSecret
}
}
}
if (expectedParams != null) when (contentType) {
ContentType.APPLICATION_JSON -> {
body equalTo ObjectMapper().writeValueAsString(expectedParams)
}
ContentType.FORM_URLENCODED -> {
formEncodedParams?.putAll(expectedParams)
}
else -> {
expectedParams.forEach { (k, v) -> queryParams contains k equalTo v.toString() }
expectedParams.forEach {(k, v) -> queryParams contains k equalTo v.toString()}
}
}
// TODO: assertion on the body once it's supported by the WireMock DSL
}, when (httpMethod) {
HttpMethod.GET -> WireMock::get
HttpMethod.POST -> WireMock::post
HttpMethod.PUT -> WireMock::put
HttpMethod.PATCH -> WireMock::patch
HttpMethod.DELETE -> WireMock::delete
else -> throw IllegalArgumentException("Unhandled HTTP method: $httpMethod")
})
}, httpMethod.toWireMockMethod())

private fun mockP(requestMethod: HttpMethod, expectedUrl: String,
expectedRequestParams: Map<String, Any>? = null,
Expand Down
29 changes: 29 additions & 0 deletions src/test/kotlin/com/vonage/client/kt/ConversionTest.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package com.vonage.client.kt

import kotlin.test.*

class ConversionTest : AbstractTest() {
private val conversionClient = vonage.conversion

private fun mockSuccess(id: String, endpoint: String, delivered: Boolean) {
mockPostQueryParams("/conversions/$endpoint", mapOf(
"message-id" to id,
"delivered" to delivered,
"timestamp" to timestampDateStr
))
}

@Test
fun `submit sms conversion`() {
val delivered = true
mockSuccess(smsMessageId, "sms", delivered)
conversionClient.convertSms(smsMessageId, delivered, timestamp)
}

@Test
fun `submit voice conversion`() {
val delivered = false
mockSuccess(callIdStr, "voice", delivered)
conversionClient.convertVoice(callIdStr, delivered, timestamp)
}
}
5 changes: 2 additions & 3 deletions src/test/kotlin/com/vonage/client/kt/MessagesTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -417,7 +417,6 @@ class MessagesTest : AbstractTest() {

@Test
fun `parse inbound MMS image`() {
val timestampStr = "2020-01-29T14:08:30.201Z"
val networkCode = "54123"
val parsed = InboundMessage.fromJson(
"""
Expand All @@ -426,7 +425,7 @@ class MessagesTest : AbstractTest() {
"message_uuid": "$messageUuid",
"to": "$toNumber",
"from": "$altNumber",
"timestamp": "$timestampStr",
"timestamp": "$timestamp2Str",
"origin": {
"network_code": "$networkCode"
},
Expand All @@ -444,7 +443,7 @@ class MessagesTest : AbstractTest() {
assertEquals(messageUuid, parsed.messageUuid)
assertEquals(toNumber, parsed.to)
assertEquals(altNumber, parsed.from)
assertEquals(Instant.parse(timestampStr), parsed.timestamp)
assertEquals(timestamp2, parsed.timestamp)
assertEquals(networkCode, parsed.networkCode)
assertEquals(MessageType.IMAGE, parsed.messageType)
assertEquals(URI.create(imageUrl), parsed.imageUrl)
Expand Down
5 changes: 2 additions & 3 deletions src/test/kotlin/com/vonage/client/kt/SmsTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@ class SmsTest : AbstractTest() {
private fun testSuccessSingleMessage(requestParams: Map<String, Any>,
invocation: () -> List<SmsSubmissionResponseMessage>) {

val messageId = "0C000000217B7F02"
val remainingBalance = "15.53590000"
val messagePrice = "0.03330000"
val network = "23410"
Expand All @@ -36,7 +35,7 @@ class SmsTest : AbstractTest() {
"messages" to listOf(
mapOf(
"to" to toNumber,
"message-id" to messageId,
"message-id" to smsMessageId,
"status" to "0",
"remaining-balance" to remainingBalance,
"message-price" to messagePrice,
Expand All @@ -54,7 +53,7 @@ class SmsTest : AbstractTest() {
val first = response.first()
assertNotNull(first)
assertEquals(toNumber, first.to)
assertEquals(messageId, first.id)
assertEquals(smsMessageId, first.id)
assertEquals(OK, first.status)
assertEquals(BigDecimal(remainingBalance), first.remainingBalance)
assertEquals(BigDecimal(messagePrice), first.messagePrice)
Expand Down
11 changes: 5 additions & 6 deletions src/test/kotlin/com/vonage/client/kt/VoiceTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ import kotlin.test.*
class VoiceTest : AbstractTest() {
private val voiceClient = vonage.voice
private val callsBaseUrl = "/v1/calls"
private val callIdStr = "63f61863-4a51-4f6b-86e1-46edebcf9356"
private val callUrl = "$callsBaseUrl/$callIdStr"
private val callObj = voiceClient.call(UUID.fromString(callIdStr))
private val conversationId = "CON-f972836a-550f-45fa-956c-12a2ab5b7d22"
Expand Down Expand Up @@ -64,8 +63,8 @@ class VoiceTest : AbstractTest() {
"rate" to rate,
"price" to price,
"duration" to duration,
"start_time" to startTime,
"end_time" to endTime,
"start_time" to startTimeStr,
"end_time" to endTimeStr,
"network" to networkCode
)
private val listCallsResponse = mapOf(
Expand Down Expand Up @@ -98,8 +97,8 @@ class VoiceTest : AbstractTest() {
assertEquals(rate, callInfo.rate)
assertEquals(price, callInfo.price)
assertEquals(duration, callInfo.duration)
assertEquals(Instant.parse(startTime), callInfo.startTime.toInstant())
assertEquals(Instant.parse(endTime), callInfo.endTime.toInstant())
assertEquals(startTime, callInfo.startTime.toInstant())
assertEquals(endTime, callInfo.endTime.toInstant())
assertEquals(networkCode, callInfo.network)
}

Expand Down Expand Up @@ -371,7 +370,7 @@ class VoiceTest : AbstractTest() {

val callsPage = voiceClient.listCalls {
status(CallStatus.UNANSWERED)
dateStart(startTime); dateEnd(endTime)
dateStart(startTimeStr); dateEnd(endTimeStr)
pageSize(pageSize); recordIndex(recordIndex)
order(CallOrder.DESCENDING); conversationUuid(conversationId)
}
Expand Down

0 comments on commit 816fd11

Please sign in to comment.