From ddb75efebe10b99c6cbdf801d7162cda0b565dcc Mon Sep 17 00:00:00 2001 From: Josep Boix Requesens Date: Mon, 11 Nov 2024 17:12:30 +0100 Subject: [PATCH] feat: index user ip field and define required properties - Added `user_ip` as an indexed field. - Added required fields: indexing will fail if any of the required fields is missing. --- .../monitoring/event/model/EventRequest.kt | 15 +++-- src/main/resources/application.yml | 3 + .../event/model/EventRequestTest.kt | 67 +++++++++++++++++++ 3 files changed, 81 insertions(+), 4 deletions(-) diff --git a/src/main/kotlin/ch/srgssr/pillarbox/monitoring/event/model/EventRequest.kt b/src/main/kotlin/ch/srgssr/pillarbox/monitoring/event/model/EventRequest.kt index 06ee1fe..1898df7 100644 --- a/src/main/kotlin/ch/srgssr/pillarbox/monitoring/event/model/EventRequest.kt +++ b/src/main/kotlin/ch/srgssr/pillarbox/monitoring/event/model/EventRequest.kt @@ -23,6 +23,7 @@ import org.springframework.data.elasticsearch.annotations.FieldType * @property sessionId The ID of the session associated with the event. * @property eventName The name of the event. * @property timestamp The timestamp of the event in epoch milliseconds. + * @property ip The ip address of the client that generated the event. * @property data Additional data associated with the event. * @property session Session data associated with the event, potentially updated later. */ @@ -31,18 +32,24 @@ data class EventRequest( @Id @JsonIgnore var id: String? = null, - @JsonProperty("session_id") + @JsonProperty("session_id", required = true) @Field("session_id") var sessionId: String, - @JsonProperty("event_name") + @JsonProperty("event_name", required = true) @Field("event_name") var eventName: String, @Field(type = FieldType.Date, format = [DateFormat.epoch_millis], name = "@timestamp") + @JsonProperty(required = true) var timestamp: Long, + @JsonProperty("user_ip") + @Field("user_ip") + var ip: String?, + @JsonProperty(required = true) var version: Long, @JsonDeserialize(using = DataDeserializer::class) - var data: Any? = null, - var session: Any? = null, + @JsonProperty(required = true) + var data: Any, + var session: Any?, ) /** diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index acad0d2..fc84f5c 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -1,6 +1,9 @@ spring: threads.virtual.enabled: true application.name: pillarbox-monitoring-transfer + jackson.deserialization: + fail-on-null-for-primitives: true + pillarbox.monitoring: dispatch.uri: "http://localhost:8080/events" diff --git a/src/test/kotlin/ch/srgssr/pillarbox/monitoring/event/model/EventRequestTest.kt b/src/test/kotlin/ch/srgssr/pillarbox/monitoring/event/model/EventRequestTest.kt index bb5bddb..0852058 100644 --- a/src/test/kotlin/ch/srgssr/pillarbox/monitoring/event/model/EventRequestTest.kt +++ b/src/test/kotlin/ch/srgssr/pillarbox/monitoring/event/model/EventRequestTest.kt @@ -1,7 +1,9 @@ package ch.srgssr.pillarbox.monitoring.event.model +import com.fasterxml.jackson.databind.JsonMappingException import com.fasterxml.jackson.databind.ObjectMapper import com.fasterxml.jackson.module.kotlin.readValue +import io.kotest.assertions.throwables.shouldThrow import io.kotest.core.spec.style.ShouldSpec import io.kotest.matchers.shouldBe import org.springframework.boot.test.context.SpringBootTest @@ -10,6 +12,68 @@ import org.springframework.boot.test.context.SpringBootTest class EventRequestTest( private val objectMapper: ObjectMapper, ) : ShouldSpec({ + should("deserialize successfully if all required fields are present") { + // Given: an event as json + val jsonInput = + """ + { + "session_id": "12345", + "event_name": "START", + "timestamp": 1630000000000, + "user_ip": "127.0.0.1", + "version": 1, + "data": { } + } + } + """.trimIndent() + + // When: the event is deserialized + val eventRequest = objectMapper.readValue(jsonInput) + + // Then: The data of the event should be correctly parsed. + eventRequest.sessionId shouldBe "12345" + eventRequest.eventName shouldBe "START" + eventRequest.timestamp shouldBe 1630000000000 + eventRequest.ip shouldBe "127.0.0.1" + eventRequest.version shouldBe 1 + } + + context("fail to deserialize if missing any required field") { + val baseJson = + mapOf( + "session_id" to "\"12345\"", + "event_name" to "\"START\"", + "timestamp" to "1630000000000", + "version" to "1", + "data" to "{}", + ) + + baseJson.keys.forEach { missingField -> + should("should fail if $missingField is missing") { + val jsonInput = + baseJson + .filterKeys { it != missingField } // Exclude the current field + .map { (key, value) -> "\"$key\": $value" } + .joinToString(prefix = "{", postfix = "}") + + shouldThrow { + objectMapper.readValue(jsonInput) + } + } + should("should fail if $missingField is null") { + val jsonInput = + baseJson + .map { (key, value) -> + "\"$key\": ${if (key == missingField) "null" else value}" + }.joinToString(prefix = "{", postfix = "}") + + shouldThrow { + objectMapper.readValue(jsonInput) + } + } + } + } + should("deserialize an event and resolve user agent") { // Given: an input with a user agent val jsonInput = @@ -18,6 +82,7 @@ class EventRequestTest( "session_id": "12345", "event_name": "START", "timestamp": 1630000000000, + "user_ip": "127.0.0.1", "version": 1, "data": { "browser": { @@ -55,6 +120,7 @@ class EventRequestTest( "session_id": "12345", "event_name": "START", "timestamp": 1630000000000, + "user_ip": "127.0.0.1", "version": 1, "data": { "browser": { @@ -92,6 +158,7 @@ class EventRequestTest( "session_id": "12345", "event_name": "START", "timestamp": 1630000000000, + "user_ip": "127.0.0.1", "version": 1, "data": { "browser": {