From 41ef916b8be807302cd948e3005c2063f1282915 Mon Sep 17 00:00:00 2001 From: Sina Madani Date: Thu, 27 Jun 2024 12:42:34 +0100 Subject: [PATCH] feat: Voice.listCalls --- src/main/kotlin/com/vonage/client/kt/Voice.kt | 18 +- .../kotlin/com/vonage/client/kt/Vonage.kt | 9 +- .../com/vonage/client/kt/AbstractTest.kt | 27 +-- .../kotlin/com/vonage/client/kt/VoiceTest.kt | 169 ++++++++++++------ 4 files changed, 142 insertions(+), 81 deletions(-) diff --git a/src/main/kotlin/com/vonage/client/kt/Voice.kt b/src/main/kotlin/com/vonage/client/kt/Voice.kt index 027b04f..f466b98 100644 --- a/src/main/kotlin/com/vonage/client/kt/Voice.kt +++ b/src/main/kotlin/com/vonage/client/kt/Voice.kt @@ -1,11 +1,9 @@ package com.vonage.client.kt -import com.vonage.client.voice.CallInfo -import com.vonage.client.voice.CallInfoPage -import com.vonage.client.voice.CallsFilter -import com.vonage.client.voice.VoiceClient +import com.vonage.client.voice.* import com.vonage.client.voice.ncco.Ncco import java.net.URI +import java.time.Instant import java.util.* class Voice(private val voiceClient: VoiceClient) { @@ -35,5 +33,13 @@ class Voice(private val voiceClient: VoiceClient) { fun transfer(nccoUrl: URI) = transfer(nccoUrl.toString()) } - fun listCalls(filter: CallsFilter? = null): CallInfoPage = voiceClient.listCalls(filter) -} \ No newline at end of file + fun listCalls(filter: (CallsFilter.Builder.() -> Unit)? = null): CallInfoPage = + if (filter == null) voiceClient.listCalls() + else voiceClient.listCalls(CallsFilter.builder().apply(filter).build()) +} + +fun CallsFilter.Builder.dateStart(dateStart: String): CallsFilter.Builder = + dateStart(Date.from(Instant.parse(dateStart))) + +fun CallsFilter.Builder.dateEnd(dateEnd: String): CallsFilter.Builder = + dateEnd(Date.from(Instant.parse(dateEnd))) \ No newline at end of file diff --git a/src/main/kotlin/com/vonage/client/kt/Vonage.kt b/src/main/kotlin/com/vonage/client/kt/Vonage.kt index 093599b..0c079dd 100644 --- a/src/main/kotlin/com/vonage/client/kt/Vonage.kt +++ b/src/main/kotlin/com/vonage/client/kt/Vonage.kt @@ -24,10 +24,7 @@ fun VonageClient.Builder.authFromEnv(): VonageClient.Builder { return this } -fun VonageClient.Builder.httpConfig(init: HttpConfig.Builder.() -> Unit): VonageClient.Builder { - return this.httpConfig(HttpConfig.builder().apply(init).build()) -} +fun VonageClient.Builder.httpConfig(init: HttpConfig.Builder.() -> Unit): VonageClient.Builder = + httpConfig(HttpConfig.builder().apply(init).build()) -private fun env(variable: String) : String? { - return System.getenv(variable) -} \ No newline at end of file +private fun env(variable: String) : String? = System.getenv(variable) diff --git a/src/test/kotlin/com/vonage/client/kt/AbstractTest.kt b/src/test/kotlin/com/vonage/client/kt/AbstractTest.kt index 2b26845..15b4a10 100644 --- a/src/test/kotlin/com/vonage/client/kt/AbstractTest.kt +++ b/src/test/kotlin/com/vonage/client/kt/AbstractTest.kt @@ -27,6 +27,7 @@ abstract class AbstractTest { 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" private val port = 8081 val wiremock: WireMockServer = WireMockServer( @@ -91,19 +92,21 @@ abstract class AbstractTest { } if (contentType != null) { headers contains "Content-Type" equalTo contentType.mime - if (expectedParams != null) when(contentType) { - ContentType.APPLICATION_JSON -> { - body equalTo ObjectMapper().writeValueAsString(expectedParams) - } - ContentType.FORM_URLENCODED -> { - // TODO recursive decomposition for nested params - expectedParams.forEach {k, v -> headers contains k equalTo v.toString() } - } - } } if (accept != null) { headers contains "Accept" equalTo accept.mime } + if (expectedParams != null) { + if (contentType == ContentType.APPLICATION_JSON) { + body equalTo ObjectMapper().writeValueAsString(expectedParams) + } + else { + url like "$expectedUrl\\?.+" + expectedParams.forEach { (k, v) -> + queryParams contains k equalTo v.toString() + } + } + } }, when (httpMethod) { HttpMethod.GET -> WireMock::get HttpMethod.POST -> WireMock::post @@ -154,10 +157,8 @@ abstract class AbstractTest { authType: AuthType? = AuthType.JWT, expectedResponseParams: Map) = - mockRequest(HttpMethod.GET, expectedUrl, - contentType = if (expectedQueryParams != null) ContentType.FORM_URLENCODED else null, - accept = ContentType.APPLICATION_JSON, authType, expectedQueryParams - ).mockReturn(status, expectedResponseParams) + mockRequest(HttpMethod.GET, expectedUrl, accept = ContentType.APPLICATION_JSON, authType = authType, + expectedParams = expectedQueryParams).mockReturn(status, expectedResponseParams) protected fun BuildingStep.mockReturn( diff --git a/src/test/kotlin/com/vonage/client/kt/VoiceTest.kt b/src/test/kotlin/com/vonage/client/kt/VoiceTest.kt index ca6a490..8f583a9 100644 --- a/src/test/kotlin/com/vonage/client/kt/VoiceTest.kt +++ b/src/test/kotlin/com/vonage/client/kt/VoiceTest.kt @@ -1,16 +1,13 @@ package com.vonage.client.kt -import com.vonage.client.voice.CallDirection -import com.vonage.client.voice.CallStatus -import com.vonage.client.voice.PhoneEndpoint +import com.vonage.client.voice.* import com.vonage.client.voice.ncco.Ncco import com.vonage.client.voice.ncco.TalkAction import java.net.URI import java.time.Instant import java.util.* import kotlin.test.Test -import kotlin.test.assertEquals -import kotlin.test.assertNotNull +import kotlin.test.* class VoiceTest : AbstractTest() { private val voiceClient = vonage.voice @@ -19,6 +16,85 @@ class VoiceTest : AbstractTest() { private val callUrl = "$callsBaseUrl/$callIdStr" private val callObj = voiceClient.call(UUID.fromString(callIdStr)) private val conversationId = "CON-f972836a-550f-45fa-956c-12a2ab5b7d22" + private val price = "23.40" + private val duration = 60 + private val rate = "0.39" + private val phoneType = "phone" + private val count = 89 + private val pageSize = 25 + private val recordIndex = 14 + private val callResponseMap = mapOf( + "_links" to mapOf( + "self" to mapOf( + "href" to "/calls/$callIdStr" + ) + ), + "uuid" to callIdStr, + "conversation_uuid" to conversationId, + "to" to mapOf( + "type" to phoneType, + "number" to toNumber + ), + "from" to mapOf( + "type" to phoneType, + "number" to altNumber + ), + "status" to "completed", + "direction" to "outbound", + "rate" to rate, + "price" to price, + "duration" to "$duration", + "start_time" to startTime, + "end_time" to endTime, + "network" to networkCode + ) + private val listCallsResponse = mapOf( + "count" to count, + "page_size" to pageSize, + "record_index" to recordIndex, + "_links" to mapOf("self" to mapOf( + "href" to "/calls?page_size=$pageSize&record_index=$recordIndex&order=desc" + )), + "_embedded" to mapOf("calls" to listOf( + mapOf(), + callResponseMap + )) + ) + + private fun assertEqualsSampleCall(callInfo: CallInfo) { + assertNotNull(callInfo) + assertEquals(callIdStr, callInfo.uuid) + assertEquals(conversationId, callInfo.conversationUuid) + val to = callInfo.to + assertNotNull(to) + assertEquals(phoneType, to.type) + assertEquals(toNumber, (to as PhoneEndpoint).number) + val from = callInfo.from + assertNotNull(from) + assertEquals(phoneType, from.type) + assertEquals(altNumber, (from as PhoneEndpoint).number) + assertEquals(CallStatus.COMPLETED, callInfo.status) + assertEquals(CallDirection.OUTBOUND, callInfo.direction) + 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(networkCode, callInfo.network) + } + + private fun assertEqualsSampleCallsPage(callsPage: CallInfoPage) { + assertNotNull(callsPage) + assertEquals(pageSize, callsPage.pageSize) + assertEquals(recordIndex, callsPage.recordIndex) + assertNotNull(callsPage.links?.self?.href) + assertEquals(count, callsPage.count) + val infos = callsPage.embedded?.callInfos + assertNotNull(infos) + assertEquals(2, infos.size) + assertNotNull(infos[0]) + assertEqualsSampleCall(infos[1]) + } private fun testModifyCall(actionName: String = "transfer", invocation: () -> Unit, nccoAction: Map? = null, nccoUrl: String? = null) { @@ -62,57 +138,8 @@ class VoiceTest : AbstractTest() { @Test fun `get call`() { - val price = "23.40" - val duration = 60 - val rate = "0.39" - val phoneType = "phone" - - mockGet(expectedUrl = callUrl, expectedResponseParams = mapOf( - "_links" to mapOf( - "self" to mapOf( - "href" to "/calls/$callIdStr" - ) - ), - "uuid" to callIdStr, - "conversation_uuid" to conversationId, - "to" to mapOf( - "type" to phoneType, - "number" to toNumber - ), - "from" to mapOf( - "type" to phoneType, - "number" to altNumber - ), - "status" to "completed", - "direction" to "outbound", - "rate" to rate, - "price" to price, - "duration" to "$duration", - "start_time" to startTime, - "end_time" to endTime, - "network" to networkCode - )) - - val response = callObj.get() - assertNotNull(response) - assertEquals(callIdStr, response.uuid) - assertEquals(conversationId, response.conversationUuid) - val to = response.to - assertNotNull(to) - assertEquals(phoneType, to.type) - assertEquals(toNumber, (to as PhoneEndpoint).number) - val from = response.from - assertNotNull(from) - assertEquals(phoneType, from.type) - assertEquals(altNumber, (from as PhoneEndpoint).number) - assertEquals(CallStatus.COMPLETED, response.status) - assertEquals(CallDirection.OUTBOUND, response.direction) - assertEquals(rate, response.rate) - assertEquals(price, response.price) - assertEquals(duration, response.duration) - assertEquals(Instant.parse(startTime), response.startTime.toInstant()) - assertEquals(Instant.parse(endTime), response.endTime.toInstant()) - assertEquals(networkCode, response.network) + mockGet(expectedUrl = callUrl, expectedResponseParams = callResponseMap) + assertEqualsSampleCall(callObj.get()) } @Test @@ -130,4 +157,34 @@ class VoiceTest : AbstractTest() { callObj.transfer(Ncco(TalkAction.builder(text).build())) }) } + + @Test + fun `list calls all filter parameters`() { + mockGet(callsBaseUrl, expectedQueryParams = mapOf( + "status" to "unanswered", + "date_start" to startTime, + "date_end" to endTime, + "page_size" to pageSize, + "record_index" to recordIndex, + "order" to "desc", + "conversation_uuid" to conversationId + + ), expectedResponseParams = listCallsResponse) + + val callsPage = voiceClient.listCalls { + status(CallStatus.UNANSWERED) + dateStart(startTime); dateEnd(endTime) + pageSize(pageSize); recordIndex(recordIndex) + order(CallOrder.DESCENDING); conversationUuid(conversationId) + } + + assertEqualsSampleCallsPage(callsPage) + } + + @Test + fun `list calls no filter`() { + mockGet(callsBaseUrl, expectedResponseParams = listCallsResponse) + assertEqualsSampleCallsPage(voiceClient.listCalls()) + } + } \ No newline at end of file