diff --git a/README.md b/README.md
index 3e8cff9..cbbe6f0 100644
--- a/README.md
+++ b/README.md
@@ -92,6 +92,41 @@ public NexmoClient.Builder customNexmoBuilder() {
```
> Note that you must include your credentials as shown in this example. This builder completely replaces the automatically configured one.
+## Listen for Incoming Events
+
+The starter will register a few different routes for accepting webhooks from Nexmo. You can hook into these events by creating an event listener.
+
+To listen for incoming SMS events, for example:
+
+```java
+import com.nexmo.client.incoming.MessageEvent;
+import com.nexmo.starter.events.SmsMessageReceivedEvent;
+import org.springframework.context.event.EventListener;
+import org.springframework.stereotype.Component;
+
+@Component
+class SmsEventListener {
+ @EventListener
+ public void onIncomingSms(SmsMessageReceivedEvent event) {
+ MessageEvent message = event.getMessage();
+ }
+}
+```
+
+By default, the SMS event webhook is configured on the `/webhooks/sms` path. This can be customized via the `application.properties`:
+
+```properties
+nexmo.webhooks.incoming-sms-endpoint=/foo/bar
+```
+
+This webhook will need to be configured in the [Nexmo Dashboard](https://dashboard.nexmo.com).
+
+You can also disable the webhook controllers:
+
+```properties
+nexmo.webhooks.disabled=true
+```
+
## Customize Nexmo Client Version
By default, the Nexmo Spring Boot Starter will transitively define Nexmo Client to the latest version at its release. You can override this by adding a dependency on the Nexmo Client, bringing in `4.2.0` for example:
diff --git a/nexmo-spring-boot-autoconfigure/pom.xml b/nexmo-spring-boot-autoconfigure/pom.xml
index ff715b9..65a5346 100644
--- a/nexmo-spring-boot-autoconfigure/pom.xml
+++ b/nexmo-spring-boot-autoconfigure/pom.xml
@@ -64,6 +64,13 @@
true
+
+ org.springframework.boot
+ spring-boot-starter-web
+ ${spring-boot.version}
+ true
+
+
com.nexmo
client
@@ -95,6 +102,13 @@
3.12.2
test
+
+
+ com.nhaarman.mockitokotlin2
+ mockito-kotlin
+ 2.1.0
+ test
+
diff --git a/nexmo-spring-boot-autoconfigure/src/main/kotlin/com/nexmo/starter/NexmoAutoConfiguration.kt b/nexmo-spring-boot-autoconfigure/src/main/kotlin/com/nexmo/starter/NexmoAutoConfiguration.kt
index c916664..2a3f2a7 100644
--- a/nexmo-spring-boot-autoconfigure/src/main/kotlin/com/nexmo/starter/NexmoAutoConfiguration.kt
+++ b/nexmo-spring-boot-autoconfigure/src/main/kotlin/com/nexmo/starter/NexmoAutoConfiguration.kt
@@ -44,37 +44,35 @@ import org.springframework.context.annotation.Lazy
@Configuration
@ConditionalOnClass(NexmoClient::class)
@EnableConfigurationProperties(NexmoCredentialsProperties::class)
-open class NexmoAutoConfiguration(
- private val nexmoProperties: NexmoCredentialsProperties
-) {
+open class NexmoAutoConfiguration(private val nexmoCredentialsProperties: NexmoCredentialsProperties) {
@Bean
@ConditionalOnMissingBean
@ConditionalOnProperty(prefix = "nexmo.creds", value = ["api-key", "secret"])
@Lazy
@NotNull
open fun nexmoBuilder(): NexmoClient.Builder {
- if (nexmoProperties.privateKeyContents.isNotBlank() && nexmoProperties.privateKeyPath.isNotBlank()) {
+ if (nexmoCredentialsProperties.privateKeyContents.isNotBlank() && nexmoCredentialsProperties.privateKeyPath.isNotBlank()) {
throw IllegalArgumentException("Found both private key path and private key contents. Only one option can be used at a time.")
}
val builder = NexmoClient.builder()
- .apiKey(nexmoProperties.apiKey)
- .apiSecret(nexmoProperties.secret)
+ .apiKey(nexmoCredentialsProperties.apiKey)
+ .apiSecret(nexmoCredentialsProperties.secret)
- if (nexmoProperties.privateKeyContents.isNotBlank()) {
- builder.privateKeyContents(nexmoProperties.privateKeyContents)
+ if (nexmoCredentialsProperties.privateKeyContents.isNotBlank()) {
+ builder.privateKeyContents(nexmoCredentialsProperties.privateKeyContents)
}
- if (nexmoProperties.privateKeyPath.isNotBlank()) {
- builder.privateKeyPath(nexmoProperties.privateKeyPath)
+ if (nexmoCredentialsProperties.privateKeyPath.isNotBlank()) {
+ builder.privateKeyPath(nexmoCredentialsProperties.privateKeyPath)
}
- if (nexmoProperties.applicationId.isNotBlank()) {
- builder.applicationId(nexmoProperties.applicationId)
+ if (nexmoCredentialsProperties.applicationId.isNotBlank()) {
+ builder.applicationId(nexmoCredentialsProperties.applicationId)
}
- if (nexmoProperties.signature.isNotBlank()) {
- builder.signatureSecret(nexmoProperties.signature)
+ if (nexmoCredentialsProperties.signature.isNotBlank()) {
+ builder.signatureSecret(nexmoCredentialsProperties.signature)
}
return builder
diff --git a/nexmo-spring-boot-autoconfigure/src/main/kotlin/com/nexmo/starter/NexmoProperties.kt b/nexmo-spring-boot-autoconfigure/src/main/kotlin/com/nexmo/starter/NexmoCredentialsProperties.kt
similarity index 100%
rename from nexmo-spring-boot-autoconfigure/src/main/kotlin/com/nexmo/starter/NexmoProperties.kt
rename to nexmo-spring-boot-autoconfigure/src/main/kotlin/com/nexmo/starter/NexmoCredentialsProperties.kt
diff --git a/nexmo-spring-boot-autoconfigure/src/main/kotlin/com/nexmo/starter/events/SmsMessageReceivedEvent.kt b/nexmo-spring-boot-autoconfigure/src/main/kotlin/com/nexmo/starter/events/SmsMessageReceivedEvent.kt
new file mode 100644
index 0000000..8e1f06e
--- /dev/null
+++ b/nexmo-spring-boot-autoconfigure/src/main/kotlin/com/nexmo/starter/events/SmsMessageReceivedEvent.kt
@@ -0,0 +1,27 @@
+/*
+ * Copyright (c) 2011-2019 Nexmo Inc
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package com.nexmo.starter.events
+
+import com.nexmo.client.incoming.MessageEvent
+import org.springframework.context.ApplicationEvent
+
+class SmsMessageReceivedEvent(source: Any, val message: MessageEvent) : ApplicationEvent(source)
diff --git a/nexmo-spring-boot-autoconfigure/src/main/kotlin/com/nexmo/starter/webhooks/NexmoWebhookAutoConfiguration.kt b/nexmo-spring-boot-autoconfigure/src/main/kotlin/com/nexmo/starter/webhooks/NexmoWebhookAutoConfiguration.kt
new file mode 100644
index 0000000..581e1e2
--- /dev/null
+++ b/nexmo-spring-boot-autoconfigure/src/main/kotlin/com/nexmo/starter/webhooks/NexmoWebhookAutoConfiguration.kt
@@ -0,0 +1,45 @@
+/*
+ * Copyright (c) 2011-2019 Nexmo Inc
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package com.nexmo.starter.webhooks
+
+import com.nexmo.client.NexmoClient
+import org.springframework.boot.autoconfigure.condition.ConditionalOnClass
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty
+import org.springframework.boot.context.properties.EnableConfigurationProperties
+import org.springframework.context.ApplicationEventPublisher
+import org.springframework.context.annotation.Bean
+import org.springframework.context.annotation.Configuration
+
+@Configuration
+@EnableConfigurationProperties(NexmoWebhookProperties::class)
+@ConditionalOnClass(NexmoClient::class)
+open class NexmoWebhookAutoConfiguration(private val nexmoWebhookProperties: NexmoWebhookProperties) {
+ @Bean
+ @ConditionalOnProperty(
+ prefix = "nexmo.webhooks",
+ value = ["disabled"],
+ havingValue = "false",
+ matchIfMissing = true
+ )
+ open fun smsMessageReceivedController(applicationEventPublisher: ApplicationEventPublisher) =
+ SmsMessageReceivedController(applicationEventPublisher)
+}
diff --git a/nexmo-spring-boot-autoconfigure/src/main/kotlin/com/nexmo/starter/webhooks/NexmoWebhookProperties.kt b/nexmo-spring-boot-autoconfigure/src/main/kotlin/com/nexmo/starter/webhooks/NexmoWebhookProperties.kt
new file mode 100644
index 0000000..5611961
--- /dev/null
+++ b/nexmo-spring-boot-autoconfigure/src/main/kotlin/com/nexmo/starter/webhooks/NexmoWebhookProperties.kt
@@ -0,0 +1,37 @@
+/*
+ * Copyright (c) 2011-2019 Nexmo Inc
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package com.nexmo.starter.webhooks
+
+import org.springframework.boot.context.properties.ConfigurationProperties
+
+@ConfigurationProperties(prefix = "nexmo.webhooks")
+data class NexmoWebhookProperties(
+ /**
+ * Set to true to disable the incoming webhook controllers from being automatically registered.
+ */
+ var disabled: Boolean = false,
+
+ /**
+ * Incoming SMS Endpoint
+ */
+ var incomingSmsEndpoint: String = SmsMessageReceivedController.DEFAULT_ENDPOINT
+)
diff --git a/nexmo-spring-boot-autoconfigure/src/main/kotlin/com/nexmo/starter/webhooks/SmsMessageReceivedController.kt b/nexmo-spring-boot-autoconfigure/src/main/kotlin/com/nexmo/starter/webhooks/SmsMessageReceivedController.kt
new file mode 100644
index 0000000..88c2aa3
--- /dev/null
+++ b/nexmo-spring-boot-autoconfigure/src/main/kotlin/com/nexmo/starter/webhooks/SmsMessageReceivedController.kt
@@ -0,0 +1,77 @@
+/*
+ * Copyright (c) 2011-2019 Nexmo Inc
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package com.nexmo.starter.webhooks
+
+import com.fasterxml.jackson.databind.ObjectMapper
+import com.nexmo.client.incoming.MessageEvent
+import com.nexmo.starter.events.SmsMessageReceivedEvent
+import org.springframework.context.ApplicationEventPublisher
+import org.springframework.http.HttpStatus
+import org.springframework.http.MediaType
+import org.springframework.web.bind.annotation.*
+
+@RequestMapping
+@ResponseStatus(HttpStatus.OK)
+class SmsMessageReceivedController(private val applicationEventPublisher: ApplicationEventPublisher) {
+ /**
+ * Handle incoming SMS webhooks where the data is sent via a get request.
+ *
+ * Nexmo HTTP Method: GET
+ */
+ @GetMapping("\${nexmo.webhooks.incoming-sms-endpoint:$DEFAULT_ENDPOINT}")
+ fun get(@RequestParam parameters: Map) {
+ val messageEvent = MessageEvent.fromJson(ObjectMapper().writeValueAsString(parameters))
+ applicationEventPublisher.publishEvent(SmsMessageReceivedEvent(this, messageEvent))
+ }
+
+ /**
+ * Handle incoming SMS webhooks where the data is sent via a post request using form parameters.
+ *
+ * Nexmo HTTP Method: POST
+ */
+ @PostMapping(
+ "\${nexmo.webhooks.incoming-sms-endpoint:$DEFAULT_ENDPOINT}",
+ consumes = [MediaType.APPLICATION_FORM_URLENCODED_VALUE + ";charset=UTF-8"]
+ )
+ fun post(@RequestParam parameters: Map) {
+ val messageEvent = MessageEvent.fromJson(ObjectMapper().writeValueAsString(parameters))
+ applicationEventPublisher.publishEvent(SmsMessageReceivedEvent(this, messageEvent))
+ }
+
+ /**
+ * Handle incoming SMS webhooks where the data is sent via a post request and the body is JSON
+ *
+ * Nexmo HTTP Method: POST-JSON
+ */
+ @PostMapping(
+ "\${nexmo.webhooks.incoming-sms-endpoint:$DEFAULT_ENDPOINT}",
+ consumes = [MediaType.APPLICATION_JSON_VALUE]
+ )
+ fun post(@RequestBody json: String) {
+ val messageEvent = MessageEvent.fromJson(json)
+ applicationEventPublisher.publishEvent(SmsMessageReceivedEvent(this, messageEvent))
+ }
+
+ companion object {
+ const val DEFAULT_ENDPOINT = "/webhooks/sms"
+ }
+}
diff --git a/nexmo-spring-boot-autoconfigure/src/test/kotlin/com/nexmo/starter/webhooks/NexmoWebhookAutoConfigurationTest.kt b/nexmo-spring-boot-autoconfigure/src/test/kotlin/com/nexmo/starter/webhooks/NexmoWebhookAutoConfigurationTest.kt
new file mode 100644
index 0000000..b1b4173
--- /dev/null
+++ b/nexmo-spring-boot-autoconfigure/src/test/kotlin/com/nexmo/starter/webhooks/NexmoWebhookAutoConfigurationTest.kt
@@ -0,0 +1,58 @@
+/*
+ * Copyright (c) 2011-2019 Nexmo Inc
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package com.nexmo.starter.webhooks
+
+import org.assertj.core.api.Assertions.assertThat
+import org.junit.Test
+import org.springframework.boot.autoconfigure.AutoConfigurations
+import org.springframework.boot.test.context.runner.ApplicationContextRunner
+
+class NexmoWebhookAutoConfigurationTest {
+ val contextRunner = ApplicationContextRunner().withConfiguration(
+ AutoConfigurations.of(NexmoWebhookAutoConfiguration::class.java)
+ )
+
+ @Test
+ fun `when webhooks are disabled the incoming sms controller is not in the container`() {
+ contextRunner.withPropertyValues(
+ "nexmo.webhooks.disabled=true"
+ ).run {
+ assertThat(it).doesNotHaveBean(SmsMessageReceivedController::class.java)
+ }
+ }
+
+ @Test
+ fun `when webhooks are not disabled the incoming sms controller is in in the container`() {
+ contextRunner.withPropertyValues(
+ "nexmo.webhooks.disabled=false"
+ ).run {
+ assertThat(it).hasSingleBean(SmsMessageReceivedController::class.java)
+ }
+ }
+
+ @Test
+ fun `when webhooks disabled property is missing the incoming sms controller is in the container`() {
+ contextRunner.withPropertyValues().run {
+ assertThat(it).hasSingleBean(SmsMessageReceivedController::class.java)
+ }
+ }
+}
diff --git a/nexmo-spring-boot-autoconfigure/src/test/kotlin/com/nexmo/starter/webhooks/SmsMessageReceivedControllerTest.kt b/nexmo-spring-boot-autoconfigure/src/test/kotlin/com/nexmo/starter/webhooks/SmsMessageReceivedControllerTest.kt
new file mode 100644
index 0000000..ec3f5d3
--- /dev/null
+++ b/nexmo-spring-boot-autoconfigure/src/test/kotlin/com/nexmo/starter/webhooks/SmsMessageReceivedControllerTest.kt
@@ -0,0 +1,163 @@
+/*
+ * Copyright (c) 2011-2019 Nexmo Inc
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package com.nexmo.starter.webhooks
+
+import com.nexmo.client.incoming.MessageType
+import com.nexmo.starter.events.SmsMessageReceivedEvent
+import com.nhaarman.mockitokotlin2.argumentCaptor
+import com.nhaarman.mockitokotlin2.verify
+import junit.framework.Assert.assertEquals
+import junit.framework.Assert.assertTrue
+import org.junit.Before
+import org.junit.Test
+import org.mockito.Mockito.mock
+import org.springframework.context.ApplicationEventPublisher
+import java.text.SimpleDateFormat
+import java.util.*
+
+class SmsMessageReceivedControllerTest {
+ lateinit var controller: SmsMessageReceivedController
+ lateinit var applicationEventPublisher: ApplicationEventPublisher
+
+ @Before
+ fun setUp() {
+ applicationEventPublisher = mock(ApplicationEventPublisher::class.java)
+ controller = SmsMessageReceivedController(applicationEventPublisher)
+ }
+
+ @Test
+ fun `when the get method is called with a map of url parameters then an sms received event is fired`() {
+ val captor = argumentCaptor()
+
+ controller.get(
+ mapOf(
+ "msisdn" to "msisdn",
+ "to" to "to",
+ "text" to "text",
+ "type" to "TEXT",
+ "concat" to "true",
+ "concat-part" to "1",
+ "concat-ref" to "2",
+ "concat-total" to "3",
+ "data" to "data",
+ "keyword" to "keyword",
+ "messageId" to "message-id",
+ "message-timestamp" to "2019-06-21 14:14:01",
+ "nonce" to "nonce",
+ "udh" to "udh"
+ )
+ )
+
+ verify(applicationEventPublisher).publishEvent(captor.capture())
+ val event = captor.firstValue
+ assertEquals("msisdn", event.message.msisdn)
+ assertEquals("to", event.message.to)
+ assertEquals("text", event.message.text)
+ assertEquals(MessageType.TEXT, event.message.type)
+ assertTrue(event.message.concat)
+ assertEquals(1, event.message.concatPart)
+ assertEquals(2, event.message.concatRef)
+ assertEquals(3, event.message.concatTotal)
+ assertEquals("data", event.message.data)
+ assertEquals("keyword", event.message.keyword)
+ assertEquals("message-id", event.message.messageId)
+ assertEquals("nonce", event.message.nonce)
+ assertEquals("udh", event.message.udh)
+
+ val dateFormat = SimpleDateFormat("yyyy-MM-dd HH:mm:ss")
+ dateFormat.timeZone = TimeZone.getTimeZone("UTC")
+ assertEquals(dateFormat.parse("2019-06-21 14:14:01"), event.message.messageTimestamp)
+ }
+
+ @Test
+ fun `when the post method is called with a map of form parameters then an sms received event is fired`() {
+ val captor = argumentCaptor()
+
+ controller.post(
+ mapOf(
+ "msisdn" to "msisdn",
+ "to" to "to",
+ "text" to "text",
+ "type" to "TEXT",
+ "concat" to "true",
+ "concat-part" to "1",
+ "concat-ref" to "2",
+ "concat-total" to "3",
+ "data" to "data",
+ "keyword" to "keyword",
+ "messageId" to "message-id",
+ "message-timestamp" to "2019-06-21 14:14:01",
+ "nonce" to "nonce",
+ "udh" to "udh"
+ )
+ )
+
+ verify(applicationEventPublisher).publishEvent(captor.capture())
+ val event = captor.firstValue
+ assertEquals("msisdn", event.message.msisdn)
+ assertEquals("to", event.message.to)
+ assertEquals("text", event.message.text)
+ assertEquals(MessageType.TEXT, event.message.type)
+ assertTrue(event.message.concat)
+ assertEquals(1, event.message.concatPart)
+ assertEquals(2, event.message.concatRef)
+ assertEquals(3, event.message.concatTotal)
+ assertEquals("data", event.message.data)
+ assertEquals("keyword", event.message.keyword)
+ assertEquals("message-id", event.message.messageId)
+ assertEquals("nonce", event.message.nonce)
+ assertEquals("udh", event.message.udh)
+
+ val dateFormat = SimpleDateFormat("yyyy-MM-dd HH:mm:ss")
+ dateFormat.timeZone = TimeZone.getTimeZone("UTC")
+ assertEquals(dateFormat.parse("2019-06-21 14:14:01"), event.message.messageTimestamp)
+ }
+
+ @Test
+ fun `when the post method is called with a json parameters then an sms received event is fired`() {
+ val captor = argumentCaptor()
+
+ controller.post(
+ "{\"msisdn\":\"msisdn\",\"to\":\"to\",\"text\":\"text\",\"type\":\"TEXT\",\"concat\":\"true\",\"concat-part\":\"1\",\"concat-ref\":\"2\",\"concat-total\":\"3\",\"data\":\"data\",\"keyword\":\"keyword\",\"messageId\":\"message-id\",\"message-timestamp\":\"2019-06-21 14:14:01\",\"nonce\":\"nonce\",\"udh\":\"udh\"}"
+ )
+
+ verify(applicationEventPublisher).publishEvent(captor.capture())
+ val event = captor.firstValue
+ assertEquals("msisdn", event.message.msisdn)
+ assertEquals("to", event.message.to)
+ assertEquals("text", event.message.text)
+ assertEquals(MessageType.TEXT, event.message.type)
+ assertTrue(event.message.concat)
+ assertEquals(1, event.message.concatPart)
+ assertEquals(2, event.message.concatRef)
+ assertEquals(3, event.message.concatTotal)
+ assertEquals("data", event.message.data)
+ assertEquals("keyword", event.message.keyword)
+ assertEquals("message-id", event.message.messageId)
+ assertEquals("nonce", event.message.nonce)
+ assertEquals("udh", event.message.udh)
+
+ val dateFormat = SimpleDateFormat("yyyy-MM-dd HH:mm:ss")
+ dateFormat.timeZone = TimeZone.getTimeZone("UTC")
+ assertEquals(dateFormat.parse("2019-06-21 14:14:01"), event.message.messageTimestamp)
+ }
+}
diff --git a/nexmo-spring-boot-test-application/src/main/kotlin/com/nexmo/starter/SmsMessageReceivedEventListener.kt b/nexmo-spring-boot-test-application/src/main/kotlin/com/nexmo/starter/SmsMessageReceivedEventListener.kt
new file mode 100644
index 0000000..6be80f4
--- /dev/null
+++ b/nexmo-spring-boot-test-application/src/main/kotlin/com/nexmo/starter/SmsMessageReceivedEventListener.kt
@@ -0,0 +1,35 @@
+/*
+ * Copyright (c) 2011-2019 Nexmo Inc
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package com.nexmo.starter
+
+import com.nexmo.starter.events.SmsMessageReceivedEvent
+import org.springframework.context.event.EventListener
+import org.springframework.stereotype.Component
+
+@Component
+class SmsMessageReceivedEventListener {
+ @EventListener
+ fun onSmsReceivedEvent(messageReceivedEvent: SmsMessageReceivedEvent) {
+ val message = messageReceivedEvent.message
+ println("(${message.msisdn}) -> ${message.to} ${message.text}")
+ }
+}