packages feed

nri-observability-0.1.0.0: tests/Spec/Reporter/File.hs

module Spec.Reporter.File (tests) where

import qualified Control.Exception.Safe as Exception
import qualified Data.Aeson as Aeson
import qualified Data.Aeson.Encode.Pretty
import qualified Data.ByteString.Lazy
import qualified Data.Text.Lazy
import qualified Data.Text.Lazy.Encoding
import qualified Data.Time.LocalTime as LocalTime
import qualified Dict
import qualified Expect
import qualified Log.HttpRequest as HttpRequest
import qualified Log.RedisCommands as RedisCommands
import qualified Log.SqlQuery as SqlQuery
import qualified Platform
import qualified Platform.Timer as Timer
import qualified Reporter.File.Internal as File
import qualified Test
import qualified Prelude

tests :: Test.Test
tests =
  Test.describe
    "Observability.FileSpec"
    [ logTest
        "logs span without details or children"
        emptyTracingSpan
          { Platform.name = "root-span",
            Platform.finished = ms 7
          },
      logTest
        "logs span that failed with an exception"
        emptyTracingSpan
          { Platform.name = "root-span",
            Platform.succeeded = Platform.FailedWith (Exception.SomeException (CustomException "help!"))
          },
      logTest
        "logs nested spans"
        emptyTracingSpan
          { Platform.name = "root-span",
            Platform.children =
              [ emptyTracingSpan
                  { Platform.name = "child1",
                    Platform.children =
                      [ emptyTracingSpan
                          { Platform.name = "grandchild",
                            Platform.children = []
                          }
                      ]
                  },
                emptyTracingSpan
                  { Platform.name = "child2",
                    Platform.children = []
                  }
              ]
          },
      logTest
        "logs information about an incoming http request"
        emptyTracingSpan
          { Platform.details =
              HttpRequest.emptyDetails
                { HttpRequest.httpVersion = Just "1",
                  HttpRequest.method = Just "GET",
                  HttpRequest.path = Just "/hats/5",
                  HttpRequest.queryString = Just "?top=flat",
                  HttpRequest.endpoint = Just "/hats/:hat_id",
                  HttpRequest.headers = Dict.fromList [("Accept", "application/json")],
                  HttpRequest.status = Just 500
                }
                |> HttpRequest.Incoming
                |> Platform.toTracingSpanDetails
                |> Just
          },
      logTest
        "logs information about an SQL query"
        emptyTracingSpan
          { Platform.details =
              SqlQuery.emptyDetails
                { SqlQuery.databaseType = Just SqlQuery.postgresql,
                  SqlQuery.query = Just (Log.mkSecret "SELECT name FROM ants WHERE id = ?"),
                  SqlQuery.queryTemplate = Just "SELECT name FROM ants WHERE id = ${id}",
                  SqlQuery.sqlOperation = Just "SELECT",
                  SqlQuery.queriedRelation = Just "ants",
                  SqlQuery.host = Just "http://antsonline.com",
                  SqlQuery.port = Just 5432,
                  SqlQuery.database = Just "nri",
                  SqlQuery.rowsReturned = Just 5
                }
                |> Platform.toTracingSpanDetails
                |> Just
          },
      logTest
        "logs information about a redis query"
        emptyTracingSpan
          { Platform.details =
              RedisCommands.emptyDetails
                { RedisCommands.host = Just "cache.noredink.com",
                  RedisCommands.port = Just 6379,
                  RedisCommands.commands = ["GET somekey"]
                }
                |> Platform.toTracingSpanDetails
                |> Just
          },
      logTest
        "logs information about an outoing http request"
        emptyTracingSpan
          { Platform.details =
              HttpRequest.emptyDetails
                { HttpRequest.httpVersion = Just "1",
                  HttpRequest.method = Just "GET",
                  HttpRequest.path = Just "/hats/5",
                  HttpRequest.queryString = Just "?top=flat",
                  HttpRequest.endpoint = Just "/hats/:hat_id",
                  HttpRequest.headers = Dict.fromList [("Accept", "application/json")],
                  HttpRequest.status = Just 500
                }
                |> HttpRequest.Incoming
                |> Platform.toTracingSpanDetails
                |> Just
          },
      logTest
        "logs log messages"
        emptyTracingSpan
          { Platform.details =
              Log.LogContexts
                [ Log.context "number" (4 :: Int),
                  Log.context "text" ("Hi!" :: Text)
                ]
                |> Platform.toTracingSpanDetails
                |> Just
          }
    ]

logTest :: Text -> Platform.TracingSpan -> Test.Test
logTest name span =
  Test.test name <| \_ -> do
    let logContext =
          File.LogContext
            { File.requestId = "request-id",
              File.namespace = ["app"],
              File.environment = "narnia",
              File.hostname = "some-host",
              File.timer = Timer.Timer 0 LocalTime.utc
            }
    let reEncodingResult =
          File.logItemRecursively logContext span []
            -- Pretty-print each log entry to make test files more readable.
            |> Prelude.traverse reEncodePretty
    case reEncodingResult of
      Err err -> Expect.fail err
      Ok reEncoded ->
        Text.join "\n" reEncoded
          |> Expect.equalToContentsOf
            ( Text.fromList "tests/golden-results/file-reporter-"
                ++ Text.replace " " "-" name
            )

reEncodePretty :: Data.ByteString.Lazy.ByteString -> Result Text Text
reEncodePretty str =
  case Aeson.eitherDecode' str of
    Prelude.Left err -> Err (Text.fromList err)
    Prelude.Right (json :: Aeson.Value) ->
      Data.Aeson.Encode.Pretty.encodePretty json
        |> Data.Text.Lazy.Encoding.decodeUtf8
        |> Data.Text.Lazy.toStrict
        |> Ok

newtype CustomException = CustomException Text deriving (Show)

instance Exception.Exception CustomException

ms :: Int -> Platform.MonotonicTime
ms n = Prelude.fromIntegral (1000 * n)

emptyTracingSpan :: Platform.TracingSpan
emptyTracingSpan =
  Platform.emptyTracingSpan
    { Platform.name = "",
      Platform.started = 0,
      Platform.finished = 0,
      Platform.frame = Nothing,
      Platform.details = Nothing,
      Platform.succeeded = Platform.Succeeded,
      Platform.allocated = 0,
      Platform.children = []
    }