Add ability to serialize audit events into json

SshAuditEvent, HttpAuditEvent and ExtendedHttpAudit events can now be
serialized into json.
Audit events will be json serialized to then be persisted into elasticsearch

Feature: Issue 9866
Change-Id: I2b1ed9c80fc4269c40c664cd5edb2d11731492bb
diff --git a/auditlog/src/main/scala/com/gerritforge/analytics/auditlog/model/AuditEvent.scala b/auditlog/src/main/scala/com/gerritforge/analytics/auditlog/model/AuditEvent.scala
index c577e78..0f4a848 100644
--- a/auditlog/src/main/scala/com/gerritforge/analytics/auditlog/model/AuditEvent.scala
+++ b/auditlog/src/main/scala/com/gerritforge/analytics/auditlog/model/AuditEvent.scala
@@ -1,4 +1,5 @@
 package com.gerritforge.analytics.auditlog.model
+import org.json4s.jackson.Serialization.write
 import org.json4s.native.JsonMethods._
 import org.json4s.{DefaultFormats, _}
 
@@ -6,7 +7,7 @@
 
 object AuditEvent {
 
-  implicit private val formats = DefaultFormats
+  implicit private val formats = DefaultFormats + AuditUUID.serializer + AccountId.serializer
 
   def fromJsonString(json: String): Try[AuditEvent] = {
     Try(parse(json)).flatMap { jsValueEvent =>
@@ -21,6 +22,10 @@
       }
     }
   }
+
+  def toJsonString[T <: AuditEvent](auditEvent: T): String = {
+    compact(render(parse(write[T](auditEvent)).snakizeKeys))
+  }
 }
 
 sealed trait AuditEvent {
@@ -74,4 +79,22 @@
 )
 
 final case class AccountId(id: Int)
-final case class AuditUUID(uuid: String)
\ No newline at end of file
+object AccountId {
+  val serializer = new CustomSerializer[AccountId]( _ =>
+      (
+        { case JObject(JField("id", JInt(id)) :: Nil) => AccountId(id.intValue()) },
+        { case a: AccountId => JInt(a.id) }
+      )
+  )
+}
+
+final case class AuditUUID(uuid: String)
+
+object AuditUUID {
+  val serializer = new CustomSerializer[AuditUUID]( _ =>
+      (
+        { case JObject(JField("uuid", JString(uuid)) :: Nil) => AuditUUID(uuid) },
+        { case a: AuditUUID => JString(a.uuid) }
+      )
+  )
+}
\ No newline at end of file
diff --git a/auditlog/src/test/scala/com/gerritforge/analytics/auditlog/model/AuditEventSpec.scala b/auditlog/src/test/scala/com/gerritforge/analytics/auditlog/model/AuditEventSpec.scala
index a6ec23d..82c9988 100644
--- a/auditlog/src/test/scala/com/gerritforge/analytics/auditlog/model/AuditEventSpec.scala
+++ b/auditlog/src/test/scala/com/gerritforge/analytics/auditlog/model/AuditEventSpec.scala
@@ -1,11 +1,16 @@
 package com.gerritforge.analytics.auditlog.model
+import org.json4s.JsonAST.JValue
+import org.json4s.JsonDSL._
 import org.json4s.MappingException
 import org.json4s.ParserUtil.ParseException
-import org.scalatest.{FlatSpec, Inside, Matchers}
+import org.json4s.native.JsonMethods._
 import org.scalatest.TryValues._
+import org.scalatest.{FlatSpec, Inside, Matchers}
 
 class AuditEventSpec extends FlatSpec with Matchers with Inside {
 
+  behavior of "fromJsonString"
+
   "parsing a string that is not json" should "result in a ParseException failure" in {
     val someJson = """this_is_not_a_valid_json"""
     AuditEvent.fromJsonString(someJson).failure.exception shouldBe a[ParseException]
@@ -209,4 +214,89 @@
         gotUUID.uuid   shouldBe auditUUID
     }
   }
+
+  behavior of "toJsonString"
+
+  "an HttpAuditEvent" should "be serializable into json" in {
+
+    val httpMethod = "GET"
+    val httpStatus = 200
+    val sessionId = "someSessionId"
+    val who = CurrentUser(accessPath = "UNKNOWN", accountId = None)
+    val timeAtStart = 1000L
+    val what="https://review.gerrithub.io/Mirantis/tcp-qa/git-upload-pack"
+    val elapsed = 22
+    val uuid = AuditUUID("audit:5f10fea5-35d1-4252-b86f-99db7a9b549b")
+
+    val event = HttpAuditEvent(httpMethod, httpStatus, sessionId, who, timeAtStart, what, elapsed, uuid)
+
+    val expectedJson: JValue =
+      ("http_method" -> httpMethod) ~
+        ("http_status" -> httpStatus) ~
+        ("session_id" -> sessionId) ~
+        ("who" ->
+          ("access_path" -> who.accessPath)
+        ) ~
+        ("time_at_start" -> timeAtStart) ~
+        ("what" -> what) ~
+        ("elapsed" -> elapsed) ~
+        ("uuid" -> uuid.uuid)
+
+    parse(AuditEvent.toJsonString(event)) shouldBe expectedJson
+  }
+
+  "an ExtendedHttpAuditEvent" should "be serializable into json" in {
+
+    val httpMethod = "GET"
+    val httpStatus = 200
+    val sessionId = "someSessionId"
+    val accountId = 123
+    val who = CurrentUser(accessPath = "/config/server/info", accountId = Some(AccountId(accountId)))
+    val timeAtStart = 1000L
+    val what="/config/server/info"
+    val elapsed = 22
+    val uuid = AuditUUID("audit:5f10fea5-35d1-4252-b86f-99db7a9b549b")
+
+    val event = ExtendedHttpAuditEvent(httpMethod, httpStatus, sessionId, who, timeAtStart, what, elapsed, uuid)
+
+    val expectedJson: JValue =
+      ("http_method" -> httpMethod) ~
+      ("http_status" -> httpStatus) ~
+      ("session_id" -> sessionId) ~
+      ("who" ->
+        ("access_path" -> who.accessPath) ~
+        ("account_id" -> accountId)
+      ) ~
+      ("time_at_start" -> timeAtStart) ~
+      ("what" -> what) ~
+      ("elapsed" -> elapsed) ~
+      ("uuid" -> uuid.uuid)
+
+    parse(AuditEvent.toJsonString(event)) shouldBe expectedJson
+  }
+
+  "an SshAuditEvent" should "be serializable into json" in {
+    val sessionId   = "2adc5bef"
+    val accountId   = 1009124
+    val accessPath  = "SSH_COMMAND"
+    val timeAtStart = 1542240322369L
+    val what        = "gerrit.stream-events.-s.patchset-created.-s.change-restored.-s.comment-added"
+    val elapsed     = 12
+    val auditUUID   = "audit:dd74e098-9260-4720-9143-38a0a0a5e500"
+
+    val event = SshAuditEvent(sessionId, Some(CurrentUser(accessPath, Some(AccountId(accountId)))), timeAtStart, what, elapsed, AuditUUID(auditUUID))
+
+    val expectedJson: JValue =
+      ("session_id" -> sessionId) ~
+      ("who" ->
+        ("access_path" -> accessPath) ~
+        ("account_id" -> accountId)
+      ) ~
+      ("time_at_start" -> timeAtStart) ~
+      ("what" -> what) ~
+      ("elapsed" -> elapsed) ~
+      ("uuid" -> auditUUID)
+
+    parse(AuditEvent.toJsonString(event)) shouldBe expectedJson
+  }
 }
\ No newline at end of file