From 4df68993f9cec0a548fa556d2578b3a4b413f35d Mon Sep 17 00:00:00 2001 From: Sina Madani Date: Fri, 23 Aug 2024 10:52:05 +0100 Subject: [PATCH] Test sessions --- src/main/kotlin/com/vonage/client/kt/Video.kt | 12 +- .../com/vonage/client/kt/AbstractTest.kt | 13 +- .../kotlin/com/vonage/client/kt/VideoTest.kt | 335 +++++++++++++++++- .../kotlin/com/vonage/client/kt/VoiceTest.kt | 2 - 4 files changed, 351 insertions(+), 11 deletions(-) diff --git a/src/main/kotlin/com/vonage/client/kt/Video.kt b/src/main/kotlin/com/vonage/client/kt/Video.kt index 9ff3596..ecf605f 100644 --- a/src/main/kotlin/com/vonage/client/kt/Video.kt +++ b/src/main/kotlin/com/vonage/client/kt/Video.kt @@ -16,6 +16,7 @@ package com.vonage.client.kt import com.vonage.client.video.* +import java.util.* class Video(private val client: VideoClient) { @@ -52,6 +53,11 @@ class Video(private val client: VideoClient) { fun sendDtmf(digits: String): Unit = client.sendDtmf(sessionId, connectionId, digits) } + fun muteStreams(active: Boolean = true, vararg excludedStreamIds: String): ProjectDetails = + client.muteSession(sessionId, active, + if (excludedStreamIds.isNotEmpty()) excludedStreamIds.toList() else null + ) + fun listStreams(): List = client.listStreams(sessionId) fun listArchives(count: Int = 1000, offset: Int = 0): List = @@ -65,10 +71,10 @@ class Video(private val client: VideoClient) { fun sendDtmf(digits: String): Unit = client.sendDtmf(sessionId, digits) - fun startCaptions(token: String, properties: CaptionsRequest.Builder.() -> Unit): CaptionsResponse = + fun startCaptions(token: String, properties: CaptionsRequest.Builder.() -> Unit): UUID = client.startCaptions(CaptionsRequest.builder() .apply(properties).sessionId(sessionId).token(token).build() - ) + ).captionsId fun stopCaptions(captionsId: String): Unit = client.stopCaptions(captionsId) @@ -150,7 +156,7 @@ class Video(private val client: VideoClient) { fun listRenders(count: Int = 1000, offset: Int = 0): List = client.listRenders(ListStreamCompositionsRequest.builder().count(count).offset(offset).build()) - fun experienceComposer(renderId: String): ExistingRender = ExistingRender(renderId) + fun render(renderId: String): ExistingRender = ExistingRender(renderId) inner class ExistingRender internal constructor(val renderId: String) { diff --git a/src/test/kotlin/com/vonage/client/kt/AbstractTest.kt b/src/test/kotlin/com/vonage/client/kt/AbstractTest.kt index 529aa72..e9a7d9b 100644 --- a/src/test/kotlin/com/vonage/client/kt/AbstractTest.kt +++ b/src/test/kotlin/com/vonage/client/kt/AbstractTest.kt @@ -45,6 +45,7 @@ abstract class AbstractTest { private val signatureSecretName = "sig" private val apiSecretName = "api_secret" private val apiKeyName = "api_key" + private val contentTypeHeaderName = "Content-Type" private val authHeaderName = "Authorization" private val basicSecretEncodedHeader = "Basic $apiKeySecretEncoded" private val jwtBearerPattern = "Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9(\\..+){2}" @@ -62,6 +63,7 @@ abstract class AbstractTest { protected val cursor = "7EjDNQrAcipmOnc0HCzpQRkhBULzY44ljGUX4lXKyUIVfiZay5pv9wg=" protected val vbcExt = "4321" protected val userName = "Sam_username" + protected val dtmf = "p*123#" protected val sipUri = "sip:rebekka@sip.example.com" protected val websocketUri = "wss://example.com/socket" protected val wsContentType = "audio/l16;rate=8000" @@ -156,13 +158,18 @@ abstract class AbstractTest { } } - private fun Map.toJson(): String = ObjectMapper().writeValueAsString(this) + private fun Any.toJson(): String = ObjectMapper().writeValueAsString(this) protected fun mockPostQueryParams(expectedUrl: String, expectedRequestParams: Map, authType: AuthType? = AuthType.API_KEY_SECRET_QUERY_PARAMS, - status: Int = 200, expectedResponseParams: Map? = null) { + contentType: Boolean = false, status: Int = 200, + expectedResponseParams: Any? = null) { val stub = post(urlPathEqualTo(expectedUrl)) + if (contentType) { + stub.withHeader(contentTypeHeaderName, equalTo(ContentType.FORM_URLENCODED.mime)) + } + when (authType) { AuthType.API_KEY_SECRET_QUERY_PARAMS -> { stub.withFormParam(apiKeyName, equalTo(apiKey)) @@ -196,7 +203,7 @@ abstract class AbstractTest { urlPath equalTo expectedUrl headers contains "User-Agent" like "vonage-java-sdk\\/.+ java\\/.+" if (contentType != null) { - headers contains "Content-Type" equalTo contentType.mime + headers contains contentTypeHeaderName equalTo contentType.mime } if (accept != null) { headers contains "Accept" equalTo accept.mime diff --git a/src/test/kotlin/com/vonage/client/kt/VideoTest.kt b/src/test/kotlin/com/vonage/client/kt/VideoTest.kt index 019e5cc..e1f1fe1 100644 --- a/src/test/kotlin/com/vonage/client/kt/VideoTest.kt +++ b/src/test/kotlin/com/vonage/client/kt/VideoTest.kt @@ -15,21 +15,350 @@ */ package com.vonage.client.kt +import com.vonage.client.video.* +import java.net.URI import java.util.* import kotlin.test.* class VideoTest : AbstractTest() { - private val verifyClient = vonage.verify + private val client = vonage.video private val baseUrl = "/v2/project/$applicationId" private val sessionId = "flR1ZSBPY3QgMjkgMTI6MTM6MjMgUERUIDIwMTN" - private val connectionId = testUuidStr + private val connectionId = "e9f8c166-6c67-440d-994a-04fb6dfed007" private val streamId = "8b732909-0a06-46a2-8ea8-074e64d43422" private val archiveId = "b40ef09b-3811-4726-b508-e41a0f96c68f" private val broadcastId = "93e36bb9-b72c-45b6-a9ea-5c37dbc49906" private val captionsId = "7c0680fc-6274-4de5-a66f-d0648e8d3ac2" private val audioConnectorId = "b0a5a8c7-dc38-459f-a48d-a7f2008da853" - private val jwt = "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJpYXQiOjE2OTkwNDMxMTEsImV4cCI6MTY5OTA2NDcxMSwianRpIjoiMW1pODlqRk9meVpRIiwiYXBwbGljYXRpb25faWQiOiIxMjMxMjMxMi0zODExLTQ3MjYtYjUwOC1lNDFhMGY5NmM2OGYiLCJzdWIiOiJ2aWRlbyIsImFjbCI6IiJ9.o3U506EejsS8D5Tob90FG1NC1cR69fh3pFOpxnyTHVFfgqI6NWuuN8lEwrS3Zb8bGxE_A9LyyUZ2y4uqLpyXRw" + private val renderId = "1248e707-0b81-464c-9789-f46ad10e7764" + private val sipCallId = "b0a5a8c7-dc38-459f-a48d-a7f2008da853" + private val token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJpYXQiOjE2OTkwNDMxMTEsImV4cCI6MTY5OTA2NDcxMSwianRpIjoiMW1pODlqRk9meVpRIiwiYXBwbGljYXRpb25faWQiOiIxMjMxMjMxMi0zODExLTQ3MjYtYjUwOC1lNDFhMGY5NmM2OGYiLCJzdWIiOiJ2aWRlbyIsImFjbCI6IiJ9.o3U506EejsS8D5Tob90FG1NC1cR69fh3pFOpxnyTHVFfgqI6NWuuN8lEwrS3Zb8bGxE_A9LyyUZ2y4uqLpyXRw" private val createdAtLong = 1414642898000L private val sessionBaseUrl = "$baseUrl/session/$sessionId" + private val connectionBaseUrl = "$sessionBaseUrl/connection/$connectionId" + private val streamBaseUrl = "$sessionBaseUrl/stream" + private val streamUrl = "$streamBaseUrl/$streamId" + private val archiveBaseUrl = "$sessionBaseUrl/archive/$archiveId" + private val broadcastBaseUrl = "$sessionBaseUrl/broadcast/$broadcastId" + private val renderBaseUrl = "$sessionBaseUrl/render/$renderId" + private val existingSession = client.session(sessionId) + private val existingConnection = existingSession.connection(connectionId) + private val existingStream = existingSession.stream(streamId) + private val existingArchive = client.archive(archiveId) + private val existingBroadcast = client.broadcast(broadcastId) + private val existingRender = client.render(renderId) + private val type = "chat" + private val data = "Text of the chat message" + private val signalRequestMap = mapOf("type" to type, "data" to data) + private val headers = mapOf("k1" to "Value 1", "Key 2" to "2 Val") + private val videoType = VideoType.CAMERA + private val streamName = "My Stream" + private val layoutClasses = listOf("full", "no-border") + private val streamLayoutMap = mapOf( + "id" to streamId, + "videoType" to videoType.name.lowercase(), + "name" to streamName, + "layoutClassList" to layoutClasses + ) + private fun assertEqualsSampleStream(response: GetStreamResponse) { + assertNotNull(response) + assertEquals(UUID.fromString(streamId), response.id) + assertEquals(videoType, response.videoType) + assertEquals(streamName, response.name) + assertEquals(layoutClasses, response.layoutClassList) + } + + @Test + fun `start audio connector all fields`() { + mockPost(expectedUrl = "$baseUrl/connect", expectedRequestParams = mapOf( + "sessionId" to sessionId, + "token" to token, + "websocket" to mapOf( + "uri" to websocketUri, + "streams" to listOf(streamId, randomUuidStr), + "headers" to headers, + "audioRate" to 16000 + ) + ), + expectedResponseParams = mapOf( + "id" to audioConnectorId, + "connectionId" to connectionId + ) + ) + + val response = client.connectToWebsocket { + uri(websocketUri); headers(headers) + sessionId(sessionId); token(token) + streams(streamId, randomUuidStr) + audioRate(Websocket.AudioRate.L16_16K) + } + assertNotNull(response) + assertEquals(UUID.fromString(audioConnectorId), response.id) + assertEquals(UUID.fromString(connectionId), response.connectionId) + } + + @Test + fun `start audio connector required fields`() { + mockPost(expectedUrl = "$baseUrl/connect", expectedRequestParams = mapOf( + "sessionId" to sessionId, "token" to token, + "websocket" to mapOf("uri" to websocketUri) + ), + expectedResponseParams = mapOf("id" to audioConnectorId)) + + val response = client.connectToWebsocket { + uri(websocketUri); sessionId(sessionId); token(token) + } + assertNotNull(response) + assertEquals(UUID.fromString(audioConnectorId), response.id) + assertNull(response.connectionId) + } + + @Test + fun `start live captions all parameters`() { + val maxDuration = 1800 + val partialCaptions = true + mockPost(expectedUrl = "$baseUrl/captions", status = 202, + expectedRequestParams = mapOf( + "sessionId" to sessionId, + "token" to token, + "languageCode" to "en-US", + "maxDuration" to maxDuration, + "partialCaptions" to partialCaptions, + "statusCallbackUrl" to statusCallbackUrl + ), + expectedResponseParams = mapOf("captionsId" to captionsId) + ) + assertEquals(UUID.fromString(captionsId), existingSession.startCaptions(token) { + languageCode(Language.EN_US); maxDuration(maxDuration) + partialCaptions(partialCaptions); statusCallbackUrl(statusCallbackUrl) + }) + } + + @Test + fun `stop live captions`() { + mockPost(expectedUrl = "$baseUrl/captions/$captionsId/stop", status = 202) + existingSession.stopCaptions(captionsId) + } + + @Test + fun `play DTMF into SIP call`() { + mockPost(expectedUrl = "$sessionBaseUrl/play-dtmf", expectedRequestParams = mapOf("digits" to dtmf)) + existingSession.sendDtmf(dtmf) + } + + @Test + fun `send DTMF to specific participant`() { + mockPost(expectedUrl = "$connectionBaseUrl/play-dtmf", expectedRequestParams = mapOf("digits" to dtmf)) + existingConnection.sendDtmf(dtmf) + } + + @Test + fun `initiate outbound SIP call all parameters`() { + val from = "from@example.com" + val secure = true + val video = false + val observeForceMute = true + val password = "P@s5w0rd123" + + mockPost(expectedUrl = "$baseUrl/dial", expectedRequestParams = mapOf( + "sessionId" to sessionId, "token" to token, + "sip" to mapOf( + "uri" to "$sipUri;transport=tls", + "from" to from, + "headers" to headers, + "auth" to mapOf( + "username" to userName, + "password" to password + ), + "secure" to secure, + "video" to video, + "observeForceMute" to observeForceMute + ) + ), + expectedResponseParams = mapOf( + "id" to sipCallId, + "connectionId" to connectionId, + "streamId" to streamId + ) + ) + val response = client.sipDial { + sessionId(sessionId); token(token) + uri(URI.create(sipUri), true) + addHeaders(headers); secure(secure) + from(from); video(video) + observeForceMute(observeForceMute) + username(userName); password(password) + } + assertNotNull(response) + assertEquals(sipCallId, response.id) + assertEquals(connectionId, response.connectionId) + assertEquals(streamId, response.streamId) + } + + @Test + fun `signal all participants`() { + mockPost(expectedUrl = "$sessionBaseUrl/signal", expectedRequestParams = signalRequestMap, status = 204) + existingSession.signalAll(type, data) + } + + @Test + fun `signal single participant`() { + mockPost(expectedUrl = "$connectionBaseUrl/signal", expectedRequestParams = signalRequestMap, status = 204) + existingConnection.signal(type, data) + } + + @Test + fun `force disconnect`() { + mockDelete(expectedUrl = connectionBaseUrl) + existingConnection.disconnect() + } + + @Test + fun `mute participant stream`() { + mockPost(expectedUrl = "$streamUrl/mute") + existingStream.mute() + } + + @Test + fun `mute all streams empty response`() { + mockPost(expectedUrl = "$sessionBaseUrl/mute", + expectedRequestParams = mapOf("active" to true), + expectedResponseParams = mapOf() + ) + val response = existingSession.muteStreams() + assertNotNull(response) + assertNull(response.applicationId) + assertNull(response.status) + assertNull(response.name) + assertNull(response.environment) + assertNull(response.createdAt) + } + + @Test + fun `mute selected streams full response`() { + val active = false + val status = ProjectStatus.ACTIVE + val name = "Project Name" + val environment = ProjectEnvironment.STANDARD + + mockPost(expectedUrl = "$sessionBaseUrl/mute", + expectedRequestParams = mapOf( + "active" to active, + "excludedStreamIds" to listOf(streamId, randomUuidStr) + ), + expectedResponseParams = mapOf( + "applicationId" to applicationId, + "status" to status.name, + "name" to name, + "environment" to environment.name.lowercase(), + "createdAt" to createdAtLong + ) + ) + val response = existingSession.muteStreams(active, streamId, randomUuidStr) + assertNotNull(response) + assertEquals(applicationId, response.applicationId) + assertEquals(status, response.status) + assertEquals(name, response.name) + assertEquals(environment, response.environment) + assertEquals(createdAtLong, response.createdAt) + } + + @Test + fun `get single stream layout`() { + mockGet(expectedUrl = streamUrl, expectedResponseParams = streamLayoutMap) + assertEqualsSampleStream(existingStream.info()) + } + + @Test + fun `get all stream layouts`() { + val count = 4 + mockGet(expectedUrl = streamBaseUrl, expectedResponseParams = mapOf( + "count" to count, + "items" to listOf( + mapOf(), + streamLayoutMap, + mapOf("id" to randomUuidStr), + mapOf("layoutClassList" to listOf()) + ) + )) + val response = existingSession.listStreams() + assertEquals(count, response.size) + val empty = response[0] + assertNotNull(empty) + assertNull(empty.id) + assertNull(empty.videoType) + assertNull(empty.name) + assertNull(empty.layoutClassList) + assertEqualsSampleStream(response[1]) + val idOnly = response[2] + assertNotNull(idOnly) + assertEquals(UUID.fromString(randomUuidStr), idOnly.id) + assertNull(idOnly.videoType) + assertNull(idOnly.name) + assertNull(idOnly.layoutClassList) + val emptyLayout = response[3] + assertNotNull(emptyLayout) + assertEquals(0, emptyLayout.layoutClassList.size) + } + + @Test + fun `change stream layout`() { + mockPut(expectedUrl = streamBaseUrl, expectedRequestParams = mapOf( + "items" to listOf(mapOf( + "id" to streamId, + "layoutClassList" to layoutClasses + )) + )) + existingStream.setLayout(*layoutClasses.toTypedArray()) + } + + @Test + fun `create session no parameters`() { + mockPostQueryParams( + expectedUrl = "/session/create", + authType = AuthType.JWT, + expectedRequestParams = mapOf(), + expectedResponseParams = listOf(mapOf()) + ) + val response = client.createSession() + assertNotNull(response) + assertNull(response.sessionId) + assertNull(response.applicationId) + assertNull(response.createDt) + assertNull(response.mediaServerUrl) + } + + @Test + fun `create session all parameters`() { + val mediaServerUrl = "$exampleUrlBase/media" + val location = "127.0.0.1" + val archiveMode = ArchiveMode.ALWAYS + + mockPostQueryParams( + expectedUrl = "/session/create", + authType = AuthType.JWT, + expectedRequestParams = mapOf( + "archiveMode" to archiveMode.name.lowercase(), + "location" to location, + "p2p.preference" to "disabled" + ), + expectedResponseParams = listOf(mapOf( + "session_id" to sessionId, + "application_id" to applicationId, + "create_dt" to createdAtLong, + "media_server_url" to mediaServerUrl + )) + ) + val response = client.createSession { + location(location) + mediaMode(MediaMode.ROUTED) + archiveMode(archiveMode) + } + assertNotNull(response) + assertEquals(sessionId, response.sessionId) + assertEquals(UUID.fromString(applicationId), response.applicationId) + assertEquals(createdAtLong.toString(), response.createDt) + assertEquals(URI.create(mediaServerUrl), response.mediaServerUrl) + } } \ No newline at end of file diff --git a/src/test/kotlin/com/vonage/client/kt/VoiceTest.kt b/src/test/kotlin/com/vonage/client/kt/VoiceTest.kt index 9d112d6..9c6be80 100644 --- a/src/test/kotlin/com/vonage/client/kt/VoiceTest.kt +++ b/src/test/kotlin/com/vonage/client/kt/VoiceTest.kt @@ -20,7 +20,6 @@ import com.vonage.client.voice.* import com.vonage.client.voice.ncco.* import java.net.URI import java.util.* -import kotlin.test.Test import kotlin.test.* class VoiceTest : AbstractTest() { @@ -36,7 +35,6 @@ class VoiceTest : AbstractTest() { private val count = 89 private val pageSize = 25 private val recordIndex = 14 - private val dtmf = "p*123#" private val fromPstn = "14155550100" private val streamUrl = "$exampleUrlBase/waiting.mp3" private val onAnswerUrl = "$exampleUrlBase/ncco.json"