Skip to content

Commit

Permalink
feat: Add Users
Browse files Browse the repository at this point in the history
  • Loading branch information
SMadani committed Aug 16, 2024
1 parent 005f1f6 commit 655f84f
Show file tree
Hide file tree
Showing 7 changed files with 272 additions and 7 deletions.
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
## [0.9.0] - 2024-08-2?

### Added
- Application API
- Application & Users API

## [0.8.0] - 2024-08-09

Expand Down
48 changes: 48 additions & 0 deletions src/main/kotlin/com/vonage/client/kt/Users.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
/*
* Copyright 2024 Vonage
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.vonage.client.kt

import com.vonage.client.users.*

class Users internal constructor(private val client: UsersClient) {

fun user(userId: String): ExistingUser = ExistingUser(userId)

fun user(user: BaseUser): ExistingUser = ExistingUser(user.id)

inner class ExistingUser internal constructor(val userId: String) {
fun get(): User = client.getUser(userId)

fun update(properties: User.Builder.() -> Unit): User =
client.updateUser(userId, User.builder().apply(properties).build())

fun delete(): Unit = client.deleteUser(userId)
}

fun create(properties: User.Builder.() -> Unit): User =
client.createUser(User.builder().apply(properties).build())

fun list(filter: (ListUsersRequest.Builder.() -> Unit)? = null): ListUsersResponse {
val request = ListUsersRequest.builder()
if (filter == null) {
request.pageSize(100)
}
else {
request.apply(filter)
}
return client.listUsers(request.build())
}
}
1 change: 1 addition & 0 deletions src/main/kotlin/com/vonage/client/kt/Vonage.kt
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ class Vonage(init: VonageClient.Builder.() -> Unit) {
val simSwap = SimSwap(client.simSwapClient)
val sms = Sms(client.smsClient)
val subaccounts = Subaccounts(client.subaccountsClient)
val users = Users(client.usersClient)
val verify = Verify(client.verify2Client)
val verifyLegacy = VerifyLegacy(client.verifyClient)
val voice = Voice(client.voiceClient)
Expand Down
9 changes: 8 additions & 1 deletion src/test/kotlin/com/vonage/client/kt/AbstractTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ abstract class AbstractTest {
protected val country = "GB"
protected val secret = "ABCDEFGH01234abc"
protected val sipUri = "sip:rebekka@sip.example.com"
protected val websocketUri = "wss://example.com/socket"
protected val clientRef = "my-personal-reference"
protected val textHexEncoded = "48656c6c6f2c20576f726c6421"
protected val entityId = "1101407360000017170"
Expand All @@ -84,6 +85,10 @@ abstract class AbstractTest {
protected val statusCallbackUrl = "$callbackUrl/status"
protected val moCallbackUrl = "$callbackUrl/inbound-sms"
protected val drCallbackUrl = "$callbackUrl/delivery-receipt"
protected val imageUrl = "$exampleUrlBase/image.jpg"
protected val audioUrl = "$exampleUrlBase/audio.mp3"
protected val videoUrl = "$exampleUrlBase/video.mp4"
protected val fileUrl = "$exampleUrlBase/file.pdf"

private val port = 8081
private val wiremock: WireMockServer = WireMockServer(
Expand Down Expand Up @@ -281,14 +286,15 @@ abstract class AbstractTest {

protected inline fun <reified E: VonageApiResponseException> assertApiResponseException(
url: String, requestMethod: HttpMethod, actualCall: () -> Any, status: Int,
errorType: String? = null, title: String? = null,
errorType: String? = null, title: String? = null, code: String? = null,
detail: String? = null, instance: String? = null): E {

val responseParams = mutableMapOf<String, Any>()
if (errorType != null) responseParams["type"] = errorType
if (title != null) responseParams["title"] = title
if (detail != null) responseParams["detail"] = detail
if (instance != null) responseParams["instance"] = instance
if (code != null) responseParams["code"] = code

mockRequest(requestMethod, url).mockReturn(status, responseParams)
val exception = assertThrows<E> { actualCall.invoke() }
Expand All @@ -298,6 +304,7 @@ abstract class AbstractTest {
assertEquals(title, exception.title)
assertEquals(instance, exception.instance)
assertEquals(detail, exception.detail)
assertEquals(code, exception.code)
return exception
}

Expand Down
4 changes: 0 additions & 4 deletions src/test/kotlin/com/vonage/client/kt/MessagesTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -37,10 +37,6 @@ class MessagesTest : AbstractTest() {
private val viberChannel = "viber_service"
private val messengerChannel = "messenger"
private val caption = "Additional text to accompany the media"
private val imageUrl = "https://example.com/image.jpg"
private val audioUrl = "https://example.com/audio.mp3"
private val videoUrl = "https://example.com/video.mp4"
private val fileUrl = "https://example.com/file.pdf"
private val captionMap = mapOf("caption" to caption)


Expand Down
214 changes: 214 additions & 0 deletions src/test/kotlin/com/vonage/client/kt/UsersTest.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,214 @@
/*
* Copyright 2024 Vonage
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.vonage.client.kt

import com.vonage.client.common.HttpMethod
import com.vonage.client.users.*
import java.net.URI
import kotlin.test.*

class UsersTest : AbstractTest() {
private val client = vonage.users
private val authType = AuthType.JWT
private val baseUrl = "/v1/users"
private val userId = "USR-82e028d9-5201-4f1e-8188-604b2d3471ec"
private val userUrl = "$baseUrl/$userId"
private val existingUser = client.user(userId)
private val name = "my_user_name"
private val displayName = "Test User"
private val ttl = 3600
private val pageSize = 10
private val customData = mapOf("custom_key" to "custom_value")
private val sipUser = "sip_user"
private val sipPassword = "Passw0rd1234"
private val messengerId = "12345abcd"
private val baseUserRequest = mapOf("name" to name)
private val userIdMapOnly = mapOf("id" to userId)
private val baseUserResponse = userIdMapOnly + baseUserRequest
private val fullUserRequest = baseUserRequest + mapOf(
"display_name" to displayName,
"image_url" to imageUrl,
"properties" to mapOf(
"custom_data" to customData,
"ttl" to ttl
),
"custom_data" to customData,
"channels" to mapOf(
"sip" to listOf(mapOf(
"uri" to sipUri,
"username" to sipUser,
"password" to sipPassword
)),
"messenger" to listOf(mapOf(
"id" to messengerId
))
)
)
private val fullUserResponse = userIdMapOnly + fullUserRequest

private fun assertEqualsUser(parsed: User) {
assertEquals(userId, parsed.id)
assertEquals(name, parsed.name)
assertEquals(displayName, parsed.displayName)
assertEquals(URI.create(imageUrl), parsed.imageUrl)
assertEquals(customData, parsed.customData)
val channels = parsed.channels
assertNotNull(channels)
// TODO the rest
}

private fun assertUserNotFoundException(method: HttpMethod, invocation: Users.ExistingUser.() -> Any) =
assertApiResponseException<UsersResponseException>(
url = userUrl, requestMethod = method,
actualCall = { invocation.invoke(existingUser) },
status = 404,
title = "Not found.",
errorType = "https://developer.vonage.com/api/conversation#user:error:not-found",
code = "user:error:not-found",
detail = "User does not exist, or you do not have access.",
instance = "00a5916655d650e920ccf0daf40ef4ee"
)

private fun assertListUsers(filter: Map<String, Any>, invocation: Users.() -> ListUsersResponse) {
mockGet(
expectedUrl = baseUrl, authType = authType,
expectedQueryParams = filter,
expectedResponseParams = mapOf(
"page_size" to pageSize,
"_embedded" to mapOf(
"users" to listOf(
mapOf(),
mapOf(
"id" to userId,
"name" to name,
"display_name" to displayName,
"_links" to mapOf(
"self" to mapOf(
"href" to "https://api.nexmo.com$userUrl"
)
)
),
userIdMapOnly
)
),
"_links" to mapOf(
"first" to mapOf(
"href" to "https://api.nexmo.com/v1/users?order=desc&page_size=10"
),
"self" to mapOf(
"href" to "https://api.nexmo.com/v1/users?order=desc&page_size=10&cursor=7EjDNQrAcipmOnc0HCzpQRkhBULzY44ljGUX4lXKyUIVfiZay5pv9wg%3D"
),
"next" to mapOf(
"href" to "https://api.nexmo.com/v1/users?order=desc&page_size=10&cursor=7EjDNQrAcipmOnc0HCzpQRkhBULzY44ljGUX4lXKyUIVfiZay5pv9wg%3D"
),
"prev" to mapOf(
"href" to "https://api.nexmo.com/v1/users?order=desc&page_size=10&cursor=7EjDNQrAcipmOnc0HCzpQRkhBULzY44ljGUX4lXKyUIVfiZay5pv9wg%3D"
)
)
)
)
val response = invocation.invoke(client)
assertNotNull(response)
val users = response.users
assertNotNull(users)
assertEquals(3, users.size)
// TODO remaining assertions
}

@Test
fun `get user`() {
mockGet(expectedUrl = userUrl, authType = authType, expectedResponseParams = fullUserResponse)
assertEqualsUser(existingUser.get())
assertUserNotFoundException(HttpMethod.GET, Users.ExistingUser::get)
}

@Test
fun `delete user`() {
mockDelete(expectedUrl = userUrl, authType = authType)
existingUser.delete()
assertUserNotFoundException(HttpMethod.DELETE, Users.ExistingUser::delete)
}

@Test
fun `update user`() {
val newDisplayName = "Updated DP"
val newPic = "$exampleUrlBase/new_pic.png"
mockPatch(
expectedUrl = userUrl, authType = authType,
expectedRequestParams = userIdMapOnly + mapOf(
"display_name" to newDisplayName,
"image_url" to newPic
),
expectedResponseParams = fullUserResponse
)
assertEqualsUser(existingUser.update {
displayName(newDisplayName)
imageUrl(newPic)
})

assertUserNotFoundException(HttpMethod.PATCH) { update { }}
}

@Test
fun `create user required parameters`() {
mockPost(
expectedUrl = baseUrl, authType = authType,
expectedRequestParams = mapOf(),
expectedResponseParams = fullUserResponse
)
assertEqualsUser(client.create {})

assert401ApiResponseException<UsersResponseException>(baseUrl, HttpMethod.POST) {
client.create {}
}
}

@Test
fun `create user all parameters`() {
mockPost(
expectedUrl = baseUrl, authType = authType,
expectedRequestParams = fullUserRequest,
expectedResponseParams = fullUserResponse
)
assertEqualsUser(client.create {
name(name)
displayName(displayName)
imageUrl(imageUrl)
customData(customData)
// TODO channels
})
}

@Test
fun `list users no filter`() {
assertListUsers(emptyMap(), Users::list)
assert401ApiResponseException<UsersResponseException>(baseUrl, HttpMethod.GET, client::list)
}

@Test
fun `list users all filters`() {
assertListUsers(mapOf(
"order" to "desc",
"page_size" to pageSize
// TODO remaining filters
)) {
list {
order(ListUsersRequest.SortOrder.DESC)
pageSize(pageSize)
}
}
}
}
1 change: 0 additions & 1 deletion src/test/kotlin/com/vonage/client/kt/VoiceTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,6 @@ class VoiceTest : AbstractTest() {
private val vbcExt = "4321"
private val streamUrl = "$exampleUrlBase/waiting.mp3"
private val onAnswerUrl = "$exampleUrlBase/ncco.json"
private val websocketUri = "wss://example.com/socket"
private val ringbackTone = "http://example.org/ringbackTone.wav"
private val wsContentType = "audio/l16;rate=8000"
private val userToUserHeader = "56a390f3d2b7310023a"
Expand Down

0 comments on commit 655f84f

Please sign in to comment.