packages feed

hs-opentelemetry-api-1.0.0.0: test/OpenTelemetry/AttributesSpec.hs

{-# LANGUAGE PatternSynonyms #-}

module OpenTelemetry.AttributesSpec where

import qualified Data.HashMap.Lazy as H
import qualified Data.Text as T
import qualified OpenTelemetry.Attributes as A
import qualified Test.Hspec as Hspec


spec :: Hspec.Spec
spec = Hspec.describe "Attributes" $ do
  -- Common §Attribute: attribute key/value collection
  -- https://opentelemetry.io/docs/specs/otel/common/#attribute
  Hspec.describe "addAttribute" $ do
    let overwritesPrevious :: (A.ToAttribute a) => T.Text -> a -> a -> A.Attributes -> Hspec.Spec
        overwritesPrevious attrKey newValue prevValue attrs = do
          let addAttr = addAttributeDefault attrKey
          -- Common §Attribute: setting an attribute replaces any existing value for the same key
          -- https://opentelemetry.io/docs/specs/otel/common/#attribute
          Hspec.it "Overwrites previous value with new value at this key." $
            addAttr newValue (addAttr prevValue attrs) `Hspec.shouldBe` addAttr newValue attrs

    overwritesPrevious Example ("new value" :: T.Text) "prev value" A.emptyAttributes
  -- Common §Attribute: batch attribute updates
  -- https://opentelemetry.io/docs/specs/otel/common/#attribute
  Hspec.describe "addAttributes" $ do
    Hspec.it "Overwrites previous values new values when keys collide" $ do
      let initialAttrs = addAttributeDefault "language" ("english" :: T.Text) A.emptyAttributes
          newAttrMap =
            H.fromList
              [ ("language", A.toAttribute ("morporkian" :: T.Text))
              , ("currency", A.toAttribute ("AM$" :: T.Text))
              ]
          updatedAttrs = A.addAttributes A.defaultAttributeLimits initialAttrs newAttrMap
      A.getAttributeMap updatedAttrs
        `Hspec.shouldBe` newAttrMap

    Hspec.it "new values override existing for same key" $ do
      let initial = addAttributeDefault Example ("old" :: T.Text) A.emptyAttributes
          batch = H.singleton Example ("new" :: T.Text)
          result = A.addAttributes A.defaultAttributeLimits initial batch
      A.lookupAttribute result Example `Hspec.shouldBe` Just (A.toAttribute ("new" :: T.Text))

  -- Implementation-specific: merge helper (not part of the portable Attributes API)
  -- https://opentelemetry.io/docs/specs/otel/common/#attribute
  Hspec.describe "unsafeMergeAttributesIgnoringLimits" $ do
    Hspec.it "Is left-biased when keys conflict" $ do
      let left = addAttributeDefault Example (1 :: Int) A.emptyAttributes
          right = addAttributeDefault Example (2 :: Int) A.emptyAttributes
      A.unsafeMergeAttributesIgnoringLimits left right `Hspec.shouldBe` left

  -- Common §Attribute: empty attribute set
  -- https://opentelemetry.io/docs/specs/otel/common/#attribute
  Hspec.describe "emptyAttributes" $ do
    Hspec.it "has count 0" $ do
      A.getCount A.emptyAttributes `Hspec.shouldBe` 0
    Hspec.it "has dropped 0" $ do
      A.getDropped A.emptyAttributes `Hspec.shouldBe` 0

  Hspec.describe "lookupAttribute" $ do
    Hspec.it "finds an added attribute" $ do
      let attrs = addAttributeDefault Example ("hello" :: T.Text) A.emptyAttributes
      A.lookupAttribute attrs Example `Hspec.shouldBe` Just (A.toAttribute ("hello" :: T.Text))

    Hspec.it "returns Nothing for missing key" $ do
      A.lookupAttribute A.emptyAttributes "no-such-key" `Hspec.shouldBe` Nothing

  -- Common §Attribute: strongly-typed attribute keys (API ergonomics)
  -- https://opentelemetry.io/docs/specs/otel/common/#attribute
  Hspec.describe "addAttributeByKey" $ do
    Hspec.it "adds attribute with typed key" $ do
      let key = A.AttributeKey "typed-key"
          attrs = A.addAttributeByKey A.defaultAttributeLimits A.emptyAttributes key (42 :: Int)
      A.lookupAttributeByKey attrs key `Hspec.shouldBe` Just (A.toAttribute (42 :: Int))

  Hspec.describe "lookupAttributeByKey" $ do
    Hspec.it "returns Nothing for missing typed key" $ do
      let key = A.AttributeKey "missing"
      A.lookupAttributeByKey A.emptyAttributes key `Hspec.shouldBe` (Nothing :: Maybe A.Attribute)

  -- Implementation-specific: exposing the underlying attribute map for interop
  -- https://opentelemetry.io/docs/specs/otel/common/#attribute
  Hspec.describe "getAttributeMap" $ do
    Hspec.it "returns the underlying map" $ do
      let attrs = addAttributeDefault Example ("v" :: T.Text) A.emptyAttributes
          m = A.getAttributeMap attrs
      H.lookup Example m `Hspec.shouldBe` Just (A.toAttribute ("v" :: T.Text))

  -- Common §Attribute limits: count and dropped-attribute accounting
  -- https://opentelemetry.io/docs/specs/otel/common/#attribute-limits
  Hspec.describe "getCount / getDropped" $ do
    Hspec.it "getCount increments with addAttribute" $ do
      let a1 = addAttributeDefault "k1" ("v1" :: T.Text) A.emptyAttributes
          a2 = addAttributeDefault "k2" ("v2" :: T.Text) a1
      A.getCount a2 `Hspec.shouldBe` 2

    Hspec.it "getDropped increases when limit exceeded" $ do
      let lim = A.AttributeLimits {attributeCountLimit = Just 1, attributeLengthLimit = Nothing}
          a1 = A.addAttribute lim A.emptyAttributes "k1" ("v1" :: T.Text)
          a2 = A.addAttribute lim a1 "k2" ("v2" :: T.Text)
      A.getDropped a2 `Hspec.shouldBe` 1

  Hspec.describe "addAttributesFromBuilder" $ do
    Hspec.it "adds attributes from a builder" $ do
      let builder = A.attr "bk" ("bv" :: T.Text)
          attrs = A.addAttributesFromBuilder A.defaultAttributeLimits A.emptyAttributes builder
      A.lookupAttribute attrs "bk" `Hspec.shouldBe` Just (A.toAttribute ("bv" :: T.Text))

  -- Implementation-specific: attribute builder helpers
  -- https://opentelemetry.io/docs/specs/otel/common/#attribute
  Hspec.describe "attr builder" $ do
    Hspec.it "attr creates a single-attribute builder" $ do
      let builder = A.attr "test" (True :: Bool)
          attrs = A.addAttributesFromBuilder A.defaultAttributeLimits A.emptyAttributes builder
      A.lookupAttribute attrs "test" `Hspec.shouldBe` Just (A.toAttribute True)

    Hspec.it "optAttr includes Just values" $ do
      let builder = A.optAttr "opt" (Just (7 :: Int))
          attrs = A.addAttributesFromBuilder A.defaultAttributeLimits A.emptyAttributes builder
      A.lookupAttribute attrs "opt" `Hspec.shouldBe` Just (A.toAttribute (7 :: Int))

    Hspec.it "optAttr skips Nothing values" $ do
      let builder = A.optAttr "opt" (Nothing :: Maybe Int)
          attrs = A.addAttributesFromBuilder A.defaultAttributeLimits A.emptyAttributes builder
      A.lookupAttribute attrs "opt" `Hspec.shouldBe` Nothing

    Hspec.it "buildAttrs builds from a builder" $ do
      let builder = A.attr "b1" ("v1" :: T.Text) <> A.attr "b2" (2 :: Int)
          m = A.buildAttrs builder
      H.size m `Hspec.shouldBe` 2


pattern Example :: T.Text
pattern Example = "example"


addAttributeDefault :: (A.ToAttribute a) => T.Text -> a -> A.Attributes -> A.Attributes
addAttributeDefault attrKey value attrs = A.addAttribute A.defaultAttributeLimits attrs attrKey value