From 3237fe5dc6737fa5c2e2b7a55bfa4dee75e12d37 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Piotr=20Poniedzia=C5=82ek?= Date: Wed, 20 Dec 2023 11:28:15 +0100 Subject: [PATCH] Add unit tests for `Alert` --- .../Alert.scala | 6 +- .../Monitoring.scala | 2 +- .../AlertSpec.scala | 121 ++++++++++++++++++ 3 files changed, 125 insertions(+), 4 deletions(-) create mode 100644 modules/core/src/test/scala/com.snowplowanalytics.snowplow.snowflake/AlertSpec.scala diff --git a/modules/core/src/main/scala/com.snowplowanalytics.snowplow.snowflake/Alert.scala b/modules/core/src/main/scala/com.snowplowanalytics.snowplow.snowflake/Alert.scala index 8b01e59..5a3ef5f 100644 --- a/modules/core/src/main/scala/com.snowplowanalytics.snowplow.snowflake/Alert.scala +++ b/modules/core/src/main/scala/com.snowplowanalytics.snowplow.snowflake/Alert.scala @@ -14,7 +14,7 @@ sealed trait Alert object Alert { /** Restrict the length of an alert message to be compliant with alert iglu schema */ - private val MaxAlertPayloadLength = 4096 // TODO increase? + private val MaxAlertPayloadLength = 4096 final case class FailedToCreateEventsTable(cause: Throwable) extends Alert final case class FailedToAddColumns(columns: List[String], cause: Throwable) extends Alert @@ -23,14 +23,14 @@ object Alert { def toSelfDescribingJson( alert: Alert, appInfo: AppInfo, - config: Config.Webhook + tags: Map[String, String] ): Json = SelfDescribingData( schema = SchemaKey("com.snowplowanalytics.monitoring.loader", "alert", "jsonschema", SchemaVer.Full(1, 0, 0)), data = Json.obj( "application" -> s"${appInfo.name}-${appInfo.version}".asJson, "message" -> getMessage(alert).asJson, - "tags" -> config.tags.asJson + "tags" -> tags.asJson ) ).normalize diff --git a/modules/core/src/main/scala/com.snowplowanalytics.snowplow.snowflake/Monitoring.scala b/modules/core/src/main/scala/com.snowplowanalytics.snowplow.snowflake/Monitoring.scala index 57daa6d..4f9dbe0 100644 --- a/modules/core/src/main/scala/com.snowplowanalytics.snowplow.snowflake/Monitoring.scala +++ b/modules/core/src/main/scala/com.snowplowanalytics.snowplow.snowflake/Monitoring.scala @@ -36,7 +36,7 @@ object Monitoring { def buildHttpRequest(webhookConfig: Config.Webhook, alert: Alert): Request[F] = Request[F](Method.POST, webhookConfig.endpoint) - .withEntity(Alert.toSelfDescribingJson(alert, appInfo, webhookConfig)) + .withEntity(Alert.toSelfDescribingJson(alert, appInfo, webhookConfig.tags)) def executeHttpRequest( webhookConfig: Config.Webhook, diff --git a/modules/core/src/test/scala/com.snowplowanalytics.snowplow.snowflake/AlertSpec.scala b/modules/core/src/test/scala/com.snowplowanalytics.snowplow.snowflake/AlertSpec.scala new file mode 100644 index 0000000..b07cf1a --- /dev/null +++ b/modules/core/src/test/scala/com.snowplowanalytics.snowplow.snowflake/AlertSpec.scala @@ -0,0 +1,121 @@ +package com.snowplowanalytics.snowplow.snowflake + +import com.snowplowanalytics.snowplow.runtime.AppInfo +import io.circe.{Json, parser} +import org.specs2.Specification +import org.specs2.matcher.MatchResult + +import java.sql.SQLException + +class AlertSpec extends Specification { + + private val testAppInfo: AppInfo = new AppInfo { + override val name: String = "testApp" + override val version: String = "testVersion" + override val dockerAlias: String = "test-docker-alias" + override val cloud: String = "test-cloud" + } + + private val configuredTags = Map( + "testTag1" -> "testValue1", + "testTag2" -> "testValue2" + ) + + def is = s2""" + Serializing alerts should produce valid self-describing JSON for: + FailedToCreateEventsTable alert $e1 + FailedToAddColumns alert $e2 + FailedToOpenSnowflakeChannel alert $e3 + Alert with SQL exception as a cause $e4 + Alert with nested exceptions as a cause $e5 + Alert with long message which should be trimmed to hardcoded max length $e6 + """ + + def expectedFullAlertBody(message: String): String = + s""" + |{ + | "schema" : "iglu:com.snowplowanalytics.monitoring.loader/alert/jsonschema/1-0-0", + | "data" : { + | "application" : "testApp-testVersion", + | "message" : "$message", + | "tags" : { + | "testTag1" : "testValue1", + | "testTag2" : "testValue2" + | } + | } + |} + """.stripMargin + + def e1: MatchResult[Json] = { + val cause = new RuntimeException("Some details from exception") + + assert( + inputAlert = Alert.FailedToCreateEventsTable(cause), + expectedAlertMessage = "Failed to create events table: Some details from exception" + ) + } + + def e2: MatchResult[Json] = { + val cause = new RuntimeException("Some details from exception") + val columns = List("unstruct_event_com_example_schema_1", "context_com_example_schema_2") + + assert( + inputAlert = Alert.FailedToAddColumns(columns, cause), + expectedAlertMessage = + "Failed to add columns: [unstruct_event_com_example_schema_1,context_com_example_schema_2]. Cause: Some details from exception" + ) + } + + def e3: MatchResult[Json] = { + val cause = new RuntimeException("Some details from exception") + + assert( + inputAlert = Alert.FailedToOpenSnowflakeChannel(cause), + expectedAlertMessage = "Failed to open Snowflake channel: Some details from exception" + ) + } + + def e4: MatchResult[Json] = { + val cause = new SQLException("Schema 'DB.TEST-SCHEMA' doesn't exist or not authorized", "SOME_SQL_STATE", 2003) + assert( + inputAlert = Alert.FailedToCreateEventsTable(cause), + expectedAlertMessage = + """Failed to create events table: Schema 'DB.TEST-SCHEMA' doesn't exist or not authorized = SqlState: SOME_SQL_STATE""" + ) + } + + def e5: MatchResult[Json] = { + val cause1 = new RuntimeException("Details from cause 1") + val cause2 = new RuntimeException("Details from cause 2", cause1) + val cause3 = new RuntimeException("Details from cause 3", cause2) + + assert( + inputAlert = Alert.FailedToCreateEventsTable(cause3), + expectedAlertMessage = "Failed to create events table: Details from cause 3: Details from cause 2: Details from cause 1" + ) + } + + def e6: MatchResult[Json] = { + val cause = new RuntimeException("A" * 5000) + + assert( + inputAlert = Alert.FailedToCreateEventsTable(cause), + expectedAlertMessage = + // Limit is 4096, so 31 chars from 'Failed to create events table: ' + 4065 from 'A's + s"Failed to create events table: ${"A" * 4065}" + ) + } + + private def assert(inputAlert: Alert, expectedAlertMessage: String): MatchResult[Json] = { + val inputJson = Alert.toSelfDescribingJson( + inputAlert, + testAppInfo, + configuredTags + ) + val outputJson = parser + .parse(expectedFullAlertBody(expectedAlertMessage)) + .getOrElse(throw new IllegalArgumentException("Invalid JSON")) + + inputJson must beEqualTo(outputJson) + } +}