diff --git a/.github/workflows/quality.yml b/.github/workflows/quality.yml index 2409f04..b719d04 100644 --- a/.github/workflows/quality.yml +++ b/.github/workflows/quality.yml @@ -31,6 +31,7 @@ jobs: paths: ${{ github.workspace }}/**/build/reports/kover/report.xml token: ${{ secrets.GITHUB_TOKEN }} update-comment: true + title: Coverage Report min-coverage-changed-files: 0 min-coverage-overall: 0 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..c111d84 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("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("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": {