Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add Number Insight (v1) #2

Merged
merged 7 commits into from
Jul 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,11 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](http://keepachangelog.com/)
and this project adheres to [Semantic Versioning](http://semver.org/).

## [0.5.0] - 2024-07-25

### Added
- Number Insight v1 API

## [0.4.0] - 2024-07-23

### Added
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ You'll need to have [created a Vonage account](https://dashboard.nexmo.com/sign-
- [Messages](https://developer.vonage.com/en/messages/overview)
- [Verify](https://developer.vonage.com/en/verify/overview)
- [Voice](https://developer.vonage.com/en/voice/voice-api/overview)
- [Number Insight](https://developer.vonage.com/en/number-insight/overview)
- [SMS](https://developer.vonage.com/en/messaging/sms/overview)
- [Conversion](https://developer.vonage.com/en/messaging/conversion-api/overview)
- [Redact](https://developer.vonage.com/en/redact/overview)
Expand Down
4 changes: 2 additions & 2 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

<groupId>com.vonage</groupId>
<artifactId>server-sdk-kotlin</artifactId>
<version>0.4.0</version>
<version>0.5.0</version>

<name>Vonage Kotlin Server SDK</name>
<description>Kotlin client for Vonage APIs</description>
Expand Down Expand Up @@ -59,7 +59,7 @@
<dependency>
<groupId>com.vonage</groupId>
<artifactId>server-sdk</artifactId>
<version>8.9.3</version>
<version>8.9.4</version>
</dependency>
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
Expand Down
27 changes: 27 additions & 0 deletions src/main/kotlin/com/vonage/client/kt/NumberInsight.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package com.vonage.client.kt

import com.vonage.client.insight.*

class NumberInsight(private val niClient: InsightClient) {

fun basic(number: String, countryCode: String? = null): BasicInsightResponse =
niClient.getBasicNumberInsight(number, countryCode)

fun standard(number: String, countryCode: String? = null, cnam: Boolean? = null): StandardInsightResponse =
niClient.getStandardNumberInsight(StandardInsightRequest.builder()
.number(number).country(countryCode).cnam(cnam).build()
)

fun advanced(number: String, countryCode: String? = null, cnam: Boolean = false,
realTimeData: Boolean = false): AdvancedInsightResponse =
niClient.getAdvancedNumberInsight(AdvancedInsightRequest.builder().async(false)
.number(number).country(countryCode).cnam(cnam).realTimeData(realTimeData).build()
)

fun advancedAsync(number: String, callbackUrl: String, countryCode: String? = null, cnam: Boolean = false) {
niClient.getAdvancedNumberInsight(
AdvancedInsightRequest.builder().async(true)
.number(number).country(countryCode).cnam(cnam).callback(callbackUrl).build()
)
}
}
15 changes: 5 additions & 10 deletions src/main/kotlin/com/vonage/client/kt/Redact.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,23 +4,18 @@ import com.vonage.client.redact.*

class Redact(private val redactClient: RedactClient) {

fun redactSms(messageId: String, direction: RedactRequest.Type = RedactRequest.Type.OUTBOUND) {
fun redactSms(messageId: String, direction: RedactRequest.Type = RedactRequest.Type.OUTBOUND) =
redactClient.redactTransaction(messageId, RedactRequest.Product.SMS, direction)
}

fun redactMessage(messageId: String, direction: RedactRequest.Type = RedactRequest.Type.OUTBOUND) {
fun redactMessage(messageId: String, direction: RedactRequest.Type = RedactRequest.Type.OUTBOUND) =
redactClient.redactTransaction(messageId, RedactRequest.Product.MESSAGES, direction)
}

fun redactCall(callId: String, direction: RedactRequest.Type = RedactRequest.Type.OUTBOUND) {
fun redactCall(callId: String, direction: RedactRequest.Type = RedactRequest.Type.OUTBOUND) =
redactClient.redactTransaction(callId, RedactRequest.Product.VOICE, direction)
}

fun redactInsight(requestId: String) {
fun redactInsight(requestId: String) =
redactClient.redactTransaction(requestId, RedactRequest.Product.NUMBER_INSIGHTS)
}

fun redactVerification(requestId: String) {
fun redactVerification(requestId: String) =
redactClient.redactTransaction(requestId, RedactRequest.Product.VERIFY)
}
}
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 @@ -12,6 +12,7 @@ class Vonage(init: VonageClient.Builder.() -> Unit) {
val conversion = Conversion(vonageClient.conversionClient)
val redact = Redact(vonageClient.redactClient)
val verifyLegacy = VerifyLegacy(vonageClient.verifyClient)
val numberInsight = NumberInsight(vonageClient.insightClient)
}

fun VonageClient.Builder.authFromEnv(): VonageClient.Builder {
Expand Down
2 changes: 2 additions & 0 deletions src/test/kotlin/com/vonage/client/kt/AbstractTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@ abstract class AbstractTest {
protected val timestamp2Str = "2020-01-29T14:08:30.201Z"
protected val timestamp2: Instant = Instant.parse(timestamp2Str)
protected val currency = "EUR"
protected val exampleUrlBase = "https://example.com"
protected val callbackUrl = "$exampleUrlBase/callback"

private val port = 8081
private val wiremock: WireMockServer = WireMockServer(
Expand Down
262 changes: 262 additions & 0 deletions src/test/kotlin/com/vonage/client/kt/NumberInsightTest.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,262 @@
package com.vonage.client.kt

import com.vonage.client.insight.*
import com.vonage.client.insight.CarrierDetails.NetworkType
import com.vonage.client.insight.RoamingDetails.RoamingStatus
import java.math.BigDecimal
import kotlin.test.*

class NumberInsightTest : AbstractTest() {
private val niClient = vonage.numberInsight
private val cnam = true
private val realTimeData = true
private val statusMessage = "Success"
private val nationalNumber = "07712 345689"
private val countryCode = "GB"
private val countryCodeIso3 = "GBR"
private val countryName = "United Kingdom"
private val countryPrefix = "44"
private val requestPrice = "0.035900000"
private val refundPrice = "0.01500000"
private val remainingBalance = "1.23456789"
private val reachable = Reachability.REACHABLE
private val ported = PortedStatus.ASSUMED_PORTED
private val callerType = CallerType.CONSUMER
private val firstName = "Max"
private val lastName = "Mustermann"
private val callerName = "$firstName $lastName"
private val originalNetworkCode = "12345"
private val originalName = "Acme Inc"
private val originalCountry = "CA"
private val originalNetworkType = NetworkType.PAGER
private val currentNetworkCode = networkCode
private val currentName = "Nexmo"
private val currentCountry = countryCode
private val currentNetworkType = NetworkType.LANDLINE_PREMIUM
private val roamingStatus = RoamingStatus.ROAMING
private val roamingCountryCode = "DE"
private val roamingNetworkCode = "26201"
private val roamingNetworkName = "Telekom Deutschland GmbH"
private val lookupOutcomeMessage = "Partial success - some fields populated"
private val validNumber = Validity.INFERRED_NOT_VALID
private val active = true
private val handsetStatus = "On"


private enum class InsightType {
BASIC, STANDARD, ADVANCED, ADVANCED_ASYNC
}

private fun mockInsight(type: InsightType, optionalParams: Boolean = false) {
val expectedRequestParams = mutableMapOf<String, Any>("number" to toNumber)
if (optionalParams) {
expectedRequestParams["country"] = countryCode
if (type != InsightType.BASIC) {
expectedRequestParams["cnam"] = cnam
}
if (type == InsightType.ADVANCED) {
expectedRequestParams["real_time_data"] = realTimeData
}
}

val expectedResponseParams = mutableMapOf<String, Any>(
"status" to 0,
"request_id" to testUuidStr,
"status_message" to statusMessage
)
if (type != InsightType.ADVANCED_ASYNC) {
expectedResponseParams.putAll(
mapOf(
"international_format_number" to toNumber,
"national_format_number" to nationalNumber,
"country_code" to countryCode,
"country_code_iso3" to countryCodeIso3,
"country_name" to countryName,
"country_prefix" to countryPrefix
)
)
}

if (type != InsightType.BASIC) {
expectedResponseParams.putAll(mapOf(
"request_price" to requestPrice,
"remaining_balance" to remainingBalance
))

if (type == InsightType.ADVANCED_ASYNC) {
expectedResponseParams.putAll(mapOf(
"number" to toNumber,
"error_text" to statusMessage
))
}
else {
val callerIdentity = mapOf(
"caller_name" to callerName,
"last_name" to lastName,
"first_name" to firstName,
"caller_type" to callerType.name.lowercase()
)

expectedResponseParams.putAll(
mapOf(
"refund_price" to refundPrice,
"current_carrier" to mapOf(
"network_code" to currentNetworkCode,
"name" to currentName,
"country" to currentCountry,
"network_type" to currentNetworkType
),
"ported" to ported.name.lowercase(),
"original_carrier" to mapOf(
"network_code" to originalNetworkCode,
"name" to originalName,
"country" to originalCountry,
"network_type" to originalNetworkType
),
"caller_identity" to callerIdentity
)
)

if (type == InsightType.STANDARD) {
expectedResponseParams.putAll(callerIdentity)
}
}
}

if (type == InsightType.ADVANCED) {
expectedResponseParams.putAll(mapOf(
"roaming" to mapOf(
"status" to roamingStatus.name.lowercase(),
"roaming_country_code" to roamingCountryCode,
"roaming_network_code" to roamingNetworkCode,
"roaming_network_name" to roamingNetworkName
),
"reachable" to reachable,
"lookup_outcome" to 1,
"lookup_outcome_message" to lookupOutcomeMessage,
"valid_number" to validNumber.name.lowercase(),
"real_time_data" to mapOf(
"active_status" to active,
"handset_status" to handsetStatus
)
))
}

mockPostQueryParams(
expectedUrl = "/ni/${type.name.lowercase().replace('_', '/')}/json",
expectedRequestParams = expectedRequestParams,
expectedResponseParams = expectedResponseParams
)
}

private fun assertBasicResponse(response: BasicInsightResponse) {
assertNotNull(response)
assertEquals(InsightStatus.SUCCESS, response.status)
assertEquals(statusMessage, response.statusMessage)
assertEquals(testUuidStr, response.requestId)
assertEquals(toNumber, response.internationalFormatNumber)
assertEquals(nationalNumber, response.nationalFormatNumber)
assertEquals(countryCode, response.countryCode)
assertEquals(countryCodeIso3, response.countryCodeIso3)
assertEquals(countryName, response.countryName)
assertEquals(countryPrefix, response.countryPrefix)
}

private fun assertStandardResponse(response: StandardInsightResponse) {
assertBasicResponse(response)
assertEquals(BigDecimal(requestPrice), response.requestPrice)
assertEquals(BigDecimal(refundPrice), response.refundPrice)
assertEquals(BigDecimal(remainingBalance), response.remainingBalance)
assertEquals(ported, response.ported)
if (response::class == StandardInsightResponse::class) {
assertEquals(firstName, response.firstName)
assertEquals(lastName, response.lastName)
assertEquals(callerName, response.callerName)
assertEquals(callerType, response.callerType)
}
val callerIdentity = response.callerIdentity
assertNotNull(callerIdentity)
assertEquals(firstName, callerIdentity.firstName)
assertEquals(lastName, callerIdentity.lastName)
assertEquals(callerName, callerIdentity.name)
assertEquals(callerType, callerIdentity.type)
val currentCarrier = response.currentCarrier
assertNotNull(currentCarrier)
assertEquals(currentName, currentCarrier.name)
assertEquals(currentCountry, currentCarrier.country)
assertEquals(currentNetworkType, currentCarrier.networkType)
assertEquals(currentNetworkCode, currentCarrier.networkCode)
val originalCarrier = response.originalCarrier
assertNotNull(originalCarrier)
assertEquals(originalName, originalCarrier.name)
assertEquals(originalCountry, originalCarrier.country)
assertEquals(originalNetworkType, originalCarrier.networkType)
assertEquals(originalNetworkCode, originalCarrier.networkCode)
}

private fun assertAdvancedResponse(response: AdvancedInsightResponse) {
assertStandardResponse(response)
assertEquals(reachable, response.reachability)
assertEquals(LookupOutcome.PARTIAL_SUCCESS, response.lookupOutcome)
assertEquals(lookupOutcomeMessage, response.lookupOutcomeMessage)
assertEquals(validNumber, response.validNumber)
val rtd = response.realTimeData
assertNotNull(rtd)
assertEquals(active, rtd.activeStatus)
assertEquals(handsetStatus, rtd.handsetStatus)
val roaming = response.roaming
assertNotNull(roaming)
assertEquals(roamingStatus, roaming.status)
assertEquals(roamingCountryCode, roaming.roamingCountryCode)
assertEquals(roamingNetworkCode, roaming.roamingNetworkCode)
assertEquals(roamingNetworkName, roaming.roamingNetworkName)
}

@Test
fun `basic insight required params`() {
mockInsight(InsightType.BASIC, false)
assertBasicResponse(niClient.basic(toNumber))
}

@Test
fun `basic insight all params`() {
mockInsight(InsightType.BASIC, true)
assertBasicResponse(niClient.basic(toNumber, countryCode))
}

@Test
fun `standard insight required params`() {
mockInsight(InsightType.STANDARD, false)
assertStandardResponse(niClient.standard(toNumber))
}

@Test
fun `standard insight all params`() {
mockInsight(InsightType.STANDARD, true)
assertStandardResponse(niClient.standard(toNumber, countryCode, cnam))
}

@Test
fun `advanced insight required params`() {
mockInsight(InsightType.ADVANCED, false)
assertAdvancedResponse(niClient.advanced(toNumber))
}

@Test
fun `advanced insight all params`() {
mockInsight(InsightType.ADVANCED, true)
assertAdvancedResponse(niClient.advanced(toNumber, countryCode, cnam, realTimeData))
}

@Test
fun `advanced async insight required params`() {
mockInsight(InsightType.ADVANCED_ASYNC, false)
niClient.advancedAsync(toNumber, callbackUrl)
}

@Test
fun `advanced async insight all params`() {
mockInsight(InsightType.ADVANCED_ASYNC, true)
niClient.advancedAsync(toNumber, callbackUrl, countryCode, cnam)
}
}