diff --git a/CHANGELOG.md b/CHANGELOG.md
index 9ee0ffc..aec7db4 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -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
diff --git a/README.md b/README.md
index 7e38ec9..3f32d03 100644
--- a/README.md
+++ b/README.md
@@ -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)
diff --git a/pom.xml b/pom.xml
index d149214..c9fbb15 100644
--- a/pom.xml
+++ b/pom.xml
@@ -5,7 +5,7 @@
com.vonage
server-sdk-kotlin
- 0.4.0
+ 0.5.0
Vonage Kotlin Server SDK
Kotlin client for Vonage APIs
@@ -59,7 +59,7 @@
com.vonage
server-sdk
- 8.9.3
+ 8.9.4
org.jetbrains.kotlin
diff --git a/src/main/kotlin/com/vonage/client/kt/NumberInsight.kt b/src/main/kotlin/com/vonage/client/kt/NumberInsight.kt
new file mode 100644
index 0000000..a12f7b7
--- /dev/null
+++ b/src/main/kotlin/com/vonage/client/kt/NumberInsight.kt
@@ -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()
+ )
+ }
+}
diff --git a/src/main/kotlin/com/vonage/client/kt/Redact.kt b/src/main/kotlin/com/vonage/client/kt/Redact.kt
index d66d347..d7e5a4f 100644
--- a/src/main/kotlin/com/vonage/client/kt/Redact.kt
+++ b/src/main/kotlin/com/vonage/client/kt/Redact.kt
@@ -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)
- }
}
diff --git a/src/main/kotlin/com/vonage/client/kt/Vonage.kt b/src/main/kotlin/com/vonage/client/kt/Vonage.kt
index 1187f63..8074596 100644
--- a/src/main/kotlin/com/vonage/client/kt/Vonage.kt
+++ b/src/main/kotlin/com/vonage/client/kt/Vonage.kt
@@ -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 {
diff --git a/src/test/kotlin/com/vonage/client/kt/AbstractTest.kt b/src/test/kotlin/com/vonage/client/kt/AbstractTest.kt
index 92b49ce..6ad88bb 100644
--- a/src/test/kotlin/com/vonage/client/kt/AbstractTest.kt
+++ b/src/test/kotlin/com/vonage/client/kt/AbstractTest.kt
@@ -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(
diff --git a/src/test/kotlin/com/vonage/client/kt/NumberInsightTest.kt b/src/test/kotlin/com/vonage/client/kt/NumberInsightTest.kt
new file mode 100644
index 0000000..a82210c
--- /dev/null
+++ b/src/test/kotlin/com/vonage/client/kt/NumberInsightTest.kt
@@ -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("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(
+ "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)
+ }
+}
\ No newline at end of file